28.8 Agent Evaluation and Safety

Alright, let’s get real about agent evaluation and safety. This isn’t some academic footnote; it’s the difference between building a useful assistant and unleashing a digital Rube Goldberg machine that accidentally spends your entire AWS budget on cat food subscriptions. We’re not just teaching agents to use tools; we’re teaching them to use them responsibly. This is where the rubber meets the road, or more accurately, where the LLM meets the API that can actually change things in the real world.

28.7 AutoGen and CrewAI: Multi-Agent Frameworks

Right, so you’ve got your single agent doing its ReAct thing, calling a tool, and feeling pretty clever. But let’s be honest, most real-world problems aren’t solved by one brilliant mind working in isolation. They’re solved by a group of specialists, some arguing, some delegating, and at least one making the coffee. Welcome to the wonderfully chaotic world of multi-agent systems. Frameworks like AutoGen and CrewAI exist to manage this chaos for you. They provide the scaffolding to define different agent personas, give them specific tools, and—most importantly—orchestrate the conversation between them. Think of it as being a director for a play where the actors are LLM instances and they’re all prone to going wildly off-script.

28.6 Multi-Agent Systems: Collaboration, Competition, and Communication

Right, so you’ve got your single agent doing its ReAct thing, using tools, feeling pretty clever. But let’s be honest, most real-world problems aren’t solved by a single brilliant mind working in isolation. They’re solved by teams, committees, and groups of specialists who (ideally) collaborate, (sometimes) bicker, and (occasionally) produce something greater than the sum of their parts. Welcome to multi-agent systems, where we take that single-agent brain and copy-paste it a few times to see what beautiful—or horrifying—chaos ensues.

28.5 Planning Agents: MRKL, Toolformer, HuggingGPT

Alright, let’s get our hands dirty with planning agents. You’ve seen the basic ReAct loop, which is like a friend who thinks out loud before doing something. Planning agents are that friend on a triple espresso, with a whiteboard and a disturbingly detailed Gantt chart. They don’t just plan the next action; they plan a whole sequence of them, often breaking your big, scary problem into smaller, chewable pieces before they even reach for a single tool.

28.4 Memory in Agents: Short-Term, Long-Term, Episodic

Right, let’s talk about memory. Because without it, your AI agent is just a glorified, one-shot API call with amnesia. It’s the difference between a colleague who remembers the entire project history and a new intern you have to re-introduce yourself to every single morning. The core problem is context windows. LLMs have a shockingly short attention span. You’re basically trying to fit the entire plot of War and Peace into a tweet. We combat this with a strategy you’re already familiar with: not remembering everything, but remembering the right things. We break it down into three key types.

28.3 Tool Use: Function Calling and MCP

Right, let’s talk about getting these LLMs to actually do things. You see, an AI that can only talk is like a brilliant philosopher locked in a sensory deprivation tank. They can reason about the world, but they can’t interact with it. Their knowledge is frozen in time, limited to their training data. They can’t tell you the weather, can’t look up your latest database entry, and can’t book you a flight to Tahiti. This is where Tool Use, often called Function Calling, comes in. It’s the mechanism we use to give our boxed-in intellects a set of hands.

28.2 ReAct: Reasoning + Acting in Interleaved Steps

Right, let’s talk about ReAct. You’ve probably hit the wall with standard LLM prompting. You ask a question, it gives you an answer that sounds plausible but is, in fact, a beautiful and confident hallucination. It’s like asking for directions from a poet. ReAct is our first solid attempt to fix that by giving the model a way to do things to find the answer, not just make one up.

28.1 What Is an AI Agent? Perception, Planning, Action

Right, let’s cut through the marketing fluff. When I say “AI agent,” I’m not talking about a chrome-plated automaton that’s going to file your TPS reports. At its core, an agent is just a program that doesn’t just think—it does. It takes a high-level goal from you, like “find the best price for a new graphics card,” and breaks it down into a series of steps, using tools (like a web browser or a calculator) to execute them. It’s the difference between a student who memorizes the textbook and one who actually knows how to use the library, the lab, and a decent search engine.

