From Raw Text to Pipelines: Parsers, LCEL, Runnables and Memory in LangChain
How LangChain turns raw model text into structured data, composes steps with LCEL and the Runnable interface, and handles conversational memory today.
This article is published in English.
Sources: LangChain Explained: Output Parsers, Chains, Runnables, and Memory
Calling a language model with a well-built prompt template is only the first step of a real application. The model still hands back an unstructured string, each call forgets everything that came before it, and wiring prompt, model and post-processing together by hand quickly turns into boilerplate. This guide covers the four LangChain concepts that solve those problems: output parsers, chains built with LCEL, the Runnable interface that makes composition possible, and memory. Afterwards you should be able to read any LCEL pipeline, choose the right parser or Runnable for a job, and know where conversational state lives in current LangChain.
It assumes you already know what an LLM wrapper and a prompt template are in LangChain, since everything below builds directly on them.
Output parsers: turning text into data
The problem parsers solve
Whatever you ask a model, the response arrives as free-form text. For a toy chatbot that shows the answer on screen, that is enough. As soon as your code has to act on the answer, for instance by writing it to a database, handing it to another function or returning it from an API as JSON, a bare string becomes a liability.
An output parser sits between the model and the rest of your program. It takes the generated text and converts it into something structured: a list, a dictionary, a JSON object or an instance of your own Python class. A helpful way to picture it is as an interpreter: the model communicates in loose prose, and the parser renders that prose into a shape your code can rely on.
Why you should not parse by hand
- Model output has no guaranteed format; the same prompt can yield slightly different layouts on different runs.
- APIs, databases and user interfaces expect data that is consistent and predictable.
- Ad hoc string splitting or regular expressions are brittle and break on the first unexpected variation.
- Parsers can also validate, so a missing field or malformed value is caught at the boundary instead of surfacing as a confusing error further downstream.
The built-in parser family
LangChain ships several parsers, each aimed at a different target shape:
- String output parser (
StrOutputParser): the simplest option, which pulls the plain text out of the model's response message. Use it when you want a clean string to display or to feed into another step. - List parser: splits the response into a Python list, which is convenient when you ask for several items such as keywords or suggestions.
- JSON output parser: converts the response into a JSON object and is one of the most frequently used parsers whenever structured data is needed.
- Pydantic output parser: maps the response straight onto a Pydantic model, so you get type validation on top of structure. It is the right choice when the data must follow a strict schema.
- Datetime parser: produces a real Python
datetimeobject rather than a date written as text. - Enum parser: constrains the output to one value from a predefined set and returns that value.
- XML parser: reads output written as XML tags and turns it into a dictionary.
The structured parsers provide a get_format_instructions() method. It returns a block of text that you insert into your prompt, telling the model precisely how to lay out its answer so that the parser can read it. The plain string parser has nothing to enforce, so it is the one member of the family where format instructions do not apply.
Parsers versus native structured output
Many current models can return structured data natively, and LangChain exposes this through with_structured_output(). When that is available, it often replaces a parser, because the model provider enforces the schema rather than your prompt asking nicely. Parsers still earn their place when a model lacks native structured output, and when you want extra validation or transformation beyond what the provider guarantees.
Chains and LCEL: connecting the steps
What a chain is
A chain is a series of steps in which each step's output becomes the next step's input. Rather than writing three separate pieces of code to format a prompt, call the model and parse the result, you connect them into one reusable pipeline.
The canonical example has three stages: a prompt template, then the model, then an output parser. You supply raw input such as a topic or a question, and the chain formats the prompt, calls the model and parses what comes back in a single flow.
What chains give you
- Less boilerplate. The wiring between steps is written once rather than repeated at every call site.
- Modularity. The prompt, model and parser are independent pieces, so you can replace any one of them without touching the others.
- Readability. A chain shows the direction of data flow at a glance, even when the workflow is complex.
- Reuse. Once defined, a chain can be called again and again with different inputs.
Composing with the pipe operator
The LangChain Expression Language, LCEL, is what makes building chains feel lightweight. It uses the | operator to join components, much as you would pipe commands together in a Unix shell, instead of nesting function calls inside one another.
The two lines below build the three-stage chain described above and run it once with a dictionary input. Notice that the dictionary key, topic, must match a variable in the prompt template.
chain = prompt | model | output_parser
result = chain.invoke({"topic": "black holes"})
The input enters prompt, which produces a formatted prompt; that goes to model, and the model's reply goes to output_parser. Each component hands its result directly to the next one, and result holds whatever the parser returns.
Every LCEL chain also comes with a standard set of execution methods:
.invoke()runs the chain once for a single input..batch()runs it over a list of inputs in one call..stream()yields output incrementally as it is generated, rather than making you wait for the complete answer. Streaming only reaches you token by token if every step in the chain can process a stream; a step that needs the whole input first will buffer.
This declarative style is a large part of why LCEL feels composable. You describe what should happen and in what order, not the mechanics of how each step calls the next.
Runnables: the interface underneath
Why everything can be piped
Behind LCEL, every component, whether it is a prompt, a model, a parser or a function you wrote yourself, is a Runnable. A Runnable is a common interface that any component can implement, and that shared contract is exactly what makes | work. Because each piece exposes the same methods, pieces can be connected regardless of what they do internally.
The same interface is why interacting with a chain feels uniform. Whether a component calls a model, reshapes data or runs several branches at once, you drive it through the same .invoke(), .batch() and .stream() methods. A composed chain is itself a Runnable, so you can nest chains inside larger chains without any special handling.
The Runnables you will use most
- RunnableSequence executes steps strictly in order, feeding each output into the next step. Joining components with
|creates one of these for you behind the scenes. Typical use: prompt, then model, then parser. - RunnableParallel runs several Runnables at once on the same input and gathers their results into one dictionary keyed by name. Typical use: summarising a text and detecting its language concurrently rather than doing one after the other.
- RunnablePassthrough returns its input unchanged. It mostly appears inside a
RunnableParallelwhen you need the original input to travel alongside transformed values. Typical use: returning the user's original question together with the generated answer. - RunnableLambda wraps an ordinary Python function so that it can be a step in a chain. Typical use: custom pre- or post-processing such as cleaning text, counting words or applying business rules between steps.
- RunnableBranch adds if/else routing, sending the input to different Runnables depending on a condition. Typical use: directing technical questions to one chain and general questions to another.
Once you see that models, parsers and chains are all Runnables sharing one interface, LangChain stops looking like a box of special cases. It becomes ordinary composition: small units with the same shape, combined into larger units with that same shape.
A caution on over-composition
Because anything can become a Runnable, it is tempting to express all application logic as nested chains. For straightforward steps that works well, but deeply nested branches and lambdas can become harder to debug than plain Python. A reasonable rule is to use LCEL for the model-facing pipeline and keep complex control flow in normal code, or move it to a graph framework when it involves loops and state.
Memory: keeping context between turns
What memory means here
Each call to a model is stateless by default. It has no knowledge of what you asked a moment ago unless your application sends the earlier exchange again. In LangChain, memory is the set of mechanisms for storing conversational context and retrieving it later, so that an application can carry a conversation forward instead of treating each message as if it were the first.
Why conversational applications need it
- Without memory, a chatbot loses everything the moment a new message arrives: no continuity, no follow-ups, no shared context.
- Natural conversation depends on earlier turns, for example knowing that "he" in a follow-up refers to someone named two messages back.
- Remembered context makes an assistant feel coherent and conversational instead of answering every message in isolation.
- It is a baseline requirement for customer support bots, personal assistants and any other multi-turn application.
Classic memory classes
The traditional approach used memory classes that lived inside the running process. The simplest kept a buffer of the entire conversation; another condensed older messages into a running summary to save tokens. On every new call, the stored context was injected back into the prompt.
Where memory lives now
Those in-process memory classes have been deprecated as LangChain has shifted conversational state to LangGraph's persistence layer. In that model, checkpointers save conversation state per session (identified by a thread), while separate stores hold longer-term memory that should survive across sessions. The concept is unchanged: prior context must be kept somewhere so it can be reused. What changed is the recommended implementation, which is now more durable and better suited to production because state can be persisted to a real database instead of vanishing when the process restarts. If you want to see how a checkpointer actually stores that state, inside LangGraph's InMemorySaver walks through its internals.
A sensible learning path is to understand the classic memory idea first, because it explains why memory matters and what goes into the prompt, and then adopt the persistence-based approach for anything meant for production. Because this area has changed quickly, check the current LangChain and LangGraph documentation for the exact APIs before you build.
Wrapping up
Together with LLM wrappers and prompt templates, these concepts form LangChain's core toolkit. Each one is small on its own. Their value appears in combination: a template feeds a model, a parser turns the model's text into data, every piece is a Runnable joined into a chain with |, and a persistence layer remembers the conversation between calls.
- Use an output parser, or native structured output where the model supports it, whenever code consumes the model's answer.
- Read any LCEL pipeline left to right as a
RunnableSequence; reach forRunnableParallel,RunnablePassthrough,RunnableLambdaandRunnableBranchwhen the flow is not a straight line. - Remember that streaming is only as incremental as the slowest-to-stream step.
- Treat legacy memory classes as a concept to learn, and use LangGraph checkpointers and stores for real applications.
With these building blocks in place, the natural next topics are retrieval-augmented generation and agents, both of which are assembled from exactly these pieces.