Módulo 11: Deep Agents

create_agent vs LangGraph vs Deep Agents: Árbol de Decisión

Descripción de la cápsula

Esta es la cápsula más importante del módulo. Posiblemente la más importante de toda la guía.

Después de 11 módulos, dominas tres niveles de abstracción para construir agentes. Puedes usar create_agent para resolver el 80% de los problemas con 15 líneas de código. Puedes usar LangGraph para workflows complejos donde necesitas control sobre cada nodo, edge, y condición. Y ahora puedes usar Deep Agents para tareas autónomas de larga duración con planning, filesystem, y subagentes built-in.

El problema no es que no sepas usar cada herramienta — es que en un proyecto real, necesitas decidir cuál usar. Y esa decisión impacta todo: tiempo de desarrollo, mantenibilidad, costos de ejecución, y capacidad de debugging.

Esta cápsula te da un framework de decisión definitivo. No opiniones — criterios técnicos con trade-offs explícitos.


Los tres niveles de abstracción

Vista rápida

NivelHerramientaLíneas de códigoControlMejor para
Altocreate_agent~10-20Limitado (loop ReAct)80% de tareas de agentes
MedioLangGraph~50-200Total (workflow custom)Workflows complejos, HITL
BatteriesDeep Agents~20-50Moderado (framework decide)Tareas autónomas largas

Nivel 1: create_agent — simple, rápido, suficiente

from dotenv import load_dotenv
load_dotenv()

from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain_community.tools import TavilySearchResults

model = ChatOpenAI(model="gpt-4.1-mini")
tools = [TavilySearchResults(max_results=3)]

agent = create_agent(model, tools)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "¿Qué es RAG y por qué es importante?"}]}
)

print(result["messages"][-1].content[:200])
# Output esperado (varía según el modelo):
# RAG (Retrieval-Augmented Generation) es una técnica que combina la generación
# de texto con la recuperación de información de fuentes externas...

Qué hace por ti:

  • ✅ Bind de tools al modelo
  • ✅ Loop ReAct (reason → act → observe → repeat)
  • ✅ Manejo de tool calling automático

Qué NO hace:

  • ❌ Custom workflow (no puedes controlar el orden de ejecución)
  • ❌ Persistencia (se pierde al terminar)
  • ❌ Branching condicional
  • ❌ Multi-agente
  • ❌ HITL

Líneas de código: ~15

Nivel 2: LangGraph — control total, tú diseñas todo

from dotenv import load_dotenv
load_dotenv()

from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver
from langchain_openai import ChatOpenAI
from langchain_community.tools import TavilySearchResults
from typing import TypedDict, Annotated
import operator

class ResearchState(TypedDict):
    query: str
    messages: Annotated[list, operator.add]
    sources: list[dict]
    analysis: str
    report: str
    quality_score: float

model = ChatOpenAI(model="gpt-4.1-mini")
search = TavilySearchResults(max_results=5)

def search_node(state: ResearchState) -> dict:
    results = search.invoke(state["query"])
    sources = [{"title": r.get("title", ""), "content": r.get("content", "")[:500]} for r in results]
    return {
        "sources": sources,
        "messages": [{"role": "system", "content": f"Found {len(sources)} sources"}],
    }

def analyze_node(state: ResearchState) -> dict:
    sources_text = "\n".join(s["content"][:200] for s in state["sources"])
    response = model.invoke(f"Analiza estas fuentes sobre '{state['query']}':\n{sources_text}")
    return {
        "analysis": response.content,
        "messages": [{"role": "system", "content": "Analysis complete"}],
    }

def report_node(state: ResearchState) -> dict:
    response = model.invoke(
        f"Genera un reporte sobre '{state['query']}' basado en:\n{state['analysis']}"
    )
    return {
        "report": response.content,
        "quality_score": 0.85,
        "messages": [{"role": "system", "content": "Report generated"}],
    }

def route_by_quality(state: ResearchState) -> str:
    if state.get("quality_score", 0) < 0.7:
        return "search"
    return END

builder = StateGraph(ResearchState)
builder.add_node("search", search_node)
builder.add_node("analyze", analyze_node)
builder.add_node("report", report_node)
builder.add_edge(START, "search")
builder.add_edge("search", "analyze")
builder.add_edge("analyze", "report")
builder.add_conditional_edges("report", route_by_quality, {"search": "search", END: END})

graph = builder.compile(checkpointer=MemorySaver())

result = graph.invoke(
    {"query": "Estado de RAG en 2025", "messages": [], "sources": [], "analysis": "", "report": "", "quality_score": 0.0},
    config={"configurable": {"thread_id": "research-001"}},
)

