Skip to main content

Tool design for agent: naming, schema, error handling that reduce errors by 60%

Tool design for agent: naming, schema, error handling that reduce errors by 60%: Tool design for agent: naming, schema, error handling that reduce...

What do you think about this article?

Tool design for agent: naming, schema, error handling that reduce errors by 60%
AI EngineeringPhotos:Tara WinsteadPexels

At a Glance

Tool design for agent: naming, schema, error handling that reduce errors by 60%: Tool design for agent: naming, schema, error handling that reduce...

Tool design for agents: naming, schema, error handling that reduce errors by 60%

Your tool is called get_dataThe agent will fail three times before it understands what it does. It's not an exaggeration - this is the most common failure pattern I observe when analyzing agent traces in production. The model receives a list of available tools and reads get_dataIf it fails to recover data from a database, API, file or cache, and starts experimenting by inference, every incorrect experiment costs tokens, latency, and - in production - money.

Designing well-made tools is not an aesthetic detail. It's the interface between the logic of the agent and the external world. Every ambiguity in naming, every parameter with too permissive types, every error returned as an exception rather than a structured data point translates to wasted iterations in the ReAct loop. Empirical tests on production systems show that applying the techniques described in this episode - specific verb-noun naming, Pydantic schema with tight enums, error-as-data with suggestion fields - reduces agent errors by 40-60% without touching the underlying LLM model.

episode we see how I rebuilt the tool catalog of my ml-service portfolio: 12 tools for a chat agent, passed from an initial naive implementation to a production-ready one. Each section is accompanied by concrete Python code, comparison tables and explanations of the reasoning behind every choice.

What You Will Learn

  • Come applicare naming convention verb_noun specifico che l'agent capisce senza ambiguità
  • Building a Pydantic Schema with Narrow Types, Enums, and Field Descriptions to Guide the Model
  • Implementing error-as-data with a suggestion field to enable auto-correction of an agent
  • How to add idempotency tokens on a tool with side effects to prevent duplicates
  • How to build a retry-on-validation system with a maximum of retries
  • How to compose tools in orchestras for complex tasks with multiple steps
  • To maintain a YAML catalog tool like a centralized registry

Pre-requisites

  • Python and Pydantic v2: models, validators, Field - we use them intensely
  • Base concept of a JSON Schema: type, properties, required, enum
  • Familiarity with LLM agent pattern ReAct or function calling (OpenAI/Ollama)
  • I'm ready to translate. Please provide the Italian text. EP04 - Debugging Agent Loop recommended for context on failure modes

Section 1 - Naming convention: verb_noun specific as contract with LLM

When an LLM agent must choose a tool, the first thing it processes is the name. The name is the densest signal of information in the entire tool schema: if it's ambiguous, the model must read the complete description (more tokens, more latency), then possibly try the tool and correct itself from the error (even more tokens). A precise name eliminates this overhead.

The convention I don't see any Italian text provided. Please paste the Italian text you'd like me to translate, and I'll be happy to assist you. functions because it reflects how an agent plans: "I must To obtain active clientsso I'm looking for a tool that list clientsThe specificity of the object reduces confusion between similar tools: list_active_customers_by_region e count_churned_customers_last_30_days They are unmistakable, while get_customers e get_customer_stats Leaves open the doubt.

I'm ready to translate. Please go ahead and provide the Italian text. Bad example Good example Problem solved
General Verb get_data fetch_article_metadata Clarifies the subject and source
Ambiguous noun process_input validate_email_address Clearly states the type of input and operation
Abbreviation calc_rev calculate_monthly_revenue Remove mental lookup from the model
Ambiguous plural name users list_registered_users_by_status Specify the action (list vs search vs count)
Passive verb is_valid check_iban_validity Clarifies the domain context
Destructive hidden action update archive_expired_subscriptions Makes visible the side effect in the agent plan

A concrete case from the ml-service of the portfolio: the original tool was called searchThe agent constantly confused semantic search on articles with a search tool from the catalog. Renaming it search_articles_by_semantic_queryThe errors in the selection tool have been reset in that category. The name has become the contract.

