Módulo 1: Anatomía de un AI Agent
4. Taxonomía de Agentes
Descripción de la cápsula
No todos los agentes son iguales. Un agente que responde "¿Cuánto es 2+2?" llamando a calculator y devolviendo el resultado no tiene nada que ver con uno que planifica una investigación de mercado en 5 pasos, ejecuta búsquedas, evalúa la calidad de cada fuente, y decide si necesita replanificar. Ambos son agentes — pero su nivel de deliberación, su complejidad y su costo son radicalmente diferentes.
Esta cápsula te da dos sistemas de clasificación complementarios. El primero es la taxonomía académica de Russell & Norvig (AIMA): simple reflex → model-based → goal-based → utility-based, organizada por sofisticación creciente. El segundo es la taxonomía práctica que usarás a diario: reactive, deliberative y hybrid. La primera te da vocabulario preciso para diseñar; la segunda te da criterio para decidir qué construir.
La realidad es que casi todo agente con LLM que construirás en producción es híbrido: reactivo cuando la tarea es simple, deliberativo cuando es compleja. Entender la taxonomía completa te permite diseñar intencionalmente en qué punto del espectro se ubica tu agente — en vez de que surja por accidente.
Taxonomía Académica: Russell & Norvig
La clasificación clásica de agentes viene del libro Artificial Intelligence: A Modern Approach (Russell & Norvig). Define cuatro niveles progresivos de sofisticación en cómo un agente toma decisiones.
Nivel 1: Simple Reflex Agent
El agente más básico. Responde directamente al input actual con una regla fija. No tiene memoria, no tiene modelo del mundo, no planifica. Es un mapping directo: condición → acción.
Input actual → Regla de condición-acción → Acción
def simple_reflex_agent(user_input: str) -> str:
"""Agente que responde con reglas if/else sin LLM ni memoria."""
user_input_lower = user_input.lower()
if "clima" in user_input_lower:
city = user_input.split("en ")[-1].strip("?")
return f"[get_weather] Consultando clima en {city}..."
if "calcula" in user_input_lower or "cuánto" in user_input_lower:
return "[calculator] Procesando cálculo..."
return "No tengo una regla para ese tipo de pregunta."
print(simple_reflex_agent("¿Cuál es el clima en Madrid?"))
print(simple_reflex_agent("¿Cuánto es 15 * 23?"))
print(simple_reflex_agent("Investiga el impacto de AI en educación"))
# Output esperado:
# [get_weather] Consultando clima en Madrid...
# [calculator] Procesando cálculo...
# No tengo una regla para ese tipo de pregunta.
El problema es obvio: si el usuario dice "¿Hace calor en Barcelona?" en vez de "clima en Barcelona", la regla falla. No hay razonamiento — solo pattern matching estático.
Cuándo aparece en la práctica: Casi nunca como agente autónomo. Pero sí como componente: un router que decide "si el input es sobre clima, enviar a weather_agent; si es sobre código, enviar a code_agent". Ese router es un simple reflex agent.
Nivel 2: Model-Based Reflex Agent
Agrega un modelo interno del mundo — estado que persiste entre interacciones. Las decisiones dependen del input actual + lo que el agente "recuerda".
Input actual + Estado interno (modelo del mundo) → Acción
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage, SystemMessage
def model_based_agent(user_input: str, history: list) -> tuple[str, list]:
"""Agente que usa historial de conversación como modelo del mundo."""
model = init_chat_model("openai:gpt-4.1-mini")
messages = [
SystemMessage(content="Eres un asistente útil. Usa el contexto previo para responder."),
*history,
HumanMessage(content=user_input)
]
response = model.invoke(messages)
history.append(HumanMessage(content=user_input))
history.append(response)
return response.content, history
history = []
resp, history = model_based_agent("Me llamo Carlos y trabajo en fintech", history)
print(f"Turno 1: {resp}")
resp, history = model_based_agent("¿Qué herramientas de AI me recomiendas?", history)
print(f"Turno 2: {resp}")
# Output esperado:
# Turno 1: ¡Hola Carlos! Genial que trabajes en fintech...
# Turno 2: Para fintech, te recomiendo... [contextualizado a fintech]
En el turno 2, el agente recuerda que Carlos trabaja en fintech. Un simple reflex trataría cada turno como independiente.
Cuándo aparece en la práctica: Cualquier chatbot con historial de conversación es model-based. Es el tipo más común de "agente básico" con LLM.
Nivel 3: Goal-Based Agent
Agrega objetivos explícitos. El agente tiene una meta definida y planifica una secuencia de acciones para alcanzarla. La decisión sobre qué acción tomar depende de si esa acción acerca al agente a su objetivo.
Objetivo + Estado actual → Plan (secuencia de acciones) → Ejecutar paso a paso
from dotenv import load_dotenv
load_dotenv()
from langchain.chat_models import init_chat_model
from langchain_core.messages import HumanMessage, SystemMessage
def goal_based_agent(goal: str) -> str:
"""Agente que descompone un objetivo en un plan y lo ejecuta."""
model = init_chat_model("openai:gpt-4.1-mini")
# Fase 1: Generar plan
plan_response = model.invoke([
SystemMessage(content="Descompón la tarea en 3-5 pasos concretos. Solo la lista numerada."),
HumanMessage(content=f"Objetivo: {goal}")
])
plan = plan_response.content
print(f"Plan generado:\n{plan}\n")
# Fase 2: Ejecutar cada paso secuencialmente
results = []
for i, step in enumerate(plan.strip().split("\n"), 1):
if not step.strip():
continue
step_result = model.invoke([
SystemMessage(content="Ejecuta el paso indicado y genera un resultado conciso."),
HumanMessage(content=f"Paso: {step}\nContexto previo: {results[-1] if results else 'Ninguno'}")
])
results.append(step_result.content)
print(f"Paso {i} completado: {step.strip()[:60]}...")
return results[-1] if results else "Sin resultados"
result = goal_based_agent("Investigar las 3 principales aplicaciones de AI en healthcare")
# Output esperado:
# Plan generado:
# 1. Identificar las áreas principales de aplicación de AI en healthcare
# 2. Investigar diagnóstico por imagen con AI
# 3. Investigar descubrimiento de fármacos con AI
# 4. Sintetizar hallazgos en un resumen comparativo
#
# Paso 1 completado: 1. Identificar las áreas principales de aplicación de A...
# Paso 2 completado: 2. Investigar diagnóstico por imagen con AI...
# ...
La estructura primero planifica, luego ejecuta es lo que distingue al goal-based. Ese patrón (plan-and-execute) es el corazón del Módulo 5.
Cuándo aparece en la práctica: Agentes de investigación, agentes que escriben documentos largos, agentes que gestionan proyectos — cualquier tarea que requiera descomposición en pasos con ejecución secuencial.
Nivel 4: Utility-Based Agent
El nivel más sofisticado. El agente tiene una función de utilidad que evalúa qué tan buena es cada posible acción. Elige la acción que maximiza el valor esperado, considerando costo, latencia, probabilidad de éxito y calidad.
Estado + Acciones posibles → Evaluar utilidad de cada una → Elegir la óptima
def utility_based_agent(query: str, tools: list[dict]) -> str:
"""Agente que elige la mejor herramienta evaluando costo/beneficio."""
best_tool = None
best_score = -1
for t in tools:
relevance = 1.0 if any(kw in query.lower() for kw in t["keywords"]) else 0.3
cost_score = 1.0 - t["cost_normalized"]
speed_score = 1.0 - t["latency_normalized"]
# Función de utilidad: pesos configurables
utility = (relevance * 0.4) + (cost_score * 0.2) + (speed_score * 0.2) + (t["accuracy"] * 0.2)
print(f" {t['name']}: utilidad = {utility:.2f}")
if utility > best_score:
best_score = utility
best_tool = t["name"]
return best_tool
tools = [
{"name": "tavily_search", "keywords": ["busca", "actual", "tendencias"], "cost_normalized": 0.2, "latency_normalized": 0.4, "accuracy": 0.85},
{"name": "wikipedia", "keywords": ["qué es", "definición", "historia"], "cost_normalized": 0.0, "latency_normalized": 0.2, "accuracy": 0.7},
{"name": "arxiv_search", "keywords": ["paper", "investigación", "estudio"], "cost_normalized": 0.0, "latency_normalized": 0.6, "accuracy": 0.95},
]
selected = utility_based_agent("Busca las tendencias actuales en quantum computing", tools)
print(f"\nHerramienta seleccionada: {selected}")
# Output esperado:
# tavily_search: utilidad = 0.81
# wikipedia: utilidad = 0.50
# arxiv_search: utilidad = 0.47
#
# Herramienta seleccionada: tavily_search
Cuándo aparece en la práctica: En sistemas multi-herramienta donde no todas las opciones son iguales. Un agente con 15 tools necesita elegir inteligentemente — no llamar al más caro cuando el barato resuelve igual. También aparece en routing de modelos: ¿uso GPT-4.1 (caro, preciso) o GPT-4.1-mini (barato, rápido)?
Progresión visual
Nivel 1 (Simple Reflex): Input → [if/else] → Acción
Nivel 2 (Model-Based): Input + Estado → [Decisión] → Acción
Nivel 3 (Goal-Based): Objetivo + Estado → [Plan] → [Paso 1] → [Paso 2] → Resultado
Nivel 4 (Utility-Based): Estado + Opciones → [Evaluar utilidad] → Acción óptima
Taxonomía Práctica: Reactive vs Deliberative vs Hybrid
La taxonomía de Russell & Norvig es precisa pero académica. En el día a día de AI Engineering, usarás una clasificación más directa basada en cuánto piensa el agente antes de actuar.
Reactive Agents
Un agente reactivo toma decisiones turno a turno sin planificación explícita. Recibe input, decide qué tool usar, y listo. Es el loop ReAct que implementaste en la cápsula anterior.
from dotenv import load_dotenv
load_dotenv()
from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from langchain_core.messages import HumanMessage, SystemMessage, ToolMessage
@tool
def get_weather(city: str) -> str:
"""Obtiene el clima actual de una ciudad."""
climates = {"Madrid": "22°C, soleado", "Barcelona": "20°C, nublado"}
return f"Clima en {city}: {climates.get(city, '15°C, parcialmente nublado')}"
@tool
def calculator(expression: str) -> str:
"""Calcula una expresión matemática."""
try:
return str(eval(expression))
except Exception as e:
return f"Error: {e}"
model = init_chat_model("openai:gpt-4.1-mini")
tools = [get_weather, calculator]
tools_by_name = {t.name: t for t in tools}
model_with_tools = model.bind_tools(tools)
def reactive_agent(user_input: str, max_iterations: int = 3) -> str:
"""Agente puramente reactivo: decide tool por tool, sin plan."""
messages = [HumanMessage(content=user_input)]
for i in range(max_iterations):
response = model_with_tools.invoke(messages)
messages.append(response)
if not response.tool_calls:
return response.content
for tc in response.tool_calls:
result = tools_by_name[tc["name"]].invoke(tc["args"])
messages.append(ToolMessage(content=result, tool_call_id=tc["id"]))
return "Límite de iteraciones alcanzado"
print(reactive_agent("¿Cuál es el clima en Madrid y cuánto es 12 * 7?"))
# Output esperado:
# El clima en Madrid es 22°C y soleado. Y 12 × 7 = 84.
Fortalezas: Baja latencia, simple de implementar, predecible para tareas simples.
Debilidades: Para tareas complejas con dependencias entre pasos, puede tomar caminos subóptimos porque no planificó.
Deliberative Agents
Un agente deliberativo planifica antes de actuar. Primero descompone la tarea, luego ejecuta el plan, y puede re-planificar si algo falla.
# Asume imports y model del ejemplo anterior
@tool
def web_search(query: str) -> str:
"""Busca información actualizada en la web."""
return f"Resultados para '{query}': datos relevantes encontrados."
def deliberative_agent(task: str) -> str:
"""Agente que planifica primero y ejecuta después."""
tools_list = [web_search, calculator]
tools_map = {t.name: t for t in tools_list}
bound_model = model.bind_tools(tools_list)
# FASE 1: Planificar
plan = model.invoke([
SystemMessage(content="Descompón la tarea en pasos numerados. Solo la lista."),
HumanMessage(content=f"Tarea: {task}")
]).content
print(f"=== PLAN ===\n{plan}\n")
# FASE 2: Ejecutar siguiendo el plan
messages = [
SystemMessage(content=f"Ejecuta este plan paso a paso:\n{plan}"),
HumanMessage(content=f"Tarea: {task}")
]
for _ in range(10):
response = bound_model.invoke(messages)
messages.append(response)
if not response.tool_calls:
return response.content
for tc in response.tool_calls:
result = tools_map[tc["name"]].invoke(tc["args"])
messages.append(ToolMessage(content=result, tool_call_id=tc["id"]))
print(f"Ejecutado: {tc['name']}({list(tc['args'].values())[0][:40]}...)")
return "Timeout"
print(deliberative_agent("Investiga AI en healthcare y genera un resumen"))
# Output esperado:
# === PLAN ===
# 1. Buscar panorama general de AI en healthcare
# 2. Buscar AI en diagnóstico por imagen
# 3. Sintetizar hallazgos en un resumen
#
# Ejecutado: web_search(AI healthcare 2026...)
# Ejecutado: web_search(AI diagnóstico imagen...)
# [Resumen sintetizado]
Fortalezas: Mejor para tareas complejas con pasos dependientes, más controllable (puedes inspeccionar el plan antes de ejecutar), se puede re-planificar si un paso falla.
Debilidades: Mayor latencia (mínimo 1 LLM call extra para planificar), over-kill para tareas simples.
Hybrid Agents
El tipo más útil en producción. Un agente híbrido decide dinámicamente si actuar de forma reactiva o deliberativa según la complejidad del input. Preguntas simples → reactivo (rápido). Tareas complejas → deliberativo (planifica primero).
# Usa los mismos imports y tools de los ejemplos anteriores (search, calculator)
def classify_complexity(user_input: str) -> str:
"""Clasifica si una tarea requiere planificación o es reactiva."""
response = model.invoke([
SystemMessage(content="""Clasifica como SIMPLE o COMPLEJA.
SIMPLE: 1-2 herramientas, respuesta directa.
COMPLEJA: múltiples pasos, investigación, o síntesis.
Responde SOLO con SIMPLE o COMPLEJA."""),
HumanMessage(content=user_input)
])
return response.content.strip().upper()
def hybrid_agent(user_input: str) -> str:
"""Agente que elige entre modo reactivo y deliberativo."""
complexity = classify_complexity(user_input)
print(f"Complejidad: {complexity}")
if "SIMPLE" in complexity:
print("→ Modo: REACTIVO")
return reactive_agent(user_input) # Reutiliza el reactive_agent definido arriba
else:
print("→ Modo: DELIBERATIVO")
# Planificar primero, luego ejecutar con tools
plan = model.invoke([
SystemMessage(content="Genera un plan de 3-5 pasos. Solo la lista."),
HumanMessage(content=user_input)
])
print(f"Plan: {plan.content[:80]}...")
messages = [
SystemMessage(content=f"Ejecuta este plan:\n{plan.content}"),
HumanMessage(content=user_input)
]
for _ in range(8):
response = model_with_tools.invoke(messages)
messages.append(response)
if not response.tool_calls:
return response.content
for tc in response.tool_calls:
result = tools_by_name[tc["name"]].invoke(tc["args"])
messages.append(ToolMessage(content=result, tool_call_id=tc["id"]))
return "Timeout deliberativo"
print(hybrid_agent("¿Cuánto es 25 * 4?"))
print(hybrid_agent("Investiga las 3 principales tendencias en AI 2026 y compáralas"))
# Output esperado:
# Complejidad: SIMPLE
# → Modo: REACTIVO
# 25 × 4 = 100
#
# Complejidad: COMPLEJA
# → Modo: DELIBERATIVO
# Plan: 1. Buscar tendencias AI 2026...
# [Resultado con investigación y comparación]
El router de complejidad es el componente clave. En producción puede usar heurísticas simples (longitud del input, palabras clave), un LLM call clasificador, o un modelo fine-tuneado ligero.
Mapeo entre Taxonomías
| Russell & Norvig | Taxonomía Práctica | Memoria | Planifica | Evalúa opciones |
|---|---|---|---|---|
| Simple Reflex | Reactive | ❌ | ❌ | ❌ |
| Model-Based | Reactive (con estado) | ✅ | ❌ | ❌ |
| Goal-Based | Deliberative | ✅ | ✅ | ❌ |
| Utility-Based | Deliberative / Hybrid | ✅ | ✅ | ✅ |
Los agentes LLM modernos (create_react_agent) son model-based por defecto — historial de mensajes como modelo del mundo. Con planning (Módulo 5) pasan a goal-based. Con optimización de tool selection por costo/calidad, a utility-based.
La mayoría de agentes en producción viven en la zona model-based a goal-based, con comportamiento hybrid. Es raro usar un LLM para simple reflex (un if/else basta) y raro construir utility-based puro (la evaluación de utilidad añade complejidad).
Comparación
| Aspecto | Reactive | Deliberative | Hybrid |
|---|---|---|---|
| Latencia | Baja (1-2 iteraciones) | Alta (planning + ejecución) | Variable |
| Complejidad | Baja (~25 líneas) | Alta (~80+ líneas) | Media (~60 + router) |
| Tareas ideales | Q&A, lookups, 1-2 tools | Investigación, reportes | Producción general |
| Costo (tokens) | Bajo | Alto (más LLM calls) | Optimizado |
| En LangGraph | create_react_agent | Plan-and-execute | Router + conditional edges |
| Russell & Norvig | Simple/Model-based | Goal-based | Utility-based |
Cuándo usar cada tipo
- ✅ Reactive: "¿Cuál es mi saldo?" → 1 tool call → respuesta. El 80% de consultas típicas.
- ✅ Deliberative: "Analiza ventas Q1, compara con Q4, genera recomendaciones." Pasos dependientes.
- ✅ Hybrid: Cualquier sistema en producción que recibe inputs variados.
- ❌ Simple reflex con LLM: Si if/else basta, no pagues tokens.
- ❌ Deliberative para todo: Planificar "¿cuánto es 2+2?" es over-engineering.
Dónde Cae create_react_agent en la Taxonomía
El agente prebuilt de LangGraph (create_react_agent) es model-based reactive por defecto:
- ✅ Model-based: Mantiene el historial de mensajes (modelo del mundo)
- ✅ Reactive: Decide tool-by-tool sin planificación explícita
- ❌ No goal-based: No genera un plan antes de actuar
- ❌ No utility-based: No evalúa alternativas, usa la primera decisión del modelo
from langgraph.prebuilt import create_react_agent
agent = create_react_agent(model, [search, calculator])
result = agent.invoke({"messages": [("user", "¿Qué es MCP en AI agents?")]})
print(result["messages"][-1].content)
# Output esperado:
# MCP (Model Context Protocol) es un protocolo estándar para conectar
# modelos de AI con herramientas y fuentes de datos externas...
Esto no es una limitación — es una decisión de diseño. Para la mayoría de tareas, reactive es suficiente. Cuando necesites deliberación, usarás planning patterns (Módulo 5) o un StateGraph custom (Módulo 4).
La Realidad en Producción: Casi Todo es Hybrid
La solución práctica en producción combina ambos modos:
- Router de complejidad — Clasifica el input como simple o complejo
- Path reactivo — Para queries simples: directo a tools
- Path deliberativo — Para tareas complejas: planificar → ejecutar → revisar
- Feedback loop — Si el path reactivo falla, escalar a deliberativo
Este patrón es exactamente lo que construirás en el proyecto evolutivo (Research Agent, Módulos 4-10).
Conexión con el Proyecto
En el proyecto de este módulo (cápsula 08): implementarás un agente ReAct manual que es model-based reactive y compararás su comportamiento en tareas simples vs complejas.
En el proyecto evolutivo (Módulos 4-10):
- Módulo 4: StateGraph permite modelar paths reactivos y deliberativos como nodos distintos
- Módulo 5: Planning y reflection convierten al agente en goal-based deliberative
- Módulo 8: Sistema multi-agente: supervisor (goal-based) coordina workers (reactive)
- Módulo 10: Pattern hybrid completo con routing de complejidad en producción
La taxonomía es el vocabulario para documentar decisiones de diseño: "Este agente es hybrid con router de complejidad, path reactivo para Q&A y path deliberativo para research."
Troubleshooting
Problema 1: El agente deliberativo es demasiado lento para tareas simples
Causa: Estás usando deliberative para todo — incluidas preguntas que se resuelven con 1 tool call.
Solución: Implementar un router de complejidad con heurísticas simples (longitud del input, keywords como "investiga", "compara"). No necesita ser perfecto — incluso una heurística básica mejora la latencia significativamente.
Problema 2: El agente reactivo se pierde en tareas multi-step
Causa: El agente toma decisiones locales que no llevan al resultado global correcto.
Solución: Detectar que la tarea falló (timeout, resultado incompleto) y reiniciar con planificación:
result = reactive_agent(user_input)
if result == "Límite de iteraciones alcanzado":
result = deliberative_agent(user_input)
Problema 3: El plan generado no tiene sentido
Causa: El prompt de planificación es genérico. El LLM genera pasos vagos.
Solución: Incluir las herramientas disponibles en el prompt de planificación para que el modelo genere pasos ejecutables: "Herramientas disponibles: web_search(query), calculator(expression). Cada paso debe usar una herramienta con argumentos concretos."
Problema 4: El router de complejidad clasifica mal
Causa: El LLM-based router añade latencia y puede equivocarse.
Solución: Usar heurísticas primero (pregunta corta con "?" = SIMPLE, input largo = COMPLEX), LLM solo como fallback para casos ambiguos.
Problema 5: No sé qué tipo de agente construir
Solución — regla de decisión rápida:
- ✅ ¿Se resuelve con 1-2 tool calls? → Reactive
- ✅ ¿Los pasos dependen de resultados previos? → Deliberative
- ✅ ¿El sistema recibe inputs variados? → Hybrid
- ✅ ¿No estás seguro? → Empieza reactive, escala si falla
Ejercicios
Ejercicio 1: Clasificar agentes existentes (Fácil)
Clasifica cada sistema según la taxonomía de Russell & Norvig Y la taxonomía práctica:
- Un chatbot de soporte que responde preguntas consultando una base de conocimiento
- Un agente que recibe "escribe un blog post" y genera outline → búsqueda → draft → revisión
- Un router que envía queries de soporte al departamento correcto basado en keywords
- Un agente que elige entre 3 APIs de traducción según costo, velocidad y calidad del idioma
Ver solución
- Model-based / Reactive — Historial de conversación (model-based), responde query por query (reactive).
- Goal-based / Deliberative — Objetivo explícito, genera plan (outline → búsqueda → draft → revisión).
- Simple reflex / Reactive — Mapea keywords a departamentos con reglas fijas. No necesita LLM.
- Utility-based / Hybrid — Evalúa APIs según costo + velocidad + calidad. Maximiza utilidad compuesta.
Explicación: Pregúntate: ¿tiene memoria? (model-based), ¿planifica? (goal-based), ¿evalúa alternativas? (utility-based). Si ninguna → simple reflex.
Ejercicio 2: Implementar un router de complejidad (Fácil)
Implementa una función classify_task(user_input: str) -> str que retorne "REACTIVE" o "DELIBERATIVE" usando solo heurísticas (sin LLM call). Debe clasificar correctamente estos 4 inputs:
- "¿Cuánto es 15 * 23?" → REACTIVE
- "Investiga las tendencias de AI en educación, compara 3 frameworks, y genera un reporte" → DELIBERATIVE
- "¿Qué hora es?" → REACTIVE
- "Analiza los pros y contras de microservicios vs monolitos para una startup de 5 personas" → DELIBERATIVE
Ver solución
def classify_task(user_input: str) -> str:
"""Clasifica complejidad con heurísticas, sin LLM."""
words = user_input.lower().split()
deliberative_keywords = ["investiga", "analiza", "compara", "reporte", "genera", "pros", "contras", "tendencias"]
action_count = sum(1 for w in words if w in deliberative_keywords)
if action_count >= 2:
return "DELIBERATIVE"
if len(words) > 20:
return "DELIBERATIVE"
if len(words) <= 10 and "?" in user_input:
return "REACTIVE"
return "REACTIVE"
test_cases = [
("¿Cuánto es 15 * 23?", "REACTIVE"),
("Investiga tendencias de AI, compara 3 frameworks, genera reporte", "DELIBERATIVE"),
("¿Qué hora es?", "REACTIVE"),
("Analiza los pros y contras de microservicios vs monolitos", "DELIBERATIVE"),
]
for text, expected in test_cases:
result = classify_task(text)
print(f"{'✅' if result == expected else '❌'} '{text[:45]}...' → {result}")
# Output esperado:
# ✅ '¿Cuánto es 15 * 23?...' → REACTIVE
# ✅ 'Investiga tendencias de AI, compara 3 framew...' → DELIBERATIVE
# ✅ '¿Qué hora es?...' → REACTIVE
# ✅ 'Analiza los pros y contras de microservicios...' → DELIBERATIVE
Explicación: Las heurísticas son imperfectas pero instantáneas (0ms vs ~500ms de un LLM call). En producción combinas heurísticas con un LLM classifier como fallback: las heurísticas resuelven el 80% de casos, el LLM solo el 20% ambiguo.
Ejercicio 3: Convertir reactive a deliberative (Medio)
Dado el reactive_agent de esta cápsula, conviértelo en uno que primero genera un plan de 3 pasos y luego ejecuta. Prueba con: "¿Cuál es el clima en Madrid y en Barcelona, y cuál ciudad tiene mejor temperatura?"
Ver solución
def deliberative_weather_agent(user_input: str) -> str:
# FASE 1: Planificar
plan = model.invoke([
SystemMessage(content="Genera un plan de 3 pasos. Solo la lista numerada."),
HumanMessage(content=user_input)
])
print(f"Plan:\n{plan.content}\n")
# FASE 2: Ejecutar con el plan como contexto
messages = [
SystemMessage(content=f"Sigue este plan:\n{plan.content}\nUsa get_weather para datos reales."),
HumanMessage(content=user_input)
]
for _ in range(6):
response = model_with_tools.invoke(messages)
messages.append(response)
if not response.tool_calls:
return response.content
for tc in response.tool_calls:
result = tools_by_name[tc["name"]].invoke(tc["args"])
messages.append(ToolMessage(content=result, tool_call_id=tc["id"]))
return "Timeout"
print(deliberative_weather_agent("¿Clima en Madrid y Barcelona, cuál mejor temperatura?"))
# Output esperado:
# Plan:
# 1. Consultar el clima actual en Madrid
# 2. Consultar el clima actual en Barcelona
# 3. Comparar temperaturas y determinar cuál es mejor
#
# Madrid tiene 22°C (soleado) y Barcelona tiene 20°C (nublado).
# Madrid tiene mejor temperatura con 22°C y cielo soleado.
Explicación: El agente deliberativo genera un plan ANTES de ejecutar. El plan le da estructura: sabe que necesita dos búsquedas y luego una comparación. El reactive haría lo mismo por "instinto" del LLM, pero para tareas más complejas el plan explícito evita que el agente olvide pasos.
Ejercicio 4: Diseñar una función de utilidad (Medio)
Implementa evaluate_tool_utility(query, tool_metadata) que asigne un puntaje 0-100 a cada herramienta. Usa heurísticas (no LLM). Metadata: name, cost_per_call, avg_latency_ms, accuracy_score.
Ver solución
def evaluate_tool_utility(query: str, tool_metadata: dict) -> float:
"""Evalúa utilidad de una herramienta para una query (0-100)."""
relevance_map = {
"calculator": ["calcula", "cuánto", "suma", "multiplica"],
"web_search": ["busca", "investiga", "tendencias", "actual"],
"get_weather": ["clima", "temperatura", "lluvia", "pronóstico"],
}
keywords = relevance_map.get(tool_metadata["name"], [])
keyword_hits = sum(1 for kw in keywords if kw in query.lower())
relevance = min(keyword_hits / max(len(keywords), 1) * 40, 40)
cost = (1 - min(tool_metadata["cost_per_call"] / 0.05, 1)) * 20
latency = (1 - min(tool_metadata["avg_latency_ms"] / 5000, 1)) * 20
accuracy = tool_metadata["accuracy_score"] * 20
return round(relevance + cost + latency + accuracy, 1)
tools_metadata = [
{"name": "calculator", "cost_per_call": 0.0, "avg_latency_ms": 10, "accuracy_score": 1.0},
{"name": "web_search", "cost_per_call": 0.01, "avg_latency_ms": 2000, "accuracy_score": 0.8},
{"name": "get_weather", "cost_per_call": 0.005, "avg_latency_ms": 500, "accuracy_score": 0.95},
]
for t in tools_metadata:
print(f" {t['name']}: {evaluate_tool_utility('¿Cuál es la temperatura en Madrid?', t)}/100")
# Output esperado:
# calculator: 40.0/100 (irrelevante pero barato/rápido)
# web_search: 38.0/100 (algo relevante pero lento)
# get_weather: 71.8/100 (muy relevante, rápido, preciso)
Explicación: La función de utilidad combina relevancia, costo, latencia y precisión con pesos configurables. En producción, los pesos se ajustan según prioridades: si la latencia importa más (UX interactivo), sube el peso de latency; si la precisión es crítica (médico, legal), sube accuracy.
Ejercicio 5: Implementar fallback reactive → deliberative (Difícil)
Implementa un agente que intenta resolver con modo reactivo (max 2 iteraciones). Si falla (llega al límite sin respuesta), automáticamente escala a modo deliberativo.
Ver solución
def reactive_attempt(user_input: str, max_iter: int = 2) -> tuple[str, bool]:
"""Intenta resolver reactivamente. Retorna (resultado, éxito)."""
messages = [HumanMessage(content=user_input)]
for _ in range(max_iter):
response = model_with_tools.invoke(messages)
messages.append(response)
if not response.tool_calls:
return response.content, True
for tc in response.tool_calls:
result = tools_by_name[tc["name"]].invoke(tc["args"])
messages.append(ToolMessage(content=result, tool_call_id=tc["id"]))
return "No completado", False
def deliberative_attempt(user_input: str) -> str:
"""Resuelve con planificación explícita."""
plan = model.invoke([
SystemMessage(content="Genera un plan de 3-5 pasos. Solo la lista."),
HumanMessage(content=user_input)
])
messages = [
SystemMessage(content=f"Ejecuta este plan:\n{plan.content}"),
HumanMessage(content=user_input)
]
for _ in range(8):
response = model_with_tools.invoke(messages)
messages.append(response)
if not response.tool_calls:
return response.content
for tc in response.tool_calls:
result = tools_by_name[tc["name"]].invoke(tc["args"])
messages.append(ToolMessage(content=result, tool_call_id=tc["id"]))
return "Timeout deliberativo"
def fallback_agent(user_input: str) -> str:
"""Escalamiento automático: reactive → deliberative."""
result, success = reactive_attempt(user_input, max_iter=2)
if success:
return result
print("Reactivo falló → escalando a deliberativo...")
return deliberative_attempt(user_input)
# Tarea simple: reactive basta
print(fallback_agent("Busca información sobre quantum computing"))
# Tarea compleja: reactive falla, deliberative resuelve
print(fallback_agent("Busca 3 temas sobre AI, resume cada uno, y genera síntesis comparativa"))
# Output esperado:
# [Información sobre quantum computing...] (reactive)
# Reactivo falló → escalando a deliberativo...
# [Síntesis comparativa de los 3 temas] (deliberative)
Explicación: El pattern de fallback es la forma más pragmática de implementar un agente hybrid. Intentas el path rápido (reactive) y solo pagas el costo extra (deliberative) cuando es necesario. El max_iter=2 es deliberadamente bajo: si no se resolvió en 2 iteraciones, probablemente necesita planificación.
Ejercicio 6: Documentar un agente con taxonomía (Difícil)
Un agente de soporte técnico: mantiene historial, 8 tools, responde preguntas directas sin planificar, pero para troubleshooting complejo genera diagnóstico paso a paso. Documéntalo usando ambas taxonomías.
Ver solución
# Agente de Soporte Técnico — Arquitectura
## Taxonomía Russell & Norvig
- **Primario:** Model-Based (historial como modelo del mundo)
- **Secundario:** Goal-Based (troubleshooting genera plan de diagnóstico)
## Taxonomía Práctica
- **Tipo:** Hybrid (reactive para Q&A, deliberative para troubleshooting)
- **Router:** Keywords + longitud de input
- Input < 15 palabras + "?" → REACTIVE
- Keywords ["error", "falla", "no funciona"] → DELIBERATIVE
- **Max iterations:** 3 (reactive), 8 (deliberative)
- **Fallback:** reactive timeout → deliberative → escalación humana
## Decisión de diseño
Hybrid porque 70% de queries son preguntas directas (1 tool call).
Solo 30% requiere diagnóstico multi-step. Deliberative para todo
añadiría ~2s de latencia innecesaria al 70%.
Explicación: Documentar con taxonomía fuerza claridad de diseño. Incluir la razón de la decisión (70/30 split) previene que alguien cambie la arquitectura sin entender el trade-off original.
Resumen
En esta cápsula aprendiste:
- La taxonomía de Russell & Norvig clasifica agentes en 4 niveles: simple reflex, model-based, goal-based, utility-based
- La taxonomía práctica divide agentes en 3 tipos: reactive, deliberative, hybrid
create_react_agentes model-based reactive por defecto — mantiene historial pero no planifica- Un agente deliberative genera un plan antes de ejecutar — mejor para tareas complejas, más lento
- Los agentes hybrid son el estándar en producción: router de complejidad elige entre path reactivo y deliberativo
- La función de utilidad permite elegir entre alternativas optimizando costo, latencia y calidad
- Documentar el tipo de agente en la arquitectura mejora la mantenibilidad del sistema
Próxima cápsula: Agentes vs Chains vs Workflows — aprenderás un decision framework para elegir cuándo usar un agente autónomo, cuándo un chain determinístico, y cuándo un workflow orquestado. Es la decisión de arquitectura más importante en AI Engineering.
Recursos Adicionales
- Russell & Norvig: Intelligent Agents (AIMA Cap. 2) — Capítulo foundational: taxonomía de 4 niveles de agentes
- Building Effective Agents (Anthropic) — Perspectiva práctica sobre diseño y cuándo usar agentes
- LangGraph create_react_agent — API del agente model-based reactive prebuilt
- Plan-and-Execute Agents (LangChain Blog) — Patrón deliberativo en LangGraph
- ReAct Paper — El paper que formalizó el loop ReAct
- Cognitive Architectures for Language Agents (CoALA) — Framework para clasificar arquitecturas cognitivas de agentes
- LangGraph Agent Architectures — Conceptos de arquitecturas de agentes
- Emerging AI Agent Architectures Survey — Survey de arquitecturas emergentes