Módulo 3: Agents con create_agent

Legacy vs Moderno: Mapeo de APIs

Descripción de la cápsula

Si buscas "LangChain tutorial" en Google, YouTube, o Medium, el 80% de los resultados que encuentras usan APIs que ya no existen. LLMChain, SequentialChain, AgentExecutor, ConversationChain, ConversationBufferMemory — todo esto era la forma oficial de usar LangChain hasta octubre de 2025. Después del lanzamiento de LangChain v1.0, estas APIs entraron en deprecation y fueron reemplazadas por alternativas más simples, más potentes, y mejor diseñadas.

El problema es que internet tiene memoria larga. Los tutoriales, cursos, y respuestas de Stack Overflow no se actualizan automáticamente. Si copias código de un tutorial de 2024 y lo pegas en tu proyecto con LangChain v1.2+, vas a recibir deprecation warnings, errores de importación, o comportamientos inesperados. Y lo peor: vas a aprender patrones que tendrás que desaprender.

Esta cápsula es tu diccionario de traducción. Mapea CADA API legacy a su equivalente moderno, con ejemplos de código antes/después que puedes copiar directamente. Después de leerla, serás capaz de tomar cualquier tutorial antiguo de LangChain y traducirlo al estilo moderno que aprendiste en este módulo.


Por qué existen las APIs legacy

LangChain evolucionó de forma extremadamente rápida. Fue creado por Harrison Chase en octubre de 2022, y en sus primeros dos años pasó por cambios arquitectónicos radicales:

PeríodoEstado de LangChainAPIs características
Oct 2022 - 2023Experimentación rápidaLLMChain, SequentialChain, AgentExecutor, ConversationChain
2024TransiciónLCEL (pipe operator), RunnableSequence, inicio de deprecations
Oct 2025LangChain v1.0 (estable)create_agent, init_chat_model, with_structured_output
2026+LangChain v1.2+Middleware system, @dynamic_prompt, APIs consolidadas

La clave es entender que las APIs legacy no eran "malas" — eran las mejores opciones en su momento. Pero LangChain descubrió que:

  • Demasiadas abstracciones: LLMChain hacía muy poco (solo prompt + model), pero requería aprender una clase nueva
  • Memoria como objeto separado: ConversationBufferMemory era frágil y difícil de debuggear
  • AgentExecutor limitado: No soportaba streaming bien, no tenía middleware, y era difícil de customizar
  • Imports fragmentados: from langchain.llms import OpenAI mezclaba completion models con chat models

La v1.0 simplificó todo esto. En vez de aprender 15 clases diferentes, ahora usas 3-4 funciones que cubren el 95% de los casos.


Tabla completa de equivalencias

Esta es la referencia central de la cápsula. Guárdala — la vas a necesitar cada vez que encuentres código legacy online.

APIs reemplazadas (ya no usar)

API LegacyAPI ModernaNotas
LLMChain(llm, prompt)model.invoke(prompt) o prompt | modelChains reemplazados por pipe operator
SequentialChain([chain1, chain2])chain1 | chain2Pipe operator encadena runnables
AgentExecutor(agent, tools)create_agent(model, tools)create_agent maneja todo internamente
ConversationChain(llm, memory=...)Agent + checkpointing (Módulo 8)Memoria ahora es checkpointing
from langchain.llms import OpenAIinit_chat_model("openai:gpt-4.1")Chat models, no completion models
ConversationBufferMemoryAgent state + MemorySaverBuilt into LangGraph
OutputParserwith_structured_output()Nativo en chat models
CallbackHandlerMiddleware system (Módulo 4)Más potente y composable

APIs que siguen vigentes

APIEstadoNotas
RunnablePassthrough✅ VigenteParte de LCEL, sigue soportado
ChatPromptTemplate✅ VigenteTemplates de prompts siguen funcionando
@tool decorator✅ VigenteLa forma estándar de crear tools
bind_tools()✅ VigenteVincular tools a modelos
ToolMessage✅ VigenteMensajes de respuesta de tools
Pipe operator (|)✅ VigenteLCEL sigue siendo core de LangChain
with_structured_output()✅ VigenteStructured output nativo

Regla práctica: Si el concepto que ves en un tutorial involucra Chain en el nombre de la clase (excepto ChatPromptTemplate), probablemente tiene un reemplazo moderno más simple.


Traducción 1: LLMChain → invoke / pipe operator

