JSON Schema for Tools

Protocols & Interoperability advanced Building with AI 11 min read

Overview

When an LLM needs to interact with the outside world — fetching weather data, querying a database, or sending an email — it cannot execute code directly. Instead, it emits a structured JSON object describing which function to call and what arguments to pass. JSON Schema is the formal language that defines the contract between the model and your application: it specifies exactly what parameters each tool accepts, their types, constraints, and which fields are required.

Without JSON Schema, tool calling would be a guessing game. The model would have no reliable way to know that a get_weather function expects a location string and an optional unit enum. JSON Schema provides the structured vocabulary that turns natural language intent into deterministic, machine-parseable function invocations. Every major LLM provider — OpenAI, Anthropic, Google, and open-source frameworks — uses JSON Schema as the foundation of their tool-calling APIs.

For AI engineers, understanding JSON Schema for tools is not optional. It is the interface layer between what the model “wants to do” and what your code actually executes. A poorly defined schema leads to hallucinated parameters, type mismatches, and failed tool calls. A well-defined schema constrains the model’s output so tightly that errors become nearly impossible.

How It Works

The Tool Definition Structure

A tool definition tells the LLM three things: the function’s name, a natural language description of what it does, and a parameters (or input_schema) object defined using JSON Schema. The model reads the name and description to decide when to call the tool, then uses the schema to construct valid arguments.

Key Concept: JSON Schema

JSON Schema is a declarative vocabulary for annotating and validating JSON documents. It defines data types (string, number, boolean, object, array), constraints (enum, minimum, maxLength), required fields, and nested structures. The current standard is Draft 2020-12.

OpenAI Tool Definition Format

OpenAI wraps tool definitions inside a tools array, with each entry specifying type: "function" and a nested function object containing the name, description, and parameters schema:

{
  "type": "function",
  "function": {
    "name": "get_weather",
    "description": "Get current temperature for a given location.",
    "parameters": {
      "type": "object",
      "properties": {
        "location": {
          "type": "string",
          "description": "City and state, e.g. 'San Francisco, CA'"
        },
        "unit": {
          "type": "string",
          "enum": ["celsius", "fahrenheit"],
          "description": "Temperature unit"
        }
      },
      "required": ["location"]
    }
  }
}

When the model decides to call this tool, it emits a tool_calls array in its response containing the function name and a JSON string of arguments matching this schema.

Anthropic Tool Definition Format

Anthropic uses a slightly different structure. Tools are defined with name, description, and input_schema at the top level — no wrapping function key:

{
  "name": "get_weather",
  "description": "Get the current weather in a given location.",
  "input_schema": {
    "type": "object",
    "properties": {
      "location": {
        "type": "string",
        "description": "The city and state, e.g. San Francisco, CA"
      }
    },
    "required": ["location"]
  }
}

Despite the structural differences, both providers use identical JSON Schema syntax for the parameter definitions themselves. If you can write one, you can write the other with minimal adaptation.

Schema Design Best Practices

The quality of your tool schema directly impacts how reliably the model calls your tools. Research and production experience have identified several critical best practices:

Strict Mode and Structured Outputs

OpenAI introduced strict: true mode for tool definitions, which guarantees that the model’s output conforms exactly to the provided schema. When strict mode is enabled, the model uses constrained decoding — at each token generation step, only tokens that could produce valid JSON according to the schema are considered. This eliminates malformed JSON, missing required fields, and type errors entirely.

Key Concept: Constrained Decoding

Instead of sampling from the full vocabulary at each step, constrained decoding restricts the token probability distribution to only tokens that keep the output on a valid path through the JSON Schema. This converts a “best effort” generation into a guaranteed-valid output, at the cost of slightly increased latency (roughly 5-15% on first call, with schema caching reducing subsequent calls).

Strict mode has limitations: it requires additionalProperties: false on all objects, does not support certain schema features like oneOf with mixed types, and all fields at every level must be listed in the required array (optional fields should use a type: ["string", "null"] union instead).

The Tool Call Lifecycle

Understanding the full lifecycle clarifies where JSON Schema fits:

  1. Registration: Your application sends tool definitions (with JSON Schemas) alongside the user message to the LLM API.
  2. Selection: The model reads tool names and descriptions to decide which tool (if any) to invoke.
  3. Argument Generation: The model generates a JSON object conforming to the tool’s parameter schema.
  4. Execution: Your application validates the JSON against the schema, then executes the corresponding function.
  5. Result Injection: The function result is sent back to the model as a tool message for final synthesis.

Validation on the Application Side

Even with strict mode, you should validate tool call arguments in your application layer. Libraries like ajv (JavaScript), jsonschema (Python), or pydantic (Python) can validate against the same schema you sent to the API. This is defense-in-depth: if you ever switch providers or use a model that doesn’t support strict mode, your validation layer catches malformed calls.

# Python example with Pydantic
from pydantic import BaseModel
from typing import Optional, Literal

class GetWeatherArgs(BaseModel):
    location: str
    unit: Optional[Literal["celsius", "fahrenheit"]] = None

# Validate tool call arguments
args = GetWeatherArgs.model_validate_json(tool_call.arguments)
result = get_weather(args.location, args.unit)

Pydantic models can also auto-generate JSON Schema via GetWeatherArgs.model_json_schema(), keeping your tool definitions and validation logic in perfect sync.

Practical Applications

Building an Agentic Toolbox

In production AI systems, you typically define a library of 5-50 tools that the model can choose from. Each tool has a JSON Schema definition. The model’s ability to select the right tool depends heavily on the quality of tool names and descriptions. Real-world tool libraries include database query tools, API integration tools, file manipulation tools, and communication tools (email, Slack, etc.).

Dynamic Tool Registration

Advanced systems generate tool schemas dynamically based on context. For example, a CRM agent might register different tools depending on the user’s permissions — an admin sees delete_account while a regular user does not. The JSON Schema definitions are constructed at runtime and passed to the API call.

Multi-Provider Compatibility

Because all major providers use JSON Schema for parameter definitions, you can build a provider-agnostic tool layer. Define your tools once using JSON Schema, then wrap them in the appropriate envelope (OpenAI’s function wrapper vs. Anthropic’s input_schema format) at the API call boundary. Libraries like LiteLLM and LangChain abstract this translation layer automatically.

Schema Generation from Code

In practice, most teams don’t write JSON Schema by hand. They define tool functions with type annotations in Python or TypeScript, then auto-generate the schema. Frameworks like Pydantic, Zod (TypeScript), and Instructor provide decorators or utility functions that produce valid JSON Schema from typed function signatures, reducing boilerplate and preventing schema drift.

Key Takeaways

Further Reading