Function Schema Design: Building Tool Contracts
A function schema is a contract between the model and your runtime. It explicitly tells the model what arguments a tool expects, their types, constraints, and defaults. A bad schema forces the model to guess—leading to hallucinated arguments, runtime errors, and wasted tokens. A precise schema acts as a guard rail, dramatically reducing invalid calls. By setting enums, minimum/maximum bounds, and pattern constraints, you give the model a clear target and allow it to ask for clarification rather than invent data.
What Is a Function Schema?
A function schema is a JSON Schema (Draft 2020-12) object that describes the input contract for a tool. It specifies: which arguments are required, their data types (string, number, integer, boolean, array, object), valid ranges, allowed values (enums), format constraints (regex patterns), and examples. The model reads this schema and uses it to decide which arguments to pass.
A minimal schema has three top-level fields: type (always "object"), properties (a dict of argument names to type definitions), and required (an array of argument names that must be provided). A professional schema adds description fields to each property, enum arrays to restrict values, bounds (minimum, maximum) for numbers, length constraints for strings, and examples to show expected inputs.
Example: A schema for a tool that fetches weather data:
{
"type": "object",
"properties": {
"city": {
"type": "string",
"description": "Name of the city (e.g., 'San Francisco', 'Tokyo')",
"minLength": 2,
"maxLength": 100
},
"country_code": {
"type": "string",
"description": "ISO 3166-1 alpha-2 country code (e.g., 'US', 'JP')",
"pattern": "^[A-Z]{2}$",
"examples": ["US", "GB", "FR"]
},
"units": {
"type": "string",
"description": "Temperature unit system",
"enum": ["celsius", "fahrenheit", "kelvin"],
"default": "celsius"
},
"include_forecast": {
"type": "boolean",
"description": "Include 7-day forecast if true",
"default": false
}
},
"required": ["city", "country_code"],
"additionalProperties": false
}
This schema tells the model: city and country_code are required; units has three valid options; include_forecast is optional and defaults to false. The model cannot invent a fourth units value—it must pick one of the three enum options or omit the field to use the default. Length and pattern constraints further reduce invalid inputs.
Schema Design Principles
Principle 1: Be explicit about required fields. The required array must list every argument the function cannot run without. Missing a required field causes a runtime error. Group related optional fields into sub-objects to reduce flat argument lists.
Principle 2: Add constraints, not just types. Constraints include minLength and maxLength for strings, minimum and maximum for numbers, pattern for regex matching, and enum for fixed sets. Each constraint shrinks the search space of valid inputs and reduces model hallucination.
Principle 3: Describe, don't assume. Every property must have a description field (40–100 words, imperative tone, with examples). The model reads this to understand intent. Never rely on property names alone.
Principle 4: Provide examples. In properties, add an examples array with 2–3 realistic values. The model uses examples as anchor points for generating arguments.
Principle 5: Use enums for fixed domains. If an argument can only take one of a finite set of values (e.g., HTTP methods: GET, POST, PUT, DELETE), use an enum array. Never ask the model to guess.
Principle 6: Forbid extra properties. Set "additionalProperties": false to prevent the model from adding made-up fields to the tool call. This is defensive and ensures the function signature matches.
Here is a more sophisticated example: a schema for a database query builder:
schema = {
"type": "object",
"properties": {
"table": {
"type": "string",
"description": "Name of the table to query (e.g., 'users', 'orders')",
"pattern": "^[a-z_][a-z0-9_]*$",
"maxLength": 64
},
"columns": {
"type": "array",
"items": {"type": "string"},
"description": "List of column names to select (e.g., ['id', 'email']). If empty, selects all columns.",
"examples": [["id", "name"], ["id", "email", "created_at"]],
"maxItems": 50
},
"where": {
"type": "object",
"description": "Filter conditions as key-value pairs (e.g., {'status': 'active'})",
"additionalProperties": {"type": ["string", "number", "boolean"]},
"maxProperties": 10
},
"limit": {
"type": "integer",
"description": "Maximum number of rows to return",
"minimum": 1,
"maximum": 10000,
"default": 100
},
"order_by": {
"type": "string",
"description": "Column name and direction (e.g., 'created_at DESC')",
"pattern": "^[a-z_][a-z0-9_]* (ASC|DESC)$",
"examples": ["created_at DESC", "id ASC"]
}
},
"required": ["table"],
"additionalProperties": false
}
This schema constrains every argument: table names match a snake_case pattern, column lists have a max of 50 items, limit is bounded to [1, 10000], and order_by must follow a specific syntax. The model cannot generate invalid queries because the schema forbids them.
Common Schema Patterns
Pattern 1: Enum selection. Use enum for tools with fixed modes:
{
"type": "object",
"properties": {
"action": {
"type": "string",
"enum": ["create", "read", "update", "delete"],
"description": "CRUD operation to perform"
}
},
"required": ["action"]
}
Pattern 2: Nested objects. For related arguments, nest them:
{
"type": "object",
"properties": {
"filter": {
"type": "object",
"properties": {
"date_start": {"type": "string", "format": "date"},
"date_end": {"type": "string", "format": "date"}
}
}
}
}
Pattern 3: Array of objects. For batch operations:
{
"type": "object",
"properties": {
"items": {
"type": "array",
"items": {
"type": "object",
"properties": {
"id": {"type": "integer"},
"quantity": {"type": "integer"}
},
"required": ["id", "quantity"]
},
"maxItems": 100
}
},
"required": ["items"]
}
Comparison: Weak vs. Strong Schemas
| Aspect | Weak Schema | Strong Schema |
|---|---|---|
| Description | Property names only | Detailed, with examples |
| Types | string, number | string with minLength, maxLength |
| Enums | None; model guesses | Fixed enum list |
| Bounds | Unbounded | minimum, maximum, maxItems |
| Pattern | None | Regex pattern for format |
| Examples | None | 2–3 realistic examples |
| Error rate | 20–40% invalid calls | <5% invalid calls |
Testing Your Schema
Before deploying, test your schema by:
- Validate against JSON Schema validators: Use an online tool like jsonschemavalidator.net or a Python library (
jsonschema) to ensure the schema itself is valid. - Test with a model: Call the model with a sample prompt and your tool definition. Inspect the generated tool calls for invalid arguments.
- Try edge cases: Ask the model to call the tool with boundary values (e.g., limit=0, an empty list, null fields) to see if it respects constraints.
- Measure error rates: In production, log every tool call and track the percentage that fail validation. Target <5%.
Here is a Python test snippet:
import json
from anthropic import Anthropic
client = Anthropic()
schema = {
"type": "object",
"properties": {
"amount": {
"type": "number",
"minimum": 0.01,
"maximum": 1000000
}
},
"required": ["amount"]
}
tools = [{"name": "transfer_money", "description": "Transfer money", "input_schema": schema}]
messages = [{"role": "user", "content": "Transfer $50"}]
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=256,
tools=tools,
messages=messages
)
for block in response.content:
if block.type == "tool_use":
print("Tool call:", block.name, block.input)
# Validate against schema
try:
jsonschema.validate(block.input, schema)
print("✓ Valid")
except jsonschema.ValidationError as e:
print("✗ Invalid:", e)
Key Takeaways
- Function schemas are JSON Schema contracts that tell the model what arguments are valid.
- Strong schemas include
enum,minimum/maximum,minLength/maxLength,pattern, andexamples. - Constraints reduce hallucination and invalid tool calls by 60–80% compared to weak schemas.
- Always set
requiredaccurately and useadditionalProperties: falsefor safety. - Test schemas with the model before production to verify they prevent invalid inputs.
Frequently Asked Questions
Can I make a schema too restrictive?
Yes, if you over-constrain, the model may refuse to call the tool because it cannot generate valid arguments. For example, if you set pattern: "^[A-Z]$" (exactly one uppercase letter), the model cannot pass a realistic name. Use constraints to eliminate invalid inputs, not to make the argument unusable.
What is the difference between enum and examples?
enum is a hard constraint—the model must pick one of the listed values. examples are hints; the model may generate a value not in the examples. Use enum for fixed sets (HTTP methods, status codes); use examples to show the format (dates, URLs, names).
Should I include the tool name in the schema?
No. The schema describes the input arguments only. The tool name is a separate field in the tool definition and is not part of the input schema.
What formats does JSON Schema support?
Common formats include date (YYYY-MM-DD), date-time (ISO 8601), email, uri, uuid, ipv4, ipv6, and hostname. The model does not validate these; they are hints for documentation and API integration. For enforcement, use pattern with a regex.
Can I set a default for a required field?
If a field is in the required array, it must be provided; a default is ignored for required fields. If you want a default, make the field optional (remove it from required) and set default in the property definition.