LLMChain era la forma básica de conectar un prompt con un modelo. Requería crear una instancia de clase, pasarle el LLM y el prompt, y luego llamar .run() o .invoke().

Código legacy

# ❌ LEGACY — No usar con LangChain v1.2+
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain.llms import OpenAI

llm = OpenAI(temperature=0.7)
prompt = PromptTemplate(
    input_variables=["topic"],
    template="Escribe un chiste sobre {topic}"
)

chain = LLMChain(llm=llm, prompt=prompt)
result = chain.run("programación")
print(result)

Código moderno (opción 1: invoke directo)

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model

model = init_chat_model("openai:gpt-4.1-mini", temperature=0.7)

result = model.invoke("Escribe un chiste sobre programación")
print(result.content)
# Output esperado: ¿Por qué los programadores prefieren el modo oscuro?
# Porque la luz atrae bugs.

Código moderno (opción 2: pipe operator con template)

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate

model = init_chat_model("openai:gpt-4.1-mini", temperature=0.7)
prompt = ChatPromptTemplate.from_template("Escribe un chiste sobre {topic}")

chain = prompt | model
result = chain.invoke({"topic": "programación"})
print(result.content)
# Output esperado: ¿Por qué los programadores prefieren el modo oscuro?
# Porque la luz atrae bugs.

¿Qué cambió?

AspectoLegacyModerno
ModeloOpenAI() (completion model)init_chat_model() (chat model)
Clase chainLLMChain(llm, prompt)prompt | model (pipe operator)
Ejecuciónchain.run("tema")chain.invoke({"topic": "tema"})
ResultadoString directoAIMessage (con .content, metadata)

Traducción 2: SequentialChain → pipe operator

SequentialChain conectaba múltiples LLMChain en secuencia. El output de uno era el input del siguiente.

Código legacy

# ❌ LEGACY — No usar con LangChain v1.2+
from langchain.chains import LLMChain, SequentialChain
from langchain.prompts import PromptTemplate
from langchain.llms import OpenAI

llm = OpenAI()

chain1 = LLMChain(
    llm=llm,
    prompt=PromptTemplate(
        input_variables=["topic"],
        template="Genera 3 ideas sobre {topic}. Responde solo las ideas."
    ),
    output_key="ideas"
)

chain2 = LLMChain(
    llm=llm,
    prompt=PromptTemplate(
        input_variables=["ideas"],
        template="Elige la mejor idea de estas y explica por qué:\n{ideas}"
    ),
    output_key="best_idea"
)

sequential = SequentialChain(
    chains=[chain1, chain2],
    input_variables=["topic"],
    output_variables=["ideas", "best_idea"]
)

result = sequential({"topic": "aplicaciones de AI"})
print(result["best_idea"])

Código moderno

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

model = init_chat_model("openai:gpt-4.1-mini")

step1 = ChatPromptTemplate.from_template(
    "Genera 3 ideas sobre {topic}. Responde solo las ideas."
) | model | StrOutputParser()

step2 = ChatPromptTemplate.from_template(
    "Elige la mejor idea de estas y explica por qué:\n{ideas}"
) | model | StrOutputParser()

ideas = step1.invoke({"topic": "aplicaciones de AI"})
best = step2.invoke({"ideas": ideas})
print(best)
# Output esperado: La mejor idea es [X] porque...

El pipe operator (|) reemplaza la necesidad de una clase SequentialChain. Si necesitas pasar datos entre pasos, simplemente usas variables Python normales. Es más explícito, más fácil de debuggear, y no requiere aprender clases nuevas.


Traducción 3: AgentExecutor → create_agent

Este es el cambio más importante de LangChain v1.2+. AgentExecutor era la forma oficial de correr agentes, pero tenía limitaciones serias: streaming parcial, no soportaba middleware, y era difícil de customizar.

Código legacy

# ❌ LEGACY — No usar con LangChain v1.2+
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool

@tool
def calculate(expression: str) -> str:
    """Evalúa una expresión matemática."""
    return str(eval(expression))

llm = ChatOpenAI(model="gpt-4")

prompt = ChatPromptTemplate.from_messages([
    ("system", "Eres un asistente útil."),
    MessagesPlaceholder(variable_name="chat_history", optional=True),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])

agent = create_openai_tools_agent(llm, [calculate], prompt)
executor = AgentExecutor(agent=agent, tools=[calculate], verbose=True)

