Tool schemas, validation and error handling
Be able to define tools with a JSON schema and handle invalid calls robustly.
Prerequisites
- EStructured output and schemasrequired
- ETool userequired
Intuition
A tool schema is a contract between the model and your code. The stricter the contract, the fewer ways there are of breaking it.
Four things that make a schema good:
enuminstead of a free string where the set of values is known — then the model cannot invent categories.- Bounds on numbers and lengths (
minimum,maxLength) — they catch unreasonable calls before they are run. requiredandadditionalProperties: false— no missing or invented fields.- Descriptions the model reads —
descriptionis not documentation for humans, it is part of the prompt.
The model sees only the schema. A field called q with no description will be used wrongly.
Code
from typing import Literal
from pydantic import BaseModel, Field, ValidationError
import json
class SearchCases(BaseModel):
"""Search cases in the support system. Returns at most 50 hits."""
status: Literal["open", "closed", "waiting"] = Field(description="The case's current status")
older_than_days: int | None = Field(default=None, ge=0, le=365,
description="Only cases older than this number of days")
free_text: str | None = Field(default=None, max_length=200,
description="A search term in the case's title or description")
model_config = {"extra": "forbid"} # additionalProperties: false
TOOLS = {"search_cases": (SearchCases, search_cases_impl)}
def spec():
return [{"type": "function", "function": {
"name": name,
"description": (model.__doc__ or "").strip(),
"parameters": model.model_json_schema()}}
for name, (model, _) in TOOLS.items()]
def run(name: str, arguments: str) -> dict:
if name not in TOOLS:
return {"error": f"unknown tool '{name}'. Available: {sorted(TOOLS)}"}
schema, fn = TOOLS[name]
try:
args = schema.model_validate_json(arguments)
except ValidationError as e:
errors = [{"field": ".".join(str(x) for x in f["loc"]), "problem": f["msg"]} for f in e.errors()]
return {"error": "invalid arguments", "details": errors} # structured → the model can correct itself
except json.JSONDecodeError as e:
return {"error": f"the arguments are not valid JSON: {e}"}
try:
return fn(args)
except Exception as e:
return {"error": f"the tool failed: {type(e).__name__}"} # never leak a stack trace
The error message is part of the interface. «invalid arguments» helps nobody; {"field": "older_than_days", "problem": "Input should be less than or equal to 365"} makes the model correct itself in the next round. Send the error back as the tool result instead of aborting.
Mastery means
- Defines tools with a JSON schema
- Validates the arguments and handles invalid calls
- Designs error messages the model can correct itself from
Sign in to do the exercises and build your mastery up.
Sources
- JSON Schema — free to read
- Anthropic — Tool use — documentation, free to read