print(f"Sources encontradas: {len(result['sources'])}")
print(f"Quality score: {result['quality_score']}")
print(f"Report length: {len(result['report'])} chars")
# Output esperado (varía según el modelo):
# Sources encontradas: 5
# Quality score: 0.85
# Report length: ~1500 chars

Qué hace por ti:

  • ✅ Estado tipado con TypedDict
  • ✅ Nodos y edges definidos por ti
  • ✅ Branching condicional (route_by_quality)
  • ✅ Checkpointing y persistencia
  • ✅ Retry, error handling, HITL
  • ✅ Multi-agente (supervisors, subgraphs)

Qué NO hace:

  • ❌ Planning automático (tú defines el workflow)
  • ❌ Filesystem built-in
  • ❌ Subagent spawning dinámico

Líneas de código: ~70 (ejemplo simplificado), ~150+ para un sistema completo

Nivel 3: Deep Agents — autónomo, batteries-included

from dotenv import load_dotenv
load_dotenv()

from deep_agents import create_deep_agent
from langchain_community.tools import TavilySearchResults

web_search = TavilySearchResults(max_results=5)

agent = create_deep_agent(
    "openai:gpt-4.1",
    tools=[web_search],
    name="Research Assistant",
    instructions=(
        "Investiga temas complejos. Descompone la investigación en pasos, "
        "escribe hallazgos en archivos separados por fuente, "
        "delega búsquedas especializadas a subagentes, "
        "y genera un reporte final consolidado."
    ),
)

result = agent.run("Investiga el estado de RAG en 2025 y genera un reporte")

print(f"Archivos generados: {list(result.files.keys())}")
print(f"Todos completados: {sum(1 for t in result.todos if t['status'] == 'completed')}/{len(result.todos)}")
print(f"Subagentes usados: {len(result.subagents_spawned)}")
# Output esperado (varía según el modelo):
# Archivos generados: ['research/web_search.md', 'analysis/synthesis.md', 'output/report.md']
# Todos completados: 5/5
# Subagentes usados: 2

Qué hace por ti:

  • ✅ Planning automático con write_todos
  • ✅ Virtual filesystem para context offloading
  • ✅ Subagent spawning dinámico
  • ✅ Long-term memory con backends pluggable
  • ✅ CLI built-in

Qué NO hace:

  • ❌ Control fino sobre cada paso (el framework decide el flujo)
  • ❌ Custom branching condicional
  • ❌ HITL granular (apruebas la tarea, no cada paso)

Líneas de código: ~20-40


El árbol de decisión

Este es el framework que te llevas. Imprime, memoriza, o referencia cuando empieces un proyecto nuevo.

¿Tu agente necesita usar tools?
  └─ NO → No necesitas un agente. Usa el modelo directamente.
  └─ SÍ ↓

¿Es un patrón simple? (Q&A con tools, chatbot, búsqueda directa)
  └─ SÍ → create_agent (~15 líneas, listo)
  └─ NO ↓

¿Necesitas control total sobre el flujo de decisiones?
  (branching condicional, retry con lógica custom, HITL en puntos específicos)
  └─ SÍ → LangGraph (StateGraph o Functional API)
  └─ NO ↓

¿La tarea es autónoma, de larga duración, y multi-paso?
  (investigación, generación de código, análisis complejo)
  └─ SÍ → Deep Agents
  └─ NO ↓

¿Necesitas multi-agente con roles fijos y coordinación explícita?
  └─ SÍ → LangGraph (supervisors, handoffs, subgraphs de M10)
  └─ NO ↓

¿Necesitas multi-agente dinámico donde el agente decide qué delegar?
  └─ SÍ → Deep Agents (subagent spawning)
  └─ NO → LangGraph (más flexible que Deep Agents sin overhead de planning)

Versión simplificada para referencia rápida

┌─────────────────────────────────────────────────────────────┐
│                   ¿Qué agente necesito?                     │
├─────────────────────────────────────────────────────────────┤
│                                                             │
│  Simple (Q&A, chatbot, búsqueda)  →  create_agent          │
│                                                             │
│  Custom workflow (branching,       →  LangGraph             │
│  HITL, retry, roles fijos)                                  │
│                                                             │
│  Autónomo + planning + delegación  →  Deep Agents           │
│                                                             │
└─────────────────────────────────────────────────────────────┘

Side-by-side: el mismo Research Agent en 3 versiones

El mejor test de un framework de decisión es aplicarlo al mismo problema. Aquí tienes el mismo agente de investigación implementado en los tres niveles.

Versión 1: create_agent (~15 líneas)

from dotenv import load_dotenv
load_dotenv()

from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain_community.tools import TavilySearchResults

model = ChatOpenAI(model="gpt-4.1-mini")
tools = [TavilySearchResults(max_results=5)]

agent = create_agent(
    model,
    tools,
    prompt="Eres un asistente de investigación. Busca información relevante y genera respuestas claras.",
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "Investiga el estado de AI agents en 2025"}]}
)

