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.

Commenti
Caricamento commenti...
Sign in to comment