result = executor.invoke({"input": "¿Cuánto es 25 * 4?"})
print(result["output"])

Código moderno

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from langgraph.prebuilt import create_agent

@tool
def calculate(expression: str) -> str:
    """Evalúa una expresión matemática."""
    return str(eval(expression))

model = init_chat_model("openai:gpt-4.1-mini")

agent = create_agent(model, [calculate])

result = agent.invoke(
    {"messages": [{"role": "user", "content": "¿Cuánto es 25 * 4?"}]}
)
print(result["messages"][-1].content)
# Output esperado: 25 × 4 = 100

¿Qué cambió?

AspectoLegacy (AgentExecutor)Moderno (create_agent)
Setup15+ líneas (prompt, agent, executor)1 línea: create_agent(model, tools)
PromptManual con MessagesPlaceholderAutomático (o system_prompt=)
Ejecuciónexecutor.invoke({"input": "..."})agent.invoke({"messages": [...]})
StreamingParcial, con AgentAction chunksNativo con agent.stream()
MiddlewareNo soportado@wrap_model_call, @before_model, etc.
CustomizaciónSubclasear AgentExecutorMiddleware composable

Traducción 4: ConversationChain + Memory → Agent con checkpointing

ConversationChain con ConversationBufferMemory era la forma de mantener historial de conversación. Era frágil — la memoria vivía en un objeto separado, era difícil de persistir, y se perdía al reiniciar.

Código legacy

# ❌ LEGACY — No usar con LangChain v1.2+
from langchain.chains import ConversationChain
from langchain.memory import ConversationBufferMemory
from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4")
memory = ConversationBufferMemory()

conversation = ConversationChain(llm=llm, memory=memory)

print(conversation.predict(input="Hola, me llamo Ana"))
print(conversation.predict(input="¿Cómo me llamo?"))

Código moderno

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langgraph.prebuilt import create_agent
from langgraph.checkpoint.memory import MemorySaver

model = init_chat_model("openai:gpt-4.1-mini")

agent = create_agent(model, tools=[], checkpointer=MemorySaver())

config = {"configurable": {"thread_id": "conversation-1"}}

result1 = agent.invoke(
    {"messages": [{"role": "user", "content": "Hola, me llamo Ana"}]},
    config=config,
)
print(result1["messages"][-1].content)

result2 = agent.invoke(
    {"messages": [{"role": "user", "content": "¿Cómo me llamo?"}]},
    config=config,
)
print(result2["messages"][-1].content)
# Output esperado:
# ¡Hola Ana! ¿En qué puedo ayudarte?
# Te llamas Ana.

Con MemorySaver y un thread_id, la conversación se persiste automáticamente. No necesitas gestionar un objeto Memory separado. Y en producción, puedes cambiar MemorySaver() por PostgresSaver para persistir en base de datos (lo verás en el Módulo 8).


Traducción 5: OutputParser → with_structured_output

OutputParser requería que el modelo generara texto en un formato específico (JSON, YAML, etc.) y luego lo parseara manualmente. Era propenso a errores porque dependía de que el modelo respetara el formato exacto.

Código legacy

# ❌ LEGACY — No usar con LangChain v1.2+
from langchain.output_parsers import PydanticOutputParser
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field

class Movie(BaseModel):
    title: str = Field(description="Título de la película")
    year: int = Field(description="Año de estreno")
    genre: str = Field(description="Género principal")

parser = PydanticOutputParser(pydantic_object=Movie)

prompt = PromptTemplate(
    template="Recomienda una película de ciencia ficción.\n{format_instructions}",
    input_variables=[],
    partial_variables={"format_instructions": parser.get_format_instructions()},
)

llm = ChatOpenAI(model="gpt-4")
chain = prompt | llm | parser
result = chain.invoke({})
print(result)

Código moderno

from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from pydantic import BaseModel, Field

class Movie(BaseModel):
    title: str = Field(description="Título de la película")
    year: int = Field(description="Año de estreno")
    genre: str = Field(description="Género principal")

model = init_chat_model("openai:gpt-4.1-mini")
structured_model = model.with_structured_output(Movie)

result = structured_model.invoke("Recomienda una película de ciencia ficción.")
print(result)
# Output esperado: title='Blade Runner 2049' year=2017 genre='Ciencia ficción'
print(f"{result.title} ({result.year}) — {result.genre}")
# Output esperado: Blade Runner 2049 (2017) — Ciencia ficción