print(f"Líneas de código: ~15")
print(f"Respuesta: {result['messages'][-1].content[:100]}...")
# Output esperado:
# Líneas de código: ~15
# Respuesta: Los AI agents en 2025 han evolucionado significativamente...
  • ✅ Funciona inmediatamente
  • ✅ Busca con TavilySearch
  • ❌ No planifica — busca y responde en un solo paso
  • ❌ Sin persistencia — se pierde al terminar
  • ❌ Sin organización de hallazgos — todo en el context window

Versión 2: LangGraph (~150 líneas)

from dotenv import load_dotenv
load_dotenv()

from langgraph.graph import StateGraph, START, END
from langgraph.checkpoint.memory import MemorySaver
from langgraph.store.memory import InMemoryStore
from langchain_openai import ChatOpenAI
from langchain_community.tools import TavilySearchResults
from typing import TypedDict, Annotated
import operator
import json

class ResearchState(TypedDict):
    query: str
    messages: Annotated[list, operator.add]
    plan: list[str]
    sources: list[dict]
    analysis: str
    report: str
    iteration: int
    max_iterations: int
    quality_score: float

model_mini = ChatOpenAI(model="gpt-4.1-mini")
model_full = ChatOpenAI(model="gpt-4.1")
search = TavilySearchResults(max_results=5)

def plan_node(state: ResearchState) -> dict:
    response = model_mini.invoke(
        f"Descompone esta investigación en 3-5 pasos concretos. "
        f"Tema: {state['query']}. Responde SOLO como JSON array de strings."
    )
    try:
        plan = json.loads(response.content)
    except json.JSONDecodeError:
        plan = ["Buscar fuentes", "Analizar información", "Generar reporte"]
    return {
        "plan": plan,
        "messages": [{"role": "system", "content": f"Plan: {len(plan)} steps"}],
    }

def search_node(state: ResearchState) -> dict:
    results = search.invoke(state["query"])
    sources = []
    for r in results:
        sources.append({
            "title": r.get("title", "Unknown"),
            "content": r.get("content", "")[:500],
            "url": r.get("url", ""),
        })
    return {
        "sources": sources,
        "messages": [{"role": "system", "content": f"Found {len(sources)} sources"}],
    }

def analyze_node(state: ResearchState) -> dict:
    sources_text = "\n\n".join(
        f"**{s['title']}**: {s['content']}" for s in state["sources"]
    )
    response = model_full.invoke(
        f"Analiza estas fuentes sobre '{state['query']}'.\n\n{sources_text}\n\n"
        f"Identifica: patrones principales, contradicciones, y insights clave."
    )
    return {
        "analysis": response.content,
        "messages": [{"role": "system", "content": "Analysis complete"}],
    }

def report_node(state: ResearchState) -> dict:
    response = model_full.invoke(
        f"Genera un reporte de investigación sobre '{state['query']}'.\n\n"
        f"Análisis: {state['analysis']}\n\n"
        f"Fuentes: {len(state['sources'])}\n\n"
        f"El reporte debe tener: Resumen Ejecutivo, Hallazgos, Conclusiones, Fuentes."
    )
    return {
        "report": response.content,
        "quality_score": 0.85,
        "iteration": state.get("iteration", 0) + 1,
        "messages": [{"role": "system", "content": "Report generated"}],
    }

def evaluate_quality(state: ResearchState) -> str:
    if state.get("quality_score", 0) < 0.7 and state.get("iteration", 0) < state.get("max_iterations", 3):
        return "search"
    return "end"

builder = StateGraph(ResearchState)
builder.add_node("plan", plan_node)
builder.add_node("search", search_node)
builder.add_node("analyze", analyze_node)
builder.add_node("report", report_node)

builder.add_edge(START, "plan")
builder.add_edge("plan", "search")
builder.add_edge("search", "analyze")
builder.add_edge("analyze", "report")
builder.add_conditional_edges("report", evaluate_quality, {"search": "search", "end": END})

checkpointer = MemorySaver()
store = InMemoryStore()
graph = builder.compile(checkpointer=checkpointer, store=store)

result = graph.invoke(
    {
        "query": "Estado de AI agents en 2025",
        "messages": [],
        "plan": [],
        "sources": [],
        "analysis": "",
        "report": "",
        "iteration": 0,
        "max_iterations": 3,
        "quality_score": 0.0,
    },
    config={"configurable": {"thread_id": "research-002"}},
)

