
Handling Complexity: Nested Tool Schemas
Move beyond simple strings and integers. Learn how to design complex, nested Pydantic models that allow agents to handle multi-step data entry in a single tool call.
Complex Input Schemas
As your agents take over more professional workflows, the data they handle becomes more complex. Instead of just a city name, they might need to send a formatted InvoiceObject, a NetworkConfig, or a NestedFinancialReport.
Providing these to an agent requires more than just strings; it requires Recursive Pydantic Models. In this lesson, we will learn how to design schemas that models can populate safely without hallucinating.
1. Why Nested Schemas?
A "Flat" schema (just keys and values) is hard to maintain and hard for the model to visualize.
- Flat (Bad):
create_user(name, address_street, address_city, address_zip, phone_home, phone_work) - Nested (Good):
create_user(name, address: AddressObj, contact: ContactObj)
Nested schemas group related data together, which aligns with the "Object-Oriented" way LLMs were trained.
2. Implementing Nested Tools in Pydantic
We can define a hierarchy of models. The LLM will navigate this hierarchy and output a valid JSON tree.
from pydantic import BaseModel, Field
from typing import List
class LineItem(BaseModel):
name: str = Field(description="Name of the product")
quantity: int = Field(gt=0)
price: float
class InvoiceRequest(BaseModel):
customer_id: str
items: List[LineItem] = Field(description="A list of 1 or more line items")
3. The "Schema Overload" Problem
If a schema has too many levels of nesting (e.g., 5 levels deep), the model will likely make a mistake in the JSON syntax.
The Rule of Two: Try to limit your tool schemas to no more than 2 levels of nesting. If you need a 5-level deep structure, split it into two tool calls:
prepare_base_reportadd_technical_details_to_report
4. Handling Dynamic Keys (The dict problem)
Avoid using dict or Any in your schemas.
- Problem: If you define a parameter as
additional_metadata: dict, the model doesn't know what to put inside. It will likely hallucinate keys or put PII in there. - Solution: Use Literal types or Enums to constrain the choices.
from typing import Literal
class ConfigTool(BaseModel):
environment: Literal["dev", "staging", "prod"]
5. Structured Output vs. Tool Calling
Sometimes, you don't need a "Tool" to be executed. You just need the agent to structure its thoughts into a Pydantic object.
LangChain provides .with_structured_output(). This is great for transforming a mess of unstructured text into a clean database row.
6. The "Missing Data" Pattern
In complex schemas, some fields might be optional. Crucial: Tell the model what to do if the data is missing.
Field Description: "If the user did not provide a phone number, set this field to
None. Do NOT invent a fake phone number."
Summary and Mental Model
Think of a Complex Schema like a Multi-page Application Form.
- If the form is just one long list of 50 boxes (Flat), it's intimidating and leads to errors.
- If the form is divided into "Personal Info," "Billing," and "Shipping" (Nested), it's easier for the brain (and the LLM) to process.
Clean structures lead to clean logic.
Exercise: Schema Construction
- Coding: Build a Pydantic model for a
Github_Projecttool.- It must include a
title(str). - It must include a
labels(List of strings). - It must include an
assignees(List of objects withusernameandrole).
- It must include a
- Validation: Add a
Fieldconstraint ensuring that thetitleis at least 5 characters long. - Logic: What happens if the agent tries to call a tool with a nested object but forgets one of the "Required" keys?
- (Hint: Review the Repair Pattern in Module 5.2). Ready to make your tools context-aware? Let's move to Lesson 3.