# tool_naming.py - Esempio di naming convention applicata al ml-service portfolio
# Installa: pip install pydantic>=2.0

from pydantic import BaseModel, Field
from typing import Literal

# SBAGLIATO: nome generico, schema aperto
def search(query: str) -> dict:
    """Cerca qualcosa."""
    pass

# CORRETTO: nome verb_noun specifico, schema tipizzato
def search_articles_by_semantic_query(
    query: str,
    language: Literal["it", "en"] = "it",
    max_results: int = Field(default=5, ge=1, le=20),
    category_filter: str | None = None
) -> dict:
    """
    Cerca articoli nel corpus del portfolio usando similarità semantica (embedding).

    USA QUESTO TOOL quando l'utente chiede di trovare articoli su un argomento,
    tecnologia o concetto. NON usare per navigare tra categorie o listare serie.

    Esempio input: query="RAG con pgvector", language="it", max_results=3
    Esempio output: {"results": [{"id": 201, "title": "...", "score": 0.89}]}
    """
    pass  # implementazione nel ml-service

the structure of the docstring: first descriptive sentence, then "USA THIS TOOL when...", then "DO NOT use for...", then concrete example. This structure is designed to mimic how the LLM processes information during tool selection.

Section 2 - JSON Schema with Pydantic: narrow types, enums, custom validators

The tool's schema is what the LLM uses to build parameters for the call. A permissive schema - everything stringNo enumeration, no constraint on values - download the task from the agent to figure out the correct format. A narrow schema is an implicit guidance system: the model receives information about allowed values before invoking the tool.

The practical difference is measurable. On a tool with a parameter status defined as string free, the production agent passed values like XPHX0XPHX "active", "enabled", "live", "running" All are semantically equivalent but differ in expected value "running"Passing to Literal["running", "stopped", "paused"]The errors of that type on that parameter have become zero.

# tool_schema.py - Schema Pydantic stretto per tool agent
# Pattern dal ml-service portfolio per il tool di raccomandazione articoli

from pydantic import BaseModel, Field, field_validator
from typing import Literal
from enum import Enum

class ContentLanguage(str, Enum):
    ITALIAN = "it"
    ENGLISH = "en"

class SortOrder(str, Enum):
    RELEVANCE = "relevance"
    DATE_DESC = "date_desc"
    DATE_ASC = "date_asc"
    READING_TIME = "reading_time"

class RecommendArticlesInput(BaseModel):
    """Schema di input per il tool recommend_articles_for_user."""

    user_context: str = Field(
        description=(
            "Contesto dell'utente: interessi attuali, articoli già letti, "
            "domanda specifica. Massimo 500 caratteri."
        ),
        max_length=500,
        min_length=10
    )
    language: ContentLanguage = Field(
        default=ContentLanguage.ITALIAN,
        description="Lingua degli articoli da raccomandare."
    )
    max_results: int = Field(
        default=5,
        ge=1,
        le=10,
        description="Numero massimo di raccomandazioni. Tipicamente 3-5 per risposte conversazionali."
    )
    sort_by: SortOrder = Field(
        default=SortOrder.RELEVANCE,
        description="Criterio di ordinamento. Usa 'relevance' per query semantiche, 'date_desc' per contenuti recenti."
    )
    exclude_article_ids: list[int] = Field(
        default_factory=list,
        description="ID articoli da escludere (già mostrati all'utente in questa sessione)."
    )

    @field_validator("user_context")
    @classmethod
    def context_not_empty_or_generic(cls, v: str) -> str:
        """Rifiuta contesti troppo generici che non portano a raccomandazioni utili."""
        generic_terms = ["qualcosa", "anything", "tutto", "everything"]
        if v.strip().lower() in generic_terms:
            raise ValueError(
                "user_context troppo generico. Fornisci un contesto specifico "
                "come 'utente interessato a RAG e vector database' o la domanda corrente."
            )
        return v.strip()