print(f"Líneas de código: ~150")
print(f"Plan: {len(result['plan'])} pasos")
print(f"Sources: {len(result['sources'])}")
print(f"Quality: {result['quality_score']}")
print(f"Report length: {len(result['report'])} chars")
# Output esperado (varía según el modelo):
# Líneas de código: ~150
# Plan: 4 pasos
# Sources: 5
# Quality: 0.85
# Report length: ~2000 chars
  • ✅ Planning explícito (tú diseñaste plan_node)
  • ✅ Retry con evaluación de calidad (conditional edge)
  • ✅ Persistencia con checkpointer
  • ✅ Modelos diferentes por nodo (mini para plan, full para análisis)
  • ❌ Tú escribiste cada nodo y edge — ~150 líneas
  • ❌ Sin filesystem (todo en state)
  • ❌ Sin subagent spawning (flujo fijo)

Versión 3: Deep Agents (~40 líneas)

from dotenv import load_dotenv
load_dotenv()

from deep_agents import create_deep_agent
from deep_agents.memory import FilesystemMemoryBackend
from langchain_community.tools import TavilySearchResults

web_search = TavilySearchResults(max_results=5)

memory = FilesystemMemoryBackend(base_path="./research_memory")

agent = create_deep_agent(
    "openai:gpt-4.1",
    tools=[web_search],
    name="Research Assistant",
    instructions=(
        "Eres un asistente de investigación. "
        "Para cada tema: "
        "1) Descompone la investigación en pasos con write_todos. "
        "2) Busca fuentes diversas (académicas, industria, noticias). "
        "3) Escribe hallazgos de cada fuente en archivos separados. "
        "4) Delega búsquedas especializadas a subagentes cuando el tema es amplio. "
        "5) Sintetiza y genera un reporte final en output/report.md."
    ),
    memory=memory,
    max_iterations=15,
)

result = agent.run("Investiga el estado de AI agents en 2025 y genera un reporte detallado")

print(f"Líneas de código: ~40")
print(f"Archivos: {list(result.files.keys())}")
print(f"Todos: {sum(1 for t in result.todos if t['status'] == 'completed')}/{len(result.todos)}")
print(f"Subagentes: {len(result.subagents_spawned)}")
# Output esperado (varía según el modelo):
# Líneas de código: ~40
# Archivos: ['research/web_search.md', 'research/academic.md', 'analysis/synthesis.md', 'output/report.md']
# Todos: 5/5
# Subagentes: 2
  • ✅ Planning automático (write_todos)
  • ✅ Virtual filesystem (archivos organizados)
  • ✅ Subagent spawning (delegación dinámica)
  • ✅ Memory entre sesiones
  • ❌ No controlas el flujo exacto
  • ❌ No defines branching condicional
  • ❌ Debugging más opaco

Tabla de comparación completa

Aspectocreate_agentLangGraphDeep Agents
Líneas de código~15~50-200~20-50
PlanningNingunoTú lo diseñasAutomático (write_todos)
WorkflowLoop ReAct fijoCustom (tú defines nodos/edges)Framework decide
PersistenciaNinguna (o +checkpointer)Checkpointer built-inVirtual filesystem
Multi-agenteNoTú lo implementasSubagent spawning automático
HITLNointerrupt() en puntos exactosAprobación de tarea general
MemoryNoStore manual (M8)Backend configurable
BranchingNoConditional edgesEl framework decide
RetryNoTú lo implementasAutomático (re-planning)
DebuggingDirecto (logs del modelo)Inspección de estado por nodoLogs de planning + archivos
Costo por ejecuciónBajo (1-3 LLM calls)Medio (5-15 LLM calls)Alto (10-30+ LLM calls)
Setup time5 minutos30-60 minutos10 minutos
CLILo construyes túLo construyes túBuilt-in
Ideal paraChatbots, Q&A, búsquedaWorkflows de negocio, pipelinesInvestigación, análisis largo

Qué abstrae cada nivel

Entender esto es clave para elegir bien. Cada nivel abstrae cosas que el anterior te obliga a construir.

create_agent abstrae:

- El loop reason → act → observe → repeat
- El bind de tools al modelo
- El parsing de tool calls y ejecución

Tú sigues manejando: estado, flujo, persistencia, multi-agente, HITL.

LangGraph abstrae:

- Estado tipado y accesible por todos los nodos
- Flujo de ejecución con edges (secuencial, condicional, paralelo)
- Persistencia con checkpointers
- Interrupt/resume para HITL
- Subgrafos para multi-agente

Tú sigues manejando: planning, filesystem, subagent spawning dinámico, memoria cross-session.

Deep Agents abstrae:

- Planning con write_todos (descomposición, tracking, re-planning)
- Virtual filesystem (leer, escribir, organizar outputs)
- Subagent spawning (crear agentes on-demand, contexto aislado)
- Long-term memory (backends pluggable, retrieve/store automático)
- CLI (interfaz de terminal completa)

Tú sigues manejando: las instrucciones que guían al agente, la selección de tools, y la evaluación de resultados.

