---
title: "Designing Tool Schemas for AI Agents: JSON Schema Best Practices"
description: "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."
canonical: https://callsphere.ai/blog/designing-tool-schemas-ai-agents-json-schema-best-practices
category: "Learn Agentic AI"
tags: ["Tool Design", "JSON Schema", "Function Calling", "AI Agents"]
author: "CallSphere Team"
published: 2026-03-17T00:00:00.000Z
updated: 2026-05-08T13:07:49.553Z
---

# 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:

```mermaid
flowchart TD
    USER(["User message"])
    LLM["LLM call
with tools schema"]
    DECIDE{"Model wants
to call a tool?"}
    EXEC["Execute tool
sandboxed runtime"]
    RESULT["Append tool_result
to messages"]
    GUARD{"Output passes
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
```

```python
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.

```python
# 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:

```python
"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.

## Nested Objects and Complex Structures

When a tool needs structured input, use nested objects with their own property definitions:

```python
"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.

```python
"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

---

Source: https://callsphere.ai/blog/designing-tool-schemas-ai-agents-json-schema-best-practices