with_structured_output() usa tool calling nativo del modelo para garantizar el formato. No depende de que el modelo genere JSON textual — es significativamente más confiable.


Cómo leer tutoriales legacy

Cuando encuentres un tutorial de LangChain en internet, sigue estos pasos para determinar si usa APIs legacy:

Paso 1: Revisa los imports

# 🚩 Señales de código legacy:
from langchain.llms import OpenAI          # → usar init_chat_model
from langchain.chains import LLMChain      # → usar pipe operator
from langchain.agents import AgentExecutor # → usar create_agent
from langchain.memory import ...           # → usar checkpointing
from langchain.output_parsers import ...   # → usar with_structured_output

Paso 2: Identifica el patrón

Si ves...El patrón es...Reemplaza con...
LLMChain(llm, prompt)Prompt + Modelprompt | model o model.invoke()
SequentialChain(chains=[...])Encadenar pasosstep1 | step2 o variables Python
AgentExecutor(agent, tools)Agente con toolscreate_agent(model, tools)
ConversationChain(memory=...)Chat con memoriacreate_agent + MemorySaver
PydanticOutputParserStructured outputmodel.with_structured_output(Schema)
CallbackHandlerInterceptar ejecuciónMiddleware (@before_model, etc.)

Paso 3: Traduce

Usa las traducciones de esta cápsula como referencia. El 90% de los tutoriales legacy caen en uno de los 5 patrones anteriores. Si encuentras un caso que no está aquí, consulta la guía de migración oficial.


Por qué migrar a APIs modernas

No se trata solo de "estar al día". Hay razones técnicas concretas para usar las APIs modernas:

1. Deprecation warnings y futuras rupturas

Las APIs legacy emiten warnings cada vez que las usas:

LangChainDeprecationWarning: The class `LLMChain` was deprecated in
LangChain 0.1.17 and will be removed in 1.0. Use RunnableSequence, e.g.,
`prompt | llm` instead.

En futuras versiones de LangChain, estas APIs serán eliminadas, no solo deprecadas. Tu código dejará de funcionar.

2. Features que no existen en legacy

FeatureDisponible en legacyDisponible en moderno
Streaming nativo❌ Parcial✅ Completo
Middleware system❌ No@wrap_model_call, etc.
Dynamic prompts❌ No@dynamic_prompt
Dynamic tools❌ No✅ Middleware
Structured output nativo❌ Parsers manualeswith_structured_output
Checkpointing❌ Memory objectsMemorySaver, PostgresSaver
Multi-provider❌ Import por proveedorinit_chat_model("provider:model")

3. Código más simple y mantenible

El mismo agente en legacy vs moderno:

# Legacy: ~20 líneas de setup
# prompt, agent, executor, memory, callbacks...

# Moderno: ~5 líneas
model = init_chat_model("openai:gpt-4.1-mini")
agent = create_agent(model, tools, prompt="Eres un asistente útil.")
result = agent.invoke({"messages": [{"role": "user", "content": "Hola"}]})

4. Mejor debugging y observabilidad

Las APIs modernas se integran nativamente con LangSmith (Módulo 12). Los traces muestran cada paso del agente de forma clara. Con las APIs legacy, el debugging era significativamente más difícil.


Conexión con el proyecto

En el proyecto de este módulo (Cápsula 08), construirás un agente de investigación usando exclusivamente APIs modernas:

  • create_agent en vez de AgentExecutor — setup en 1 línea
  • init_chat_model en vez de ChatOpenAI — multi-proveedor
  • with_structured_output en vez de OutputParser — structured report confiable
  • agent.stream() en vez de callbacks — streaming nativo

Si en algún momento buscas referencia online y encuentras código legacy, ahora sabes exactamente cómo traducirlo.


Troubleshooting

Problema 1: LangChainDeprecationWarning al usar LLMChain

LangChainDeprecationWarning: The class `LLMChain` was deprecated in LangChain 0.1.17

Causa: Estás usando LLMChain en tu código o importando una librería que lo usa internamente.

Solución: Reemplaza LLMChain(llm=model, prompt=prompt) con el pipe operator:

chain = prompt | model
result = chain.invoke({"variable": "valor"})

Problema 2: ImportError: cannot import name 'OpenAI' from 'langchain.llms'

Causa: En LangChain v1.2+, los completion models (langchain.llms) fueron removidos o movidos. Los chat models son el estándar.

Solución: Usa init_chat_model en vez de importar directamente:

