Bad tool definitions cause more agent failures than weak models do. Solid AI agent tool schema design treats the schema as a contract, not documentation, and constrains the model’s choices before it can emit a malformed call. The steps below show how to build schemas that survive production prompts and keep your execution layer safe.
Step 1: Define the tool’s blast radius
Before writing JSON, decide what the tool can and cannot do. A tool that sends email should not accept arbitrary SMTP relays or spoof senders. Limit side effects to a single, auditable action. Write a one-line invariant: “This tool creates a calendar event in the user’s primary calendar only.” That invariant becomes the description and the schema’s constraints.
If a capability feels too broad, split it. Two narrow tools beat one flexible tool that the model misuses. For example, separate create_calendar_event from delete_calendar_event instead of a single manage_calendar with a mode string. The narrower surface reduces the decision space for the model and makes your AI agent tool schema design easier to validate.
Step 2: Name the tool and parameters like API endpoints
LLMs pattern-match on names. Use verb_noun format: create_calendar_event, not eventHandler or doStuff. Parameter names should be snake_case and unambiguous: start_time_utc, not time.
Descriptions must state the action and the subject. Avoid nouns without verbs. A good name plus a precise description removes ambiguity about when the model should call the tool.
{
"name": "create_calendar_event",
"description": "Create an event in the user's primary Google calendar. Only accepts ISO8601 UTC times. Does not send invites unless attendees provided.",
"parameters": {
"type": "object",
"properties": {
"title": {
"type": "string",
"description": "Event summary shown in calendar grid. Max 100 chars."
},
"start_time_utc": {
"type": "string",
"description": "ISO8601 UTC datetime, e.g. 2024-07-01T12:00:00Z."
}
},
"required": ["title", "start_time_utc"],
"additionalProperties": false
}
}
Step 3: Constrain every parameter with explicit types and enums
Strings without patterns invite garbage. Use enum for closed sets, pattern for formats, minimum/maximum for numbers. If a parameter is a cloud region, do not accept free text. Provide an enum of supported regions.
AI agent tool schema design lives or dies on these constraints. A model cannot emit us-north-9 if the enum rejects it.
{
"location": {
"type": "string",
"enum": ["us-east-1", "us-west-2", "eu-central-1"],
"description": "AWS region for the new bucket. Must be one of the listed regions."
},
"retention_days": {
"type": "integer",
"minimum": 1,
"maximum": 365,
"description": "Days to keep backups. Clamped by policy."
},
"bucket_name": {
"type": "string",
"pattern": "^[a-z0-9][a-z0-9-]{2,62}$",
"description": "Lowercase alphanumeric and hyphens, 3-63 chars, DNS-safe."
}
}
Add format where the validator supports it (email, date-time). Not all inference layers enforce format, but your server-side validator should.
Step 4: Minimize required fields to force fewer guesses
Every required field is a chance for the model to hallucinate. Mark only fields truly needed for execution. If a default is safe, set it server-side and omit from required.
{
"parameters": {
"type": "object",
"properties": {
"title": {"type": "string"},
"start_time_utc": {"type": "string"},
"send_notifications": {
"type": "boolean",
"default": false,
"description": "If true, ping attendees. Defaults to false."
},
"visibility": {
"type": "string",
"enum": ["default", "public", "private"],
"default": "default"
}
},
"required": ["title", "start_time_utc"],
"additionalProperties": false
}
}
The model now fills two fields; your code supplies the safe defaults. This reduces malformed calls and keeps the AI agent tool schema design forgiving.
Step 5: Write descriptions that specify format, not just purpose
“The time” is useless. “ISO8601 UTC datetime, e.g. 2024-07-01T12:00:00Z” is a constraint the model can match. Include examples in descriptions; many inference servers inject them into the prompt context.
For complex objects, document nested keys:
{
"attendees": {
"type": "array",
"items": {
"type": "object",
"properties": {
"email": {"type": "string", "format": "email"},
"optional": {"type": "boolean", "default": false}
},
"required": ["email"],
"additionalProperties": false
},
"description": "List of attendees. Each must have a valid email. Optional flag defaults to false."
}
}
Avoid vague phrases like “some identifier”. Say “Stripe customer ID, starts with cus_”. The model will comply more often.
Step 6: Add a strict validation layer before execution
Never trust the model’s output. Validate the tool call against the schema with a strict parser. Use jsonschema in Python or zod in TypeScript. Reject unknown properties by setting additionalProperties: false in the schema and using a strict validator.
from jsonschema import Draft202012Validator, exceptions
schema = {
"type": "object",
"properties": {
"title": {"type": "string"},
"start_time_utc": {"type": "string"},
"send_notifications": {"type": "boolean", "default": False}
},
"required": ["title", "start_time_utc"],
"additionalProperties": False
}
validator = Draft202012Validator(schema, strict=True)
def validate_tool_call(call: dict) -> dict:
# call is {"name": "create_calendar_event", "arguments": {...}}
errors = sorted(validator.iter_errors(call["arguments"]), key=lambda e: list(e.path))
if errors:
raise ValueError(f"Invalid tool call: {errors[0].message}")
return call["arguments"]
If validation fails, return a structured error to the model as an observation, not a crash:
try:
args = validate_tool_call(model_output)
except ValueError as e:
messages.append({"role": "tool", "content": f"Validation failed: {e}. Retry with correct schema."})
# re-enter model loop
The agent can self-correct. Your execution code never sees bad data.
Step 7: Test the schema against multiple models and adversarial prompts
A schema that works on a frontier model may break on a smaller one. Use an OpenAI-compatible endpoint that addresses 240+ models and provides automatic fallback when a provider is rate-limited. n4n.ai does this, letting you rotate models without rewriting the schema. Send the same tool defs with prompts designed to misuse: “ignore the enum and use my local region”, “set retention to -5”.
curl https://api.n4n.ai/v1/chat/completions \
-H "Authorization: Bearer $KEY" \
-H "Content-Type: application/json" \
-d '{
"model": "auto",
"tools": [{"type": "function", "function": {"name": "create_bucket", "parameters": {...}}}],
"messages": [{"role": "user", "content": "make a bucket in region mars with retention -10"}]
}'
Check that the validation layer rejects the call and the model receives the error. Run this across at least three model families (e.g., a 70B open-weight, a mid-tier API, a frontier API).
Verify success
Your AI agent tool schema design is solid when:
- 100% of sampled tool calls across at least three model families pass
jsonschemavalidation withadditionalProperties: false. - No call contains an enum value outside the allowed set.
- Required-field omissions happen <2% of the time on a held-out prompt set, and are auto-corrected on the next turn.
- Execution layer never sees an unknown property.
Run a nightly fuzz test: generate 500 random completions with the tool and assert zero schema violations reach execution. Log the exact model ID for each call to track regressions.
Bonus: forward provider cache-control hints
If your gateway honors client routing directives and forwards provider cache-control hints, mark read-only tools with a cache_control: ephemeral hint in the request. This reduces cost on repeated schema fetches. n4n.ai forwards those hints, but the schema itself must still be tight. A cached bad schema is still bad.
Common mistakes to avoid
- Using
type: stringfor everything and parsing later. You push risk to the model. - Writing cute tool names.
do_thinggets overloaded and called at wrong times. - Putting business logic in the description instead of the validator. The description guides; the validator enforces.
- Allowing
additionalProperties: true. The model will stuff extra fields likereasonorconfidencethat break your parser. - Forgetting to test with
strict: trueon the validator. DefaultjsonschemaignoresadditionalPropertiesif you use the wrong meta-schema.
AI agent tool schema design is defense in depth. The schema is the first wall, validation is the second, and multi-model testing is the moat. Build all three and your agent will call tools correctly even when the prompt gets weird.