◢ build log

Designing Effective Tool Schemas for AI Agents

Learn how to design robust tool schemas for AI agents to prevent invalid function calls and improve reliability in your AI applications.

Building AI agents means giving them tools. These tools are functions your agent can call to interact with the outside world. But a common problem arises: agents often make invalid tool calls. They might hallucinate arguments, use incorrect data types, or omit required fields.

The solution lies in well-designed tool schemas. A schema acts as a contract, telling the agent exactly what a tool expects. When an agent understands this contract clearly, it makes fewer mistakes. This is about building reliable AI systems, not just functional ones.

The Problem: LLMs and Tool Calling Errors

Large Language Models (LLMs) are powerful. They can understand natural language and generate code-like structures. But they are not compilers or strict type checkers. An LLM predicts the next token based on its training data and the context. When it comes to calling a tool, it tries to generate JSON that matches a schema description.

However, if the schema is vague or incomplete, the LLM will guess. It might invent parameters, use an incorrect format for a date, or pass a single string where an array is expected. These errors lead to runtime failures in your agent's execution pipeline. You get invalid JSON, missing arguments, or type mismatches. This breaks the agent's workflow and requires complex error handling or retries.

Why Clear Schemas are Critical

Think of a tool schema as an API specification for your agent. Just as developers need clear API docs to integrate correctly, your LLM agent needs precise instructions to use its tools.

An effective schema does more than just define parameters. It guides the LLM towards generating valid and meaningful arguments. This reduces the cognitive load on the LLM and minimizes the chances of it going off-script. It's about constraint and clarity.

Principles of Effective Schema Design

When you design a tool schema, focus on these principles:

1. Precise Descriptions

Every tool and every parameter needs a clear, unambiguous description. This is the primary way you communicate with the LLM. Avoid jargon where plain language works. Explain the purpose of the parameter, not just its name.

Consider due_date. Is it a date string? A timestamp? A relative term like "tomorrow"? A precise description clarifies this:

"description": "The exact date and time the task is due, in ISO 8601 format (e.g., '2023-10-27T14:30:00Z')."

2. Strict Typing

Always specify the type of each parameter. JSON Schema offers basic types: string, integer, number, boolean, array, object, null. Use them correctly.

  • string: For text. Add format (like date-time, email, uri) or pattern (regex) for stricter validation hints.
  • integer/number: For numerical values. Use minimum and maximum to define ranges.
  • boolean: For true/false values. Don't let the LLM guess with strings like "yes" or "no".
  • array: For lists of items. Always specify items to define the type of elements within the array. This is crucial for inputs like a list of email addresses.
  • object: For nested structures. Define its properties and required fields.

3. Explicit required Fields

Clearly mark which parameters are required. The LLM needs to know what it absolutely must provide. If a parameter is optional, the LLM might omit it, which is fine. If it's required and missing, your tool call will fail.

4. Enums for Constrained Choices

If a parameter has a limited set of valid values, use enum. This is incredibly powerful. Instead of letting the LLM invent a priority level like "super urgent", you can constrain it to ["low", "medium", "high"].

{
  "type": "string",
  "description": "The priority level of the task.",
  "enum": ["low", "medium", "high"]
}

5. Avoid Ambiguity and Over-Generality

If a parameter can be interpreted in multiple ways, it will be. Break down complex parameters into simpler ones. Instead of a single details string for an event, provide separate fields for description, location, and attendees.

Example: Building a create_event Tool Schema

Let's look at a hypothetical create_event tool. We want to create a calendar event with a title, start/end times, location, and attendees.

A Less Effective Schema

{
  "name": "create_event",
  "description": "Create a new calendar event.",
  "parameters": {
    "type": "object",
    "properties": {
      "title": { "type": "string" },
      "time": { "type": "string" },
      "location": { "type": "string" },
      "attendees": { "type": "string" }
    }
  }
}

Problems with this schema:

  • time is ambiguous. Is it a start time? A duration? What format?
  • attendees as a single string is hard to parse into a list of emails.
  • No fields are required, so the LLM might call it with no information.
  • Descriptions are minimal.

An Effective Schema

Now, let's improve it:

{
  "name": "create_event",
  "description": "Creates a new calendar event with a title, start time, end time, location, and optional attendees. Use this tool to schedule meetings or appointments.",
  "parameters": {
    "type": "object",
    "properties": {
      "title": {
        "type": "string",
        "description": "The title or subject of the event. This is a concise summary."
      },
      "start_time": {
        "type": "string",
        "format": "date-time",
        "description": "The ISO 8601 formatted start time of the event, e.g., '2023-10-27T10:00:00Z'. Ensure the timezone is included."
      },
      "end_time": {
        "type": "string",
        "format": "date-time",
        "description": "The ISO 8601 formatted end time of the event, e.g., '2023-10-27T11:00:00Z'. Must be after start_time."
      },
      "location": {
        "type": "string",
        "description": "The physical location of the event, such as 'Conference Room A' or 'Zoom Link'."
      },
      "attendee_emails": {
        "type": "array",
        "description": "A list of email addresses for attendees. Each email must be a valid format.",
        "items": {
          "type": "string",
          "format": "email",
          "description": "An individual attendee's email address."
        },
        "minItems": 0
      },
      "is_all_day": {
          "type": "boolean",
          "description": "Set to true if this is an all-day event. If true, start_time and end_time should represent dates only (e.g., '2023-10-27T00:00:00Z' to '2023-10-28T00:00:00Z')."
      }
    },
    "required": ["title", "start_time", "end_time"]
  }
}

This improved schema:

  • Splits time into start_time and end_time, both with format: "date-time" and clear descriptions.
  • Uses attendee_emails as an array of strings, with format: "email" for each item.
  • Marks title, start_time, and end_time as required.
  • Adds is_all_day as a boolean to handle different event types.
  • Provides much more detail in all descriptions, guiding the LLM explicitly.

Iteration and Testing

Schema design is not a one-shot process. You need to iterate. Test your schemas with a variety of prompts. See how the LLM interprets different requests. Pay attention to edge cases. The goal is to make it as difficult as possible for the LLM to make an invalid call.

Even with a perfect schema, robust error handling in your agent's execution layer is still necessary. Schemas are the first line of defense; runtime validation and graceful failure are the next.

Where to go from here

Start reviewing your existing tool schemas. Look for ambiguity, missing types, or vague descriptions. Refining these will make your AI agents more reliable and reduce debugging time. If you need help building robust AI agents, consider AI agent development.

  • AI agent tools
  • tool schema design
  • LLM function calling
  • prevent invalid tool calls
  • JSON schema for agents
  • AI agent reliability
  • agent development best practices
  • tool use in LLMs
05 — execute

Ready to build
the future?

Join the developers learning to build with AI. New tutorials, tools and breakdowns every week — straight to your feed.

$ aidevguy --start --now_