from langchain.chat_models import init_chat_model
model = init_chat_model("openai:gpt-4.1-mini")

Problema 3: AgentExecutor no soporta streaming como espero

Causa: AgentExecutor.stream() emite AgentAction y AgentFinish chunks, que son difíciles de procesar. El formato no es intuitivo.

Solución: Migra a create_agent, que soporta streaming nativo:

from langgraph.prebuilt import create_agent

agent = create_agent(model, tools)
for chunk in agent.stream(
    {"messages": [{"role": "user", "content": "Hola"}]},
    stream_mode="messages",
):
    # Chunks son mensajes estándar, fáciles de procesar
    print(chunk)

Problema 4: ConversationBufferMemory no persiste entre reinicios

Causa: ConversationBufferMemory almacena todo en RAM. Al reiniciar el proceso, se pierde.

Solución: Usa MemorySaver con create_agent para persistencia en memoria, o PostgresSaver para persistencia durable:

from langgraph.checkpoint.memory import MemorySaver

agent = create_agent(model, tools=[], checkpointer=MemorySaver())
config = {"configurable": {"thread_id": "user-123"}}
result = agent.invoke({"messages": [...]}, config=config)

Problema 5: Tutorial usa create_openai_tools_agent que no reconozco

Causa: create_openai_tools_agent era una función intermedia entre la API legacy y la moderna. Creaba un agente compatible con AgentExecutor.

Solución: Reemplaza todo el patrón create_openai_tools_agent + AgentExecutor con create_agent:

# Legacy:
# agent = create_openai_tools_agent(llm, tools, prompt)
# executor = AgentExecutor(agent=agent, tools=tools)

# Moderno:
agent = create_agent(model, tools, prompt="Tu system prompt aquí")

Ejercicios

Ejercicio 1: Traducir LLMChain a pipe operator (Básico)

Tienes este código legacy que genera un haiku sobre un tema. Tradúcelo a APIs modernas usando init_chat_model y el pipe operator.

# Código legacy a traducir:
from langchain.chains import LLMChain
from langchain.prompts import PromptTemplate
from langchain.llms import OpenAI

llm = OpenAI(temperature=0.9)
prompt = PromptTemplate(
    input_variables=["tema"],
    template="Escribe un haiku sobre {tema}. Solo el haiku, nada más."
)
chain = LLMChain(llm=llm, prompt=prompt)
result = chain.run("el mar")
print(result)
Ver solución
from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

model = init_chat_model("openai:gpt-4.1-mini", temperature=0.9)
prompt = ChatPromptTemplate.from_template(
    "Escribe un haiku sobre {tema}. Solo el haiku, nada más."
)

chain = prompt | model | StrOutputParser()
result = chain.invoke({"tema": "el mar"})
print(result)
# Output esperado:
# Olas en la orilla
# sal y espuma al atardecer
# el mar siempre vuelve

Cambios realizados:

  • OpenAI()init_chat_model("openai:gpt-4.1-mini") (chat model, no completion)
  • PromptTemplateChatPromptTemplate.from_template() (para chat models)
  • LLMChain(llm, prompt)prompt | model | StrOutputParser() (pipe operator)
  • chain.run("el mar")chain.invoke({"tema": "el mar"}) (dict de inputs)
  • StrOutputParser() extrae el string del AIMessage

Ejercicio 2: Traducir SequentialChain (Básico)

Traduce este código legacy que genera ideas y luego las evalúa:

# Código legacy a traducir:
from langchain.chains import LLMChain, SequentialChain
from langchain.prompts import PromptTemplate
from langchain.llms import OpenAI

llm = OpenAI()
chain1 = LLMChain(
    llm=llm,
    prompt=PromptTemplate(input_variables=["product"], template="Genera 3 nombres para un producto de {product}"),
    output_key="names"
)
chain2 = LLMChain(
    llm=llm,
    prompt=PromptTemplate(input_variables=["names"], template="Elige el mejor nombre de: {names} y explica por qué"),
    output_key="winner"
)
seq = SequentialChain(chains=[chain1, chain2], input_variables=["product"], output_variables=["names", "winner"])
result = seq({"product": "café orgánico"})
print(result["winner"])
Ver solución
from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser

model = init_chat_model("openai:gpt-4.1-mini")

step1 = (
    ChatPromptTemplate.from_template(
        "Genera 3 nombres para un producto de {product}"
    )
    | model
    | StrOutputParser()
)

