Conceptos básicos de LangChain: qué problema resuelve y cuándo omitirlo por completo.
Aprende qué ofrece realmente LangChain, por qué las herramientas que utilizan múltiples proveedores impulsaron a los equipos a adoptarlo, qué paquetes instalar y cuándo es mejor usar un wrapper simple.
Fuentes: [Day 11/100] LangChain 101: Why It Exists, and When You Should (and Should Not) Use It
El agente de cincuenta líneas deja de serlo
Añadir una nueva herramienta a un agente pequeño y desarrollado a mano es económico. Escribes una función, la registras en un diccionario, la describes en un esquema, y el bucle en sí nunca cambia. Esa parte del trabajo con agentes resulta agradable, y hace que la gente piense que todo el proyecto seguirá siendo tan sencillo.
Luego aparecen los requisitos reales. El endpoint de OpenAI devuelve un código 503 de vez en cuando, por lo que se necesitan intentos repetidos con retroceso temporal. A veces el proveedor está inactivo durante veinte minutos, por lo que se desea contar con otro proveedor como solución de respaldo. Un usuario que espere ocho segundos a que terminen cuatro llamadas en cadena asumirá que la aplicación se ha congelado, así que se añade la transmisión de tokens. Un servicio posterior rechaza una respuesta porque un campo regresa como cadena en lugar de número, por lo que ahora se valida la salida estructurada. Y una vez que diez personas lo usan al mismo tiempo, las llamadas bloqueantes dejan de ser aceptables, por lo que todo debe ser asíncrono.
Todo eso se construye sobre las librerías básicas openai y requests, y el agente crece silenciosamente hasta alcanzar unas quinientas líneas de código. Muy poco de ese código se refiere a lo que realmente hace el agente: se trata de análisis de JSON, estrategias de reintentos exponenciales y conversión entre formatos de mensaje que cada proveedor desarrolló por su cuenta. Cada equipo acaba escribiendo esta infraestructura interna, y nadie quiere volver a hacerlo.
Esa infraestructura es el producto que ofrece LangChain. A continuación se explica en qué consiste realmente la biblioteca, los problemas que fue creada para resolver y, igualmente importante, las situaciones en las que usarla sería un error.
En qué consiste (y en qué no consiste) la biblioteca
LangChain no ejecuta tu agente por ti, no aloja nada y no hará que un modelo razone mejor. Considéralo como un conjunto de interfaces compartidas además de implementaciones listas para los componentes de una aplicación de LLM que de lo contrario tendrías que programar tú mismo.
Si ya has creado un agente básico, en realidad ya has desarrollado la mayor parte de estas componentes bajo nombres diferentes. La lista de mensajes que se intercambian corresponde a ChatMessageHistory. Un esquema de herramienta se convierte en un Tool, o en una función sencilla decorada con @tool. El bucle while que sigue llamando al modelo hasta que deja de solicitar herramientas es exactamente lo que hacía AgentExecutor. Además, la llamada directa client.chat.completions.create se transforma en un ChatModel. Nada de esto es conceptualmente nuevo. Lo que cambia es que ahora estas partes tienen una estructura común que ya utilizan miles de otros proyectos, y las combinaciones habituales vienen ya configuradas.
Los problemas para los que fue creado
Comience con las llamadas a herramientas, ya que muestran claramente el desorden. OpenAI adjunta tool_calls al mensaje del asistente y desea que el resultado se devuelva como un mensaje con role: "tool". Anthropic envía bloques de contenido tool_use y espera bloques tool_result en el siguiente turno del usuario. Gemini utiliza las partes function_call y function_response. La misma idea, tres APIs, tres formatos que no encajan entre sí.
Con un único proveedor esto nunca supone un problema. El día en que necesites otro, quizás porque el primero tenga una interrupción o simplemente se haya vuelto más caro, o bien debes escribir y mantener tres adaptadores o bien recurrir a código que ya se ocupe de ello. Esa fue la razón original de LangChain: una interfaz ChatModel en la que .invoke() y .bind_tools() funcionan de la misma manera, independientemente de la empresa que produzca los tokens. Pasar de ChatOpenAI a ChatAnthropic implica solo un cambio en la importación, no una reescritura del manejo de los mensajes.
La composición es el segundo problema. Al introducir una instrucción en un modelo, luego ese modelo en un analizador y finalmente el analizador en otro modelo, se necesita código de enlace en cada punto de conexión, y dicho código debe reescribirse cada vez que la secuencia de procesamiento cambia. LangChain uniformiza esos puntos de conexión. El mecanismo para ello es LCEL, el lenguaje de expresiones de LangChain, que merece un análisis aparte.
El tercer problema es la reutilización directa. Un agente que hace más que simplemente llamar a un modelo necesita elementos ya desarrollados por otros: cargadores para unos cuarenta formatos de documentos, clientes para una docena de bases de datos vectoriales, políticas de reintentos, funcionalidades de transmisión en tiempo real, agrupamiento de datos y mecanismos de seguimiento. Una integración ya mantenida es casi siempre mejor que un analizador de PDF que uno escribió un viernes y ahora debe depurar.
Lo que obtienes sin tener que escribirlo
Una única interfaz de modelo. ChatOpenAI, ChatAnthropic, ChatGoogleGenerativeAI y ChatOllama para modelos locales exponen .invoke(), .stream() y .bind_tools(). Tu código ya no necesita saber quién entrenó el modelo detrás de la llamada.
Elementos componibles. Con el operador de tubería de LCEL escribes prompt | model | parser y obtienes un único objeto que funciona como cada uno de sus componentes. Puede transmitir datos en flujo, procesarlos en lotes y ejecutarse de forma asíncrona sin esfuerzo adicional.
Resiliencia a través de llamadas a métodos. Cualquier objeto Runnable cuenta con .with_retry() y .with_fallbacks(), por lo que no es necesario escribir manualmente un bucle de reintentos ni un mecanismo de cambio de proveedor.
Transmisión en tiempo real y operaciones asíncronas en todas partes. Cada objeto Runnable dispone de .stream(), .ainvoke(), .astream() y .batch(), incluso cuando el elemento al que se le aplicó esta funcionalidad nunca las soportó de forma nativa.
Un amplio catálogo de integraciones. Cargadores de documentos, almacenes de vectores, herramientas de búsqueda, ejecución de código, herramientas de base de datos. Muchas de ellas son mantenidas por los propios proveedores, y todas utilizan la misma interfaz Runnable.
Un camino directo hacia LangGraph. Cuando su agente necesita bucles, estado guardado o un paso de aprobación humana que una cadena lineal no puede manejar, el bucle de control se traslada a LangGraph. Está construido sobre las mismas abstracciones básicas, por lo que lo que aprenda en LangChain se aplica directamente.
Cuándo omitirlo
La mayoría de las introducciones omiten esta parte, y es precisamente la que evita la mayoría de los problemas más adelante.
Un proveedor, una llamada, para siempre. Si el producto completo consiste en “enviar esta solicitud a GPT-4o y devolver el texto”, un pequeño wrapper propio es más fácil de leer y depurar que una dependencia que implica cientos de paquetes transitivos.
Necesitas ver cada solicitud. Las abstracciones son útiles precisamente porque ocultan los detalles, y ese ocultamiento te cuesta tiempo cuando algo falla. Tener que seguir un rastro de llamadas a través de seis capas de wrappers Runnable antes de llegar al verdadero error representa una pérdida real de tiempo. Los equipos que quieren leer cada línea entre su código y la llamada HTTP suelen mantenerse más cerca del SDK original.
Rutas críticas en cuanto a latencia. Cada capa añade algo de sobrecarga. Junto a una llamada al modelo que tarda 500 ms o más, esa sobrecarga suele ser irrelevante. Pero en una ruta crítica donde cada milisegundo cuenta, a veces no lo es.
Actualizaciones no fijadas. LangChain introduce cambios que pueden romper la funcionalidad del sistema con regularidad. Un proyecto que depende de la versión más reciente eventualmente se encontrará con importaciones renombradas o valores predeterminados modificados. Bloquee la versión como cualquier otra dependencia y actualícela cuando usted lo decida, no cuando pip lo decida por usted.
Nada de esto significa que nunca deba usar LangChain. Significa que la decisión debe tomarse para cada proyecto de forma intencionada, y no por costumbre.
Qué paquete es qué
LangChain está dividido en varios paquetes, y vale la pena saber qué hace cada uno antes de instalar algo.
langchain-corecontiene las abstracciones básicas:Runnable,BaseChatModel,BaseTool, plantillas de prompts y analizadores de salida. Tiene pocas dependencias, y casi todo lo demás se basa en él.langchainagrega cadenas, agentes y lógica de recuperación sobre la capa básica, sin obligar al usuario a depender de un proveedor específico.langchain-communityreúne una gran variedad de integraciones de terceros. Algunas son excelentes y otras están abandonadas, ya que cualquier persona puede contribuir. Revise cuándo se actualizó por última vez una integración antes de utilizarla en entornos de producción.- Los paquetes de partners como
langchain-openai,langchain-anthropicylangchain-google-genaise mantienen junto con el SDK propio de cada proveedor. Instale únicamente aquellos que realmente vaya a utilizar. langchain-classices donde se encontraban los constructores de agentes anteriores a la versión 1.0 en la v1:AgentExecutor,create_tool_calling_agenty el antiguocreate_react_agent. El código existente sigue funcionando, pero no se deben crear nuevos agentes aquí.langgraphes un paquete separado que se encuentra sobrelangchain-corey que gestiona el bucle de control para todo aquello que no sigue una línea recta. Desde la versión v1 delangchain, el constructor de agentes recomendado,langchain.agents.create_agent, está implementado directamente en él.
Para comenzar, solo necesita tres paquetes:
uv add langchain langchain-openai python-dotenv
Sustituyendo el cliente original por ChatOpenAI
No espere nada drástico en este paso. El objetivo es reemplazar el cliente simple de OpenAI por el envoltorio del modelo de chat de LangChain y verificar que reciba la misma respuesta.
from langchain_openai import ChatOpenAI
from dotenv import load_dotenv
load_dotenv()
llm = ChatOpenAI(model="gpt-4o", temperature=0)
response = llm.invoke("What is the weather in Paris?")
print(response.content)
llm.invoke(...) te entrega un AIMessage en lugar de un diccionario en bruto, pero .content te proporciona el texto de la misma manera que lo hacía .choices[0].message.content. En el fondo, ChatOpenAI sigue utilizando el mismo endpoint de OpenAI al que accederías manualmente, y el modelo no ha cambiado en absoluto. La diferencia es que llm ahora es un Runnable, por lo que .stream(), .batch() y .ainvoke() ya están disponibles y tú no tuviste que escribirlos.
for chunk in llm.stream("Tell me one fact about Paris."):
print(chunk.content, end="", flush=True)
Échalo a correr y los tokens llegarán uno por uno. Nunca escribiste un manejador de streaming, y ese pequeño bucle es prácticamente toda la justificación de la biblioteca.
Un breve comentario sobre los nombres de los modelos: los ejemplos utilizan "gpt-4o" como sustituto. La oferta de proveedores cambia más rápido de lo que cualquier tutorial puede seguir, así que considera cada cadena model="..." como un marcador de posición y reemplázala con el modelo estrella que ofrezca tu proveedor al ejecutar el código. La mecánica sigue siendo la misma.
Pruébalo en tu propio agente
- Agrega
langchainylangchain-openaia tu proyecto conuv add. - En el paso de modelo de tu agente, reemplaza la llamada directa a
OpenAI()porChatOpenAIy deja el resto del bucle sin cambios. Verifica que el agente se comporte exactamente como antes. - Llama a
llm.stream(...)con una pregunta simple, sin utilizar herramientas adicionales, e imprime cada fragmento a medida que llega. Ahora dispones de funcionalidad de streaming para un método que nunca implementaste.
Tu agente no se vuelve más inteligente tras este cambio. Lo que has ganado es una base que permite que las reintentos, soluciones alternativas, el streaming y el cambio de proveedor tengan un costo equivalente a una llamada al método, en lugar de requerir cientos de líneas de código adicional.