Visualización del espectro

                    Control
                      ▲
                      │
          LangGraph ──┤  Total: nodos, edges, estado, flujo
                      │
    create_agent ─────┤  Parcial: tools sí, flujo no
                      │
      Deep Agents ────┤  Mínimo: instrucciones y tools
                      │
                      └──────────────────────────► Conveniencia

10 escenarios reales: ¿qué usarías?

Para cada escenario, la respuesta incluye la herramienta y la razón.

1. Chatbot de soporte técnico

Herramienta: create_agent

Por qué: El patrón es simple — recibir pregunta, buscar en base de conocimiento, responder. No necesita planning ni multi-agente. Un loop ReAct con tools de búsqueda resuelve el 95% de los tickets.

2. Pipeline de aprobación de préstamos

Herramienta: LangGraph

Por qué: Hay un flujo regulado: verificar identidad → evaluar crédito → análisis de riesgo → aprobación humana → notificación. Cada paso tiene reglas específicas. Necesitas HITL en puntos exactos (aprobación humana). Necesitas audit trail (checkpoints). Este es el caso textbook de LangGraph.

3. Investigación de mercado autónoma

Herramienta: Deep Agents

Por qué: "Investiga el mercado de X, compara 5 competidores, genera un reporte." Es autónomo, de larga duración, multi-paso. Planning, filesystem, y subagentes para búsquedas paralelas son exactamente lo que Deep Agents provee.

4. Generador de emails personalizados

Herramienta: create_agent

Por qué: Input → buscar datos del cliente → generar email → output. Es un flujo lineal sin branching. No necesita planning. create_agent con un tool de CRM resuelve esto en 15 líneas.

5. Sistema de code review multi-paso

Herramienta: LangGraph

Por qué: El flujo requiere: leer código → identificar issues → clasificar por severidad → generar sugerencias → si hay issues críticos, notificar al lead. Branching condicional, roles diferentes por paso, y HITL para issues críticos. LangGraph te da el control necesario.

6. Generación de documentación de proyecto completo

Herramienta: Deep Agents

Por qué: Leer todo el código del proyecto, entender la arquitectura, generar docs para cada módulo, y crear un README consolidado. Requiere planning (qué documentar primero), filesystem (un doc por módulo), y potencialmente subagentes (un subagente por módulo grande).

7. Asistente de agenda/calendario

Herramienta: create_agent

Por qué: "¿Qué tengo hoy?" → buscar en calendario → responder. "Agenda reunión con X" → crear evento. Tools simples, flujo lineal, sin necesidad de planning.

8. Pipeline de ETL con validación

Herramienta: LangGraph

Por qué: Extraer datos → validar formato → transformar → cargar. Si la validación falla, retry con diferentes parámetros. Si falla 3 veces, notificar y parar. Este flujo necesita conditional edges, retry con lógica custom, y estado persistente. LangGraph.

9. Agente de análisis financiero multi-fuente

Herramienta: Deep Agents

Por qué: Investigar indicadores de múltiples fuentes (mercado, SEC filings, noticias), analizar correlaciones, generar un reporte con gráficos. Es autónomo, multi-fuente, multi-paso, y requiere subagentes especializados por tipo de fuente. Deep Agents abstrae la coordinación.

10. Moderador de contenido con escalación

Herramienta: LangGraph

Por qué: Analizar contenido → clasificar riesgo → si es bajo, aprobar automáticamente; si es medio, flaggear para review; si es alto, bloquear y notificar. HITL en el nivel medio, reglas de negocio estrictas, y audit trail obligatorio. LangGraph da el control que un moderador de contenido requiere.

Resumen visual

create_agent (simple):     1, 4, 7
LangGraph (control):       2, 5, 8, 10
Deep Agents (autónomo):    3, 6, 9

La distribución refleja la realidad: ~30% de los proyectos son simples (create_agent), ~40% necesitan workflow custom (LangGraph), ~30% son tareas autónomas largas (Deep Agents).


Enfoques híbridos

No estás limitado a elegir uno solo. Las herramientas se componen entre sí.

create_agent dentro de LangGraph

Usa create_agent como un nodo dentro de un grafo LangGraph:

from dotenv import load_dotenv
load_dotenv()

from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain_community.tools import TavilySearchResults
from langgraph.graph import StateGraph, START, END
from typing import TypedDict, Annotated
import operator

class State(TypedDict):
    messages: Annotated[list, operator.add]
    query: str

model = ChatOpenAI(model="gpt-4.1-mini")
search_tools = [TavilySearchResults(max_results=3)]
search_agent = create_agent(model, search_tools)

def search_node(state: State) -> dict:
    result = search_agent.invoke(
        {"messages": [{"role": "user", "content": state["query"]}]}
    )
    return {"messages": [{"role": "system", "content": result["messages"][-1].content}]}

