Designing AI Tools: Avoiding Common Mistakes
Le brief IA que les pros lisent chaque soir
Les 7 actus IA du jour, décryptées en 5 min. Gratuit.
Inclus dès l'inscription : notre sélection des meilleurs guides & comparatifs IA.
Choisis ton rythme
Gratuit · Pas de spam · Désabonnement en 1 clic
Introduction
In the world of artificial intelligence agents, failures are often attributed to model errors, such as choosing the wrong tool or poor error management. However, these issues often reflect inadequate tool design. Indeed, a model can only function with the information provided by the tool's interface, such as the name, description, and parameters. When these elements are poorly designed, failures become inevitable.
Design problems, such as vague tool names, ambiguous instructions, or inconsistent schemas, increase the likelihood of failure. Even the most advanced models cannot compensate for a defective interface. This article explores design practices that enhance the reliability of tools, failure modes that escape demonstrations but fail in real-world conditions, and how rigorous design can reduce errors at the tool's boundary. Each model is associated with its failure counterpart, as understanding why a design fails is just as important as knowing what to replace it with.
What Works in AI Agent Tool Design
1. One Tool, One Responsibility
In agent systems, each tool should be dedicated to a specific and clear operation. When a tool is designed to handle multiple behaviors through an action parameter, the model must first determine which mode to use before solving the task. This unnecessarily complicates the process.
Take, for example, a multi-action tool that manages several behaviors:
@tool
def manage_customer(
action: str,
customer_id: str | None = None,
data: dict | None = None
):
"""
action: create | get | update | delete | suspend
"""
...
In contrast, single-responsibility tools allow for unambiguous functionality and better error management:
@tool
def create_customer(data: CustomerInput) -> Customer:
"""Create a new customer record."""
...
@tool
def get_customer(customer_id: str) -> Customer:
"""Retrieve a customer by ID."""
...
@tool
def suspend_customer(customer_id: str, reason: str) -> SuspensionResult:
"""Suspend a customer account."""
...
Single-responsibility tools provide better clarity and facilitate error observability. However, certain domains, such as shell tools, file systems, browsers, or calendars, may benefit from a constrained multi-action interface, as the action space is part of the underlying abstraction.
2. Schemas That Make Invalid States Impossible
Agents that call tools build the call arguments based on the provided schema. A loose schema forces the model to guess the constraints, while a strict schema encodes these constraints, thus eliminating assumptions.
Here’s an example of a strict schema:
from pydantic import BaseModel, Field
from enum import Enum
class Priority(str, Enum):
LOW = "low"
MEDIUM = "medium"
HIGH = "high"
class CreateTaskInput(BaseModel):
title: str = Field(
description="Short, actionable task title. Use the imperative form: 'Review PR', not 'PR Review'.",
min_length=5,
max_length=100
)
priority: Priority = Field(
description="Task priority. Use HIGH only for blockers affecting other work.",
default=Priority.MEDIUM
)
due_date: str = Field(
description="Due date in ISO 8601 format: YYYY-MM-DD. Must be a future date.",
pattern=r"^\d{4}-\d{2}-\d{2}$"
)
Enumerations are particularly useful for fields with a limited set of valid values, as they eliminate plausible but invalid outcomes. Validation failures appear at the tool's boundary rather than as cryptic errors downstream.
3. Descriptions That Define Scope, Not Just Purpose
Tool descriptions serve as model-oriented documentation. They should not only explain when to use the tool but also when not to use it. Most descriptions only do the former.
- Weak: explains what it does, not when not to use it
"""Search for documents in the knowledge base."""
- Strong: defines purpose, scope, and limits
"""
Search the internal knowledge base for documents, policies, and reference materials.
Use this when the user asks questions about company procedures, product specifications, or documented workflows.
DO NOT use this for real-time data (prices, availability, current status) — use get_live_data() instead.
Returns up to 5 results ranked by relevance. If no results are returned, the information is not in the knowledge base.
"""
Without this clarification, the model infers the scope solely from the tool's name, which is often a source of large-scale selection errors. A good tool definition includes clear limits compared to other tools, not just usage instructions.
4. Structured and Actionable Error Feedback
When a tool fails, the model reads the error and decides on the next steps. An unhandled exception or a stack trace produces random follow-up behavior. A structured error gives the model a solid basis for deciding what to do next.
Structured errors should not only signal what failed but also help the agent decide what to do next. A good error format makes retry behavior explicit and gives the model a clear recovery path:
class ToolError(BaseModel):
error_code: str # machine-readable, for the model to branch
message: str # human-readable description
recoverable: bool # can the agent retry?
suggested_action: str # what should the agent do next
- Record not found: retryable
return ToolError(
error_code="RECORD_NOT_FOUND",
message="No user record found with ID 'usr_123'.",
recoverable=True,
suggested_action="Use list_users() to get valid user IDs before calling get_user()."
)
- Quota exceeded: non-retryable
return ToolError(
error_code="QUOTA_EXCEEDED",
message="The [API](/glossaire/api) quota for this tool has been reached for today.",
recoverable=False,
suggested_action="Inform the user and stop. Do not retry this tool today."
)
The recoverable flag and the suggested_action field are what change the agent's behavior. Without them, models retry non-retryable errors or abandon those that are.
5. Idempotent State-Changing Operations
Every tool that modifies state — creates a record, sends a message, transfers funds — must be safe to call twice. In practice, agents retry, networks fail, and the LLM loop may issue a second call because the confirmation of the first never arrived.
A simple way to prevent duplicated side effects is to require an idempotency key for each write operation:
@tool
def send_email(
to: str,
subject: str,
body: str,
idempotency_key: str
):
...
Brief IA — L'actualité IA en français
L'essentiel de l'actualité de l'intelligence artificielle, décrypté et expliqué chaque jour.