What you'll learn: what a tool definition contains, how a Pydantic model turns into the schema the model sees, how the application validates what the model sends back, and what an error result should and should not say.
At a glance
Figure 1. One proposed tool call, checked before anything runs
How to read this: the model's reply enters at the top as untrusted text, and only the innermost branch ever runs your tool function.
- A tool is a name, a description, an input schema and a function. The model sees the first three.
- The schema is written once, as a Pydantic model, and the JSON Schema for the model is generated from it.
- The application validates the arguments again on receipt. The model's output is a proposal.
- Failures of every kind come back as a result object, so one bad call never crashes the loop.
Scenario
Composite scenario, not a real company. At Lumen Industrial Supply, a fictional distributor, a pilot of the product assistant logs every tool call the model proposes. A reviewer reads a sample and finds three kinds of bad call: a SKU written in lower case, a result limit of 99 where the catalog allows 10, and a call to a tool that does not exist.
In an illustrative test of 200 proposed calls, 9 have a wrong shape and 3 name an unknown tool. Nothing may crash, and the reviewer wants the rate of each kind.
What you will be able to do
- Write a tool as a Pydantic argument model plus a function, and register it.
- Print the JSON Schema the model would receive for a tool.
- Predict whether a given malformed call is rejected, coerced or silently trimmed.
- Return an error that names the field and the rule without echoing the submitted value.
Prerequisites
- Tool calling with one read-only tool and Structured outputs and schema validation.
- A bounded workflow or an agent?: the loop that calls these tools.
Core ideas
A tool has four parts
| Part | Example | Seen by the model | Written by |
|---|---|---|---|
| Name | get_product_spec |
Yes | You |
| Description | "Read one product record by SKU." | Yes | You |
| Input schema | sku must match a pattern |
Yes, as JSON Schema | Generated from the Pydantic model |
| Function | Reads the catalog, returns a dict | No | You |
The Claude API documentation lists the same parts: a name, a description and an input_schema that is "a JSON Schema object defining the expected parameters for the tool" (Define tools). It also says detailed descriptions are the most important factor in tool performance and suggests at least three or four sentences per tool.
The capstone's descriptions are one short sentence because its default mock provider never reads them; with a real model, write longer ones. This is what the model would receive for one tool (real output from the lesson code):
{
"name": "get_product_spec",
"description": "Read one product record by SKU.",
"input_schema": {
"properties": {
"sku": {
"pattern": "^LUM-[A-Z]{2}-\\d{3}quot;,
"title": "Sku",
"type": "string"
}
},
"required": [
"sku"
],
"title": "GetProductSpecArgs",
"type": "object"
}
}
The registry joins the parts
Figure 2. The tool registry as entities
How to read this: a tool never touches the catalog directly; it receives a context built by the application.
Two design choices are worth copying:
- The registry refuses a tool that is not read-only, so a write action cannot be registered by accident.
callnever raises: unknown names, bad arguments and tool failures are all values.
Validate on receipt, even with a strict mode
Pydantic's model_validate "will raise a ValidationError exception whenever it finds an error in the data it's validating" (Pydantic models). The Claude API also offers strict: true on a tool definition, which it describes as guaranteeing that tool inputs match the schema (Strict tool use).
The default here is to validate anyway, for these reasons:
- Schema-valid is not business-valid:
LUM-XX-000matches the pattern and still does not exist. - The same registry may serve a provider or a test double that offers no strict mode.
- Validation costs very little next to a model call.
- Drop it only where the provider guarantee is documented and tested, and the tool is read-only.
Spot the flaw. What is wrong with this tool dispatcher? (Pseudocode.)