step2 = (
    ChatPromptTemplate.from_template(
        "Elige el mejor nombre de: {names} y explica por qué"
    )
    | model
    | StrOutputParser()
)

names = step1.invoke({"product": "café orgánico"})
print(f"Nombres generados:\n{names}\n")

winner = step2.invoke({"names": names})
print(f"Ganador:\n{winner}")
# Output esperado:
# Nombres generados:
# 1. Raíz Pura  2. Tierra Viva  3. Grano Noble
#
# Ganador:
# El mejor nombre es "Tierra Viva" porque evoca naturaleza,
# frescura y el origen orgánico del producto...

Cambios realizados:

  • SequentialChain eliminado — los pasos se conectan con variables Python
  • Cada paso es un pipe: prompt | model | StrOutputParser()
  • El resultado de step1 se pasa manualmente a step2
  • Más explícito, más fácil de debuggear

Ejercicio 3: Traducir AgentExecutor a create_agent (Medio)

Traduce este agente legacy con dos tools a la API moderna:

# Código legacy a traducir:
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_openai import ChatOpenAI
from langchain_core.tools import tool

@tool
def get_weather(city: str) -> str:
    """Obtiene el clima de una ciudad."""
    data = {"Madrid": "22°C, soleado", "London": "14°C, nublado"}
    return data.get(city, f"Sin datos para {city}")

@tool
def calculator(expression: str) -> str:
    """Calcula una expresión matemática."""
    return str(eval(expression))

llm = ChatOpenAI(model="gpt-4", temperature=0)
prompt = ChatPromptTemplate.from_messages([
    ("system", "Eres un asistente con acceso a herramientas."),
    MessagesPlaceholder(variable_name="chat_history", optional=True),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])

agent = create_openai_tools_agent(llm, [get_weather, calculator], prompt)
executor = AgentExecutor(agent=agent, tools=[get_weather, calculator], verbose=True)
result = executor.invoke({"input": "¿Clima en Madrid y cuánto es 100*1.16?"})
print(result["output"])
Ver solución
from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from langgraph.prebuilt import create_agent

@tool
def get_weather(city: str) -> str:
    """Obtiene el clima de una ciudad."""
    data = {"Madrid": "22°C, soleado", "London": "14°C, nublado"}
    return data.get(city, f"Sin datos para {city}")

@tool
def calculator(expression: str) -> str:
    """Calcula una expresión matemática."""
    return str(eval(expression))

model = init_chat_model("openai:gpt-4.1-mini", temperature=0)

agent = create_agent(
    model,
    [get_weather, calculator],
    prompt="Eres un asistente con acceso a herramientas.",
)

result = agent.invoke(
    {"messages": [{"role": "user", "content": "¿Clima en Madrid y cuánto es 100*1.16?"}]}
)
print(result["messages"][-1].content)
# Output esperado: En Madrid hace 22°C y está soleado. 100 × 1.16 = 116.

Cambios realizados:

  • 15 líneas de setup → 3 líneas
  • ChatOpenAI(model="gpt-4")init_chat_model("openai:gpt-4.1-mini")
  • ChatPromptTemplate con MessagesPlaceholderprompt="..." como string
  • create_openai_tools_agent + AgentExecutorcreate_agent(model, tools)
  • executor.invoke({"input": "..."})agent.invoke({"messages": [...]})
  • result["output"]result["messages"][-1].content

Ejercicio 4: Traducir OutputParser a with_structured_output (Medio)

Traduce este código que usa PydanticOutputParser para extraer información estructurada:

# Código legacy a traducir:
from langchain.output_parsers import PydanticOutputParser
from langchain.prompts import PromptTemplate
from langchain_openai import ChatOpenAI
from pydantic import BaseModel, Field
from typing import List

class BookReview(BaseModel):
    title: str = Field(description="Título del libro")
    author: str = Field(description="Autor")
    rating: float = Field(description="Calificación de 1 a 5")
    pros: List[str] = Field(description="Aspectos positivos")
    cons: List[str] = Field(description="Aspectos negativos")

parser = PydanticOutputParser(pydantic_object=BookReview)
prompt = PromptTemplate(
    template="Genera una reseña del libro '{book}'.\n{format_instructions}",
    input_variables=["book"],
    partial_variables={"format_instructions": parser.get_format_instructions()},
)