The Field description inside Field This is not documentation for the developer: it's text that literally ends up in the JSON schema sent to the LLM. Every word must be useful to the model, not to the developer. Also note the custom validator: it doesn't just launch an error, but provides a message with suggested correction.

Section 3 - Error-as-data: return structured errors that the agent can use

The traditional way of handling errors in tools is to throw exceptions. This creates a problem in the ReAct loop: the exception is caught by the framework agent, converted into a rough error message, and passed to the model as a tool result. The model receives a Python traceback, doesn't know what to do, and often produces a generic fallback response or worse gets stuck in a blind retry loop.

The pattern I don't see any Italian text provided. Please provide the Italian text you'd like me to translate, and I'll be happy to assist you. Invert this logic: the tool always returns a structured object, even in case of an error. The structure includes a field suggestion What the agent says exactly is what to correct. This field is key: it allows self-correction without an additional round trip to the model to "understand the error".

# error_as_data.py - Pattern error-as-data con suggestion field
# Tassonomia errori standardizzata per il tool catalog ml-service

from pydantic import BaseModel
from typing import Any, Literal
from enum import Enum

class ErrorCode(str, Enum):
    VALIDATION_ERROR = "VALIDATION_ERROR"
    NOT_FOUND = "NOT_FOUND"
    PERMISSION_DENIED = "PERMISSION_DENIED"
    RATE_LIMITED = "RATE_LIMITED"
    DEPENDENCY_UNAVAILABLE = "DEPENDENCY_UNAVAILABLE"
    IDEMPOTENCY_CONFLICT = "IDEMPOTENCY_CONFLICT"

class ToolResult(BaseModel):
    """Struttura di risposta unificata per tutti i tool agent."""
    success: bool
    data: Any | None = None
    error_code: ErrorCode | None = None
    error_message: str | None = None
    # Campo chiave: indica all'agent cosa fare per correggere l'errore
    suggestion: str | None = None
    # Flag che indica se il retry automatico ha senso
    retry_allowed: bool = True

def search_articles_by_semantic_query(
    query: str,
    language: str = "it",
    max_results: int = 5
) -> ToolResult:
    """Cerca articoli per similarità semantica. Restituisce sempre ToolResult."""

    # Validazione parametri con errore strutturato
    if language not in ("it", "en"):
        return ToolResult(
            success=False,
            error_code=ErrorCode.VALIDATION_ERROR,
            error_message=f"Lingua '{language}' non supportata.",
            suggestion=(
                "Usa language='it' per italiano o language='en' per inglese. "
                "Nessun altro valore è accettato."
            ),
            retry_allowed=True  # l'agent può riprovare con il valore corretto
        )

    if max_results < 1 or max_results > 20:
        return ToolResult(
            success=False,
            error_code=ErrorCode.VALIDATION_ERROR,
            error_message=f"max_results={max_results} fuori dal range valido.",
            suggestion="Usa un valore tra 1 e 20. Per chat conversazionale, 3-5 è ottimale.",
            retry_allowed=True
        )

    # Simulazione dipendenza non disponibile (Ollama offline)
    try:
        results = _run_semantic_search(query, language, max_results)
        return ToolResult(success=True, data=results)
    except ConnectionError:
        return ToolResult(
            success=False,
            error_code=ErrorCode.DEPENDENCY_UNAVAILABLE,
            error_message="Il servizio di embedding non è raggiungibile.",
            suggestion=(
                "Non riprovare questo tool. Usa invece 'search_articles_by_keyword' "
                "che non dipende dal servizio di embedding."
            ),
            retry_allowed=False  # non ha senso riprovare, l'errore è strutturale
        )

def _run_semantic_search(query: str, language: str, max_results: int) -> list[dict]:
    """Implementazione interna - non esposta come tool."""
    pass  # logica Redis HNSW del ml-service

The Field retry_allowed: False On a dependency error it is as important as suggestionDoes not waste iterations to retry. The field suggestion Indicates an alternative tool - this is a guided error-composition tool.

Section 4 - Error Taxonomy: Classifying to Decide on the Agent

