El mes pasado vi a un agente usar 47 llamadas a la API para responder una pregunta que podría haber resuelto en dos. La configuración parecía razonable: llamada a herramientas habilitada, búfer de memoria en su lugar, manejo de errores en el papel. El agente simplemente no tenía idea de qué herramienta usar primero, seguía llamando a la misma y no tenía forma de saber que estaba en un bucle.
Esto es lo que sucede cuando construyes un agente sin comprender sus modos de falla reales. La arquitectura importa. Esto es lo que he aprendido al lanzar agentes en producción.
Los Tres Patrones Centrales
Un agente de IA necesita tres cosas para funcionar: una forma de decidir qué hacer, acceso a herramientas y memoria de lo que sucedió. La forma en que conectas estas tres cosas determina si tu agente funciona limpiamente o quema tokens persiguiendo su cola.
Patrón 1: Enrutamiento Simple (Decisión → Herramienta → Respuesta)
Esta es la base. El agente ve una solicitud del usuario, elige una herramienta, la ejecuta y devuelve un resultado. Sin bucles. Sin recursión. Punto de decisión único.
from anthropic import Anthropic
client = Anthropic()
def route_and_execute(user_message: str) -> str:
tools = [
{
"name": "lookup_price",
"description": "Obtener el precio actual de un producto",
"input_schema": {
"type": "object",
"properties": {
"product_id": {"type": "string"}
},
"required": ["product_id"]
}
},
{
"name": "check_stock",
"description": "Verificar inventario de un producto",
"input_schema": {
"type": "object",
"properties": {
"product_id": {"type": "string"}
},
"required": ["product_id"]
}
}
]
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
tools=tools,
messages=[{
"role": "user",
"content": user_message
}]
)
# Verificar tipo de respuesta
for block in response.content:
if block.type == "tool_use":
tool_name = block.name
tool_input = block.input
# Ejecutar herramienta (implementación simulada)
if tool_name == "lookup_price":
result = f"Precio para {tool_input['product_id']}: $29.99"
elif tool_name == "check_stock":
result = f"Stock para {tool_input['product_id']}: 15 unidades"
else:
result = "Herramienta no encontrada"
return result
# Si no se llamó a ninguna herramienta, devolver respuesta de texto
return response.content[0].text if response.content else ""
# Uso
response = route_and_execute("¿Cuál es el precio del producto ABC-123?")
print(response)
Esto funciona bien para tareas sencillas: consultas de atención al cliente, búsquedas de datos, decisiones únicas. Falla cuando la respuesta requiere múltiples pasos.
Patrón 2: Bucle Agente (Decidir → Herramienta → Evaluar → Decidir de Nuevo)
Cuando una tarea necesita varios pasos (investigar algo, luego calcular, luego formatear), necesitas un bucle. El agente toma una decisión, la ejecuta, recibe retroalimentación y decide qué hacer a continuación. La trampa: sin condiciones de salida, esto se vuelve infinito.
def agentic_loop(user_message: str, max_iterations: int = 5) -> str:
tools = [
{
"name": "search_knowledge_base",
"description": "Buscar información en documentos internos",
"input_schema": {
"type": "object",
"properties": {
"query": {"type": "string"}
},
"required": ["query"]
}
},
{
"name": "calculate_metric",
"description": "Realizar cálculo sobre datos",
"input_schema": {
"type": "object",
"properties": {
"values": {"type": "array", "items": {"type": "number"}},
"operation": {"type": "string", "enum": ["sum", "average", "max"]}
},
"required": ["values", "operation"]
}
}
]
messages = [{
"role": "user",
"content": user_message
}]
for iteration in range(max_iterations):
response = client.messages.create(
model="claude-3-5-sonnet-20241022",
max_tokens=1024,
tools=tools,
messages=messages
)
# Verificar si tenemos una respuesta final
if response.stop_reason == "end_turn":
return response.content[0].text if response.content else ""
# Procesar llamadas a herramientas
if response.stop_reason == "tool_use":
# Agregar respuesta del asistente al historial de mensajes
messages.append({
"role": "assistant",
"content": response.content
})
# Ejecutar herramientas y recopilar resultados
tool_results = []
for block in response.content:
if block.type == "tool_use":
# Ejecución simulada
if block.name == "search_knowledge_base":
result = f"Documentos encontrados sobre: {block.input['query']}"
elif block.name == "calculate_metric":
values = block.input['values']
op = block.input['operation']
if op == "sum":
result = f"Resultado: {sum(values)}"
elif op == "average":
result = f"Resultado: {sum(values)/len(values)}"
else:
result = f"Resultado: {max(values)}"
else:
result = "Herramienta desconocida"
tool_results.append({
"type": "tool_result",
"tool_use_id": block.id,
"content": result
})
# Agregar resultados de herramientas de vuelta a los mensajes
messages.append({
"role": "user",
"content": tool_results
})
return "Se alcanzó el máximo de iteraciones"
# Uso
result = agentic_loop("Encuentra nuestros datos de ingresos del tercer trimestre y calcula el valor mensual promedio")
print(result)
Punto clave: El límite max_iterations no es opcional. He visto agentes alcanzar más de 100 llamadas porque nadie estableció un límite. Establécelo en 5 por defecto y aumenta solo cuando las pruebas demuestren que necesitas más.
Patrón 3: Planificación Jerárquica (Planificar → Ejecutar Subtareas → Sintetizar)
Para flujos de trabajo complejos (analizar un informe, planificar una campaña, depurar un sistema), descompón el problema en subtareas primero. Haz que el agente planifique, luego ejecute el plan y, finalmente, sintetice. Esto te da visibilidad sobre lo que el agente cree que necesita hacer antes de que desperdicie tokens haciéndolo.
Llamada a Herramientas: Configuración y Modos de Falla
La llamada a herramientas en Claude (desde marzo de 2025) es confiable, pero la configuración importa. Defines las herramientas como esquemas JSON. El modelo decide qué herramienta llamar y con qué parámetros. Ejecutas la herramienta y devuelves los resultados.
La mayoría de las fallas provienen de dos lugares:
1. Descripciones de Herramientas Vagas
Malo: "search" - buscar información
Bueno: "search_customer_database" - buscar por correo electrónico, nombre o ID. Devuelve el registro del cliente con el historial de compras. Usa esto cuando necesites verificar los detalles del cliente antes de procesar pedidos.
El modelo elige herramientas basándose en las descripciones. Descripciones vagas llevan a una selección incorrecta de herramientas.
2. Falta de Retroalimentación de Errores
Cuando una herramienta falla, dile al agente específicamente por qué. No devuelvas solo un error genérico. En el código del bucle agente anterior, cada resultado de herramienta regresa como un mensaje. Si una búsqueda falla, di "No se encontraron resultados para la consulta 'xyz'. Intenta con un término de búsqueda diferente." en lugar de "Error."
Memoria: Sin Estado vs. Persistente
Los agentes necesitan recordar el contexto. Tienes dos opciones:
Historial de Conversación (sin estado): Pasa todo el hilo de mensajes al modelo cada vez. Funciona para interacciones cortas. Se vuelve caro rápidamente. Costo de tokens = O(longitud_conversación) por llamada.
Memoria Persistente: Almacena el resumen de la conversación, hechos clave y registros de ejecución de herramientas en una base de datos. Pasa solo el contexto relevante. El costo de tokens se mantiene plano.
Para agentes en producción, usa memoria persistente. Almacena los últimos 10-15 mensajes en el historial del hilo, más un resumen rodante. Si la conversación tiene más de ~20 mensajes, pídele al modelo que resuma y almacene por separado.
Un Patrón para Probar Hoy
Elige el Patrón 1 (Enrutamiento Simple) e impleméntalo para una tarea real en tu sistema: búsqueda de clientes, recuperación de datos, cualquier cosa de un solo paso. Define 2-3 herramientas con descripciones específicas. Ejecuta 10 consultas de prueba y verifica:
- ¿Eligió la herramienta correcta? (Si no, reescribe la descripción).
- ¿Usó los parámetros correctos? (Si no, tu esquema de entrada no está claro).
- ¿La salida realmente responde a la pregunta del usuario?
Una vez que eso funcione, pasa al Patrón 2. Los agentes de múltiples pasos no son más difíciles, son solo el Patrón 1 en un bucle. Pero solo agrega el bucle cuando el paso único no sea suficiente.