AI/TLDR

How to Define a Function Schema: JSON Schema for Tool Calls

Write tool definitions — names, descriptions, and JSON Schema parameters — that the model reads correctly and calls with valid arguments.

BEGINNER14 MIN READUPDATED 2026-06-12

In plain English

Before a model can call one of your tools, it needs to know three things: what the tool is called, what it does, and what arguments to pass. A function schema is the small JSON document you write to answer all three questions. Think of it as the instruction card you hand a new intern before sending them to handle a task: the card names the task, explains its purpose in a sentence, and lists every piece of information they must gather (and which pieces are truly required versus nice-to-have).

The model never reads your source code. It only reads the schema you provide at request time. Everything the model knows about your tool — its capabilities, its constraints, the names and types of its inputs — comes entirely from what you write in that schema. If the schema is vague, the model fills in the blanks with guesses. If the schema is precise, the model passes exactly the arguments your code expects.

The parameter section of a schema is a standard JSON Schema object — the same format used by API validators, code generators, and form libraries. JSON Schema defines types (string, number, boolean, array, object), constraints (enum, minimum, maximum, pattern), and a required array that lists which fields must be present. You don't need to know all of JSON Schema to write good tool definitions; the subset used for function calling is small and learnable in an afternoon.

Why it matters

The schema is the model's only map to your tool. If the map is wrong, the model will call the tool incorrectly — or not at all. There are two failure modes that show up constantly in practice. The first is wrong arguments: the model passes a string where you expected a number, or omits a field it should have included, because the schema didn't make the constraint clear. The second is wrong tool selection: the model picks the wrong tool from a list of several, or skips calling a tool entirely, because the description wasn't clear enough about what the tool does.

Research on tool-augmented models has found that precise parameter descriptions can improve the accuracy of argument values by more than 30% compared to vague descriptions. That's not a small margin — it's the difference between a tool that works reliably in production and one that requires constant babysitting.

Well-written schemas also reduce the number of follow-up turns. If the model knows a parameter is required and understands its format, it will gather that information before calling the tool rather than calling with incomplete arguments and getting an error back. This keeps your agentic loops short and your users happy.

How it works

A function schema has three top-level fields: name, description, and parameters. The parameters field is itself a JSON Schema object — always "type": "object" at the top level, with a properties map and a required array.

Here is a minimal but complete example for a weather-lookup tool, written in the format both OpenAI and Anthropic accept (OpenAI wraps it in {"type": "function", "function": {...}}; Anthropic uses it directly with input_schema instead of parameters):

Minimal function schema — weather lookupjson
{
  "name": "get_weather",
  "description": "Returns the current temperature and weather condition for a city. Use this when the user asks about current weather.",
  "parameters": {
    "type": "object",
    "properties": {
      "city": {
        "type": "string",
        "description": "City name, e.g. Tokyo or London"
      },
      "unit": {
        "type": "string",
        "enum": ["celsius", "fahrenheit"],
        "description": "Temperature unit. Defaults to celsius."
      }
    },
    "required": ["city"],
    "additionalProperties": false
  }
}

In this schema, city is required — the model must include it, and the API will reject the call if it doesn't. The unit parameter is optional: it's declared in properties but absent from required. The model may include it if the user specified a preference, but it can safely omit it and let your code apply a default.

How the model uses the schema

When you include a tools array in your API request, the provider converts your schema definitions into a special instruction block that becomes part of the model's context — you don't write this prompt yourself, the API constructs it. The model reads it the same way it reads a system prompt, understanding the available tools and their signatures before it starts generating a response.

When the model decides to call a tool, it emits a structured tool call object — not prose — containing the tool name and a JSON object of arguments that should conform to your schema. The API returns this as a special message role (tool_calls in OpenAI's format, tool_use content block in Anthropic's). Your code intercepts it, runs the actual function, and returns the result in a follow-up message. The model then continues generating its response using that result.

Field-by-field reference

The name field

Use a concise snake_case identifier that describes the action: get_weather, search_documents, send_email. OpenAI limits function names to 64 characters. Avoid generic names like tool1 or helper — the name appears in the model's context and influences tool selection when multiple tools are available. Action verbs first: get_, search_, create_, update_, delete_.

The description field

This is the single most impactful field for correct behavior. Start with an action verb: "Fetches the current temperature...", "Searches a product catalog...", "Sends an email to...". Include when to use it — especially if you have multiple similar tools. If the tool has important limitations, state them here: "Only covers current conditions, not forecasts." Aim for two to four sentences.

Parameter types