Not all errors are equal for an agent. A validation error is immediately correctable. A rate limiting error requires waiting. A dependency error requires a fallback to an alternative tool. An empty response (no result) is not an error but requires a different strategy. Without explicit taxonomy, the agent treats everything equally: retry or give up.

Type error I don't see any Italian text provided. Please provide the text you'd like me to translate, and I'll be happy to assist you. Are you ready to retry? Agent strategy I don't see any Italian text provided. Please share the text you'd like me to translate, and I'll be happy to assist you.
Parameter validation VALIDATION_ERROR Yes, with fix Correct the parameter and try again The language is not supported.
Resource not found NOT_FOUND No Informs the user, suggests an alternative article_id=9999 does not exist
Permission denied PERMISSION_DENIED No Escalation or negative response Authentication tool required
Rate Limit RATE_LIMITED Yes, with wait. Wait for retry_after_seconds, then try again. API Quota Exhausted
Dependence DEPENDENCY_UNAVAILABLE No Use an alternative tool from the suggestions The phone is unreachable.
Idempotence conflict IDEMPOTENCY_CONFLICT No (already done) Retrieve result from cache Token already used, operation already performed

Section 5 - Idempotency token: prevent duplication in tools with side effects

An agent in production can retry a tool call for thousands of reasons: network timeout, transient error, explicit retry loop in the code. If the tool has side effects - writing to database, sending an email, recording user interaction - each retry produces a duplicate. On a system that tracks article views in a portfolio, this meant inflated counts of 15-30% in the first implementations.

Idempotency tokens resolve the issue at a protocol level. The caller generates a UUID for each logical operation (not per every retry). The server tracks processed tokens. Retries with the same token receive the stored result, not a new execution. This pattern is standard in payment systems (Stripe uses this approach) and can be applied directly to agent tools.

# idempotency_token.py - Pattern idempotency token per tool con side effects
# Implementazione con cache in-memory (in produzione: Redis con TTL)

import hashlib
import time
from functools import wraps
from typing import Any, Callable

# Cache semplicistica per demo (produzione: Redis SETEX con TTL 24h)
_idempotency_cache: dict[str, dict] = {}

def with_idempotency(ttl_seconds: int = 86400):
    """
    Decorator che aggiunge idempotency token support a un tool.
    Il tool decorato accetta un parametro idempotency_key opzionale.
    Se la chiave è già in cache, restituisce il risultato precedente.
    """
    def decorator(func: Callable) -> Callable:
        @wraps(func)
        def wrapper(*args, idempotency_key: str | None = None, **kwargs) -> Any:
            if idempotency_key is None:
                # Nessun token: esegui normalmente (tool read-only o test)
                return func(*args, **kwargs)

            cache_entry = _idempotency_cache.get(idempotency_key)
            if cache_entry and (time.time() - cache_entry["timestamp"]) < ttl_seconds:
                # Token già visto: restituisce risultato cached senza rieseguire
                return {
                    **cache_entry["result"],
                    "idempotent_response": True,
                    "original_timestamp": cache_entry["timestamp"]
                }

            # Prima esecuzione: esegui e memorizza il risultato
            result = func(*args, **kwargs)
            _idempotency_cache[idempotency_key] = {
                "result": result,
                "timestamp": time.time()
            }
            return result
        return wrapper
    return decorator

@with_idempotency(ttl_seconds=3600)
def track_article_view(
    article_id: int,
    user_session_id: str,
    idempotency_key: str | None = None
) -> ToolResult:
    """
    Registra la visualizzazione di un articolo per analytics.
    Usa un idempotency_key (UUID) per prevenire duplicazioni da retry.

    Esempio: idempotency_key="view-session-abc123-article-201"
    """
    # Logica di scrittura su PostgreSQL del backend
    _write_view_to_db(article_id, user_session_id)
    return ToolResult(
        success=True,
        data={"article_id": article_id, "tracked": True}
    )

, the agent must generate the token once when planning the action, and reuse it for every retry of the same logical action. In the ml-service of the portfolio, the convention is "{operation}-{session_id}-{target_id}" as a string constructed deterministically by the agent itself.