28.7 Typing Third-Party Hooks

Alright, let’s talk about the real world. You’re not just writing your own beautiful, perfectly typed hooks; you’re also going to be using other people’s. And let’s be honest, some of those people might have been having a very long day when they wrote the type definitions. Our job is to build a sturdy, type-safe scaffold around their code, even when it feels like they’re actively working against us. The golden rule here is simple: Never use any to shut the compiler up. That’s like fixing a gas leak by removing the smoke alarm. We’re better than that. We’re going to understand what we’re dealing with and apply the correct level of type safety.

28.6 Custom Hook Return Types: Tuple vs Object

Alright, let’s settle a debate that’s less about technical superiority and more about developer ergonomics and how you like your data served: in a neatly wrapped object package or a structured tuple takeout box. When you craft a custom hook, the most important contract you design is its return value. It’s the entire API for anyone using your hook. Get it wrong, and you’ll have a chorus of frustrated developers (or future-you) complaining. TypeScript gives you two primary ways to define this return type: an array/tuple or a plain object.

28.5 useCallback and useMemo: Generic Memoization

Right, let’s talk about useCallback and useMemo. You’ve probably heard they’re for “performance,” and then you saw a dozen articles telling you to wrap everything in them. Please, for the love of all that is holy, don’t do that. They’re not magic performance dust; they’re precision tools with very specific—and honestly, somewhat niche—use cases. Misusing them can actually make your app slower and definitely more complicated. I’m here to give you the straight talk on when and why you’d actually need them.

28.4 useContext: Avoiding undefined with Non-Nullable Context

Right, so you’ve decided to use Context. Good for you. It’s a fantastic tool for passing data around without resorting to what I call “prop-drilling” – the tedious and error-prone practice of threading data through a dozen components that don’t care about it just to get it to the one that does. But here’s the first tripwire you’re going to hit, the one that makes everyone’s TypeScript compiler scream in unison: undefined.

28.3 useRef: Mutable Refs vs DOM Refs

Alright, let’s talk about useRef. This is the hook you reach for when you need to break the rules. React is all about the purity of components, the sanctity of props and state leading to predictable renders. useRef is your backdoor, your escape hatch. It says, “I need to remember something, but I swear, pinky promise, its changing has nothing to do with rendering.” It’s the hook of last resort, and when you need it, you really need it.

28.2 useReducer: Typing Actions and State

Right, so you’ve graduated from the tyranny of ten useState calls for a single component and you’re ready to embrace useReducer. It feels more grown-up, doesn’t it? Like switching from a scooter to a manual transmission. But with TypeScript, you’re not just getting the keys—you’re also getting the full, annotated repair manual. Our job is to make sure that manual doesn’t read like it was translated through three languages. The core idea of useReducer is simple: you give it a reducer function (state, action) => newState and an initial state, and it gives you back the current state and a dispatch function to send actions. TypeScript’s job is to lock this whole system down, making it impossible to dispatch an action you didn’t account for or to access a state property that doesn’t exist. It’s your personal bouncer for state management.

28.1 useState<T>: When TypeScript Can and Cannot Infer the Type

Right, let’s talk about useState and TypeScript. This is where you and the compiler start a beautiful, occasionally pedantic, friendship. The good news is that TypeScript is shockingly good at figuring out what type of state you’re trying to manage. The bad news is that when it can’t, it fails in the most spectacularly unhelpful ways. We’re going to learn how to make it work for us, not against us.

27.7 Event Types: React.MouseEvent, React.ChangeEvent, and More

