02 Langchain Chains
━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 2.5/2.5 MB 68.6 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 45.2/45.2 kB 3.0 MB/s eta 0:00:00 ━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━ 50.9/50.9 kB 3.3 MB/s eta 0:00:00
LangChain Handbook
Getting Started with Chains
Chains are the core of LangChain. They are simply a chain of components, executed in a particular order.
The simplest of these chains is the LLMChain. It works by taking a user's input, passing in to the first element in the chain — a PromptTemplate — to format the input into a particular prompt. The formatted prompt is then passed to the next (and final) element in the chain — a LLM.
Nowadays, chains are mostly built using LangChain Expression Language (LCEL) rather that using objects like the LLMChain. So, when we refer to the LLMChain we are references this past object but nowadays this would be implemented via LCEL syntax - which we'll explore soon.
Let's start by importing all the libraries that we'll be using in this example.
To run this notebook, we will need to use an OpenAI LLM. Here we will setup the LLM we will use throughout the notebook, just input your OpenAI API key below.
Chains and LCEL
Chains in LangChain are now built using the LangChain Expression Language (LCEL), which takes a declarative approach to combining components. Instead of using predefined chain classes, LCEL lets you compose chains using the | operator and other composition primitives.
Types of Chain Composition
-
Sequential Chains (
|operator)- Chain components one after another
- Example:
prompt | llm | output_parser
-
Parallel Chains (
RunnableParallel)- Run multiple operations concurrently
- Example: Running multiple prompts or retrievers in parallel
-
Complex Workflows
- For more complex scenarios involving branching, cycles, or multiple agents
- Recommended to use LangGraph instead of LCEL directly
Let's start with a simple example: creating a sequential math chain that can handle calculations...
The result is: 2.4116004626599237
Let's see what is going on here. The chain processes our input through several sequential steps:
- The prompt template formats our question
- The LLM converts it to a mathematical expression
- The StrOutputParser ensures we get a clean string
- Finally, our calculate function computes the result
But how did the LLM know to return just the mathematical expression? 🤔
Enter prompts
The question we send isn't the only input the LLM receives 😉. Look at our prompt template:
The system message explicitly instructs the LLM to return only the mathematical expression. Without this context, the LLM would try to calculate the result itself. Let's test this by trying without the system message:
13^0.3432 is approximately equal to 2.732.
This demonstrates the power of prompting in LCEL: by carefully designing our prompts, we can guide the LLM's behavior precisely.
The beauty of LCEL's sequential composition is how clearly we can see each step in the chain:
Each step flows naturally into the next using the | operator, making it easy to understand and modify the chain's behavior. This is much more flexible than the old approach of using predefined chain classes - we can easily add, remove, or modify steps as needed!
*Note: The calculate function uses numexpr to safely evaluate mathematical expressions without needing a full Python REPL (Read-Eval-Print Loop).
Building Complex Chains with LCEL
Let's build a more complex example that shows how to combine different components using LCEL. We'll create a chain that cleans up messy text and then paraphrases it in a specific style.
First, let's create a function to clean up text by removing extra spaces and newlines. In LCEL, we can use regular functions directly in our chain:
Now, let's create our prompt template for the paraphrasing:
Now we can combine everything into a sequential chain using LCEL's | operator. The beauty of LCEL is how naturally we can compose these components:
Yo, check it - chains in the game, Bringin' all the components together, ain't it insane? Craftin' a slick app that's all in sync, User input to PromptTemplate, then pass it to LLM, that's how we link. Mix and match, buildin' up them chains, Complexity on the rise, ain't no reins. Merge 'em all, blend with other parts, In the world of creation, we're makin' our marks.
Let's look at how this chain works:
- The dictionary
{"text": clean_text, "style": lambda x: x}processes our inputs in parallel usingRunnableParallel - The
|operator connects each component, showing the clear flow of data - Each step in the chain serves a specific purpose and is easily modifiable
- The components work together seamlessly to process and transform the text
This demonstrates how LCEL lets us compose simple components into powerful chains while keeping the code readable and maintainable. Whether you're processing text, generating content, or building complex workflows, LCEL's composition primitives make it easy to build exactly what you need! 🔥
Using RunnableParallel and RunnablePassthrough
Let's explore how to use RunnableParallel for running multiple operations concurrently and RunnablePassthrough for passing data through unchanged:
Sentiment: The sentiment of the statement is positive. The use of words like "exceeded my expectations" and "great quality" indicates a high level of satisfaction and positivity towards the product. Summary: The product surpassed expectations with its excellent quality. Original: The product exceeded my expectations. Great quality!
Batch Processing with LCEL
LCEL chains support efficient batch processing using the .batch() method:
Q: What is the capital of France? A: The capital of France is Paris. Q: Who wrote Romeo and Juliet? A: William Shakespeare wrote Romeo and Juliet. Q: What is the speed of light? A: The speed of light in a vacuum is approximately 299,792 kilometers per second (or about 186,282 miles per second).
That's it for this example on chains with LCEL.