Section 6 - Retry on validation: automatic correction with maximum retries

A well-built agent does not give up at the first validation error: it understands the error, corrects the parameter, and tries again. But without a guard explicitly set, this self-correcting mechanism becomes an infinite loop. We have seen in Episode 4 on Loop Agent The blind retry pattern is one of the six most common failure modes. The solution is a conscious retry system aware of attempts.

# retry_on_validation.py - Sistema di retry con max_retries e backoff
# Pattern applicato al loop ReAct del ml-service portfolio

from dataclasses import dataclass, field
from typing import Callable, Any

@dataclass
class RetryContext:
    """Traccia i tentativi per tool call all'interno di un agent step."""
    max_retries: int = 3
    attempts: dict[str, int] = field(default_factory=dict)

    def can_retry(self, tool_name: str) -> bool:
        """Verifica se il tool ha ancora tentativi disponibili."""
        return self.attempts.get(tool_name, 0) < self.max_retries

    def record_attempt(self, tool_name: str) -> None:
        """Registra un tentativo per il tool specificato."""
        self.attempts[tool_name] = self.attempts.get(tool_name, 0) + 1

    def attempts_left(self, tool_name: str) -> int:
        """Restituisce i tentativi rimasti per il tool."""
        return self.max_retries - self.attempts.get(tool_name, 0)

def execute_tool_with_retry(
    tool_fn: Callable,
    tool_name: str,
    params: dict,
    retry_ctx: RetryContext
) -> ToolResult:
    """
    Esegue un tool con supporto retry-on-validation.
    Aggiorna retry_ctx e restituisce sempre un ToolResult.
    """
    if not retry_ctx.can_retry(tool_name):
        # Limite raggiunto: termina con errore definitivo
        return ToolResult(
            success=False,
            error_code=ErrorCode.VALIDATION_ERROR,
            error_message=f"Superato il limite di {retry_ctx.max_retries} tentativi per '{tool_name}'.",
            suggestion=(
                "Interrompi il tentativo su questo tool e informa l'utente "
                "che non è stato possibile completare l'operazione con i parametri forniti."
            ),
            retry_allowed=False
        )

    retry_ctx.record_attempt(tool_name)
    result = tool_fn(**params)

    if not result.success and result.error_code == ErrorCode.VALIDATION_ERROR and result.retry_allowed:
        # Errore correggibile: il loop agent userà result.suggestion per correggere
        remaining = retry_ctx.attempts_left(tool_name)
        result.error_message = (
            f"{result.error_message} "
            f"[Tentativi rimasti: {remaining}]"
        )

    return result

Il RetryContext Lives for a single step of the agent, not for the entire session. This is important: a tool that fails 3 times on one step does not exclude its use on subsequent steps with different parameters. The retry counter is for pairs (step, tool_name), not for (session, tool_name).

Section 7 - Tool composition and tool catalog: the YAML registry of the ml-service

An agent with 12 tools has an organization problem: the model receives all schemas in context, even those not relevant to the current task. With tool composition, simple tools become building blocks for more complex operators. With a centralized YAML registry, the team maintains visibility on what's available, who uses it, and which contracts are applicable.

# tool_composition.py - Orchestratore che compone tool semplici in task complessi
# Pattern usato nel ml-service per la funzionalità "articolo del giorno"

from typing import NamedTuple

class ArticleSuggestionResult(NamedTuple):
    """Risultato composto da multiple tool call."""
    articles: list[dict]
    explanation: str
    total_tokens_used: int