def summarize_node(state: State) -> dict:
    response = model.invoke(f"Resume en 3 bullet points:\n{state['messages'][-1]['content']}")
    return {"messages": [{"role": "assistant", "content": response.content}]}

builder = StateGraph(State)
builder.add_node("search", search_node)
builder.add_node("summarize", summarize_node)
builder.add_edge(START, "search")
builder.add_edge("search", "summarize")
builder.add_edge("summarize", END)

graph = builder.compile()

result = graph.invoke({"query": "AI agents trends 2025", "messages": []})
print(f"Mensajes generados: {len(result['messages'])}")
print(f"Último mensaje: {result['messages'][-1]['content'][:100]}...")
# Output esperado:
# Mensajes generados: 2
# Último mensaje: • Los AI agents han evolucionado hacia...

Cuándo: cuando un nodo específico necesita tool calling autónomo pero el flujo general tiene lógica custom.

LangGraph como componente de Deep Agents

Un Deep Agent puede invocar un grafo LangGraph como herramienta:

from dotenv import load_dotenv
load_dotenv()

from deep_agents import create_deep_agent
from deep_agents.tools import create_graph_tool
from langgraph.graph import StateGraph, START, END
from typing import TypedDict

class AnalysisState(TypedDict):
    data: str
    result: str

def analyze_node(state: AnalysisState) -> dict:
    return {"result": f"Analysis of: {state['data'][:50]}..."}

builder = StateGraph(AnalysisState)
builder.add_node("analyze", analyze_node)
builder.add_edge(START, "analyze")
builder.add_edge("analyze", END)

analysis_graph = builder.compile()

analysis_tool = create_graph_tool(
    graph=analysis_graph,
    name="detailed_analysis",
    description="Runs detailed analysis on provided data using a custom pipeline",
)

agent = create_deep_agent(
    "openai:gpt-4.1",
    tools=[analysis_tool],
    name="Research Agent",
    instructions="Usa detailed_analysis para análisis que requieren pipeline custom.",
)

print(f"Tools disponibles: {[t.name for t in agent.tools]}")
# Output esperado:
# Tools disponibles: ['detailed_analysis', 'write_todos', 'read_file', 'write_file', 'spawn_agent']

Cuándo: cuando el Deep Agent necesita ejecutar un flujo con control fino como parte de una tarea autónoma más grande.


La perspectiva del profesional

Saber los tres niveles no es un lujo académico — es lo que diferencia un developer de un AI Engineer.

En entrevistas

Entrevistador: "¿Cómo construirías un agente de investigación?"

Junior: "Usaría LangGraph porque es el que conozco."

Senior: "Depende del caso. Si es Q&A simple, create_agent con TavilySearch
resuelve en 15 líneas. Si necesito un pipeline con aprobaciones y retry,
LangGraph me da control sobre cada paso. Si es investigación autónoma
de larga duración, Deep Agents incluye planning y filesystem out-of-the-box.
¿Cuál es el caso específico?"

En producción

Decisión real:

Proyecto A: Chatbot para FAQ → create_agent
  → En producción en 1 día
  → Costo: ~$0.001 por consulta
  → Mantenimiento: mínimo

Proyecto B: Pipeline de onboarding → LangGraph
  → En producción en 1 semana
  → Costo: ~$0.05 por ejecución
  → Mantenimiento: cada cambio de flujo = editar el grafo

Proyecto C: Research automation → Deep Agents
  → En producción en 2 días
  → Costo: ~$0.10-0.50 por investigación
  → Mantenimiento: ajustar instructions y tools

En tu portfolio

Si tienes las tres versiones del Research Assistant (create_agent, LangGraph, Deep Agents), puedes demostrar que:

  • ✅ Sabes construir desde lo básico hasta lo complejo
  • ✅ Entiendes trade-offs de conveniencia vs control
  • ✅ Puedes elegir la herramienta correcta para el problema
  • ✅ No eres dependiente de un solo framework

Eso es lo que te hace AI Engineer, no un "usuario de LangGraph."


Ejercicios

Ejercicio 1: Diagnóstico de nivel

Para cada descripción, identifica qué herramienta usarías y justifica en una oración:

  1. Un bot de Slack que responde preguntas sobre la documentación interna
  2. Un sistema de triage de bugs que clasifica, prioriza, y asigna
  3. Un agente que investiga competidores y genera un reporte semanal automático
  4. Un asistente de código que sugiere refactorings
  5. Un pipeline de generación de contratos legales con aprobaciones
