Patrones de Uso de Herramientas: Interfaces Agente-Herramienta Confiables
Tu agente llamó a una herramienta y recibió un JSON de 40 líneas — respuesta cruda de la API, objetos anidados, códigos de error enterrados dentro de un campo status. El modelo lo leyó, eligió un valor que parecía plausible y continuó. El valor era incorrecto. Tres pasos después, el agente redactó con confianza un informe basado en datos incorrectos.
La herramienta funcionó. La interfaz falló.
El uso de herramientas es el mecanismo que convierte un modelo de lenguaje en un agente. Cada capacidad de tu agente — buscar en bases de datos, escribir archivos, llamar APIs, consultar servicios — llega a través de una interfaz de herramienta. Si la interfaz está mal diseñada, el modelo toma peores decisiones incluso cuando el servicio subyacente funciona correctamente. Esta guía cubre cinco patrones para construir interfaces de herramientas precisas, confiables y listas para producción.
Prerrequisitos: Familiaridad con Python y la API de Claude. Para contexto sobre MCP como capa de transporte de herramientas, consulta Construyendo tu Primer Servidor MCP.
Por Qué Importa el Diseño de la Interfaz
Cuando un agente elige y usa una herramienta, toma dos decisiones:
- Qué herramienta llamar — impulsado por el
nameydescriptionde la herramienta - Qué argumentos pasar — impulsado por el
input_schemade la herramienta
Las descripciones ambiguas llevan a selección incorrecta de herramientas. Los esquemas flexibles permiten que el modelo pase entradas malformadas. Los resultados no estructurados hacen que el modelo adivine qué ocurrió. La mayoría de los errores de agentes no viven en el razonamiento — viven en el límite de la herramienta.
Patrón 1: Diseño de Esquema Primero
Escribe el esquema JSON antes de escribir la implementación. Un esquema estricto restringe el comportamiento del modelo en la etapa de entrada — antes de que se ejecute cualquier cosa.
import anthropic
client = anthropic.Anthropic()
tools = [ { "name": "search_products", "description": ( "Busca en el catálogo de productos por palabra clave. " "Devuelve una lista de productos coincidentes con IDs, nombres y precios. " "Úsalo cuando el usuario quiera encontrar o explorar productos." ), "input_schema": { "type": "object", "properties": { "query": { "type": "string", "description": "Palabras clave para buscar" }, "category": { "type": "string", "enum": ["electronics", "clothing", "food", "home", "all"], "description": "Categoría de producto para filtrar. Usa 'all' si no se especifica." }, "max_results": { "type": "integer", "minimum": 1, "maximum": 20, "description": "Número de resultados a devolver. Por defecto: 5" } }, "required": ["query", "category"] } }]
response = client.messages.create( model="claude-sonnet-4-6", max_tokens=1024, tools=tools, messages=[{"role": "user", "content": "Encuentra electrónica por menos de $100"}])Reglas de esquema que reducen errores:
- Usa
enumpara cualquier campo con un conjunto fijo de valores válidos - Establece
minimum/maximumen campos numéricos para prevenir entradas fuera de rango - Marca los campos como
requiredsolo cuando la herramienta genuinamente no puede ejecutarse sin ellos - Escribe descripciones desde la perspectiva del modelo: “Úsalo cuando…” le dice cuándo llamar a la herramienta
Cuándo usar: En cada definición de herramienta.
Patrón 2: Resultados de Herramienta Estructurados
Devuelve resultados tipados y legibles por máquina. Nunca devuelvas respuestas crudas de API o descripciones en prosa.
import jsonfrom dataclasses import dataclass, asdictfrom typing import Any, Optional
@dataclassclass ToolResult: success: bool data: Optional[Any] = None error: Optional[str] = None
def to_content(self) -> str: return json.dumps(asdict(self))
def search_products( query: str, category: str, max_results: int = 5,) -> ToolResult: try: raw_results = _query_database(query, category, limit=max_results) products = [ {"id": r["product_id"], "name": r["title"], "price": r["price_usd"]} for r in raw_results ] return ToolResult(success=True, data={"products": products, "count": len(products)}) except ConnectionError as e: return ToolResult(success=False, error=f"Base de datos no disponible: {e}") except Exception as e: return ToolResult(success=False, error=f"Búsqueda fallida: {type(e).__name__}: {e}")
def _query_database(query, category, limit): return []El sobre consistente {success, data, error} significa que el modelo siempre sabe dónde buscar. Para manejar fallos con elegancia, consulta Patrones de Recuperación de Errores en Agentes.
Cuándo usar: En cada implementación de herramienta.
Patrón 3: Llamadas de Herramienta en Paralelo
Claude puede solicitar múltiples herramientas en una sola respuesta. Procésalas en paralelo en lugar de secuencialmente.
from concurrent.futures import ThreadPoolExecutor, as_completed
TOOL_REGISTRY = { "search_products": search_products,}
def dispatch_tool(name: str, inputs: dict) -> ToolResult: handler = TOOL_REGISTRY.get(name) if not handler: return ToolResult(success=False, error=f"Herramienta desconocida: {name}") return handler(**inputs)
def process_tool_calls(response: anthropic.types.Message) -> list[dict]: tool_uses = [ block for block in response.content if block.type == "tool_use" ] if not tool_uses: return []
def execute(tool_use): result = dispatch_tool(tool_use.name, tool_use.input) return { "type": "tool_result", "tool_use_id": tool_use.id, "content": result.to_content(), }
with ThreadPoolExecutor(max_workers=len(tool_uses)) as executor: futures = {executor.submit(execute, tu): tu for tu in tool_uses} results = [] for future in as_completed(futures): results.append(future.result())
return results
def run_agent(user_message: str) -> str: messages = [{"role": "user", "content": user_message}]
while True: response = client.messages.create( model="claude-sonnet-4-6", max_tokens=4096, tools=tools, messages=messages, )
if response.stop_reason == "end_turn": for block in response.content: if hasattr(block, "text"): return block.text return ""
if response.stop_reason == "tool_use": tool_results = process_tool_calls(response) messages.append({"role": "assistant", "content": response.content}) messages.append({"role": "user", "content": tool_results}) else: break
return ""Para orquestar múltiples agentes que cada uno llama herramientas, consulta Patrones Multi-Agente.
Patrón 4: Envoltorio de Llamada de Herramienta Segura
Nunca dejes que las excepciones de herramientas lleguen al bucle del agente sin manejar.
import signal
def timeout_handler(signum, frame): raise TimeoutError("Tiempo de ejecución de herramienta agotado")
def safe_tool_call( name: str, inputs: dict, timeout_seconds: int = 30,) -> ToolResult: """ Ejecuta una herramienta con tiempo límite, capturando todas las excepciones. Siempre devuelve un ToolResult — nunca lanza excepciones. """ signal.signal(signal.SIGALRM, timeout_handler) signal.alarm(timeout_seconds) try: return dispatch_tool(name, inputs) except TimeoutError: return ToolResult( success=False, error=f"Herramienta '{name}' agotó el tiempo después de {timeout_seconds}s" ) except Exception as e: return ToolResult( success=False, error=f"Herramienta '{name}' lanzó {type(e).__name__}: {e}" ) finally: signal.alarm(0)Cuando una herramienta devuelve success: false, el modelo puede decidir si reintentar, probar una alternativa o informar el fallo. Para estrategias de reintento más amplias, consulta Patrones de Recuperación de Errores.
Patrón 5: Validación y Truncado de Resultados
Valida los resultados de herramientas antes de devolverlos al modelo.
MAX_TOOL_RESULT_CHARS = 8000
def validate_result(result: ToolResult, expected_keys: list[str]) -> ToolResult: if not result.success or not isinstance(result.data, dict): return result missing = [k for k in expected_keys if k not in result.data] if missing: return ToolResult( success=False, error=f"Respuesta de herramienta con campos faltantes: {missing}" ) return result
def truncate_result(result: ToolResult) -> ToolResult: content = result.to_content() if len(content) <= MAX_TOOL_RESULT_CHARS: return result
truncated_data = { "truncated": True, "chars_omitted": len(content) - MAX_TOOL_RESULT_CHARS, "content": content[:MAX_TOOL_RESULT_CHARS], } return ToolResult( success=result.success, data=truncated_data, error="Resultado truncado — demasiado grande para la ventana de contexto", )
def safe_tool_call_validated( name: str, inputs: dict, expected_keys: list[str] | None = None,) -> ToolResult: result = safe_tool_call(name, inputs) if expected_keys: result = validate_result(result, expected_keys) result = truncate_result(result) return resultPara observar y depurar estos fallos en producción, consulta Depuración y Observabilidad.
Errores Comunes
Error 1: Devolver Respuestas Crudas de API
El modelo recibe un objeto anidado con 30 campos, la mayoría irrelevantes. Elige el incorrecto.
Solución: Da forma a la respuesta antes de devolverla. Devuelve solo lo que el modelo necesita para tomar su próxima decisión.
Error 2: Herramientas con Efectos Secundarios sin Confirmación
Una herramienta que envía un correo o elimina un registro no debe ejecutarse silenciosamente.
Solución: Para acciones irreversibles, usa un patrón de dos herramientas: plan_email devuelve una vista previa, send_email realmente envía.
Error 3: Responsabilidades de Herramientas Superpuestas
Dos herramientas que hacen cosas similares obligan al modelo a adivinar cuál usar.
Solución: Cada herramienta debe tener un propósito distinto y no superpuesto.
Error 4: Sin Tiempo Límite en Herramientas Externas
Una llamada API lenta de terceros bloquea indefinidamente todo el bucle del agente.
Solución: Siempre establece un tiempo límite (Patrón 4).
Lista de Verificación para Producción
- Cada herramienta tiene una descripción desde la perspectiva del modelo (“Úsalo cuando…”)
- Campos enum para todas las entradas de valor fijo
- Cada herramienta devuelve
{success, data, error}— nunca respuestas crudas de API - Llamadas de herramienta envueltas en
safe_tool_call - Ejecución paralela para respuestas de múltiples herramientas
- Tiempo límite establecido en cada herramienta externa
- Truncado de resultados para cargas de tamaño variable
- Validación para herramientas que llaman APIs externas
Próximos Pasos
- Empieza con el esquema — escribe tu
input_schemaantes del cuerpo de la función - Añade el envoltorio
ToolResulta cada herramienta existente - Incorpora
safe_tool_callpara reforzar tu bucle de agente - Configura validación de resultados para herramientas que llaman APIs externas
Guías relacionadas:
- Construyendo tu Primer Servidor MCP
- Patrones Multi-Agente
- Sistemas de Memoria para Agentes
- Patrones de Recuperación de Errores en Agentes
- Depuración y Observabilidad