Si le pides a un modelo de lenguaje que resuelva un problema complejo solo con texto, alucina. Si le das herramientas pero no lo dejas razonar, ejecuta acciones a ciegas y rompe tu base de datos.
En 2022, un paper de investigadores de Princeton y Google (Yao et al.) demostró una verdad incómoda:
Un LLM que solo "piensa" (Chain-of-Thought) vive desconectado del mundo real y acumula errores lógicos. Un LLM que solo "actúa" (Action-only) carece de plan y ejecuta llamadas a APIs sin evaluar si la respuesta previa tuvo sentido.
La solución fue combinarlos en un ciclo continuo: ReAct (Reasoning + Acting).
Cualquier agente moderno que uses hoy —desde Claude Code hasta un asistente con LangGraph o el SDK de OpenAI— desciende directamente de este patrón.
Aquí te explico la mecánica interna del patrón ReAct en agentes de IA, cómo implementarlo en TypeScript puro sin librerías hinchadas y los tres fallos críticos que debes evitar en producción.
Anatomía del patrón ReAct en agentes de IA: Thought → Action → Observation
El patrón ReAct descompone la resolución de cualquier tarea en tres fases iterativas:
┌───────────────────────────────┐
▼ │
┌───────────────┐ │
│ 1. THOUGHT │ Razona el estado │
└───────┬───────┘ │
│ │
▼ │
┌───────────────┐ │
│ 2. ACTION │ Ejecuta herramienta │
└───────┬───────┘ │
│ │
▼ │
┌───────────────┐ │
│ 3.OBSERVATION │ Recibe resultado │
└───────┬───────┘ │
│ │
└───────── ¿Finalizado? ────────┘
│ │
[No] [Sí]
│
▼
[Respuesta Final]
- Thought (Pensamiento): El modelo analiza el historial de la conversación, identifica qué sub-objetivo tiene pendiente y decide qué información le falta.
- Action (Acción): El modelo emite una llamada estructurada a una herramienta externa (una consulta SQL, un comando de terminal, una llamada HTTP o una búsqueda en vector store).
- Observation (Observación): El entorno ejecuta la herramienta y devuelve el resultado crudo al contexto del modelo.
- Evaluación: El modelo lee la observación, actualiza su pensamiento y decide si ya puede responder al usuario o si necesita otra iteración.
Implementación mínima en TypeScript (sin frameworks)
Para entender ReAct no necesitas instalar un framework de 40 dependencias. El núcleo del patrón es un bucle while que evalúa las llamadas a funciones devueltas por el LLM:
import Anthropic from "@anthropic-ai/sdk";
const client = new Anthropic();
// 1. Definición de herramientas
const tools: Anthropic.Tool[] = [{
name: "search_database",
description: "Busca registros de clientes por email",
input_schema: {
type: "object",
properties: { email: { type: "string" } },
required: ["email"]
}
}];
// 2. Ejecutor de herramientas del entorno
async function executeTool(name: string, input: any): Promise<string> {
if (name === "search_database") {
// Simulación de consulta real
return JSON.stringify({ id: "usr_402", status: "active", plan: "pro" });
}
throw new Error(`Herramienta ${name} no encontrada`);
}
// 3. El bucle ReAct
export async function runReActAgent(userGoal: string) {
const messages: Anthropic.MessageParam[] = [
{ role: "user", content: userGoal }
];
const MAX_ITERATIONS = 5;
let iterations = 0;
while (iterations < MAX_ITERATIONS) {
iterations++;
// Fase THOUGHT + ACTION (generación del modelo)
const response = await client.messages.create({
model: "claude-sonnet-5",
max_tokens: 1024,
tools,
messages
});
// Si el modelo decide responder directamente, salimos
if (response.stop_reason === "end_turn") {
const finalReply = response.content.find(c => c.type === "text");
return finalReply?.type === "text" ? finalReply.text : "";
}
// Si el modelo solicita ejecutar una herramienta (ACTION)
if (response.stop_reason === "tool_use") {
messages.push({ role: "assistant", content: response.content });
const toolUse = response.content.find(c => c.type === "tool_use");
if (toolUse && toolUse.type === "tool_use") {
// Fase OBSERVATION (ejecución real en tu infraestructura)
const observation = await executeTool(toolUse.name, toolUse.input);
// Se inyecta la observación al historial para la siguiente vuelta
messages.push({
role: "user",
content: [{
type: "tool_result",
tool_use_id: toolUse.id,
content: observation
}]
});
}
}
}
throw new Error("El agente alcanzó el límite máximo de iteraciones sin resolver.");
}
Fíjate en lo que ocurre aquí: el LLM no tiene acceso a tu base de datos. El LLM solo emite la intención de consultar. Tu backend ejecuta la acción, captura la salida y se la devuelve como una Observation.
ReAct con Tool Calling nativo vs. ReAct por texto plano
En los primeros papers de 2022, el modelo generaba texto plano con prefijos:
Thought: Necesito consultar el saldo del usuario con ID 102.
Action: get_balance[102]
Observation: 450.00 EUR
Thought: El usuario tiene 450 euros. Ya puedo responder.
Final Answer: Tu saldo disponible es de 450,00 €.
Ese enfoque por texto requería expresiones regulares frágiles para parsear qué herramienta quería invocar el modelo.
Hoy, los proveedores (Anthropic, OpenAI) integran Tool Calling nativo a nivel de tokens. El modelo garantiza JSON válido para los argumentos y separa el bloque de razonamiento del bloque de ejecución mediante bloques tipados (tool_use y tool_result). El principio conceptual sigue siendo 100% ReAct.
Los 3 problemas del patrón ReAct en producción
Cuando pasas de una demo a un sistema con usuarios reales, te enfrentas a tres trampas:
1. Bucles infinitos (Infinite Loop Trap)
Si una herramienta devuelve un error que el modelo no sabe interpretar (por ejemplo, 500 Internal Server Error), un agente ingenuo intentará llamar a la misma herramienta con los mismos parámetros una y otra vez.
Solución: Límite estricto de iteraciones (MAX_ITERATIONS = 5-8) y formatear las excepciones dentro de la Observation explicando qué falló para que el modelo cambie de estrategia.
2. Explosión del context window
En cada ciclo, la Observation completa se añade al historial. Si una herramienta devuelve un JSON de 4.000 líneas con logs o registros de base de datos, el consumo de tokens y el coste se disparan exponencialmente en la siguiente iteración.
Solución: Truncar, filtrar y resumir las salidas de las herramientas antes de entregárselas al modelo. Si quieres profundizar en cómo estructurar esa memoria sin que se dispare el contexto, lo cubrimos en context engineering: cómo estructurar la memoria de tus agentes de IA.
3. Falta de determinismo en pipelines críticos
ReAct es ideal para exploración y tareas no lineales. Pero si tu flujo de negocio tiene pasos rígidos (Paso 1: Validar DNI → Paso 2: Cobrar en Stripe → Paso 3: Enviar email), dejar que un agente ReAct decida el orden en tiempo real es una irresponsabilidad.
Para procesos estructurados, la combinación óptima es un grafo determinista (como LangGraph o flujos guiados por especificaciones) donde ReAct se use solo dentro de nodos específicos que requieran adaptabilidad.
Esta metodología de ingeniería de agentes con límites estrictos es el núcleo del curso Construye con IA: De la Idea al Producto con Claude y Specs, donde mostramos cómo gobernar agentes sin perder el control de la ejecución.
ReAct vs. Plan-and-Solve: cuándo elegir cuál
| Criterio | Patrón ReAct | Patrón Plan-and-Solve |
|---|---|---|
| Estrategia | Decide el siguiente paso sobre la marcha tras cada observación | Genera un plan completo de $N$ pasos antes de actuar |
| Adaptabilidad | Alta: Si un paso falla, recalcula inmediatamente | Baja: Si el entorno cambia, el plan inicial queda obsoleto |
| Latencia | Mayor (un round-trip al LLM por cada herramienta) | Menor (menos llamadas al modelo si el plan es estático) |
| Caso de uso ideal | Búsqueda interactiva, debugging, navegación web, soporte | Generación de informes largos, migraciones batch |
Para patrones avanzados de observabilidad y control de memoria en arquitecturas de agentes, en Dominicode Labs construimos y testeamos implementaciones en producción semana a semana.
Preguntas frecuentes
¿Qué significa ReAct en inteligencia artificial?
ReAct es el acrónimo de Reasoning and Acting. Es un patrón de diseño para agentes de IA que combina el razonamiento paso a paso (Thought) con la ejecución de herramientas externas (Action) y la lectura de resultados (Observation).
¿ReAct es una librería o un concepto arquitectónico?
Es un concepto arquitectónico. LangGraph tiene un factory prebuilt, create_react_agent (aunque ya está marcado como deprecado a favor de create_agent), y otros frameworks como CrewAI ofrecen sus propias implementaciones de referencia. Pero el patrón en sí no depende de ninguna librería: se implementa en cualquier lenguaje con llamadas nativas a la API del LLM.
¿Cuál es la diferencia entre Chain-of-Thought (CoT) y ReAct?
Chain-of-Thought solo produce razonamiento interno sin interactuar con el entorno exterior, lo que suele derivar en alucinaciones cuando faltan datos. ReAct conecta ese razonamiento con herramientas externas reales (APIs, bases de datos, código).
¿Cómo evitar que un agente ReAct gaste demasiados tokens?
Estableciendo un límite máximo de pasos por sesión, filtrando el contenido de las observaciones antes de inyectarlas al contexto y utilizando modelos más pequeños y rápidos para las llamadas intermedias. Antes de optimizar a ciegas, mide en qué parte del bucle se te va el presupuesto.
Por Bezael Pérez — Developer senior con más de 15 años de experiencia y fundador de Dominicode.