Ver solución
  1. create_agent — Q&A con tool de búsqueda en docs, flujo lineal, sin necesidad de workflow custom.
  2. LangGraph — Flujo con clasificación → priorización → asignación con reglas de negocio específicas, HITL para casos ambiguos.
  3. Deep Agents — Investigación autónoma recurrente, multi-fuente, requiere planning y filesystem para reportes.
  4. create_agent — Lee código, sugiere mejoras. Un solo flujo: analizar → sugerir. No necesita planning.
  5. LangGraph — Flujo regulado con aprobaciones en puntos específicos, audit trail obligatorio, reglas de negocio estrictas.

Ejercicio 2: Implementación en 3 niveles

Elige un agente simple: "un agente que busca el clima de una ciudad y responde en español." Impleméntalo en create_agent (~10 líneas), LangGraph (~40 líneas), y Deep Agents (~15 líneas). Compara: ¿cuál tiene sentido para este caso?

Ver solución

create_agent:

from dotenv import load_dotenv
load_dotenv()

from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain_community.tools import TavilySearchResults

model = ChatOpenAI(model="gpt-4.1-mini")
agent = create_agent(model, [TavilySearchResults(max_results=1)])

result = agent.invoke(
    {"messages": [{"role": "user", "content": "¿Cuál es el clima en Madrid hoy?"}]}
)
print(result["messages"][-1].content)
# Output esperado: El clima en Madrid hoy es...

LangGraph:

from dotenv import load_dotenv
load_dotenv()

from langgraph.graph import StateGraph, START, END
from langchain_openai import ChatOpenAI
from langchain_community.tools import TavilySearchResults
from typing import TypedDict, Annotated
import operator

class State(TypedDict):
    messages: Annotated[list, operator.add]
    city: str

search = TavilySearchResults(max_results=1)
model = ChatOpenAI(model="gpt-4.1-mini")

def search_weather(state: State) -> dict:
    results = search.invoke(f"weather today {state['city']}")
    return {"messages": [{"role": "system", "content": str(results)}]}

def format_response(state: State) -> dict:
    response = model.invoke(f"Di el clima de {state['city']} en español:\n{state['messages'][-1]}")
    return {"messages": [{"role": "assistant", "content": response.content}]}

builder = StateGraph(State)
builder.add_node("search", search_weather)
builder.add_node("format", format_response)
builder.add_edge(START, "search")
builder.add_edge("search", "format")
builder.add_edge("format", END)
graph = builder.compile()

result = graph.invoke({"city": "Madrid", "messages": []})
print(result["messages"][-1]["content"])
# Output esperado: El clima en Madrid hoy es...

Deep Agents:

from dotenv import load_dotenv
load_dotenv()

from deep_agents import create_deep_agent
from langchain_community.tools import TavilySearchResults

agent = create_deep_agent(
    "openai:gpt-4.1-mini",
    tools=[TavilySearchResults(max_results=1)],
    name="Weather Agent",
    instructions="Busca el clima y responde en español.",
)

result = agent.run("¿Cuál es el clima en Madrid hoy?")
print(result.output)
# Output esperado: El clima en Madrid hoy es...

Veredicto: create_agent es la opción correcta. LangGraph agrega complejidad innecesaria para un flujo lineal. Deep Agents tiene overhead de planning para una tarea que no necesita planificación.

Ejercicio 3: Diseño de sistema híbrido

Diseña (en pseudocódigo o diagrama) un sistema que use los tres niveles: un Deep Agent principal que coordina la investigación, un grafo LangGraph para el pipeline de análisis con aprobación humana, y create_agent para búsquedas individuales dentro de nodos.

Ver solución
Arquitectura híbrida:

Deep Agent (nivel principal):
  └─ Planning: write_todos para descomponer la investigación
  └─ Tools:
      ├─ web_search_agent (create_agent con TavilySearch)
      │   → Búsquedas simples, una pregunta → una respuesta
      ├─ analysis_pipeline (LangGraph graph como tool)
      │   → Pipeline: validate → analyze → human_approve → report
      │   → HITL: aprobación antes de publicar hallazgos
      └─ write_file, read_file (built-in)
  └─ Filesystem: resultados en research/, análisis en analysis/
  └─ Subagents: por cada fuente compleja, spawn un subagente

Flujo:
1. Deep Agent planifica: "Investigar X en 5 pasos"
2. Paso 1: usa web_search_agent (create_agent) para búsqueda rápida
3. Paso 2: escribe hallazgos en research/web.md
4. Paso 3: spawn subagente para búsqueda académica
5. Paso 4: invoca analysis_pipeline (LangGraph) con HITL
6. Paso 5: genera reporte final en output/report.md
from dotenv import load_dotenv
load_dotenv()

from deep_agents import create_deep_agent
from deep_agents.tools import create_graph_tool
from langchain.agents import create_agent
from langchain_openai import ChatOpenAI
from langchain_community.tools import TavilySearchResults
from langgraph.graph import StateGraph, START, END
from typing import TypedDict

