De texto sin procesar a pipelines: analizadores, LCEL, elementos ejecutables y memoria en LangChain
Cómo LangChain convierte el texto bruto del modelo en datos estructurados, compone pasos con LCEL e la interfaz Runnable, y gestiona la memoria de conversación en la actualidad.
Fuentes: LangChain Explained: Output Parsers, Chains, Runnables, and Memory
Llamar a un modelo de lenguaje con una plantilla de prompt bien estructurada es solo el primer paso en una aplicación real. El modelo sigue devolviendo una cadena no estructurada, cada llamada olvida todo lo que ocurrió antes, y conectar manualmente el prompt, el modelo y el procesamiento posterior se convierte rápidamente en código genérico. Esta guía aborda los cuatro conceptos de LangChain que resuelven esos problemas: analizadores de salida, cadenas construidas con LCEL, la interfaz Runnable que permite la composición y la memoria. Después de esto, deberías poder leer cualquier pipeline LCEL, elegir el analizador o Runnable adecuado para una tarea y saber dónde se almacena el estado de la conversación en LangChain actual.
Se asume que ya sabes qué son un envoltorio de LLM y una plantilla de prompt en LangChain, ya que todo lo que sigue se basa directamente en ellos.
Analizadores de salida: convertir texto en datos
El problema que resuelven los analizadores
Cualquier cosa que le pidas a un modelo, la respuesta llega como texto en formato libre. Para un chatbot sencillo que muestra la respuesta en pantalla, eso es suficiente. Pero en cuanto tu código necesita actuar sobre esa respuesta, por ejemplo al guardarla en una base de datos, pasarla a otra función o devolverla desde una API en formato JSON, una simple cadena de texto se convierte en un problema.
Un analizador de salida se sitúa entre el modelo y el resto de tu programa. Toma el texto generado y lo convierte en algo estructurado: una lista, un diccionario, un objeto JSON o una instancia de tu propia clase en Python. Una forma útil de imaginarlo es como un intérprete: el modelo comunica mediante prosa desestructurada, y el analizador transforma esa prosa en una forma en la que tu código pueda confiar.
Por qué no debes analizar manualmente
- La salida del modelo no tiene un formato garantizado; la misma solicitud puede generar estructuras ligeramente diferentes en ejecuciones distintas.
La familia de analizadores integrados
LangChain incluye varios analizadores, cada uno diseñado para un formato de salida diferente:
- Analizador de salida de cadena (
StrOutputParser): la opción más sencilla, que extrae el texto plano del mensaje de respuesta del modelo. Úsalo cuando quieras una cadena limpia para mostrarla o para pasarla a otra etapa. - Analizador de lista: divide la respuesta en una lista de Python, lo cual es útil cuando se solicitan varios elementos como palabras clave o sugerencias.
- Analizador de salida JSON: convierte la respuesta en un objeto JSON y es uno de los analizadores más utilizados cuando se necesitan datos estructurados.
- Analizador de salida Pydantic: asigna directamente la respuesta a un modelo Pydantic, lo que permite la validación de tipos además de la estructura. Es la opción adecuada cuando los datos deben seguir un esquema estricto.
- Analizador de fechas y horas: genera un objeto real de Python
datetimeen lugar de una fecha escrita como texto. - Analizador Enum: restringe la salida a un valor de un conjunto predefinido y devuelve ese valor.
- Analizador XML: lee la salida escrita en formato de etiquetas XML y la convierte en un diccionario.
Los analizadores estructurados ofrecen un método get_format_instructions(). Este devuelve un bloque de texto que se inserta en la instrucción, indicando al modelo con precisión cómo organizar su respuesta para que el analizador pueda leerla. El analizador de cadenas simples no tiene nada que imponer, por lo que es el único miembro de la familia para el cual las instrucciones de formato no son aplicables.
Analizadores versus salida estructurada nativa
Muchos modelos actuales pueden devolver datos estructurados de forma nativa, y LangChain expone esta funcionalidad a través de with_structured_output(). Cuando está disponible, suele reemplazar a un analizador, ya que el proveedor del modelo aplica el esquema en lugar de que la instrucción lo solicite amablemente. Los analizadores siguen siendo útiles cuando un modelo carece de salida estructurada nativa, y cuando se desea una validación o transformación adicional más allá de lo que garantiza el proveedor.
Cadenas y LCEL: conectando las etapas
Qué es una cadena
Una cadena es una serie de pasos en los que la salida de cada paso se convierte en la entrada del siguiente. En lugar de escribir tres fragmentos de código separados para formatear una solicitud, llamar al modelo y analizar el resultado, los conectas en un único pipeline reutilizable.
El ejemplo canónico consta de tres etapas: un modelo de plantilla para la solicitud, luego el modelo en sí y finalmente un analizador de resultados. Proporcionas una entrada bruta como un tema o una pregunta, y la cadena formatea la solicitud, llama al modelo y analiza lo que devuelve todo en un solo flujo.
Qué te ofrecen las cadenas
- Menos código genérico. La conexión entre los pasos se escribe una sola vez en lugar de repetirse en cada punto de llamada.
- Modularidad. La plantilla de solicitud, el modelo y el analizador son componentes independientes, por lo que puedes reemplazar cualquiera de ellos sin afectar a los demás.
Composición con el operador pipe
El Lenguaje de Expresiones de LangChain, LCEL, es lo que hace que la creación de cadenas parezca sencilla. Utiliza el operador | para unir componentes, de manera similar a como se concatenan comandos en una shell de Unix, en lugar de anidar llamadas a funciones entre sí.
Las dos líneas siguientes construyen la cadena de tres etapas descrita anteriormente y la ejecutan una vez con una entrada en forma de diccionario. Tenga en cuenta que la clave del diccionario, topic, debe coincidir con una variable en el plantilla de prompt.
chain = prompt | model | output_parser
result = chain.invoke({"topic": "black holes"})
La entrada llega a prompt, lo que genera un mensaje de solicitud formateado; este se envía a model, y la respuesta del modelo va a output_parser. Cada componente transfiere su resultado directamente al siguiente, y result almacena lo que devuelva el analizador.
Cada cadena LCEL también incluye un conjunto estándar de métodos de ejecución:
.invoke()ejecuta la cadena una vez para una sola entrada..batch()la ejecuta sobre una lista de entradas en una sola llamada..stream()genera la salida de forma incremental a medida que se produce, en lugar de hacer esperar al usuario hasta obtener la respuesta completa. El flujo por lotes solo te entrega los tokens uno por uno si cada paso de la cadena puede procesar un flujo; un paso que necesita toda la entrada primero la almacenará en búfer.
Este estilo declarativo es en gran medida la razón por la cual LCEL parece compuesto. Se describe qué debe ocurrir y en qué orden, no la mecánica de cómo cada paso llama al siguiente.
Runnables: la interfaz subyacente
Por qué todo puede ser canalizado
Detrás de LCEL, cada componente, ya sea una prompt, un modelo, un analizador o una función que hayas escrito tú mismo, es un Runnable. Un Runnable es una interfaz común que cualquier componente puede implementar, y ese contrato compartido es exactamente lo que hace posible el uso de |. Dado que cada componente expone los mismos métodos, se pueden conectar independientemente de lo que hagan internamente.
La misma interfaz es la razón por la cual interactuar con una cadena resulta uniforme. Ya sea que un componente llame a un modelo, reestructure datos o ejecute varios procesos al mismo tiempo, se controla mediante los mismos métodos .invoke(), .batch() y .stream(). Una cadena compuesta es en sí misma un Runnable, por lo que se pueden anidar cadenas dentro de otras más grandes sin necesidad de ningún tratamiento especial.
Los Runnables que usarás con más frecuencia
- RunnableSequence ejecuta los pasos estrictamente en orden, pasando la salida de cada uno al siguiente paso. Al unir componentes con
|se crea automáticamente uno de estos en segundo plano. Uso típico: primero el prompt, luego el modelo y finalmente el analizador.
RunnableParallel cuando se necesita que la entrada original viaje junto con los valores transformados. Uso típico: devolver la pregunta original del usuario junto con la respuesta generada.Cuando se comprende que los modelos, analizadores y cadenas son todos componentes ejecutables que comparten una misma interfaz, LangChain deja de parecer un conjunto de casos especiales. Se convierte en una composición ordinaria: pequeñas unidades con la misma estructura, combinadas en unidades más grandes que mantienen esa misma estructura.
Advertencia sobre la sobrecomposición
Dado que cualquier cosa puede convertirse en un Runnable, resulta tentador expresar toda la lógica de la aplicación como cadenas anidadas. Esto funciona bien para pasos sencillos, pero las ramas y lambdas profundamente anidadas pueden ser más difíciles de depurar que en Python puro. Una regla razonable es utilizar LCEL para el pipeline orientado al modelo y mantener los flujos de control complejos en código normal, o bien trasladarlos a un framework basado en grafos cuando involucren bucles y estado.
Memoria: mantener el contexto entre turnos
Qué significa la memoria aquí
Cada llamada a un modelo es, por defecto, sin estado. No tiene conocimiento de lo que se le preguntó hace un momento a menos que la aplicación envíe nuevamente el intercambio anterior. En LangChain, la memoria es el conjunto de mecanismos para almacenar el contexto de la conversación y recuperarlo posteriormente, de modo que una aplicación pueda continuar una conversación en lugar de tratar cada mensaje como si fuera el primero.
Por qué las aplicaciones de conversación lo necesitan
- Sin memoria, un chatbot pierde todo en el momento en que llega un nuevo mensaje: sin continuidad, sin seguimientos y sin contexto compartido.
- La conversación natural depende de los intercambios anteriores; por ejemplo, saber que “él” en un mensaje posterior se refiere a alguien mencionado dos mensajes antes.
- El contexto recordado hace que un asistente parezca coherente y capaz de mantener una conversación, en lugar de responder a cada mensaje de forma aislada.
- Es un requisito básico para los bots de soporte al cliente, los asistentes personales y cualquier otra aplicación que requiera múltiples intercambios.
Clases clásicas de memoria
El enfoque tradicional utilizaba clases de memoria que residían dentro del proceso en ejecución. La más sencilla guardaba un búfer con toda la conversación; otra condensaba los mensajes antiguos en un resumen continuo para ahorrar tokens. En cada nueva llamada, el contexto almacenado se volvía a incluir en la petición.
Dónde reside ahora la memoria
Esas clases de memoria en proceso han quedado obsoletas ya que LangChain ha trasladado el estado de las conversaciones a la capa de persistencia de LangGraph. En ese modelo, los mecanismos de checkpointing guardan el estado de la conversación por sesión (identificada por un hilo), mientras que almacenes separados conservan la memoria a largo plazo que debe perdurar entre sesiones. El concepto no ha cambiado: el contexto anterior debe guardarse en algún lugar para poder reutilizarse. Lo que sí ha cambiado es la implementación recomendada, que ahora es más duradera y más adecuada para entornos de producción, ya que el estado puede persistirse en una base de datos real en lugar de desaparecer cuando se reinicia el proceso. Si desea ver cómo un mecanismo de checkpointing almacena realmente ese estado, inside LangGraph's InMemorySaver explica en detalle su funcionamiento interno.
Un camino de aprendizaje sensato es primero comprender la idea clásica sobre la memoria, ya que explica por qué es importante y qué se incluye en el prompt, y luego adoptar el enfoque basado en la persistencia para cualquier cosa destinada a producción. Dado que esta área ha cambiado rápidamente, consulte la documentación actual de LangChain y LangGraph para conocer las APIs exactas antes de comenzar a desarrollar.
Conclusión
Junto con los envoltorios para LLM y las plantillas de prompt, estos conceptos forman la herramienta básica de LangChain. Cada uno es sencillo por separado, pero su valor radica en su combinación: una plantilla alimenta al modelo, un analizador convierte el texto del modelo en datos, cada componente es un elemento ejecutable unido en una cadena con |, y una capa de persistencia guarda la conversación entre llamadas.
- Utilice un analizador de salida o una salida estructurada nativa, siempre que el modelo la soporte, cada vez que el código consuma la respuesta del modelo.
RunnableSequence; recurra a RunnableParallel, RunnablePassthrough, RunnableLambda y RunnableBranch cuando el flujo no sea lineal.Con estos componentes básicos en su lugar, los temas siguientes naturales son la generación mejorada mediante recuperación de información y los agentes, ambos construidos exactamente a partir de estos elementos.
Lecturas relacionadas
- Dentro de LangGraph’s InMemorySaver: Cómo encajan los checkpoints, escrituras y blobs — Exploramos los diccionarios de almacenamiento, escrituras y blobs dentro de LangGraph’s InMemorySaver y seguimos cómo una sola ejecución de un grafo pequeño se convierte en tres checkpoints interconectados.
- Diseñando una memoria de agente en cuatro capas con LangGraph y Amazon Bedrock — Aprende a dotar a los agentes de LLM de memoria episódica, semántica y procedural en Bedrock y LangGraph, así como a protegerla contra envenenamiento, fugas de PII y filtraciones entre usuarios.