JSON Schema typeUse forExample
stringText, identifiers, dates as text"city": {"type": "string"}
numberAny numeric value including decimals"temperature": {"type": "number"}
integerWhole numbers only"page": {"type": "integer", "minimum": 1}
booleanTrue/false flags"include_details": {"type": "boolean"}
arrayLists of values"tags": {"type": "array", "items": {"type": "string"}}
objectNested structured data"address": {"type": "object", ...}

Constraining values with enum

Whenever a parameter has a fixed set of valid values, use enum. This is the most reliable way to prevent the model from inventing a value. Instead of asking for a string named sort_order and hoping the model writes "asc" or "desc", declare "enum": ["asc", "desc"] and the model will always pick one of those two values.

Using enum to constrain a parameterjson
"status": {
  "type": "string",
  "enum": ["open", "in_progress", "closed"],
  "description": "Filter tickets by status. Use 'open' for unresolved issues."
}

Required vs optional parameters

List every parameter that your function cannot operate without in the required array. Parameters that have sensible defaults or are truly optional should be omitted from required. Make the split deliberate: if a parameter is omitted from required, the model may skip it even when the user's request would benefit from it — so only mark things optional when your code can handle the absence gracefully.

required vs optionaljson
{
  "type": "object",
  "properties": {
    "query":       { "type": "string", "description": "The search query text" },
    "max_results": { "type": "integer", "description": "Max items to return (default 10)", "minimum": 1, "maximum": 50 },
    "language":    { "type": "string", "enum": ["en", "fr", "de", "ja"], "description": "Filter results by language" }
  },
  "required": ["query"],
  "additionalProperties": false
}

OpenAI vs Anthropic schema format

The JSON Schema content is essentially identical across providers, but the wrapper structure differs slightly. Knowing the exact shape each provider expects saves debugging time when you first wire up a new API.

OpenAI — tools array formatpython
import openai

client = openai.OpenAI()

tools = [
    {
        "type": "function",
        "function": {
            "name": "get_weather",
            "description": "Returns current weather for a city.",
            "parameters": {
                "type": "object",
                "properties": {
                    "city": {
                        "type": "string",
                        "description": "City name, e.g. Paris"
                    },
                    "unit": {
                        "type": "string",
                        "enum": ["celsius", "fahrenheit"]
                    }
                },
                "required": ["city"],
                "additionalProperties": False
            },
            "strict": True
        }
    }
]

response = client.chat.completions.create(
    model="gpt-4o",
    messages=[{"role": "user", "content": "What's the weather in Tokyo?"}],
    tools=tools
)
Anthropic — input_schema formatpython
import anthropic

client = anthropic.Anthropic()

tools = [
    {
        "name": "get_weather",
        "description": "Returns current weather for a city.",
        "input_schema": {
            "type": "object",
            "properties": {
                "city": {
                    "type": "string",
                    "description": "City name, e.g. Paris"
                },
                "unit": {
                    "type": "string",
                    "enum": ["celsius", "fahrenheit"]
                }
            },
            "required": ["city"]
        }
    }
]

response = client.messages.create(
    model="claude-opus-4-5",
    max_tokens=1024,
    tools=tools,
    messages=[{"role": "user", "content": "What's the weather in Tokyo?"}]
)
FeatureOpenAIAnthropic (Claude)
Wrapper keyfunction inside {"type":"function",...}Tool object directly
Schema keyparametersinput_schema
Strict mode"strict": true on the functionNot required; additionalProperties optional
Strict requires all fields in requiredYes, when strict: trueNo
Name limit64 characters64 characters

Common pitfalls

Most function-calling bugs trace back to one of five mistakes in the schema. Each one is easy to prevent once you know to look for it.

Vague descriptions

A description like "Does the thing" or "Handles user requests" gives the model nothing to work with. The model uses your description to decide both whether to call the tool and how to populate the arguments. Descriptions like "Fetches live stock price for a ticker symbol. Use this when the user asks for a current price or recent quote, not for historical data." are unambiguous and tell the model exactly when the tool applies.

Omitting description on parameters

Every parameter should have its own description field inside properties. The type alone is not enough. A string named date could mean today's date, a past date, a future date, an ISO 8601 timestamp, or a relative expression like "last week". Write the description: "Date in YYYY-MM-DD format, e.g. 2025-03-15. Use today's date if the user doesn't specify."

Over-nesting the schema

Deeply nested objects slow down the model and make argument construction more error-prone. If you find yourself writing parameters.filters.date_range.start, consider flattening: date_start and date_end as top-level strings are easier for the model to reason about and easier for your code to validate.

