SISTEMA DE ACCESO INFORMÁTICO

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:

  1. Qué herramienta llamar — impulsado por el name y description de la herramienta
  2. Qué argumentos pasar — impulsado por el input_schema de 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 enum para cualquier campo con un conjunto fijo de valores válidos
  • Establece minimum/maximum en campos numéricos para prevenir entradas fuera de rango
  • Marca los campos como required solo 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 json
from dataclasses import dataclass, asdict
from typing import Any, Optional
@dataclass
class 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 result

Para 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

  1. Empieza con el esquema — escribe tu input_schema antes del cuerpo de la función
  2. Añade el envoltorio ToolResult a cada herramienta existente
  3. Incorpora safe_tool_call para reforzar tu bucle de agente
  4. Configura validación de resultados para herramientas que llaman APIs externas

Guías relacionadas: