DEV Community

Moon Robert
Moon Robert

Posted on

AutoGen vs LangGraph vs CrewAI: Lo que descubrí después de dos semanas probándolos en producción

Hace tres meses construí un sistema de agentes para automatizar parte del proceso de investigación en mi trabajo. La idea era simple en papel: un agente busca información, otro la filtra, otro redacta el resumen. Cinco horas después de mi primer intento con AutoGen, tenía agentes hablando entre sí indefinidamente sin llegar a ninguna conclusión. Eso me llevó a probar los tres frameworks más usados del momento.

Dos semanas, un equipo de tres personas, y bastantes frustraciones después, aquí está lo que encontré.

Por qué esto no es otra comparativa de benchmarks artificiales

Soy escéptico de las comparativas con ejemplos de "hola mundo". Comparar frameworks con tareas triviales no dice nada útil sobre cómo se comportan cuando las cosas se complican.

Por eso armé una tarea real: un pipeline de investigación de contenido que hace lo siguiente — recibe un tema como input, busca artículos relevantes usando herramientas web, extrae puntos clave de cada fuente, sintetiza todo en un resumen ejecutivo, y genera preguntas de seguimiento si la información es incompleta. El sistema corre en producción (con supervisión humana), usa claude-sonnet-4-6 como LLM principal, y tiene que manejar errores porque las búsquedas web fallan constantemente. Esa última parte resultó ser el mayor diferenciador entre los tres.

AutoGen 0.4: Potente, pero el control de flujo te puede volver loco

AutoGen de Microsoft tiene una propuesta clara: conversaciones entre agentes. Defines agentes con roles, los conectas, y ellos se comunican para llegar a un resultado. La API de 0.4 es bastante diferente a la 0.2 — ojo si vienes de tutoriales viejos — y usa el modelo de ConversableAgent con soporte para el patrón Swarm.

import asyncio
from autogen_agentchat.agents import AssistantAgent
from autogen_agentchat.teams import RoundRobinGroupChat
from autogen_agentchat.conditions import TextMentionTermination
from autogen_ext.models.anthropic import AnthropicChatCompletionClient

# Asegúrate de tener ANTHROPIC_API_KEY en tu .env
model_client = AnthropicChatCompletionClient(model="claude-sonnet-4-6")

researcher = AssistantAgent(
    name="Researcher",
    model_client=model_client,
    system_message="""Eres un investigador experto. Busca información relevante
    sobre el tema dado y extrae los puntos más importantes.
    Cuando termines tu análisis, escribe exactamente: INVESTIGACIÓN_COMPLETA""",
)

synthesizer = AssistantAgent(
    name="Synthesizer",
    model_client=model_client,
    system_message="""Recibes investigación ya hecha y produces un resumen ejecutivo.
    Sé conciso. Al terminar, escribe exactamente: SÍNTESIS_LISTA""",
)

# RoundRobin: los agentes se turnan en orden fijo
team = RoundRobinGroupChat(
    [researcher, synthesizer],
    termination_condition=TextMentionTermination("SÍNTESIS_LISTA"),
    max_turns=10,  # límite duro — sin esto, prepárate para facturas inesperadas
)

async def run_pipeline(topic: str):
    result = await team.run(task=f"Investiga y sintetiza: {topic}")
    return result
Enter fullscreen mode Exit fullscreen mode

Lo que me gustó: la abstracción de "conversación" es natural. Si tu problema es realmente un diálogo entre especialistas — donde el agente A le responde al B y viceversa hasta converger — esto fluye bien. AutoGen Studio (la interfaz web incluida) también es decente para prototipar sin código, lo cual le gustó a un colega mío que no es dev.

El problema serio: el control de terminación. Con TextMentionTermination, si tu agente olvida escribir la palabra mágica (y los LLMs olvidan, créeme), el loop sigue. Pasé un buen rato mirando logs de conversaciones infinitas antes de agregar el max_turns=10 como límite duro. En retrospectiva, esto debería ser el default del framework, no algo que tienes que recordar agregar.

Otro gotcha que nadie menciona: el manejo de herramientas en flujos con múltiples agentes es menos obvio de lo que parece. Si solo un agente del grupo necesita acceso a herramientas web, tienes que ser muy explícito con el enrutamiento — de lo contrario, el agente equivocado intenta ejecutar una herramienta que no tiene, y el error que recibes no es particularmente útil para diagnosticar qué pasó.

AutoGen funciona bien cuando tienes conversaciones relativamente lineales entre 2-3 agentes con roles bien definidos. Si necesitas flujo condicional complejo — "si el investigador no encontró nada, vuelve a buscar con términos diferentes" — la cosa se complica rápido.

LangGraph 0.3: Más código, más control, más sanidad mental a largo plazo

LangGraph me exigió más al principio. Bastante más. Pero ahora que lo entiendo, es el que usaría para cualquier sistema que tenga que vivir en producción más de un mes.

La idea central: defines un grafo donde los nodos son funciones y las aristas son las transiciones entre ellos. Puedes tener aristas condicionales, lo que significa que el flujo puede bifurcarse según el estado actual. Eso puede sonar abstracto, pero en la práctica se traduce en un control de flujo explícito que no tienes que inferir del comportamiento del LLM.

from langgraph.graph import StateGraph, END
from langgraph.checkpoint.memory import MemorySaver
from langchain_anthropic import ChatAnthropic
from typing import TypedDict, Annotated
import operator

class ResearchState(TypedDict):
    topic: str
    research_notes: Annotated[list[str], operator.add]  # se acumula entre nodos
    summary: str
    needs_more_research: bool
    iterations: int

llm = ChatAnthropic(model="claude-sonnet-4-6")

def research_node(state: ResearchState) -> dict:
    response = llm.invoke([
        {"role": "system", "content": "Eres un investigador. Extrae puntos clave con fuentes."},
        {"role": "user", "content": f"Investiga: {state['topic']}"},
    ])
    return {
        "research_notes": [response.content],
        "iterations": state.get("iterations", 0) + 1,
    }

def synthesize_node(state: ResearchState) -> dict:
    notes = "\n\n---\n\n".join(state["research_notes"])
    response = llm.invoke([
        {"role": "system", "content": "Produce un resumen ejecutivo conciso. Sin relleno."},
        {"role": "user", "content": f"Sintetiza estas notas:\n{notes}"},
    ])
    # Lógica de control explícita — no dependemos del LLM para esto
    needs_more = len(state["research_notes"]) < 2 and state["iterations"] < 3
    return {"summary": response.content, "needs_more_research": needs_more}

def route_after_synthesis(state: ResearchState) -> str:
    """Arista condicional — aquí está el control de flujo real."""
    if state["needs_more_research"]:
        return "research"  # loop de vuelta
    return END

workflow = StateGraph(ResearchState)
workflow.add_node("research", research_node)
workflow.add_node("synthesize", synthesize_node)
workflow.set_entry_point("research")
workflow.add_edge("research", "synthesize")
workflow.add_conditional_edges("synthesize", route_after_synthesis)

# El checkpointing guarda estado entre ejecuciones — invaluable para procesos largos
memory = MemorySaver()
app = workflow.compile(checkpointer=memory)
Enter fullscreen mode Exit fullscreen mode

Lo del checkpointer merece su propio párrafo. Si tu agente falla a mitad del proceso — y fallará, las APIs tienen timeouts, los LLMs tienen rate limiting — LangGraph puede reanudar desde el último checkpoint en lugar de empezar de cero. Para procesos que corren por varios minutos, esto cambia completamente la ecuación.

El error que cometí: olvidé el caso base en una arista condicional durante una prueba nocturna. LangGraph me dejó correr sin problema hasta que ejecuté 40 iteraciones sobre "mejores prácticas de Docker" a las 2am. Los errores de ciclos infinitos son más difíciles de diagnosticar que en AutoGen porque no hay un timeout implícito — tú eres responsable de definir tus condiciones de salida. Con AutoGen al menos tienes max_turns como red de seguridad obvia.

El TypedDict para el estado también se puede poner verbose en proyectos grandes. En uno de mis proyectos terminé con un state object de 15+ campos y empezó a parecerse al store de Redux de los peores tiempos del front-end. Hay formas de modularizarlo, pero requiere disciplina.

La documentación mejoró bastante en 2025 pero sigue asumiendo familiaridad con el ecosistema LangChain. Si llegas de afuera, hay una curva real.

CrewAI 0.9: El más rápido para arrancar, el más frustrante para salirte del camino feliz

CrewAI tiene la propuesta más clara de los tres: defines agentes con roles como si fueran empleados, les asignas tareas, y el "Crew" coordina todo. La abstracción de "equipo de trabajo" resuena bien en demos.

Y en demos funciona genial. Tardé 20 minutos en tener un prototipo corriendo — genuinamente impresionante para alguien que venía de pelear con LangGraph.

from crewai import Agent, Task, Crew, Process
from crewai_tools import SerperDevTool

search_tool = SerperDevTool()