Not setting additionalProperties: false

Without "additionalProperties": false, the model can include extra fields that your code doesn't expect. This is especially problematic when you pass the arguments directly to a database query or external API. Setting additionalProperties: false closes the schema and prevents unexpected keys. It's also required when you enable strict mode on OpenAI.

Too many tools at once

Providing a large list of tools increases the chance of wrong tool selection. As a rule of thumb, a model can reliably choose among five to ten well-described tools. If you have more, group related tools, use an agent architecture that routes to specialized sub-agents, or dynamically inject only the relevant tools for each user message.

Going deeper

Once you've mastered the basics, a few advanced techniques let you squeeze more reliability and flexibility out of your schemas.

Generating schemas from types

Maintaining JSON schemas by hand is tedious and error-prone. In Python, the Pydantic library can generate a JSON Schema from a BaseModel definition: MyModel.model_json_schema(). In TypeScript, Zod does the same: zodToJsonSchema(mySchema). This means your function signature and your schema definition stay in sync automatically — change the Python type hint and the schema updates with it.

Auto-generate a schema from a Pydantic modelpython
from pydantic import BaseModel, Field
from typing import Literal

class WeatherParams(BaseModel):
    city: str = Field(description="City name, e.g. Tokyo")
    unit: Literal["celsius", "fahrenheit"] = Field(
        default="celsius",
        description="Temperature unit"
    )

# Use in your tools array:
schema = WeatherParams.model_json_schema()
print(schema)
# => {"properties": {"city": {...}, "unit": {...}}, "required": ["city"], ...}

Strict mode and its requirements

OpenAI introduced "strict": true on function definitions starting with gpt-4o-2024-08-06. When strict mode is on, the API uses constrained decoding to guarantee that the model's output exactly matches your schema — no extra keys, no wrong types. The trade-off: every field in properties must be in required, all nested objects must also have "additionalProperties": false, and some less-common JSON Schema keywords (like if/then) are not supported. For most production use cases the guarantee is worth the constraint.

Tool choice control

Both OpenAI and Anthropic let you control whether the model is forced to use a tool. OpenAI's tool_choice parameter accepts "auto" (model decides), "none" (no tool calls), or {"type": "function", "function": {"name": "..."}} (force a specific function). Anthropic's equivalent is tool_choice with {"type": "auto"}, {"type": "any"}, or {"type": "tool", "name": "..."}. Forcing a specific tool is useful when you want structured data extraction: define a tool for the schema you want, force the model to call it, and ignore the tool-call mechanics — you're just using the schema as a parsing harness.

Parallel tool calls

Modern frontier models can emit multiple tool call objects in a single response turn — for example, calling get_weather(city="Tokyo") and get_weather(city="London") simultaneously. Your code should handle an array of tool calls, run them in parallel when possible, and return all results before the model continues. This is enabled by default on OpenAI and can be disabled with "parallel_tool_calls": false if your tools have ordering dependencies.

FAQ

What is the difference between parameters and input_schema in tool definitions?

parameters is OpenAI's key name; input_schema is Anthropic's. Both hold exactly the same JSON Schema object — {"type": "object", "properties": {...}, "required": [...]}. The JSON Schema content inside is interchangeable; only the wrapper key differs between providers.

Does every parameter need a description, or just the tool itself?

Every parameter should have its own description field inside properties. The parameter name and type alone are rarely enough — the model uses the description to understand acceptable values, formats (e.g. ISO 8601 for dates), and when to include the argument at all. Missing parameter descriptions are a leading cause of wrong argument values.

What happens if I put a parameter in properties but not in required?

The model treats it as optional and may omit it from the call. Your code must handle the case where the key is absent from the arguments object. If the parameter has a sensible default, omitting it from required is fine — just document the default in the description. If the function genuinely cannot run without it, add it to required.

How do I stop the model from inventing values for an enum parameter?

Declare "enum": ["value1", "value2"] directly on the property. The model is constrained to the values you list and will not generate anything outside that set. This is the most reliable guard against hallucinated parameter values.

When should I use strict mode on OpenAI?

Use strict mode whenever you need a hard guarantee that the arguments match your schema exactly — production pipelines, database writes, external API calls. The cost is that all properties must be in required and additionalProperties must be false on every nested object. It's almost always worth enabling for reliability.

Can I define a tool that takes no parameters?

Yes. Set "parameters": {"type": "object", "properties": {}} with an empty properties object and an empty required array. The model will call the tool with an empty arguments object. A common use case is a get_current_datetime tool that always returns the current server time with no inputs needed.

Further reading