What this is
The last lesson in the course. No new concepts, just the entire course pulled into one small "order assistant" agent: it reads a plain-English order, computes its total using an MCP-hosted calculator, not the model's own arithmetic, and returns a validated OrderResult, wrapped in a FallbackModel for resilience, and checked against a pydantic_evals dataset. If you can read the code below and explain why every piece is there, you've completed the course.
What it does
Language models are unreliable at multi-step arithmetic; this lesson's add/multiply MCP tools guarantee a correct sum no matter how complex the order gets, exactly like offloading arithmetic to a calculator tool in the LangChain course. Routing it through MCP specifically, rather than a local @agent.tool, demonstrates that an MCP server is a fully normal source of tools for a Pydantic AI agent, not a special case.
Where each piece came from
class OrderResult(BaseModel): items: list[str] total: float note: str
@dataclassclass CatalogDeps: prices: dict[str, float]
transport = StdioTransport(command=sys.executable, args=[str(SERVER_SCRIPT)])toolset = MCPToolset(transport)
fallback_model = FallbackModel( "google:gemini-9.9-does-not-exist", "google:gemini-3.5-flash-lite",)
agent = Agent( fallback_model, deps_type=CatalogDeps, output_type=OrderResult, toolsets=[toolset], system_prompt=( "You compute order totals. Look up each item's price with " "get_price, then use the add and multiply MCP tools to compute " "the total, never do arithmetic yourself. Return items, the " "final total, and a one-sentence note." ),)
@agent.tooldef get_price(ctx: RunContext[CatalogDeps], item: str) -> float: """Look up the catalog price for an item.""" return ctx.deps.prices[item.lower()]Lesson 3: the final answer is a validated OrderResult, not free text. Lesson 5, 7: the order's known catalog prices are injected as CatalogDeps, not a global. Lesson 21: arithmetic is delegated to a real MCP server, server.py in this lesson's folder. Lesson 22: a broken "primary" model falls back to the real one, now wired into a realistic agent instead of a toy example.
@dataclassclass CloseEnough(Evaluator): def evaluate(self, ctx: EvaluatorContext) -> bool: return abs(ctx.output - ctx.expected_output) < 0.01
dataset = Dataset( name="order_totals", cases=[ Case( name="two_widgets", inputs="2 widgets", expected_output=2 * CATALOG["widget"], ), Case( name="gadget_and_gizmo", inputs="1 gadget and 1 gizmo", expected_output=CATALOG["gadget"] + CATALOG["gizmo"], ), ], evaluators=[CloseEnough()],)Lesson 15: a dataset checks that the computed total matches the expected total for a couple of sample orders, with a custom Evaluator tolerant of floating-point rounding rather than requiring an exact match.
What "done" looks like
The agent prints a validated OrderResult for a sample order, with a total that matches manual arithmetic on the catalog prices. The FallbackModel printout shows the real model answered, even though the first model in the list was deliberately broken. The eval report at the end shows every case passing.
Checkpoint
- A single agent combining a validated
output_type, injected deps, MCP-hosted tools, a fallback model, and an eval dataset, the full shape of a production Pydantic AI agent. - Arithmetic goes through an MCP tool because the model itself shouldn't be trusted to add numbers correctly, the same lesson the LangChain course taught with a local calculator tool.
- Every piece in this capstone traces back to one specific earlier lesson; nothing here is new.
Try this yourself, without looking anything up: add a fourth catalog item, add a case to the dataset ordering it, and confirm the eval report still shows every case passing.
If everything here makes sense, you're ready to reach for Pydantic AI anywhere you'd have reached for raw LangChain before, when the shape of the agent's output matters as much as what it says. That's the whole course.