def suggest_daily_reading(
    user_context: str,
    session_id: str,
    retry_ctx: RetryContext
) -> ToolResult:
    """
    Orchestratore: compone search + filter + track per la raccomandazione giornaliera.
    Tool di alto livello che l'agent chiama per task complessi.
    """
    # Step 1: cerca articoli rilevanti
    search_result = execute_tool_with_retry(
        tool_fn=search_articles_by_semantic_query,
        tool_name="search_articles_by_semantic_query",
        params={"query": user_context, "max_results": 10, "language": "it"},
        retry_ctx=retry_ctx
    )
    if not search_result.success:
        return search_result  # propaga il ToolResult con errore strutturato

    # Step 2: filtra quelli già letti in sessione
    candidates = search_result.data
    filter_result = execute_tool_with_retry(
        tool_fn=filter_unread_articles,
        tool_name="filter_unread_articles",
        params={"article_ids": [a["id"] for a in candidates], "session_id": session_id},
        retry_ctx=retry_ctx
    )
    if not filter_result.success:
        return filter_result

    unread = filter_result.data[:3]  # massimo 3 suggerimenti

    # Step 3: registra la visualizzazione (con idempotency)
    for article in unread:
        idem_key = f"suggest-{session_id}-{article['id']}"
        execute_tool_with_retry(
            tool_fn=track_article_view,
            tool_name="track_article_view",
            params={
                "article_id": article["id"],
                "user_session_id": session_id,
                "idempotency_key": idem_key
            },
            retry_ctx=retry_ctx
        )

    return ToolResult(success=True, data={"articles": unread, "count": len(unread)})

The orchestrator is itself a registered tool in the catalog. The agent can choose to call directly the atomic tools or delegate to the orchestrator for composite tasks. This dual exposure - atomic and composite - offers flexibility to the model without forcing it to build orchestration from scratch.

The YAML registry centralizes tool governance. In the ml-service of the portfolio, this file is the source of truth for documentation, testing and monitoring:

# tool_catalog.yaml - Registry centralizzato per il chat agent del ml-service portfolio
# 12 strumenti per l'agent conversazionale su federicocalo.dev

version: "1.0"
agent: chat-assistant
last_updated: "2026-05-01"

tools:
  # ============================================================
  # CATEGORIA: Contenuti e Ricerca
  # ============================================================
  - name: search_articles_by_semantic_query
    category: content
    version: "2.1"
    has_side_effects: false
    idempotency_required: false
    max_retries: 3
    dependencies:
      - ollama-embeddings
      - redis-vector-store
    fallback_tool: search_articles_by_keyword
    description: >
      Ricerca semantica nel corpus degli articoli del portfolio.
      Usa embedding all-MiniLM-L6-v2 + HNSW su RediSearch.
    owner: ml-service
    sla_ms: 800

  - name: search_articles_by_keyword
    category: content
    version: "1.3"
    has_side_effects: false
    idempotency_required: false
    max_retries: 2
    dependencies:
      - postgresql
    fallback_tool: null
    description: >
      Ricerca full-text su titolo e tag degli articoli. Fallback
      per search_articles_by_semantic_query se Ollama non disponibile.
    owner: backend-api
    sla_ms: 200

  - name: recommend_articles_for_user
    category: content
    version: "1.0"
    has_side_effects: false
    idempotency_required: false
    max_retries: 2
    dependencies:
      - ollama-embeddings
      - redis-vector-store
      - postgresql
    fallback_tool: search_articles_by_keyword
    description: >
      Raccomanda articoli basandosi sul contesto utente e sugli articoli
      già letti nella sessione corrente.
    owner: ml-service
    sla_ms: 1200

  # ============================================================
  # CATEGORIA: Analytics e Tracking (con side effects)
  # ============================================================
  - name: track_article_view
    category: analytics
    version: "1.1"
    has_side_effects: true
    idempotency_required: true
    max_retries: 1
    dependencies:
      - postgresql
    fallback_tool: null
    description: >
      Registra visualizzazione articolo per analytics. SEMPRE passare
      idempotency_key per prevenire duplicazioni da retry.
    owner: backend-api
    sla_ms: 100

  - name: suggest_daily_reading
    category: content-composite
    version: "1.0"
    has_side_effects: true
    idempotency_required: false
    max_retries: 1
    dependencies:
      - search_articles_by_semantic_query
      - filter_unread_articles
      - track_article_view
    fallback_tool: null
    description: >
      Orchestratore: compone search + filter + track per la raccomandazione
      giornaliera. Preferisci questo tool per task di tipo "suggerisci letture".
    owner: ml-service
    sla_ms: 2500

