Agentes de IA: Arquitectura y Patrones de Diseño
¿Qué es un Agente de IA?
Un agente de IA es un sistema que usa un LLM como "cerebro" para tomar decisiones, ejecutar acciones y adaptarse en función de los resultados. A diferencia de una cadena lineal (prompt → respuesta), un agente puede:
- Razonar sobre qué hacer a continuación
- Usar herramientas (APIs, bases de datos, código)
- Iterar hasta completar la tarea
- Autocorregirse cuando algo falla
┌────────────────┐
│ AGENTE │
│ (LLM + │
│ Razonamiento)│
└───────┬────────┘
│
┌───────┼───────┐
▼ ▼ ▼
[Tool 1] [Tool 2] [Tool 3]
(Search) (API) (Code)
│ │ │
└───────┼───────┘
▼
Observar resultados
│
┌───────┴───────┐
│ ¿Tarea │
│ completa? │
└───────┬───────┘
No │ Sí
▼ │ ▼
Iterar │ Respuesta
│ │ final
└────┘
Patrones de Razonamiento
ReAct (Reasoning + Acting)
El patrón más usado. El agente alterna entre razonar y actuar.
Pregunta: ¿Cuál es la población de la capital de Francia?
Pensamiento: Necesito saber la capital de Francia.
Acción: search("capital de Francia")
Observación: La capital de Francia es París.
Pensamiento: Ahora necesito la población de París.
Acción: search("población París 2025")
Observación: París tiene ~2.1 millones de habitantes (12M área metropolitana).
Pensamiento: Ya tengo toda la información necesaria.
Respuesta: La población de París es ~2.1M (ciudad) / ~12M (área metro).
Plan-and-Execute
Primero planifica todos los pasos, luego los ejecuta.
Plan:
1. Buscar documentación de la API de pagos
2. Identificar endpoints de webhook
3. Generar código de integración
4. Escribir tests
Ejecución:
Paso 1: search_docs("payment API webhooks") → [resultados]
Paso 2: extract_endpoints(resultados) → [POST /webhooks, ...]
Paso 3: generate_code(endpoints) → código Python
Paso 4: generate_tests(código) → tests pytest
LATS (Language Agent Tree Search)
Explora múltiples caminos de solución en paralelo y elige el mejor.
Query
/ | \
/ | \
Path1 Path2 Path3
/ | \
Tool A Tool B Tool A
Score:8 Score:6 Score:9 ← Evaluar
| |
Expandir Expandir ← Seguir mejores paths
Function Calling / Tool Use
El mecanismo que permite a los LLMs invocar funciones externas.
from openai import OpenAI
client = OpenAI()
# Definir herramientas
tools = [
{
"type": "function",
"function": {
"name": "search_knowledge_base",
"description": "Busca información en la base de conocimiento interna",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "La consulta de búsqueda"
},
"filters": {
"type": "object",
"properties": {
"category": {"type": "string"},
"date_from": {"type": "string", "format": "date"},
}
}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "execute_sql",
"description": "Ejecuta una consulta SQL de solo lectura",
"parameters": {
"type": "object",
"properties": {
"query": {
"type": "string",
"description": "Consulta SQL SELECT"
}
},
"required": ["query"]
}
}
},
]
# El LLM decide cuándo y qué herramienta usar
response = client.chat.completions.create(
model="gpt-4o",
messages=[{"role": "user", "content": "¿Cuántas ventas hubo ayer?"}],
tools=tools,
# tool_choice controla si el LLM puede usar herramientas:
# "auto" = el LLM decide si usar tools o responder directamente (recomendado)
# "none" = nunca usar tools (fuerza respuesta de texto)
# "required" = DEBE usar al menos una tool
# {"type": "function", "function": {"name": "..."}} = fuerza una tool específica
tool_choice="auto",
)
# Loop de tool calls: el LLM NO ejecuta las tools — solo las "solicita".
# Nosotros ejecutamos la función y enviamos el resultado de vuelta al LLM
# para que genere la respuesta final (o solicite otra tool).
if response.choices[0].message.tool_calls:
for tool_call in response.choices[0].message.tool_calls:
function_name = tool_call.function.name
arguments = json.loads(tool_call.function.arguments)
# Ejecutar la herramienta (nosotros, no el LLM)
result = execute_tool(function_name, arguments)
# Enviar resultado al LLM para que genere la respuesta final
# role: "tool" indica que este mensaje es el resultado de una tool call
# tool_call_id vincula este resultado con la solicitud específica del LLM
messages.append({"role": "tool", "tool_call_id": tool_call.id, "content": result})
Diseño de Herramientas para Agentes
Principios de Diseño
- Descripción clara — El LLM elige la herramienta basándose en la descripción
- Parámetros mínimos — Menos parámetros = menos errores
- Idempotencia — Las herramientas deben ser seguras de re-ejecutar
- Errores descriptivos — Mensajes que el agente pueda usar para corregir
- Timeout y rate limiting — Prevenir loops infinitos
# MAL: Descripción vaga, parámetros confusos
{"name": "do_stuff", "description": "Does stuff", ...}
# BIEN: Descripción precisa, parámetros claros
{
"name": "search_customer_orders",
"description": "Busca pedidos de un cliente por email o ID. "
"Retorna los últimos 10 pedidos con estado, fecha y monto. "
"Usar cuando el usuario pregunte sobre pedidos, entregas o compras.",
"parameters": {
"type": "object",
"properties": {
"customer_email": {"type": "string", "description": "Email del cliente"},
"status": {"type": "string", "enum": ["pending", "shipped", "delivered"]},
},
"required": ["customer_email"]
}
}
Manejo de Estado y Memoria
class AgentState:
"""Estado persistente del agente durante una conversación."""
def __init__(self):
self.messages: list = []
self.tool_results: dict = {}
self.iteration_count: int = 0
self.max_iterations: int = 10
def add_message(self, role: str, content: str):
self.messages.append({"role": role, "content": content})
def should_stop(self) -> bool:
"""Condiciones de parada para evitar loops infinitos."""
if self.iteration_count >= self.max_iterations:
return True
return False
Guardrails para Agentes
class AgentGuardrails:
BLOCKED_ACTIONS = ["DROP", "DELETE", "TRUNCATE", "ALTER"]
@staticmethod
def validate_sql(query: str) -> bool:
"""Solo permite SELECT queries."""
normalized = query.strip().upper()
if not normalized.startswith("SELECT"):
return False
for blocked in AgentGuardrails.BLOCKED_ACTIONS:
if blocked in normalized:
return False
return True
@staticmethod
def validate_api_call(url: str, method: str) -> bool:
"""Solo permite llamadas a dominios aprobados."""
from urllib.parse import urlparse
allowed_domains = ["api.internal.com", "data.company.com"]
parsed = urlparse(url)
return parsed.hostname in allowed_domains
@staticmethod
def check_budget(cost_so_far: float, max_budget: float = 1.0) -> bool:
"""Detiene el agente si supera el presupuesto."""
return cost_so_far < max_budget
Patrones de Error y Recovery
Agente intenta Tool A ──► Error: timeout
│
▼
Retry con backoff (1s, 2s, 4s)
│
┌─────┴─────┐
▼ ▼
Éxito Max retries
│ │
▼ ▼
Continuar Fallback: Tool B alternativa
│
┌────┴────┐
▼ ▼
Éxito Error final
│ │
▼ ▼
Continuar Respuesta parcial
+ explicación
Resumen
Los agentes de IA son poderosos pero requieren diseño cuidadoso:
- Patrón ReAct — Razonamiento + acción iterativa
- Function calling — Mecanismo estándar de tool use
- Herramientas bien diseñadas — Descripciones claras, parámetros mínimos
- Guardrails — Validación de acciones, budgets, rate limits
- Recovery patterns — Retries, fallbacks, respuestas parciales
🧠 Preguntas de Repaso
1. En el patrón ReAct (Reasoning + Acting), ¿cuál es el ciclo que sigue un agente para resolver una tarea?
- A) Input → Output → Validación
- B) Pensamiento → Acción → Observación, repitiendo hasta completar
- C) Planificación completa → Ejecución secuencial
- D) Búsqueda paralela → Selección del mejor resultado
Respuesta: B) — ReAct alterna entre Pensamiento (razonamiento sobre qué hacer), Acción (ejecutar una herramienta) y Observación (interpretar el resultado), repitiendo el ciclo hasta completar la tarea. Es el patrón de agentes más utilizado.
2. Cuando un LLM hace "function calling", ¿quién ejecuta realmente la función/herramienta?
- A) El LLM ejecuta la función directamente en el servidor
- B) El LLM solo solicita la ejecución; el desarrollador/sistema ejecuta la función y envía el resultado de vuelta al LLM
- C) La función se ejecuta automáticamente en el navegador del usuario
- D) Un servicio externo independiente ejecuta todas las funciones
Respuesta: B) — El LLM NO ejecuta herramientas directamente — solo genera una solicitud estructurada (nombre de función + argumentos). El sistema del desarrollador ejecuta la función y devuelve el resultado al LLM con role: "tool" y el tool_call_id correspondiente.
3. ¿Por qué es importante configurar max_iterations en un agente de IA?
- A) Para aumentar la calidad de las respuestas
- B) Para evitar loops infinitos que consumen tokens y presupuesto sin avanzar
- C) Para mejorar la velocidad de respuesta del modelo
- D) Para limitar el número de usuarios concurrentes
Respuesta: B) — Sin un límite de iteraciones (ej: max_iterations=10), un agente podría entrar en loops infinitos, consumiendo tokens y presupuesto sin resolver la tarea. Es un guardrail esencial para control de costos y estabilidad.
4. ¿Cuál es la estrategia correcta de recovery cuando una herramienta del agente falla repetidamente?
- A) Detener el agente inmediatamente sin respuesta
- B) Retry con backoff exponencial → si falla, fallback a herramienta alternativa → si falla, respuesta parcial con explicación
- C) Reiniciar el agente desde cero con un prompt diferente
- D) Ignorar el error y continuar con la siguiente herramienta
Respuesta: B) — La estrategia de recovery sigue una cascada: primero retry con backoff exponencial (1s, 2s, 4s), si se agotan los reintentos se usa una herramienta alternativa (fallback), y si todo falla se da una respuesta parcial explicando qué información falta.