Agentes de reembolso: LangGraph sobrevivió donde CrewAI y AutoGen fracasaron
Las mismas herramientas y políticas en los tres marcos: solo el estado explícito, la idempotencia y los puntos de control sobrevivieron a las eliminaciones causadas por el caos.
La carga de trabajo que agota a los agentes de demostración
Los frameworks multiagente parecen impecables en las demostraciones de investigación y blogs. No hay consecuencias graves si una herramienta se ejecuta dos veces. Un agente encargado de procesar reembolsos es menos tolerante: al recibir un mensaje del cliente, debe clasificar la intención, cargar el pedido, evaluar las políticas (plazo, categoría, reembolsos anteriores), llamar a la pasarela de pago como máximo una vez cuando sea aplicable, escalar el caso con una justificación por escrito en caso contrario y dejar un registro de auditoría.
Esa estructura es común en entornos de producción: tomar decisiones, interactuar con sistemas con estado y mantener la responsabilidad. Requiere un estado estable a lo largo de los pasos, una toma de decisiones determinista entre reembolsar o escalar el caso, efectos secundarios idempotentes y pausas humanas que sobrevivan a los reinicios del proceso, no simplemente un estado de inactividad.
Tres implementaciones utilizaron las mismas herramientas, modelo y lógica de políticas. Solo una sobrevivió a las pruebas de caos que interrumpían el proceso en pleno funcionamiento.
Herramientas compartidas
Mantenga la competencia justa: funciones de herramienta idénticas, incluyendo una pasarela inestable y una clave de idempotencia vinculada al ID del pedido.
# tools.py — identical across all three implementations
import time
import uuid
from dataclasses import dataclass
from typing import Literal
class PaymentGatewayError(Exception):
pass
@dataclass
class Order:
order_id: str
customer_id: str
item_category: str
amount_cents: int
purchased_at: float
refund_count: int
# Fake DB — in prod this is Postgres behind a repository class
_ORDERS = {
"ORD-4471": Order("ORD-4471", "CUST-991", "electronics", 8999, time.time() - 86400 * 5, 0),
"ORD-2210": Order("ORD-2210", "CUST-102", "electronics", 4200, time.time() - 86400 * 45, 1),
}
_PROCESSED_REFUNDS: set[str] = set() # idempotency ledger
def get_order(order_id: str) -> Order | None:
return _ORDERS.get(order_id)
def check_refund_policy(order: Order) -> tuple[bool, str]:
days_since_purchase = (time.time() - order.purchased_at) / 86400
if days_since_purchase > 30:
return False, f"Purchase was {days_since_purchase:.0f} days ago, outside the 30-day window."
if order.refund_count >= 1:
return False, "Customer has already received a refund on this order."
return True, "Eligible: within window, no prior refund."
def issue_refund(order_id: str, idempotency_key: str) -> dict:
"""Calls the payment gateway. MUST be idempotent — retries are expected."""
if idempotency_key in _PROCESSED_REFUNDS:
return {"status": "already_processed", "idempotency_key": idempotency_key}
order = _ORDERS[order_id]
# simulate a flaky gateway — this matters later
if uuid.uuid4().int % 5 == 0:
raise PaymentGatewayError("gateway timeout, retry with same idempotency_key")
_PROCESSED_REFUNDS.add(idempotency_key)
return {"status": "refunded", "amount_cents": order.amount_cents, "idempotency_key": idempotency_key}
La idempotencia no es un adorno; representa la diferencia entre un marco de trabajo para agentes y un simple juguete para ellos.
CrewAI: demostraciones sólidas, control deficiente
CrewAI modela roles y tareas dentro de un Crew. A los materiales de presentación de productos les encanta la metáfora del organigrama.
Patrón ingenuo
from crewai import Agent, Task, Crew, Process
from crewai.tools import tool
@tool("Get Order")
def get_order_tool(order_id: str) -> str:
"""Fetch order details by ID."""
order = get_order(order_id)
return str(order) if order else "NOT_FOUND"
@tool("Check Policy")
def check_policy_tool(order_id: str) -> str:
"""Check refund eligibility for an order."""
order = get_order(order_id)
if not order:
return "NOT_FOUND"
eligible, reason = check_refund_policy(order)
return f"eligible={eligible}, reason={reason}"
@tool("Issue Refund")
def issue_refund_tool(order_id: str) -> str:
"""Issue a refund for an order."""
result = issue_refund(order_id, idempotency_key=f"refund-{order_id}")
return str(result)
triage_agent = Agent(
role="Refund Triage Specialist",
goal="Decide whether a customer refund request should be approved or escalated",
backstory="You are an experienced support agent who follows policy strictly.",
tools=[get_order_tool, check_policy_tool, issue_refund_tool],
verbose=True,
)
triage_task = Task(
description="A customer says: '{customer_message}'. Order ID: {order_id}. "
"Decide if this qualifies for a refund and act accordingly.",
expected_output="A short summary of the action taken.",
agent=triage_agent,
)
crew = Crew(agents=[triage_agent], tasks=[triage_task], process=Process.sequential)
result = crew.kickoff(inputs={"customer_message": "I want a refund, item broke", "order_id": "ORD-4471"})
Los caminos óptimos parecen estar bien. Como servicio, falló de tres maneras. El orden de las herramientas era no determinista: a veces issue_refund se ejecutaba antes que check_policy porque el modelo relaciona libremente las herramientas entre sí. Las intentonas posteriores tras PaymentGatewayError podían generar nuevas llamadas a herramientas y nuevos códigos, a menos que la herramienta incluyera de forma fija mecanismos de idempotencia. kickoff() se ejecuta hasta el final sin interrupciones humanas duraderas; simular la reanudación requería reconstruir el estado fuera del marco de trabajo.
Intento jerárquico reforzado
manager_agent = Agent(
role="Refund Process Manager",
goal="Enforce strict order: lookup, then policy check, then refund or escalate. Never skip steps.",
backstory="You strictly enforce process compliance and never let steps be skipped.",
allow_delegation=True,
)
crew = Crew(
agents=[triage_agent],
tasks=[triage_task],
process=Process.hierarchical,
manager_agent=manager_agent,
)
Un agente gestor y prompts más claros redujeron los pasos omitidos, pero no crearon invariantes sólidas. El lenguaje natural no puede garantizar una secuenciación al nivel de cumplimiento normativo. CrewAI se adapta a juegos de roles flexibles —primero investigación y luego crítica—, no a secuencias relacionadas con pagos.
AutoGen: chat flexible, control difuso
El chat en grupo con selección automática de orador agrega una llamada basada en LLM en cada turno para decidir quién hablará.
import autogen
config_list = [{"model": "gpt-4o", "api_key": "..."}]
llm_config = {"config_list": config_list, "temperature": 0}
triage_agent = autogen.AssistantAgent(
name="TriageAgent",
system_message=(
"You triage refund requests. Look up the order, check policy, "
"then either call issue_refund or hand off to EscalationAgent."
),
llm_config=llm_config,
)
escalation_agent = autogen.AssistantAgent(
name="EscalationAgent",
system_message="You write a human-readable escalation note explaining why a refund needs manual review.",
llm_config=llm_config,
)
user_proxy = autogen.UserProxyAgent(
name="ToolExecutor",
human_input_mode="NEVER",
code_execution_config=False,
function_map={
"get_order": lambda order_id: str(get_order(order_id)),
"check_refund_policy_tool": lambda order_id: str(check_refund_policy(get_order(order_id))),
"issue_refund": lambda order_id: str(issue_refund(order_id, f"refund-{order_id}")),
},
)
groupchat = autogen.GroupChat(
agents=[user_proxy, triage_agent, escalation_agent],
messages=[],
max_round=10,
speaker_selection_method="auto", # an LLM call decides who speaks next
)
manager = autogen.GroupChatManager(groupchat=groupchat, llm_config=llm_config)
user_proxy.initiate_chat(manager, message="Customer wants a refund on ORD-2210, item broke on arrival.")
Fallas: bucles de conversación entre la triaje y la escalada sin un estado estructurado de “ya decidido”, solo transcripciones, lo que obliga a usar límites rígidos de max_round. La pregunta “¿Hemos reembolsado?” se resolvía mediante búsquedas en texto libre. El no determinismo influenció todo el grafo de ejecución, por lo que los incidentes se reconstruían a partir de los registros de chat en lugar de las trazas digitadas.
LangGraph: el sobreviviente
El estado digitalizado, los bordes explícitos, los puntos de control y las reintentos se adaptaron al problema.
from typing import TypedDict, Literal, Optional
from langgraph.graph import StateGraph, END
from langgraph.checkpoint.sqlite import SqliteSaver
from langgraph.types import interrupt, Command
import uuid
class RefundState(TypedDict):
order_id: str
customer_message: str
order: Optional[dict]
eligible: Optional[bool]
policy_reason: Optional[str]
decision: Optional[Literal["refund", "escalate", "denied"]]
refund_result: Optional[dict]
audit_log: list[str]
def lookup_order_node(state: RefundState) -> RefundState:
order = get_order(state["order_id"])
log = state["audit_log"] + [f"Looked up {state['order_id']}: {'found' if order else 'not found'}"]
if not order:
return {**state, "decision": "escalate", "audit_log": log}
return {**state, "order": order.__dict__, "audit_log": log}
def policy_check_node(state: RefundState) -> RefundState:
order = Order(**state["order"])
eligible, reason = check_refund_policy(order)
log = state["audit_log"] + [f"Policy check: eligible={eligible}, reason={reason}"]
return {**state, "eligible": eligible, "policy_reason": reason, "audit_log": log}
def route_after_policy(state: RefundState) -> str:
# Plain Python. No LLM call decides this branch. This is the whole point.
if state.get("decision") == "escalate":
return "escalate"
return "refund" if state["eligible"] else "escalate"
def human_approval_node(state: RefundState) -> RefundState:
# Durable pause: this literally suspends the graph run and persists state
# via the checkpointer. It can resume hours or days later, across restarts.
decision = interrupt({
"reason": "Ambiguous or ineligible refund needs human sign-off",
"order": state["order"],
"policy_reason": state["policy_reason"],
})
return {**state, "decision": decision, "audit_log": state["audit_log"] + [f"Human decision: {decision}"]}
def issue_refund_node(state: RefundState) -> RefundState:
idempotency_key = f"refund-{state['order_id']}" # stable across retries — this is the whole trick
try:
result = issue_refund(state["order_id"], idempotency_key)
except PaymentGatewayError as e:
# LangGraph re-raises into the node; retry policy (below) handles this,
# and because the key is stable, a retried call is safe.
raise
log = state["audit_log"] + [f"Refund issued: {result}"]
return {**state, "decision": "refund", "refund_result": result, "audit_log": log}
def escalate_node(state: RefundState) -> RefundState:
log = state["audit_log"] + ["Escalated to human queue"]
return {**state, "audit_log": log}
from langgraph.pregel.retry import RetryPolicy
graph = StateGraph(RefundState)
graph.add_node("lookup_order", lookup_order_node)
graph.add_node("policy_check", policy_check_node)
graph.add_node(
"issue_refund",
issue_refund_node,
retry=RetryPolicy(max_attempts=3, retry_on=PaymentGatewayError),
)
graph.add_node("human_approval", human_approval_node)
graph.add_node("escalate", escalate_node)
graph.set_entry_point("lookup_order")
graph.add_edge("lookup_order", "policy_check")
graph.add_conditional_edges("policy_check", route_after_policy, {
"refund": "issue_refund",
"escalate": "human_approval",
})
graph.add_edge("human_approval", "issue_refund") # human can still approve
graph.add_edge("issue_refund", END)
graph.add_edge("escalate", END)
checkpointer = SqliteSaver.from_conn_string("refunds.db")
app = graph.compile(checkpointer=checkpointer)
Las configuraciones de hilo se reanudan después de las caídas:
config = {"configurable": {"thread_id": "order-2210-refund-req"}}
# Kick off the run — it will pause at human_approval_node
result = app.invoke(
{"order_id": "ORD-2210", "customer_message": "second refund please", "audit_log": []},
config=config,
)
# result contains an interrupt payload; the process can now exit entirely.
# ... hours later, possibly a different process, different machine ...
final_result = app.invoke(Command(resume="escalate"), config=config)
Las pruebas deterministas verifican que los pedidos no elegibles se escalen:
def test_ineligible_order_escalates():
state = {"eligible": False, "decision": None}
assert route_after_policy(state) == "escalate"
def test_eligible_order_refunds():
state = {"eligible": True, "decision": None}
assert route_after_policy(state) == "refund"
Al eliminar el caos durante el reembolso, el proceso se reiniciaba a partir del punto de control con la misma clave de idempotencia. Esa prueba decidió el resultado final.
Cuando CrewAI o AutoGen siguen ganando
CrewAI para la redacción colaborativa con un orden flexible. AutoGen para investigaciones exploratorias multiagente donde la conversación es el producto. Ninguno de los dos reemplaza a una máquina de estados cuando hay dinero en juego.
Lección
Ajuste la abstracción al modo de fallo. Si el orden incorrecto de las herramientas o los efectos secundarios dobles son inaceptables, prefiera grafos explícitos con un estado duradero en lugar de equipos formados por prompts. Los frameworks no son simples capas intercambiables sobre “agentes”; codifican apuestas diferentes respecto al control, la memoria y la recuperación.
Lista de verificación para producción después del concurso
Antes de promocionar cualquier agente similar a uno de reembolso, se deben exigir: un estado escrito con una marca already_refunded (o equivalente); claves de idempotencia generadas a partir de IDs empresariales; verificaciones de pólizas como nodos de código, no como sugerencias en prompts; interrupciones HITL detrás de un checkpointer; pruebas de caos que detengan a los procesos en medio de efectos secundarios; y registros de auditoría que no dependan de búsquedas en texto sin formato. Si un framework no puede expresar esas propiedades sin contar con un motor de flujo en segundo plano, ese motor es el verdadero orquestador, y el framework no es más que un aditivo costoso.
Mida las tasas de fallo específicas en la fase de pruebas: políticas omitidas, intentos duplicados de reembolso, interrupciones perdidas tras reiniciar y registros de auditoría ilegibles. Los prototipos Crew y AutoGen que no logren superar a LangGraph en esas métricas deberían permanecer en el laboratorio. Celebre su retiro; la expansión descontrolada tiene un costo elevado.
Dokumente la decisión para los equipos futuros de modo que no sea necesario realizar otro concurso. Incluya el enlace al documento de instrucciones (README) donde se explique cómo manejar el caos. Prefiera gráficos sencillos que sigan siendo útiles incluso tras los fallos, en lugar de conversaciones ingeniosas que solo sirvan durante las demostraciones. Ese estándar se aplica no solo a los reembolsos, sino también a cualquier agente que modifique sistemas externos bajo presión de auditoría; eso es precisamente lo que la mayoría de las empresas realmente quieren de los “agentes de IA” una vez que termine el período de pruebas y los registros contables vuelvan a ser importantes cada trimestre.
Asociación de fallos con suposiciones del marco
CrewAI parte de la premisa de una descomposición flexible de tareas. AutoGen asume que la conversación constituye un plano de control suficiente. LangGraph supone que usted mismo diseñará el plano de control. La automatización de reembolsos viola las dos primeras premisas: la elegibilidad no es negociable, y la selección del hablante no constituye un mecanismo de autorización de pagos. Cuando las premisas entran en conflicto con las normas del sector, gana el marco con menos premisas, incluso si al principio parece menos eficaz.
A veces los ingenieros intentan “arreglar” CrewAI o AutoGen con instrucciones al sistema cada vez más extensas. Eso es como tratar una cerca contra ciclones como una puerta de bóveda. Coloque los requisitos de cumplimiento en nodos tipados y mantenga a los modelos de lenguaje dentro de nodos que clasifiquen o redacten, no en nodos que decidan si se realiza un pago.
Requisitos de observabilidad compartida
Sea cual sea su elección, genere spans para cada llamada a la herramienta con el ID del pedido, la clave de idempotencia y el resultado de la política. Sin eso, los debates en torno al framework se vuelven extremos mientras la producción permanece sin información. LangGraph hizo que esos mecanismos fueran evidentes porque los nodos son funciones; puede aplicar la misma disciplina en otros lugares, pero la competencia demostró que era el camino más sencillo para este tipo de trabajo.
Cierre
Construya el agente que se adapte a la forma en que falla su sistema. En el caso de reembolsos, ese agente era un grafo con memoria, no un equipo basado en intuiciones. Reserve las demás herramientas para los problemas para los que realmente sirven, y deje de fingir que una sola abstracción puede manejar todos los casos pendientes.
Revisión del formato de LangGraph que funcionó
El grafo exitoso mantuvo la clasificación, la recuperación de datos, la política aplicable, el reembolso, la escalada y la auditoría como nodos separados. Los bordes codificaban las únicas transiciones legales permitidas. El modelo nunca decidía si omitir la política; simplemente completaba los campos estructurados que el código de la política interpretaba. Las intentonas en el nodo de pasarela reutilizaban la misma clave de idempotencia almacenada en el estado. La escalada utilizaba interrupt() junto con un checkpointer para que un administrador pudiera aprobarla horas después en otra réplica.
Ese diseño parece excesivamente detallado en comparación con la definición de Crew con tres agentes. La excesiva detallación es precisamente el objetivo: cada paso irreversible puede ser identificado durante una revisión de código. Los nuevos ingenieros pueden leer el grafo y predecir su comportamiento sin tener que reproducir diez conversaciones estocásticas.
Comparación de historias de respuesta a incidentes
Cuando CrewAI ejecutó dos veces una herramienta en la fase de pruebas, el análisis posterior culpó al “desvío del prompt”. Cuando AutoGen entró en bucle, el análisis posterior culpó a la “selección del hablante”. Cuando LangGraph falló, el análisis posterior señaló un nodo específico y la falta de un reductor; problemas que se podían solucionar sin discusiones innecesarias. Solo la taxonomía de incidentes justificó la elección de un flujo de trabajo relacionado con los pagos.
Notas sobre costos y latencia del concurso
El modelo LLM de AutoGen para cada turno aumentaba la latencia y el número de tokens. Las reintentos de CrewAI a veces multiplicaban las llamadas a las herramientas. LangGraph tenía un costo moderado constante por la escritura de puntos de control y se destacó por su gasto predecible. Para volúmenes de soporte de miles al día, la previsibilidad supera a los ahorros ocasionales y astutos.
Habilidades del equipo y contratación
La demanda de empleados con experiencia en “CrewAI” es menor que la de aquellos con conocimientos en “máquinas de estado más LLMs”. Las habilidades relacionadas con LangGraph son transferibles a cualquier sistema de orquestación. Si la empresa estandariza, es mejor priorizar conceptos transferibles como estado, idempotencia, HITL y evaluaciones. Los ciclos de moda en los frameworks son más rápidos que esos conceptos.
Ampliando el conjunto de herramientas para manejar caos
Más allá de reiniciar todo: se deben introducir errores 503 en los gateways, entregas duplicadas de webhook, desfases horarios durante los períodos establecidos por las políticas, y personas que rechazan las escalaciones. Los gráficos que solo funcionan en escenarios sin problemas siguen siendo meros juguetes. Automatice este conjunto de herramientas en CI con datos falsos deterministas para que los refactores no eliminen silenciosamente las medidas de seguridad.
Aterrizaje suave para prototipos
Mantenga los entornos de pruebas de CrewAI/AutoGen para flujos de trabajo de contenido con editores humanos. No bloquee la experimentación, sino las credenciales de producción. Una política de plataforma que establece “ningunas herramientas de pago en equipos orquestados por comandos” evita futuros incidentes sin restringir la curiosidad.
Énfasis final
La afirmación del artículo es precisa y contundente: en lo que respecta a la automatización de reembolsos con efectos secundarios reales, LangGraph logró sobrevivir donde CrewAI y AutoGen fracasaron, pese a contar con herramientas idénticas. Extrapole con cuidado. Pero sí extrapole libremente la lección principal: evalúe los marcos de trabajo según sus modos de fallo, no según su apariencia en las demostraciones; así, la competencia valió la semana que ocupó.
Cronología detallada de los fallos desde la fase de pruebas
Semana uno: La demostración de CrewAI impresionó a los interesados. Semana dos: dos intentos de reembolso doble en el entorno de pruebas tras fallos del gateway. Semana tres: El piloto de AutoGen gastó tokens en bucles de reproducción al rechazarse los pedidos. Semana cuatro: El sistema de control del caos de LangGraph activó la opción de reembolso parcial. El calendario es importante porque el impulso organizacional a menudo se detiene con la primera demostración; registre la cronología de los fallos en el ADR para que ese impulso no elimine las pruebas.
La idempotencia como requisito transversal
Cada framework probado aquí podría utilizar herramientas. Solo los diseños que incluyen una clave estable en todos los intentos de reintentar son seguros para transacciones de pago. Almacene la clave en el estado del grafo antes del primer intento. Niegue la creación de una nueva clave en los nodos de reintentar. Registre junto con la clave, el ID del pedido y los códigos de respuesta del gateway. Si un framework dificulta esto, esa dificultad es una señal, no un problema burocrático.
Ergonomía en la escalada humana
El texto de escalación debe incluir los IDs de las cláusulas de la póliza, las fechas y horas del pedido, así como el número de reembolsos anteriores; debe seguir estructuras definidas, no solo un texto en formato libre. Los administradores deben ver los mismos campos que vio el nodo de la póliza. LangGraph lo facilitó porque el estado se representa mediante TypedDict; Crew/AutoGen requería reconstruir los datos a partir de transcripciones, lo que genera brechas en las auditorías.
Lo que conservamos de los proyectos fallidos
Las metáforas relacionadas con roles de CrewAI ayudaron en las conversaciones sobre el producto; se tradujeron a nombres de nodos en LangGraph. La lista explícita de agentes de AutoGen facilitó una asignación más clara de las responsabilidades por nodo. Está permitido tomar ideas de experiencia de usuario mientras se rechazan planes de control inseguros.
Lista de verificación ampliada para producción
- Caos: fallos durante el uso de herramientas, mientras se espera una interrupción o durante los reintentos con retardo.
- Pruebas de propiedades: las solicitudes no elegibles nunca llegan al nodo de reembolso.
Por qué falló la idea de “simplemente agregar otro agente”
Agregar un agente “PolicyEnforcer” en CrewAI seguía dejando la aplicación de políticas probabilística. Incluir un “RefundGuardian” en AutoGen seguía limitándose al chat. Los guardianes que no pueden bloquear completamente las conexiones son meras decoraciones; el bloqueo total debe realizarse en la topología del grafo.
Cierre
Misma herramienta, mismo modelo, filosofía de control diferente. Solo el gráfico explícito sobrevivió a los fallos relevantes para los reembolsos. Utilice equipos y chats cuando un pedido incorrecto tiene poco costo; utilice gráficos cuando ese error constituye un evento contable. Publique esa regla internamente y evite que el siguiente equipo pase cuatro semanas en competiciones intensivas.
Notas adicionales de refuerzo para los gráficos de reembolsos
Las ventanas de política que dependen de los relojes deben utilizar la hora del servidor almacenada al obtener datos, no las fechas indicadas por el modelo. Las verificaciones de categoría deben definirse mediante la pertenencia en el código. Los recuentos previos de reembolsos provienen del libro mayor, no de la memoria del chat. Cada una de estas decisiones elimina un tipo de inyección de instrucciones que intenta reescribir los requisitos de elegibilidad en lenguaje natural. Los gráficos hacen que esas decisiones sean evidentes; los equipos las ocultan en los antecedentes de los agentes, donde los revisores dejan de buscarlas.
Lecturas relacionadas
- Comparación de frameworks para agentes AI: LangChain, LangGraph, CrewAI y más — Elija la orquestación según las necesidades de flujo de control: cadenas, grafos, equipos, agentes de chat o agentes de datos, sin tener que combinar todos los frameworks en un servicio difícil de mantener.
- Agentes AI en producción con FastAPI, LangGraph y arquitectura limpia — Límites entre capas, estado de grafos tipado, servicios probables y una estructura de despliegue que mantienen a los agentes fáciles de mantener.