llm = ChatOpenAI(model="gpt-4")
chain = prompt | llm | parser
result = chain.invoke({"book": "1984 de George Orwell"})
print(f"{result.title} por {result.author}: {result.rating}/5")
Ver solución
from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from pydantic import BaseModel, Field

class BookReview(BaseModel):
    title: str = Field(description="Título del libro")
    author: str = Field(description="Autor")
    rating: float = Field(description="Calificación de 1 a 5")
    pros: list[str] = Field(description="Aspectos positivos")
    cons: list[str] = Field(description="Aspectos negativos")

model = init_chat_model("openai:gpt-4.1-mini")
structured_model = model.with_structured_output(BookReview)

result = structured_model.invoke("Genera una reseña del libro '1984 de George Orwell'.")
print(f"{result.title} por {result.author}: {result.rating}/5")
print(f"Pros: {', '.join(result.pros)}")
print(f"Cons: {', '.join(result.cons)}")
# Output esperado:
# 1984 por George Orwell: 4.8/5
# Pros: Narrativa atrapante, Reflexión profunda sobre el poder, Personajes memorables
# Cons: Tono pesimista que puede abrumar, Ritmo lento en algunos pasajes

Cambios realizados:

  • PydanticOutputParser eliminado — ya no necesitas un parser intermedio
  • PromptTemplate con format_instructions eliminado — el modelo sabe qué campos generar
  • prompt | llm | parsermodel.with_structured_output(BookReview) + .invoke()
  • Más confiable: usa tool calling nativo en vez de depender de que el modelo genere JSON textual
  • List[str]list[str] (Python 3.11+ syntax)

Ejercicio 5: Identificar y traducir código mixto (Difícil)

Este código mezcla APIs legacy y modernas. Identifica qué partes son legacy, cuáles son modernas, y reescribe todo con APIs modernas:

# Código mixto a traducir:
from langchain_openai import ChatOpenAI
from langchain.chains import LLMChain
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.tools import tool
from langchain.agents import AgentExecutor, create_openai_tools_agent
from langchain.prompts import MessagesPlaceholder

@tool
def search(query: str) -> str:
    """Busca información."""
    return f"Resultado para: {query}"

llm = ChatOpenAI(model="gpt-4", temperature=0)

summary_chain = LLMChain(
    llm=llm,
    prompt=ChatPromptTemplate.from_template("Resume esto: {text}")
)

prompt = ChatPromptTemplate.from_messages([
    ("system", "Eres un investigador."),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])

agent = create_openai_tools_agent(llm, [search], prompt)
executor = AgentExecutor(agent=agent, tools=[search])

result = executor.invoke({"input": "Busca sobre Python"})
summary = summary_chain.run(result["output"])
print(summary)
Ver solución

Partes legacy identificadas:

  • ChatOpenAI(model="gpt-4") → usar init_chat_model
  • LLMChain(llm, prompt) → usar pipe operator
  • create_openai_tools_agent + AgentExecutor → usar create_agent
  • MessagesPlaceholder("agent_scratchpad") → no necesario con create_agent
  • chain.run() → usar chain.invoke()
  • @tool decorator — vigente
  • ChatPromptTemplate — vigente
from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langgraph.prebuilt import create_agent

@tool
def search(query: str) -> str:
    """Busca información."""
    return f"Resultado para: {query}"

model = init_chat_model("openai:gpt-4.1-mini", temperature=0)

agent = create_agent(model, [search], prompt="Eres un investigador.")

result = agent.invoke(
    {"messages": [{"role": "user", "content": "Busca sobre Python"}]}
)
agent_output = result["messages"][-1].content

summary_chain = (
    ChatPromptTemplate.from_template("Resume esto: {text}")
    | model
    | StrOutputParser()
)
summary = summary_chain.invoke({"text": agent_output})
print(summary)
# Output esperado: Python es un lenguaje de programación de alto nivel,
# interpretado y de propósito general...

Cambios realizados:

  • ChatOpenAIinit_chat_model (multi-proveedor)
  • LLMChain → pipe operator (prompt | model | StrOutputParser())
  • create_openai_tools_agent + AgentExecutorcreate_agent
  • chain.run()chain.invoke({"text": ...})
  • Resultado del agente: result["output"]result["messages"][-1].content

Ejercicio 6: Construir agente moderno desde especificación legacy (Difícil)

Lee esta especificación escrita para APIs legacy y constrúyela desde cero usando APIs modernas. NO traduzcas línea por línea — construye desde cero.

