PydanticFunctionTool Validation Errors¶
This notebook demonstrates what happens when a PydanticFunctionTool
receives malformed arguments: a missing required field, or a value that
fails a custom Pydantic validator.
When validation fails, PydanticFunctionTool does not raise an
exception. Instead it catches the error and returns a
ToolCallResult(error=True) with a descriptive message. This matters
because in an LLM agent loop the result must always be returned to the LLM
so it can understand what went wrong and decide how to recover. For
example, it may fix the arguments and retry.
Setting up the LLM¶
I recommend running these notebooks on Ollama Cloud. It serves much larger models than most laptops can run. The default cloud model is picked by benchmarking candidates on these notebooks, and an automated run checks every notebook against it each week. The notebook status page shows the current model and results.
- Create an API key at https://ollama.com/settings/keys.
- Export it before launching Jupyter:
export OLLAMA_API_KEY=<your key>. - That's all. When a notebook builds its LLM, it detects the key and uses Ollama Cloud.
Prefer to run locally? Install Ollama from https://ollama.com/download
and pull the model with ollama pull qwen3:14b. If no key is set, the
notebook starts the Ollama service for you, so there is no need to run
ollama serve. Local models are smaller, so your outputs will differ from
the recorded ones.
Prefer OpenAI or Anthropic?
- Install the extra:
uv sync --extra openaioruv sync --extra anthropic. - Export
OPENAI_API_KEYorANTHROPIC_API_KEYbefore launching Jupyter. If you forget, you will be prompted for it, which keeps the key out of the saved notebook. - Set
LLM_PROVIDER=openai(oranthropic), or passprovider="openai"tomake_llm(). A key on its own never switches providers, so one exported for unrelated work cannot reroute you.
The switch applies wherever a notebook builds its LLM with make_llm().
A notebook that constructs OllamaLLM directly stays on Ollama. Output on
gpt-5 or Claude will differ from the recorded outputs, and prompt-sensitive
examples (the ch09 evaluator pattern, ch08 supervised trajectories) may
behave noticeably differently.
# Uncomment the line below to install `llm-agents-from-scratch` from PyPI
# !pip install llm-agents-from-scratch
Defining the Tool¶
We wrap a divide function whose DivideParams model enforces two
constraints: both fields are required, and denominator must not be zero
(enforced by a @field_validator).
from pydantic import BaseModel, Field, field_validator
from llm_agents_from_scratch.data_structures import ToolCall
from llm_agents_from_scratch.tools import PydanticFunctionTool
class DivideParams(BaseModel):
"""Parameters for the divide tool."""
numerator: float = Field(description="The dividend.")
denominator: float = Field(description="The divisor (must not be zero).")
@field_validator("denominator")
@classmethod
def denominator_not_zero(cls, v: float) -> float:
"""Reject zero denominators."""
if v == 0:
raise ValueError("denominator must not be zero")
return v
def divide(params: DivideParams) -> float:
"""Divide numerator by denominator and return the result."""
return params.numerator / params.denominator
divide_tool = PydanticFunctionTool(func=divide)
Example 1: Valid Call¶
Both fields are present and valid. The tool executes and returns a
successful ToolCallResult.
tool_call = ToolCall(
tool_name="divide",
arguments={"numerator": 10.0, "denominator": 4.0},
)
result = divide_tool(tool_call)
print(f"error: {result.error}")
print(f"content: {result.content}")
error: False content: 2.5
Example 2: Missing Required Field¶
denominator is omitted. Pydantic raises a ValidationError which
PydanticFunctionTool catches and surfaces as an error result.
tool_call = ToolCall(
tool_name="divide",
arguments={"numerator": 10.0},
)
result = divide_tool(tool_call)
print(f"error: {result.error}")
print(f"content: {result.content}")
error: True
content: {"error_type": "ValidationError", "message": "Internal error while executing tool: 1 validation error for DivideParams\ndenominator\n Field required [type=missing, input_value={'numerator': 10.0}, input_type=dict]\n For further information visit https://errors.pydantic.dev/2.13/v/missing"}
Example 3: Custom Validator Failure¶
Both fields are present but denominator is 0, which fails the
@field_validator. The validator raises a ValueError, Pydantic wraps
it in a ValidationError, and the tool returns an error result.
tool_call = ToolCall(
tool_name="divide",
arguments={"numerator": 10.0, "denominator": 0},
)
result = divide_tool(tool_call)
print(f"error: {result.error}")
print(f"content: {result.content}")
error: True
content: {"error_type": "ValidationError", "message": "Internal error while executing tool: 1 validation error for DivideParams\ndenominator\n Value error, denominator must not be zero [type=value_error, input_value=0, input_type=int]\n For further information visit https://errors.pydantic.dev/2.13/v/value_error"}