Designing Tool Schemas for AI Agents: JSON Schema Best Practices
Learn how to write effective JSON Schema tool definitions that help LLMs understand parameters, constraints, and expected inputs. Covers parameter types, descriptions, required vs optional fields, nested objects, and enums.
Why Tool Schemas Matter More Than You Think
When an LLM decides to call a tool, the only thing guiding that decision is the schema you provide. A vague schema produces vague tool calls. A precise schema produces precise ones. The JSON Schema definition you attach to each tool is not just a validation layer — it is the primary interface between the LLM's reasoning and your code's execution.
This post covers the practical patterns that make tool schemas work reliably across OpenAI, Anthropic, and open-source function-calling models.
The Anatomy of a Tool Schema
Every tool schema has three critical parts: the function name, the description, and the parameters object. Here is a minimal example:
search_tool = {
"type": "function",
"function": {
"name": "search_documents",
"description": "Search internal documents by keyword query. Returns the top matching document titles and snippets. Use this when the user asks about company policies, procedures, or internal knowledge.",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "The search query. Use specific keywords rather than full sentences."
},
"max_results": {
"type": "integer",
"description": "Maximum number of results to return. Defaults to 5.",
"default": 5,
"minimum": 1,
"maximum": 20
},
"department": {
"type": "string",
"description": "Filter results to a specific department.",
"enum": ["engineering", "sales", "hr", "finance", "legal"]
}
},
"required": ["query"]
}
}
}
Notice a few things. The function description explains both what the tool does and when to use it. Each parameter description guides the LLM on how to fill that parameter. The enum constrains free-text to valid values. The required array distinguishes mandatory from optional parameters.
Writing Descriptions That Guide Selection
The function description is the most important field. The LLM reads it to decide whether to call the tool at all. Write descriptions that answer three questions: what does this tool do, when should it be used, and when should it not be used.
# Bad: too vague
"description": "Gets weather data"
# Good: explains what, when, and constraints
"description": "Fetch current weather conditions for a specific city. Returns temperature, humidity, and wind speed. Use this when the user asks about current weather. Do NOT use this for weather forecasts or historical weather data."
Parameter Types and Constraints
JSON Schema supports types that map well to tool calling. Use them precisely:
"properties": {
"name": {
"type": "string",
"description": "Full name of the customer",
"minLength": 1,
"maxLength": 200
},
"age": {
"type": "integer",
"minimum": 0,
"maximum": 150
},
"tags": {
"type": "array",
"items": {"type": "string"},
"description": "List of tags to apply. Each tag is a lowercase string.",
"maxItems": 10
},
"is_active": {
"type": "boolean",
"description": "Whether the account is currently active"
}
}
The minimum, maximum, minLength, maxLength, and maxItems constraints are not always enforced by every model, but they serve as documentation that helps the LLM generate reasonable values.
See AI Voice Agents Handle Real Calls
Book a free demo or calculate how much you can save with AI voice automation.
Nested Objects and Complex Structures
When a tool needs structured input, use nested objects with their own property definitions:
"properties": {
"filters": {
"type": "object",
"description": "Search filters to narrow results",
"properties": {
"date_from": {
"type": "string",
"description": "Start date in YYYY-MM-DD format"
},
"date_to": {
"type": "string",
"description": "End date in YYYY-MM-DD format"
},
"status": {
"type": "string",
"enum": ["open", "closed", "pending"]
}
},
"required": ["date_from"]
}
}
Keep nesting to two levels at most. Deeply nested schemas confuse most models and lead to malformed calls.
Required vs Optional: The Decision Framework
Mark a parameter as required when the tool cannot function without it. Mark it as optional when there is a sensible default or when the parameter narrows results but is not essential.
A common mistake is making everything required. This forces the LLM to hallucinate values for parameters the user never mentioned. Another mistake is making everything optional, which leads to vague, unfocused tool calls.
Enums: Your Most Powerful Constraint
Enums are the single most effective way to prevent invalid tool calls. Whenever a parameter has a finite set of valid values, use an enum. The LLM will almost always pick from the enum list rather than generating a free-text value.
"priority": {
"type": "string",
"enum": ["low", "medium", "high", "critical"],
"description": "Priority level for the ticket"
}
FAQ
How many parameters should a single tool have?
Keep tools to 5-7 parameters at most. Beyond that, models start making mistakes with parameter mapping. If you need more, consider splitting the tool into two or grouping related parameters into a nested object.
Should I use camelCase or snake_case for parameter names?
Use snake_case. Most function-calling models were trained primarily on Python tool definitions, and snake_case produces marginally more reliable calls. Be consistent across all your tools regardless of which convention you choose.
Do all LLM providers support the same JSON Schema features?
No. OpenAI supports most of JSON Schema including enum, minimum, maximum, and nested objects. Anthropic supports a similar subset. Open-source models vary widely. Stick to basic types, enums, and one level of nesting for maximum compatibility.
#ToolDesign #JSONSchema #FunctionCalling #AIAgents #AgenticAI #LearnAI #AIEngineering
CallSphere Team
Expert insights on AI voice agents and customer communication automation.
Try CallSphere AI Voice Agents
See how AI voice agents work for your industry. Live demo available -- no signup required.