Tool Definitions: Function Calling in Agents
A tool definition is the contract between an AI agent and the functions it can call. Written as a JSON Schema, it describes what inputs a function accepts, what each parameter means, which are required, and what constraints apply. Precise tool definitions are critical: vague descriptions lead to hallucinated parameters; missing type information causes validation errors; unclear constraints allow the agent to misuse tools. This article teaches you to design tool schemas that are clear, safe, and easy for models to understand.
Function calling is the mechanism by which an agent invokes a tool: the model examines tool definitions, reasons about which tool fits the user's request, generates the tool name and parameters, and your code executes that call. This is distinct from prompt engineering (where you try to coerce text generation) and from code generation (where the model writes code you then eval). Function calling is structured, safe, and deterministic.
JSON Schema Fundamentals for Tools
A tool schema is a JSON object with specific keys:
{
"name": "get_user_by_id",
"description": "Fetch user profile information from the database",
"input_schema": {
"type": "object",
"properties": {
"user_id": {
"type": "integer",
"description": "The unique user ID (1-1000000)"
},
"include_history": {
"type": "boolean",
"description": "If true, include purchase history; default false",
"default": false
}
},
"required": ["user_id"]
}
}
The input_schema follows JSON Schema Draft 7 (a widely supported specification). Key properties:
type: The data type:string,integer,number,boolean,array,object, nulldescription: Plain English describing what this field is and how to use itenum: A list of allowed values (enforces choice from a fixed set)minimum/maximum: Numeric constraintsminLength/maxLength: String length constraintspattern: A regex the string must matchdefault: A default value if the parameter is omitted
When the model generates a tool call, the API validates the parameters against this schema. If the model provides invalid types or violates constraints, the API rejects the call and can optionally ask the model to retry. This prevents garbage data from reaching your functions.
Designing Effective Tool Parameters
Good tool definitions make the model's job easier. Compare a vague definition with a precise one:
Poor:
{
"name": "search",
"description": "Search for something",
"input_schema": {
"type": "object",
"properties": {
"q": { "type": "string" }
},
"required": ["q"]
}
}
The model won't know what it can search (products? users? articles?), how to structure complex queries, or what format results will be. It might hallucinate parameters.
Good:
{
"name": "search_products",
"description": "Search the product catalog by keyword, category, or price range",
"input_schema": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Search keywords, e.g. 'blue running shoes' or 'laptop under 1000'"
},
"category": {
"type": "string",
"enum": ["electronics", "clothing", "home", "sports"],
"description": "Filter by category (optional)"
},
"max_price": {
"type": "number",
"minimum": 0,
"maximum": 100000,
"description": "Maximum price in dollars (optional)"
},
"sort_by": {
"type": "string",
"enum": ["relevance", "price_low_to_high", "price_high_to_low", "rating"],
"description": "Sort results by this criterion (default: relevance)"
}
},
"required": ["query"]
}
}
This tells the model: it's a product search (not users or articles), it accepts a text query, offers filtering by category and price, and explains what sort options exist. The model can now reason accurately about when and how to use this tool.
Handling Complex Parameter Types
Some tools need nested objects or arrays. For example, a batch-processing tool might accept multiple items:
tools = [
{
"name": "batch_translate",
"description": "Translate text to one or more target languages",
"input_schema": {
"type": "object",
"properties": {
"texts": {
"type": "array",
"items": {
"type": "string",
"minLength": 1,
"maxLength": 5000
},
"minItems": 1,
"maxItems": 10,
"description": "Array of texts to translate (1-10 items)"
},
"source_language": {
"type": "string",
"enum": ["en", "es", "fr", "de", "ja"],
"description": "Source language code"
},
"target_languages": {
"type": "array",
"items": {
"type": "string",
"enum": ["en", "es", "fr", "de", "ja"]
},
"minItems": 1,
"maxItems": 3,
"description": "Target language codes (1-3)"
}
},
"required": ["texts", "source_language", "target_languages"]
}
}
]
Here, texts is an array of strings with length constraints. target_languages is an array of specific values. These constraints guide the model and prevent it from sending malformed data.
Type Safety in Tool Execution
In Python, validate the parameters before executing a tool. Here's a pattern that safely parses and validates tool calls:
import json
from typing import Any, Callable, Dict
def execute_tool_safely(
tool_name: str,
tool_input: Dict[str, Any],
tool_registry: Dict[str, Callable]
) -> str:
"""Execute a tool with type checking and error handling."""
if tool_name not in tool_registry:
return f"Error: Unknown tool '{tool_name}'"
func = tool_registry[tool_name]
try:
# Python's type hints don't enforce at runtime, but you can use
# a library like pydantic for validation
result = func(**tool_input)
return str(result)
except TypeError as e:
return f"Error: Invalid parameters for {tool_name}: {e}"
except ValueError as e:
return f"Error: Value error in {tool_name}: {e}"
except Exception as e:
return f"Error: Unexpected error in {tool_name}: {e}"
For stricter validation, use Pydantic models:
from pydantic import BaseModel, Field
class GetUserInput(BaseModel):
user_id: int = Field(..., ge=1, le=1000000, description="User ID")
include_history: bool = Field(False, description="Include purchase history")
def get_user(user_id: int, include_history: bool = False) -> dict:
# Actual implementation
return {"id": user_id, "name": "Alice", "history": [] if not include_history else [...]}
# In your agent loop:
try:
validated_input = GetUserInput(**tool_input)
result = get_user(**validated_input.dict())
except Exception as e:
result = f"Validation error: {e}"
Pydantic validates types, ranges, and string patterns at runtime before your function is called.
Common Tool Definition Patterns
Read-only tools (safe to call repeatedly):
{
"name": "get_current_time",
"description": "Return the current date and time in UTC",
"input_schema": {
"type": "object",
"properties": {},
"required": []
}
}
No parameters needed; the tool is deterministic. Safe for agents to call multiple times.
Write/mutation tools (require careful handling):
{
"name": "delete_account",
"description": "IRREVERSIBLE: Delete a user account and all associated data",
"input_schema": {
"type": "object",
"properties": {
"user_id": { "type": "integer" },
"confirmation_code": {
"type": "string",
"description": "Must be a valid confirmation code sent to user email"
}
},
"required": ["user_id", "confirmation_code"]
}
}
Mutation tools should require explicit confirmation parameters and clear descriptions of side effects.
Filtering/search tools:
{
"name": "search_database",
"description": "Query the database with filters and pagination",
"input_schema": {
"type": "object",
"properties": {
"table": {
"type": "string",
"enum": ["users", "orders", "products"],
"description": "Table to search"
},
"filters": {
"type": "object",
"description": "Key-value filters, e.g. {'status': 'active'}",
"additionalProperties": true
},
"limit": {
"type": "integer",
"minimum": 1,
"maximum": 100,
"default": 10
}
},
"required": ["table"]
}
}
This pattern allows flexible filtering without predefined all possible fields.
Key Takeaways
- A tool definition is a JSON Schema describing a function's name, purpose, and input parameters
- Clear descriptions and type constraints guide the model and prevent hallucinated parameters
- Use
enumto restrict choices; useminimum,maximumfor numeric ranges; usepatternfor string validation - Validate parameters in your code before execution using try-except or Pydantic models
- Write/mutation tools should require explicit confirmation to prevent accidental misuse
- Tool definitions are data; you can build, modify, or generate them dynamically at runtime
Frequently Asked Questions
Can a tool have no parameters?
Yes. Set "properties": {} and "required": []. This is useful for stateless, deterministic tools like get_current_time.
What if the model passes extra parameters not in the schema?
The API ignores them by default. You can enforce strict validation by rejecting calls with extra keys, but most systems just ignore unknowns.
How should I name tool parameters?
Use camelCase or snake_case consistently. Make names specific and descriptive: not q but query or search_term. This helps the model understand what it's doing.
Can tool definitions be updated without restarting the agent?
Yes, they're data passed with each request. Update the tools list and send a new API call. The model will see the new definitions immediately.