model = ChatOpenAI(model="gpt-4.1-mini")
simple_search = create_agent(model, [TavilySearchResults(max_results=3)])

class AnalysisState(TypedDict):
    data: str
    approved: bool
    result: str

def validate(state): return {"result": "Validated"}
def analyze(state): return {"result": "Analyzed: " + state["data"][:50]}
def approve(state): return {"approved": True}

builder = StateGraph(AnalysisState)
builder.add_node("validate", validate)
builder.add_node("analyze", analyze)
builder.add_node("approve", approve)
builder.add_edge(START, "validate")
builder.add_edge("validate", "analyze")
builder.add_edge("analyze", "approve")
builder.add_edge("approve", END)
analysis_graph = builder.compile()

analysis_tool = create_graph_tool(analysis_graph, "analysis_pipeline", "Runs analysis with approval")

agent = create_deep_agent(
    "openai:gpt-4.1",
    tools=[TavilySearchResults(max_results=5), analysis_tool],
    name="Hybrid Research Agent",
    instructions="Usa búsqueda web para datos, analysis_pipeline para análisis con aprobación.",
)

print(f"Tools: {[t.name for t in agent.tools]}")
# Output esperado:
# Tools: ['tavily_search_results', 'analysis_pipeline', 'write_todos', 'read_file', 'write_file', 'spawn_agent']

Ejercicio 4: Trade-off analysis

Para un proyecto de "asistente legal que revisa contratos, identifica cláusulas problemáticas, y sugiere cambios con aprobación de un abogado," argumenta:

  1. Por qué podrías usar LangGraph
  2. Por qué podrías usar Deep Agents
  3. Cuál elegirías y por qué
Ver solución

Argumento para LangGraph:

  • Los contratos legales requieren un flujo regulado y auditable
  • HITL es obligatorio en puntos específicos (un abogado aprueba cada sugerencia)
  • Las reglas para identificar cláusulas son específicas y requieren branching condicional
  • Necesitas audit trail detallado (qué se revisó, quién aprobó, cuándo)
  • ~150-200 líneas te dan control total sobre cada paso

Argumento para Deep Agents:

  • La revisión de contratos largos es una tarea autónoma multi-paso
  • Planning automático: el agente decide cómo descomponer un contrato de 50 páginas
  • Filesystem: cada cláusula analizada se guarda en un archivo separado
  • Subagentes: un subagente por sección del contrato

Elección: LangGraph.

Las regulaciones legales requieren flujos predecibles, auditables, y con HITL en puntos exactos. Un abogado necesita aprobar cada sugerencia individualmente, no la tarea completa. Deep Agents es demasiado autónomo para un dominio donde cada paso tiene implicaciones legales. El overhead de escribir más código se justifica por la auditabilidad y el control.


Resumen

  • Tienes tres niveles de abstracción: create_agent (simple, ~15 líneas, 80% de casos), LangGraph (control total, ~150 líneas, custom workflows), y Deep Agents (autónomo, ~40 líneas, batteries-included)
  • El árbol de decisión es: ¿simple? → create_agent. ¿Necesitas control? → LangGraph. ¿Tarea autónoma larga? → Deep Agents
  • Side-by-side: el mismo Research Agent implementado en 3 versiones demuestra que la diferencia no es la capacidad — es cuánto controlas vs cuánto delega el framework
  • Cada nivel abstrae lo que el anterior te obliga a construir: create_agent abstrae el loop ReAct; LangGraph abstrae estado y flujo; Deep Agents abstrae planning, filesystem, subagentes, y memoria
  • 10 escenarios reales con justificación te dan práctica aplicando el framework: ~30% create_agent, ~40% LangGraph, ~30% Deep Agents
  • Enfoques híbridos son válidos y comunes: create_agent dentro de nodos LangGraph, grafos LangGraph como tools de Deep Agents
  • La perspectiva profesional: saber los tres niveles te permite elegir la herramienta correcta para cada problema. Eso es lo que define a un AI Engineer

Próxima cápsula: Proyecto — reimplementar el Research Assistant como Deep Agent y hacer la comparación side-by-side definitiva con la versión LangGraph.


Recursos adicionales

  1. LangChain — Agent Architectures — Vista general de create_agent y su API
  2. LangGraph — Overview — Documentación de StateGraph, edges, checkpointers
  3. Deep Agents — Documentation — API reference de create_deep_agent y sus capacidades
  4. Building Effective Agents (Anthropic) — Perspectiva sobre cuándo usar agentes simples vs complejos
  5. LangGraph Multi-Agent Systems — Patrones multi-agente en LangGraph
  6. Cognitive Architectures for Language Agents — Paper académico que informa el diseño de los tres niveles

Módulo 11 — LangChain & LangGraph: From Chains to Agents