Skip to content
Back to Blog
July 23, 2026

Tool Schemas That LLMs Actually Understand

Poorly written JSON Schema tool definitions cause LLM hallucination. Precise descriptions, enum constraints, required fields, and examples maximize correct tool invocations.

Tool Schemas That LLMs Actually Understand
M

The flight search hallucinated an airport code. The agent called our search tool with origin "NYC" — a metropolitan area code, not a valid IATA airport code. The search failed. The agent tried "New York." Failed again. Then "NewYork." Then "NEWYORK." Four failed attempts before it gave up.

The problem was not the LLM. The problem was our tool schema. The `origin` field was typed as `string` with no constraints, no format specification, and no examples. The LLM had to guess what format we expected, and it guessed wrong.

When we added a regex pattern (`^[A-Z]{3}$`), example values ("JFK", "LAX", "LHR"), and a description that explicitly said "3-letter IATA airport code," the hallucination rate for that field dropped to near zero. The LLM knew exactly what to send because the schema told it exactly what to send.

Tool schemas are documentation for LLMs. They are the only thing the model reads before calling your API. If the schema is vague, the model's calls will be vague. If the schema is precise, the calls will be precise. There is a direct, measurable relationship.

Writing effective descriptions

The tool-level description tells the LLM what the tool does and when to use it. This is the single most important piece of text in your schema.

Bad: "Searches for flights."

Better: "Search for available flights between two airports on a specific date. Returns ranked flight offers with pricing, duration, and airline details."

The better description tells the model three things: what the tool does (searches for flights), what inputs it needs (airports and a date), and what it returns (ranked offers with specific data). The model uses this to decide whether this is the right tool for the user's request.

We found that descriptions averaging two to three sentences produce the highest accuracy. One-line descriptions do not give the model enough context to distinguish between similar tools. Paragraph-length descriptions overflow the model's attention, and it starts ignoring details.

For parameter descriptions, be concrete. Instead of "The departure location," write "IATA airport code for departure airport, e.g., 'JFK' for New York John F. Kennedy, 'LHR' for London Heathrow." The examples anchor the model's understanding of the expected format.

Enum constraints

Enums are the highest-impact constraint you can add to a schema. They restrict a field to a fixed set of valid values, eliminating the possibility of hallucination for that field entirely.

Without an enum:

"cabinClass": {
 "type": "string",
 "description": "The desired cabin class"
}

The model might send "Economy", "economy", "ECONOMY", "coach", "standard", "Y class", or any number of variations.

With an enum:

"cabinClass": {
 "type": "string",
 "enum": ["economy", "premium_economy", "business", "first"],
 "description": "Preferred cabin class"
}

The model can only send one of the four valid values. Hallucination for this field is impossible.

In our testing, adding enum constraints to previously unconstrained fields reduced hallucinated values by over 80%. The improvement is immediate and consistent across different LLM models.

Use enums for any field with a finite set of valid values: cabin classes, sort orders, currencies, status filters, event types. The more fields you constrain, the more accurate the agent's tool calls become.

Required versus optional fields

Supporting diagram

Marking fields as `required` tells the model that it must provide a value. Marking them as optional tells the model it can skip them. Getting this wrong causes two types of errors.

If an optional field is marked required, the model will hallucinate a value when it does not have one from the user. The user asks "find me flights to Paris" without specifying a cabin class, but the model sees `cabinClass` as required and sends "economy" as a guess. This might be correct, but it might override the user's actual preference.

If a required field is marked optional, the model might omit it. The user asks for flights but does not specify a date. The model sends the request without a date. The API returns a validation error. The agent has to recover and ask the user for the date.

The correct approach: mark fields as required only if the API will reject the request without them. Mark everything else as optional with documented defaults. The agent should ask the user for information rather than guessing at required fields.

Example values

Examples anchor the model's understanding of expected formats. They are especially valuable for fields where the type alone is ambiguous.

A `string` type could be anything. A `string` with description "IATA airport code" and example "JFK" is unambiguous. The model sees the example and understands the format — three uppercase letters, representing a specific airport.

Include examples in the description text rather than relying on the JSON Schema `examples` keyword, because some LLM frameworks parse descriptions but ignore other schema annotations. "Date in ISO 8601 format, e.g., '2026-06-15'" puts the example right where the model reads it.

For complex types, show the full structure:

"passengers": {
 "type": "object",
 "description": "Passenger counts. Example: {\"adults\": 2, \"children\": 1, \"infants\": 0}",
 "properties": { ... }
}

Nested objects and depth

LLM accuracy degrades with schema depth. A flat schema with ten top-level fields is easier for a model to fill correctly than a deeply nested schema with three levels of objects.

This does not mean you should flatten everything. It means you should be thoughtful about nesting. Group related fields into objects when it makes the schema more readable (passengers, preferences, dates). But do not nest for nesting's sake.

In our experience, two levels of nesting is the sweet spot. The top level contains the main parameters. One level down contains grouped sub-parameters (passenger counts, preference options). Going three or more levels deep starts to produce errors where the model puts values at the wrong level or omits inner objects entirely.

If your API requires deep nesting, consider providing a flattened alternative in the tool schema. The tool handler can restructure the flat parameters into the nested format the API expects. This gives the model a simpler schema to work with while preserving the API's data structure.

Evaluation-driven iteration

We run evaluation datasets that measure correct tool invocation rates across our 70+ tools. Each test case provides a user message and verifies that the agent selects the correct tool with the correct parameters.

When a tool's invocation accuracy drops below our threshold, we investigate the schema. Usually the fix is one of: adding enum constraints, improving the description, adding examples, or reducing nesting depth. We change the schema, re-run the evaluation, and measure the improvement.

This evaluation-driven approach makes schema design empirical rather than intuitive. We do not guess at what the model understands. We measure it and iterate based on data.

The evaluation suite runs on every commit that touches tool definitions. If a schema change degrades invocation accuracy, the CI pipeline flags it before it ships. This discipline keeps our tool schemas tight as the toolset grows.

Tool schemas are not documentation you write once and forget. They are a living interface between your API and the AI agents that consume it. Invest in them with the same rigor you invest in your API endpoints, and the agents will reward you with accurate, reliable behavior.


Nowah is an AI travel agent that searches and books real flights and hotels through conversation — no filters, no thirty open tabs. Plan your next trip.

Share this article

Ready to Plan with Nowah?

Bring the idea. Nowah will help turn it into a trip.

Try Nowah