Especificación:

"Crea un AgentExecutor con ConversationBufferMemory que use dos tools: una para buscar precios de productos y otra para calcular descuentos. El agente debe recordar productos mencionados previamente. Usa verbose=True para ver el proceso."

Ver solución
from dotenv import load_dotenv
load_dotenv()

from langchain.chat_models import init_chat_model
from langchain_core.tools import tool
from langgraph.prebuilt import create_agent
from langgraph.checkpoint.memory import MemorySaver

@tool
def get_price(product: str) -> str:
    """Obtiene el precio de un producto."""
    prices = {
        "laptop": 999.99,
        "mouse": 29.99,
        "keyboard": 79.99,
        "monitor": 349.99,
        "headphones": 149.99,
    }
    product_lower = product.lower().strip()
    if product_lower not in prices:
        available = ", ".join(sorted(prices.keys()))
        return f"Producto '{product}' no encontrado. Disponibles: {available}"
    return f"{product_lower}: ${prices[product_lower]:.2f}"

@tool
def calculate_discount(price: float, percent: float) -> str:
    """Calcula el precio con descuento aplicado."""
    if percent < 0 or percent > 100:
        return f"Porcentaje {percent} inválido. Debe estar entre 0 y 100."
    discount = price * (percent / 100)
    final = price - discount
    return f"Original: ${price:.2f} → Descuento: ${discount:.2f} ({percent}%) → Final: ${final:.2f}"

model = init_chat_model("openai:gpt-4.1-mini")

agent = create_agent(
    model,
    [get_price, calculate_discount],
    prompt="Eres un asistente de compras. Ayudas a buscar precios y calcular descuentos.",
    checkpointer=MemorySaver(),
)

config = {"configurable": {"thread_id": "shopping-1"}}

result1 = agent.invoke(
    {"messages": [{"role": "user", "content": "¿Cuánto cuesta un laptop?"}]},
    config=config,
)
print(f"R1: {result1['messages'][-1].content}")

for chunk in agent.stream(
    {"messages": [{"role": "user", "content": "Aplícale un 20% de descuento"}]},
    config=config,
    stream_mode="messages",
):
    msg, metadata = chunk
    if msg.content and metadata.get("langgraph_node") == "agent":
        print(msg.content, end="", flush=True)
print()

# Output esperado:
# R1: El laptop cuesta $999.99.
# Con un 20% de descuento, el laptop pasa de $999.99 a $799.99 (te ahorras $200.00).

Equivalencias con la especificación legacy:

  • AgentExecutorcreate_agent
  • ConversationBufferMemorycheckpointer=MemorySaver() + thread_id
  • verbose=Trueagent.stream() con stream_mode="messages" (mejor que verbose)
  • "Recordar productos" → El thread_id persiste el historial completo

Resumen

En esta cápsula aprendiste:

  • LangChain evolucionó radicalmente entre 2022 y 2025 — las APIs legacy existen porque fueron la versión original del framework
  • LLMChain se reemplaza con model.invoke() o el pipe operator (prompt | model)
  • SequentialChain se reemplaza con el pipe operator o variables Python entre pasos
  • AgentExecutor se reemplaza con create_agent(model, tools) — de ~20 líneas a ~3
  • ConversationChain + Memory se reemplaza con create_agent + MemorySaver (checkpointing)
  • OutputParser se reemplaza con with_structured_output() — más confiable por usar tool calling nativo
  • Lo que sigue vigente: @tool, bind_tools, pipe operator, ChatPromptTemplate, RunnablePassthrough
  • Cómo leer tutoriales legacy: revisar imports, identificar el patrón, traducir con la tabla de equivalencias
  • Por qué migrar: deprecation warnings, features exclusivas de APIs modernas, código más simple, mejor debugging

Próxima cápsula: Proyecto — construirás un agente de investigación con tools usando exclusivamente las APIs modernas que aprendiste en todo este módulo.


Recursos adicionales

  1. LangChain Migration Guide — Guía oficial de migración de chains a LCEL y APIs modernas
  2. LangChain v1.0 Release Notes — Anuncio oficial de v1.0 con lista de cambios
  3. create_agent API Reference — Referencia de la API moderna de agentes
  4. init_chat_model — Guía de inicialización universal de modelos
  5. Structured Output — Guía oficial de structured output sin parsers
  6. LangGraph MemorySaver — Referencia de checkpointing moderno

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