By Sagar Shankaran, Founder of CallSphere
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.
Key takeaways
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.
Every tool schema has three critical parts: the function name, the description, and the parameters object. Here is a minimal example:
flowchart TD
USER(["User message"])
LLM["LLM call<br/>with tools schema"]
DECIDE{"Model wants<br/>to call a tool?"}
EXEC["Execute tool<br/>sandboxed runtime"]
RESULT["Append tool_result<br/>to messages"]
GUARD{"Output passes<br/>guardrails?"}
DONE(["Final reply"])
BLOCK(["Refuse and log"])
USER --> LLM --> DECIDE
DECIDE -->|Yes| EXEC --> RESULT --> LLM
DECIDE -->|No| GUARD
GUARD -->|Yes| DONE
GUARD -->|No| BLOCK
style LLM fill:#4f46e5,stroke:#4338ca,color:#fff
style EXEC fill:#ede9fe,stroke:#7c3aed,color:#1e1b4b
style GUARD fill:#f59e0b,stroke:#d97706,color:#1f2937
style DONE fill:#059669,stroke:#047857,color:#fff
style BLOCK fill:#dc2626,stroke:#b91c1c,color:#fff
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.
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.
Hear it before you finish reading
Talk to a live CallSphere AI voice agent in your browser — 60 seconds, no signup.
# 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."
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.
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.
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.
Still reading? Stop comparing — try CallSphere live.
CallSphere ships complete AI voice agents per industry — 14 tools for healthcare, 10 agents for real estate, 4 specialists for salons. See how it actually handles a call before you book a demo.
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 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"
}
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.
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.
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
Written by
Sagar Shankaran· Founder, CallSphere
Sagar Shankaran is the founder of CallSphere, where he builds production AI voice and chat agents deployed across healthcare, hospitality, real estate, and home services. He writes about agentic AI, LLM engineering, and shipping voice agents that handle real calls in production.
See how AI voice agents work for your industry. Live demo available -- no signup required.
How we built a fault-tolerant HVAC emergency triage and tech-dispatch platform on Kubernetes — three-tier CQRS, 11 micro-agents on the OpenAI Agents SDK + LangGraph, NATS JetStream, DTMF/SMS/WebSocket acceptance, circuit breakers, and an evaluation pipeline that catches regressions before they wake a tech at 3 AM.
Head-to-head: OpenAI Frontier and Anthropic's managed agent stack — strengths, fit, and what each means for enterprise AI voice and chat deployment.
Meta is building Hatch, a consumer AI agent that operates DoorDash, Reddit, and other third-party apps — Meta's answer to OpenClaw and Google Remy.
OpenAI Frontier — the new enterprise platform announced this week for building, deploying, and managing AI agents that do real work.
Q1 2026 saw a record acquisition wave: Aircall bought Vogent (May), Meta acquired Manus and PlayAI, OpenAI closed six deals. The voice AI consolidation phase has begun.
Microsoft's Copilot for Sales shipped 2026 updates that knit Dynamics, Outlook, and Teams into a single agentic surface. Here's the playbook, the per-seat pricing.
© 2026 CallSphere LLC. All rights reserved.
Made within New York
Watch how CallSphere handles real customer calls, schedules appointments, and processes payments — live.
Try Live DemoBook a DemoCalculate Your ROI