The Field fallback_tool registry, the name of the tool that the agent must use in case of DEPENDENCY_UNAVAILABLEThe field has_side_effects Report to the system of monitoring which tools require tracking of idempotency tokens. The field sla_ms It is used to configure timeouts in the agent loop.

Frequently Asked Questions

Why is the name of the tool so important for an LLM agent?

The name is the first piece of information that the agent processes to decide which tool to invoke. A name like XPHX0XPHX, get_data Forces LLM to read the entire description to understand the context. A name like XPHX0XPHX list_active_customers_by_region The model is self-documenting: the model understands intent, scope and expected parameters before even reading the schema. Empirical tests show that a specific naming convention for verb_noun reduces incorrect calls by 40-60%.

When to use an enumeration instead of a string in a schema tool?

Use an enumeration every time the parameter has a known set of finite and valid values. If your tool accepts one enum, then it should accept all its variants, such as XPHX0XPHX or XPHX1XPHX. status with values active, inactive, pendingDefining it as a free string leaves the LLM's freedom to pass enabled, disabled, waiting All are semantically similar but cause validation errors. With an enum in the JSON Schema, the probability of error drops below 2%.

What is the error-as-data pattern and why is it superior to exceptions?

Error-as-data means returning always a structured object from the tool, even in case of error, instead of throwing exceptions. The structure includes ... error_code, message, suggestion What actions should the agent take to correct it and retry_allowedThe field suggestion allows the agent to self-correct without additional round-trip to the model.

How does idempotency token work in an agent tool?

An idempotency token is a UUID that the caller includes in the request. The server-side tool tracks processed tokens in a cache with TTL. If a second request arrives with the same token, the server returns the result of the first without re-executing the operation. It's crucial for agents on tools with side effects such as writing, sending emails, or tracking analytics to prevent duplication from retrying.

How many tools should an agent have to work well?

More than 15-20 tools, the performance of LLM in tool selection degrades significantly. For complex contexts with many tools, consider a pattern of tool routing: a lookup tool that returns the 3-5 most relevant tools for the current query, reducing the context that the LLM must process in every step.

What is the difference between a tool's description and its documentation string?

The Python docstring is for the developer: explains internal implementation. The tool description is for LLM: must explain when to use this tool, exactly what it does, examples of input/output, and important edge cases. They are different texts for different audiences. A good tool description starts with the main use case and specifies what the tool does NOT do to avoid improper usage.

How to implement retry-on-validation without infinite loops?

Catch the validation error of Pydantic, build a structured message with the erroneous field, received value and expected format, pass it as a tool result to the agent. Add a counter max_retries Typically 2-3 for every tool call in the same step. If it exceeds the limit, returns a definitive error with escalation_required: trueThis allows for self-correction of common errors without infinite loops.

Conclusion

The tool design is the interface between the agent's logic and the external world. Every choice - from names to curly brackets in the field description of Field Pydantic - directly influences how many wasted iterations the agent performs before completing the task. The seven techniques described in this episode do not require larger models or longer prompts: they require applied project discipline to the tool interface.

Applying the specific naming convention verb_noun, schema with strict enums, error-as-data with a suggestion field and idempotency token on the tool with side effects, the ML service of the portfolio has moved from an agent with an error rate of 35% on initial naive implementations to less than 15% - using the same underlying LLM model (Ollama llama3.1:8b on-prem). The improvement is not coming from the LLM: it's coming from the interface that you give it.

Next episode: EP06 - Multi-agent: Coordinator vs Orchestration PatternWhen a single agent is not enough and how to structure communication between specialized agents without the context exploding.

Try These Tools

Free developer tools related to this topic.

Commenti

Caricamento commenti...

Sign in to comment

Discuss this article in the community

Join the Telegram group to discuss with other developers, ask questions and share your experiences.

Did you like this article?

Explore other content on the blog or discover my projects