Alright, let’s talk about events. You’ve handled events in vanilla JavaScript: a simple onclick here, an onchange there. It’s a straightforward, if slightly clunky, affair. Then you waltz into the React ballroom, and suddenly you’re expected to dance with React.MouseEvent<HTMLButtonElement>. It feels like your plain old Event object got a fancy, overly verbose makeover. Why the ceremony? It boils down to one beautiful, non-negotiable TypeScript truth: type safety. React wraps the browser’s native event object into its own synthetic event system for performance and consistency. Our job is to tell TypeScript exactly what kind of event we’re handling and, crucially, which HTML element is producing it. This stops you from trying to get .value from a <div> (which would be embarrassing) or .checked from a <select> (which is just nonsense). We’re not just preventing runtime errors; we’re designing our component contracts to be unbreakable.

27.6 Discriminated Union Props for Polymorphic Components

Right, so you’ve built a few components. A Button, a Card. You pass some strings, maybe a boolean for disabled. It feels good. Then the product manager waltzes in and says, “Great, but now we need a component that can either be a primary button, a secondary button, or a hyperlink that looks like a button. Oh, and if it’s a link, it needs an href and a target, but if it’s a button, it needs an onClick and a type.”

27.5 Generic Components: Reusable Components with Type Parameters

Right, so you’ve built a few <Button> and <Card> components. They work. They’re type-safe. You feel good. Then your PM walks over and says, “Great, now can we have a <Select> where the options can be strings, numbers, or maybe even entire user objects? And a <DataTable> that can take any sort of row shape? And, oh, a function that can log any value but also return it?” This is where you graduate from just using TypeScript to wielding it. You need generic components. The concept is simple: it’s a component that defers specifying its exact data types until you use it. Think of it like a function argument, but for types. You’re making a promise: “I will work with whatever type you give me later, and I’ll make sure you use it correctly.”

27.4 Children Prop: React.ReactNode, React.ReactElement, and PropsWithChildren

Alright, let’s talk about the children prop. You’ve seen it. You’ve used it. You’ve probably typed it as any or just ignored it entirely to make the red squiggly line in your editor go away. We’ve all been there, but it’s time to level up. In React, children is a special prop, passed implicitly. It’s the content you put between your component’s opening and closing tags. Think of <Button>Click Me!</Button> – "Click Me!" is the children prop. Simple, right? The complexity comes when you, the brilliant developer, want to type this in TypeScript. What the heck goes in that interface?

27.3 Typing Props: Required, Optional, and Defaults with Destructuring

Right, let’s talk about props. They’re the lifeblood of your components, the parameters of your UI functions. And in TypeScript, we get to move from the wild west of “I hope this object has the right stuff” to the rigorously patrolled border of “I will not let you pass without the correct paperwork.” The core concept is the interface (or a type alias, but I prefer interface for objects—it feels more intentional). This is where you define the contract for your component.

27.2 Typing Functional Components: React.FC vs Direct Return Types

Alright, let’s settle a holy war, shall we? When you define a functional component in TypeScript, you’re immediately faced with a choice: do you slap a React.FC type on it, or do you just type the props and let the return type be inferred? This seems like a trivial stylistic decision, but it comes with real-world consequences that range from “mildly annoying” to “genuinely problematic.” Let’s break it down. The Case for React.FC The React.FC (or its longer alias, React.FunctionComponent) approach is the old guard. It was the officially recommended way for a long time, and you’ll see it in a ton of legacy code. Here’s what it looks like:

27.1 JSX and TSX: The jsx Compiler Option

Right, let’s talk about the jsx compiler option. This is one of those things you set once in your tsconfig.json and then blissfully forget about until your editor starts screaming at you for a reason you don’t understand. It’s not magic, but it is the secret sauce that makes your <div /> turn into something JavaScript can actually run. The core job of this option is to tell the TypeScript compiler, “Hey, you’re about to see some XML-like syntax that isn’t valid JavaScript. I need you to do one of two things: 1) transform it into regular JavaScript function calls for me, or 2) just leave it alone and let something else (like Babel) handle it.” This decision is crucial because it dictates your entire build pipeline.

— joke —

...