researcher = Agent(
    role="Investigador de Contenido",
    goal="Encontrar información precisa y relevante sobre {topic}",
    backstory="""Tienes experiencia en investigación académica y periodística.
    Eres meticuloso con las fuentes y no inventas datos.""",
    tools=[search_tool],
    llm="anthropic/claude-sonnet-4-6",
    verbose=True,
)

synthesizer = Agent(
    role="Editor Senior",
    goal="Transformar investigación técnica en resúmenes ejecutivos claros",
    backstory="Has escrito para publicaciones técnicas durante años. Odias el relleno.",
    llm="anthropic/claude-sonnet-4-6",
)

research_task = Task(
    description="Investiga {topic} y extrae los 5 puntos más importantes con fuentes.",
    expected_output="Lista numerada de puntos clave con URLs de referencia.",
    agent=researcher,
)

synthesis_task = Task(
    description="Usando la investigación anterior, escribe un resumen ejecutivo de 200 palabras.",
    expected_output="Resumen ejecutivo en un párrafo único, sin bullets.",
    agent=synthesizer,
    context=[research_task],  # CrewAI pasa el output automáticamente
)

crew = Crew(
    agents=[researcher, synthesizer],
    tasks=[research_task, synthesis_task],
    process=Process.sequential,
)

result = crew.kickoff(inputs={"topic": "state management en React 2026"})
Enter fullscreen mode Exit fullscreen mode

El context=[research_task] es una buena idea bien ejecutada — CrewAI pasa automáticamente el output de una tarea como contexto de la siguiente sin que tengas que escribir ese boilerplate tú mismo.

Dicho eso, mi experiencia con CrewAI a mediano plazo fue frustrante. Una vez que quieres hacer algo fuera del happy path — manejo de errores personalizado, lógica condicional entre tareas, control preciso sobre qué información fluye entre agentes — empiezas a pelear contra el framework en lugar de trabajar con él.

El modo Process.hierarchical (donde un agente "manager" delega a los demás) produce resultados inconsistentes en mi experiencia. El manager LLM a veces decide hacer cosas inesperadas con la delegación. El issue #2341 del repo de GitHub documenta bien este problema — básicamente el manager puede ignorar las instrucciones de delegación si el LLM interpreta la situación diferente. No estoy 100% seguro de que esto escale bien para equipos donde varios devs necesitan entender y modificar el flujo.

Lo que realmente usaría, sin rodeos

Después de todo esto, aquí está mi opinión directa.

Usa LangGraph si vas a poner esto en producción y el sistema tiene que ser mantenible en seis meses. El grafo explícito es documentación viva — tus colegas (o tú del futuro) pueden leer el grafo y entender el flujo sin ejecutarlo. El checkpointing te salva en procesos largos. Sí, cuesta más al principio, pero en ningún momento me arrepentí de haber elegido esta ruta para el sistema que está en producción ahora mismo.

Usa AutoGen si tu caso de uso es realmente un diálogo iterativo entre especialistas y no necesitas flujo condicional complejo. También si quieres usar AutoGen Studio para que alguien no técnico pueda prototipar flujos — eso tiene valor real en equipos mixtos. El patrón Swarm de la versión 0.4 es interesante para casos de uso de handoff entre agentes especializados.

Usa CrewAI si necesitas un prototipo funcional esta tarde para mostrar a stakeholders, o si tu equipo no tiene mucha experiencia con sistemas de agentes y el paradigma de "empleados con roles" les resulta más intuitivo. Para PoCs y demos, es el más rápido con diferencia. Para producción con lógica compleja, me preocuparía el mantenimiento.

One thing I noticed — y esto me sorprendió del proceso completo: el problema más difícil no fue elegir el framework. Fue definir cuándo y cómo los agentes deben pasarse el control entre sí. Ese diseño conceptual — qué decide cada agente, qué información necesita, cuándo escalar o reintentar — es donde se gana o se pierde, independientemente de qué herramienta uses.

Cualquier framework que elijas, invierte tiempo en eso primero. El código viene después.

Top comments (1)

Collapse
 
nyrok profile image
Hamza KONTE

The conclusion lands — framework choice matters less than getting the inter-agent handoff design right. What each agent decides, what information it needs, when to retry or escalate.

I'd add one more layer: the system prompt for each agent is where that conceptual design actually lives. And because those prompts are usually unstructured blobs of text, the intended behavior is hard to reason about or modify when something breaks.

Separating role, objective, constraints, and output format as typed blocks makes the design explicit and editable. That's the gap flompt targets — structured prompt building before you wire up your LangGraph nodes. flompt.dev / github.com/Nyrok/flompt