Del agente ReAct a CodeAct: el cambio de paradigma en arquitecturas autónomas

Durante más de dos años, casi cualquier sistema autónomo que he implementado en producción ha descansado sobre el mismo pilar: el patrón ReAct (Reasoning + Acting). La premisa era limpia y elegante para su momento. Le dabas al modelo un espacio de pensamiento paso a paso, una lista de herramientas formateadas en esquemas JSON y esperabas que devolviera una llamada a función estructurada.

Sin embargo, cuando llevas estos sistemas a procesos con dependencias complejas, ese esquema cruje. El ciclo interminable de ida y vuelta para parsear JSON, sumado a la incapacidad del modelo para gestionar bucles o transformaciones intermedias de datos sin consultar de nuevo a la API, convierte la arquitectura en un pozo sin fondo de latencia y costes.

Mi experiencia con arquitecturas agentuales cambió cuando empecé a probar CodeAct (Code as Actions). En lugar de pedirle al modelo que elija herramientas predefinidas en formatos serializados rígidos, el agente genera directamente código ejecutable (habitualmente Python) que corre en un entorno aislado. Este giro no es una optimización menor; es una redefinición completa de cómo interactúan los LLMs con el mundo exterior.

Cómo funciona el agente ReAct clásico y dónde se rompe en producción

El concepto del agente ReAct nació para resolver la alucinación pura en modelos de lenguaje combinando razonamiento verbal con acciones discretas. El ciclo estándar es bien conocido: Thought (razonamiento interno), Action (elección de herramienta y argumentos en JSON) y Observation (la respuesta del entorno devuelta al modelo).

Ciclo ReAct tradicional:
[Pensamiento] -> [Acción JSON: tool_call] -> [Entorno ejecuta tool] -> [Observación] -> [Pensamiento] ...

Este bucle funcionó razonablemente bien para demostraciones de laboratorio y tareas de consulta simple de APIs. Pero cuando comencé a desplegarlo en clientes con pipelines de datos reales, los problemas estructurales salieron a la superficie:

  1. La sobrecarga de esquemas JSON: Si tienes 15 o 20 herramientas, debes inyectar sus esquemas OpenAPI en cada llamada del agente. Esto no solo consume miles de tokens de contexto antes de que el modelo procese el primer dato, sino que aumenta la probabilidad de que el LLM confunda tipos de variables o invente parámetros inexistentes. Para profundizar en cómo el exceso de información innecesaria degrada las decisiones del modelo, escribí sobre Context Engineering: qué es y cómo lo aplico.
  2. Incapacidad de ejecutar flujos de control sin consultar al LLM: Si un agente ReAct necesita procesar una lista de 50 elementos devuelta por una base de datos y filtrar los que cumplan una condición, el patrón tradicional fuerza al modelo a realizar 50 llamadas sucesivas de herramienta o a recibir toda la carga cruda en el contexto. Esto revienta el consumo de tokens y genera latencias inviables.
  3. Fragilidad sintáctica: Los errores de parseo de JSON siguen siendo uno de los principales motivos de fallo en producción. A pesar de los modos de salida estructurada (structured outputs) de los proveedores, cualquier inconsistencia mínima rompe la cadena de razonamiento y exige mecanismos de retry costosos.

Qué propone CodeAct: Python ejecutable como espacio de acción universal

CodeAct replantea el espacio de acción. En vez de restringir al modelo a un conjunto artificial de llamadas a herramientas mapeadas en JSON, su acción básica es emitir un bloque de código ejecutable. El entorno contiene un intérprete (normalmente un sandbox de Python como Docker, gVisor o WASM) que ejecuta el script, captura stdout/stderr y devuelve el resultado como observación.

Ciclo CodeAct:
[Pensamiento / Plan] -> [Acción: Bloque Python ejecutable] -> [Sandbox ejecuta código] -> [Stdout / Error] -> [Siguiente acción o respuesta final]

Al adoptar este enfoque, el modelo ya no necesita una herramienta específica para “filtrar una lista”, “calcular un promedio” o “concatenar cadenas”. Utiliza la biblioteca estándar de Python o paquetes previamente instalados (como requests, pandas o httpx).

En mis pruebas con flujos de trabajo autónomos complejos, pasar de ReAct a CodeAct ha reducido las rondas de interacción (los “turns” del agente) entre un 40% y un 60%. Si comparamos esto con lo que discutí al analizar BMAD Method vs Workflow de Agentes IA, la diferencia radica en que reducimos la necesidad de orquestaciones excesivamente rígidas porque el propio lenguaje de programación absorbe la lógica de control.

Comparativa técnica: flujo JSON vs. bloque de código ejecutable

Para entender la diferencia práctica, veamos cómo resuelven ambos paradigmas una tarea idéntica: obtener una lista de usuarios de una API interna, filtrar los activos y calcular la media de antigüedad.

El enfoque ReAct tradicional

El agente necesita dos herramientas: fetch_users() y, potencialmente, una herramienta para calcular estadísticas o volcar todo al prompt.

// Turno 1: El agente pide los datos
{
  "thought": "Necesito obtener la lista de usuarios para calcular la media de los activos.",
  "action": "fetch_users",
  "action_input": {"department": "engineering"}
}

El backend ejecuta la función y devuelve una lista de 200 objetos JSON al contexto del LLM.

// Turno 2: El contexto ahora está inundado de 200 registros
// El agente debe procesar todo con sus propios tokens de atención
{
  "thought": "He recibido 200 usuarios. Voy a sumar la antigüedad de los que tienen status='active'...",
  "action": "calculate_average",
  "action_input": {"values": [3, 5, 2, 8, 1, 4, ...]}
}

El riesgo de alucinación aritmética es alto, el gasto de tokens es desproporcionado y la latencia sube varios segundos.

El enfoque CodeAct

El agente recibe la descripción de que existe una librería o API accesible desde Python. Emite un script autocontenido:

## Pensamiento: Descargo los usuarios, filtro en memoria y saco la media directamente.
import requests
import statistics

response = requests.get("https://api.internal.local/users?department=engineering")
users = response.json()

active_tenures = [u["tenure_years"] for u in users if u.get("status") == "active"]

if active_tenures:
    avg = statistics.mean(active_tenures)
    print(f"Total activos: {len(active_tenures)} | Media: {avg:.2f}")
else:
    print("No se encontraron usuarios activos.")

El sandbox ejecuta el script. El LLM recibe una única observación limpia:

Total activos: 142 | Media: 4.18

El modelo no vio los 200 registros en su ventana de contexto. Manejó la lógica de control, el filtrado y el cálculo numérico dentro del entorno de ejecución, reservando su capacidad cognitiva para interpretar el resultado final.

Costes, tokens y fiabilidad: lo que dicen los benchmarks y mis métricas

El paper original de CodeAct (Wang et al., 2024) demostró que en benchmarks como Mint y HumanEval, los agentes basados en código superan ampliamente a los agentes ReAct tradicionales basados en llamadas a herramientas. Alcanzaron tasas de éxito de hasta un 20% superiores usando exactamente el mismo modelo base (como GPT-4 o Claude 3.5 Sonnet).

En mi propia infraestructura, donde mido de forma estricta los tokens consumidos por tarea completada, los números respaldan esta tendencia:

Métrica operativaAgente ReAct clásicoAgente CodeActImpacto relativo
Tokens consumidos (promedio)~14.200 tokens/tarea~5.800 tokens/tarea-59% de tokens
Rondas LLM-Entorno (turns)6 a 9 turnos2 a 4 turnosMenos de la mitad
Errores de parseo estructural8.4% de fallos1.2% (errores de código recuperables)Reducción drástica
Coste por ejecución completa0.082 $0.034 $-58% de gasto

Un punto crítico que he comprobado es la autorrecuperación. Cuando un agente ReAct produce un JSON inválido, el mensaje de error suele ser abstracto (“Failed to parse tool call”). El modelo a menudo repite el mismo error en el siguiente intento. En cambio, cuando un script en CodeAct falla, el intérprete devuelve un Traceback exacto de Python:

Traceback (most recent call last):
  File "main.py", line 4, in <module>
    avg = statistics.mean(active_tenures)
statistics.StatisticsError: mean requires at least one data point

Los LLMs de frontera son excepcionales leyendo tracebacks de código. En el siguiente turno, el modelo entiende de inmediato qué falló (la lista estaba vacía) y añade una condición try/except o una comprobación previa antes de recalcular.

Cuándo mantener ReAct y cuándo migrar a CodeAct

A pesar de mi preferencia por CodeAct para tareas complejas, no considero que ReAct deba descartarse en todos los escenarios. Hay límites técnicos y de seguridad que determinan la elección:

Cuándo mantener un agente ReAct

  • Entornos sin posibilidad de sandbox seguro: Si tu infraestructura no permite desplegar contenedores efímeros aislados por políticas corporativas estrictas, ejecutar código generado por un LLM es un riesgo de seguridad inadmisible.
  • Operaciones CRUD atómicas: Si tu agente solo debe llamar a una función para enviar un email puntual o actualizar un campo en un CRM, ReAct con tool calling nativo es más directo y no requiere la infraestructura de un intérprete.
  • Modelos pequeños o locales (<8B parámetros): Los modelos compactos a menudo tienen dificultades para generar código Python complejo sin errores de sintaxis, mientras que siguen relativamente bien un esquema JSON simple con restricciones guiadas.

Cuándo migrar a CodeAct

  • Manipulación y análisis de datos: Cualquier tarea que requiera transformar JSONs, procesar CSVs, cruzar registros o generar gráficos debe ejecutarse mediante código.
  • Procesos con múltiples dependencias: Cuando una acción depende del resultado de otra en bucles repetitivos.
  • Agentes de ingeniería de software o DevOps: Tareas donde el output natural del sistema ya es código o comandos de sistema (como los frameworks estilo SWE-agent u OpenDevin).

El cambio hacia CodeAct no es una moda superficial; responde a una necesidad elemental de eficiencia computacional. Tratar al código como el lenguaje nativo de acción de los agentes permite que los modelos hagan aquello para lo que son mejores (planificar, razonar y corregir) delegando la ejecución determinista a la CPU.

Scroll al inicio