What are Chains in LLM (Large Language Model)
A chain is an end-to-end wrapper around multiple individual components executed in a defined order.
Chains are a fundamental concept in LangChain, enabling advanced capabilities beyond a single API call to a language model. They facilitate the integration of multiple calls into a cohesive sequence, allowing for the creation of complex applications.
Benefits of Using Chains:
- Task Decomposition: Chains allow the breakdown of complex tasks into smaller, manageable steps, handled sequentially by different models or utilities. This enables the leveraging of various systems’ distinct strengths.
- State and Memory Management: Chains enable the retention of state and context between calls. The output of one call can serve as the input for the next, preserving context throughout the process.
- Intermediate Processing: Chains allow the insertion of additional processing, filtering, or validation logic between calls, enhancing the robustness and accuracy of the overall application.
- Debugging and Instrumentation: Chains simplify the debugging and instrumentation of a sequence of calls, providing better visibility and control over the execution flow.
By utilizing chains, developers can construct sophisticated and resilient AI-driven applications, maximizing the utility of each component involved.
Foundational Chain Types in LangChain
The LLMChain, RouterChain, SimpleSequentialChain, and TransformChain are the core foundational building blocks in LangChain. These chains provide essential patterns such as chaining language models, implementing conditional logic, managing sequential workflows, and performing data transformations.
- LLMChain: Chains multiple calls to language models, ideal for decomposing complex prompts into manageable segments.
- RouterChain: Enables conditional routing between different chains based on logic, facilitating branching workflows.
- SimpleSequentialChain: Links multiple chains in a linear sequence, suitable for straightforward sequential tasks.
- TransformChain: Applies data transformations between chains, useful for preprocessing and data manipulation.
These foundational chains support more advanced constructs like Agents and RetrievalChain, which enable sophisticated use cases such as goal-oriented conversations and knowledge-grounded generation.
LLMChain
The LLMChain is the most frequently utilized type of chain, comprising a PromptTemplate, a language model, and an optional output parser. It facilitates advanced interaction by allowing the chaining of multiple prompts.
Key Differences Between LLMChain and Direct LLM Usage:
- Multi-Prompt Chaining: LLMChain allows multiple prompts to be linked together, breaking down complex prompts into simpler components.
- State and Memory Management: Maintains context by feeding the output of one prompt into the next.
- Preprocessing and Validation: Facilitates the insertion of preprocessing logic, validation, and instrumentation between prompts, aiding in debugging and quality control.
- Convenience Methods: Offers methods like
apply
andgenerate
, simplifying the execution of the chain over multiple inputs.
Creating an LLMChain
To create an LLMChain, you need to specify:
- The language model to use.
- The prompt template.
By leveraging LLMChain, developers can create intricate and dynamic AI applications with enhanced control and flexibility.
Here is how you can implement chain
from langchain import PromptTemplate, OpenAI, LLMChain
# the language model
llm = OpenAI(temperature=0)
# the prompt template
prompt_template = "Act like a comedian and write a super funny two-sentence short story about {act}?"
llm_chain = LLMChain(
llm=llm,
prompt=PromptTemplate.from_template(prompt_template)
)
llm_chain("A toddler hiding his dad's laptop")
###output##
{'act': "A toddler hiding his dad's laptop",
'text': '\n\nThe toddler thought he was being sneaky, but little did he know his dad was watching the whole time from the other room, laughing.'}
Use apply
when you have a list of inputs and want to get the LLM to generate text for each one, it will run the LLMChain for every input dictionary in the list and return a list of outputs.
input_list = [
{"act1": "a Punjabi rapper who eats too many samosas"},
{"act2": "a blind eye doctor"},
{"act3": "a data scientist who can't do math"}
]
llm_chain.apply(input_list)
[{'text': "\n\nThe Punjabi rapper was so famous that he was known as the 'Samosa King', but his fame was short-lived when he ate so many samosas that he had to be hospitalized for a stomachache!"},
{'text': "\n\nA blind eye doctor was so successful that he was able to cure his own vision - but he still couldn't find his glasses."},
{'text': "\n\nA data scientist was so bad at math that he had to hire a calculator to do his calculations for him. Unfortunately, the calculator was even worse at math than he was!"}]
How does generate works?
generate
is similar to apply, except it returns an LLMResult
instead of a string. Use this when you want the entire LLMResult
object returned, not just the generated text. This gives you access to metadata like the number of tokens used.
llm_chain.generate(input_list)
LLMResult(generations=
[[Generation(text="\n\nThe Punjabi rapper was so famous that he was known as the 'Samosa King',
but his fame was short-lived when he ate so many samosas that he had to be hospitalized for a stomachache!",
generation_info={'finish_reason': 'stop', 'logprobs': None})],
[Generation(text="\n\nA blind eye doctor was so successful that he was able to cure his own vision - but he still couldn't find his glasses.", generation_info={'finish_reason': 'stop', 'logprobs': None})],
[Generation(text='\n\nA data scientist was so bad at math that he had to hire a calculator to do his calculations for him. Unfortunately, the calculator was even worse at math than he was!', generation_info={'finish_reason': 'stop', 'logprobs': None})]],
llm_output={'token_usage': {'prompt_tokens': 75, 'total_tokens': 187, 'completion_tokens': 112}, 'model_name': 'text-davinci-003'}, run=[RunInfo(run_id=UUID('b638d2c6-77d9-4346-8494-866892e36bc5')), RunInfo(run_id=UUID('427f9e51-4848-49d3-83c1-e96131f2b34f')), RunInfo(run_id=UUID('4201eea9-1616-42e7-8cb2-a5b26128decd'))])
we can usepredict
when you want to pass inputs as keyword arguments instead of a dictionary. This can be convenient if you don’t want to construct an input dictionary
llm_chain.predict(thing="colorful socks")
#0utput###
The socks were so colorful that when the washing machine finished its cycle, the socks had formed a rainbow in the laundry basket!
Use LLMChain.run
when you need to pass input as a dictionary and receive the raw text output from the language model.
LLMChain.run
is particularly convenient when your LLMChain involves a single input key and a single output key.
Parsing output
To parse the output, you simply pass an output parser directly to LLMChain.
llm_chain.run("the red hot chili peppers")
##output##
['1. Wear a Hawaiian shirt\n2. Sing along to the wrong lyrics\n3. Bring a beach ball to the concert\n4. Try to start a mosh pit\n5. Bring a kazoo and try to join in on the music']
from langchain.output_parsers import CommaSeparatedListOutputParser
llm = OpenAI(temperature=0)
# the prompt template
prompt_template = "Act like a Captain Obvious and list 5 funny things to not do at {place}?"
output_parser=CommaSeparatedListOutputParser()
llm_chain = LLMChain(
llm=llm,
prompt=PromptTemplate.from_template(prompt_template),
output_parser= output_parser
)
llm_chain.predict(place='Disneyland')
##output##
['1. Wear a costume of a Disney villain.\n2. Bring your own food and drinks into the park.\n3. Try to ride the roller coasters without a ticket.\n4. Try to sneak into the VIP area.\n5. Try to take a selfie with a Disney character without asking permission.']
What are Router Chains?
Router chains allow routing inputs to different destination chains based on the input text. This allows the building of chatbots and assistants that can handle diverse requests.
- Router chains examine the input text and route it to the appropriate destination chain
- Destination chains handle the actual execution based on the input
- Router chains are powerful for building multi-purpose chatbots/assistants
The following example will show routing chains used in a MultiPromptChain
to create a question-answering chain that selects the prompt which is most relevant for a given question and then answers the question using that prompt.
from langchain.chains.router import MultiPromptChain
from langchain.llms import OpenAI
from langchain.chains import ConversationChain
from langchain.chains.llm import LLMChain
from langchain.prompts import PromptTemplate
physics_template = """You are a very smart physics professor. \
You are great at answering questions about physics in a concise and easy to understand manner. \
When you don't know the answer to a question you admit that you don't know.
Here is a question:
{input}"""
math_template = """You are a very good mathematician. You are great at answering math questions. \
You are so good because you are able to break down hard problems into their component parts, \
answer the component parts, and then put them together to answer the broader question.
Here is a question:
{input}"""
prompt_infos = [
{
"name": "physics",
"description": "Good for answering questions about physics",
"prompt_template": physics_template,
},
{
"name": "math",
"description": "Good for answering math questions",
"prompt_template": math_template,
},
]
destination_chains = {}
for p_info in prompt_infos:
name = p_info["name"]
prompt_template = p_info["prompt_template"]
prompt = PromptTemplate(template=prompt_template, input_variables=["input"])
chain = LLMChain(llm=llm, prompt=prompt)
destination_chains[name] = chain
default_chain = ConversationChain(llm=llm, output_key="text")
default_chain.run("What is math?")
##output###
Math is the study of numbers, shapes, and patterns. It is used to solve problems and understand the world around us. It is a fundamental part of our lives and is used in many different fields, from engineering to finance.
What is Sequential Chain?
A SimpleSequentialChain represents the most straightforward form of a sequential chain, where each step has a single input and a single output. The output from one step is automatically passed as the input to the next step.
When to Use SimpleSequentialChain:
- You have a linear pipeline where each step has a single input and output.
- Each step directly builds on the output of the previous step.
- Ideal for straightforward linear workflows with one input and one output per step.
Steps to Create and Use SimpleSequentialChain:
- Define Each Step: Create each step as an LLMChain with a single input and output.
- Create the Chain: Pass a list of these LLMChains to the SimpleSequentialChain.
- Run the Chain: Call
run()
on the SimpleSequentialChain with the initial input.
When to Use SequentialChain vs SimpleSequentialChain
SimpleSequentialChain is ideal for linear sequences where each step has a single input and a single output. It’s perfect for straightforward pipelines where the output of one step is automatically passed as the input to the next.
SequentialChain is suited for more complex sequences that require multiple inputs and outputs per step. It allows for explicit mapping of variables between steps, providing greater flexibility and control.
Key Difference:
- SimpleSequentialChain: Designed for linear workflows with a single input and output per step. It implicitly passes variables between steps.
- SequentialChain: Handles complex workflows with multiple inputs and outputs per step. It allows for explicit variable mapping, enabling intricate sequences.
Example Usage
Using a standard ChatOpenAI model and prompt template, you can chain them together with the |
operator and then invoke the chain with chain.invoke
. Additionally, you can leverage async, batch, and streaming support directly.
from langchain import OpenAI, PromptTemplate, LLMChain, SequentialChain
# Initialize the OpenAI model with the desired temperature
llm = OpenAI(temperature=0.7)
# Define the template for generating a rap
rap_template = """
You are a Punjabi Jatt rapper, like AP Dhillon or Sidhu Moosewala.
Given two topics, it is your job to create a rhyme of two verses and one chorus
for each topic.
Topic: {topic1} and {topic2}
Rap:
"""
# Create the prompt template for the rap
rap_prompt_template = PromptTemplate(input_variables=["topic1", "topic2"], template=rap_template)
# Create the LLMChain for generating the rap
rap_chain = LLMChain(llm=llm, prompt=rap_prompt_template, output_key="rap")
# Define the template for writing a rap review
review_template = """
You are a rap critic from Rolling Stone magazine and Metacritic.
Given a rap, it is your job to write a review for that rap.
Your review style should be scathing, critical, and no holds barred.
Rap:
{rap}
Review from the Rolling Stone magazine and Metacritic critic of the above rap:
"""
# Create the prompt template for the review
review_prompt_template = PromptTemplate(input_variables=["rap"], template=review_template)
# Create the LLMChain for writing the review
review_chain = LLMChain(llm=llm, prompt=review_prompt_template, output_key="review")
# Combine the chains into a SequentialChain
overall_chain = SequentialChain(
chains=[rap_chain, review_chain],
input_variables=["topic1", "topic2"],
output_variables=["rap", "review"],
verbose=True
)
# Run the overall chain with the given topics
result = overall_chain({"topic1": "Tractors and sugar canes", "topic2": "Dasuya, Punjab"})
print(result)
##output##
{
"rap": "Yo, I'm rollin' on my tractor, fields of green,
Sugar canes so tall, it's a Jatt's dream scene.
Dasuya in my heart, Punjab in my veins,
Reppin' my roots, through the joy and the pains.
Chorus:
Tractors and sugar canes, that's the Jatt's way,
Dasuya pride, every single day.
We ride strong, we ride high,
Punjab's legacy will never die.
Verse 2:
From dawn till dusk, we work the land,
With tractors and sugar canes, united we stand.
Dasuya tales, in every rap I weave,
Punjab's heritage, in every breath I breathe.",
"review": "This so-called 'rap' feels like a painful caricature of what real music should be. The forced rhymes about tractors and sugar canes are as uninspired as they come, and the chorus is a bland repetition with no real hook. The verses, attempting to evoke a sense of pride in Dasuya and Punjab, fall flat with clichéd imagery and lackluster delivery. As a critic for Rolling Stone and Metacritic, it's clear this track is far from hitting any noteworthy charts. The execution is mediocre at best, making it a forgettable addition to the genre."
}
What are Transformation Chains
Transformation Chains enable you to define custom data transformation logic as a step in your LangChain pipeline. This is particularly useful when you need to preprocess or modify data before it is passed to the subsequent step.
from langchain.chains import TransformChain, LLMChain, SimpleSequentialChain
from langchain.llms import OpenAI
from langchain.prompts import PromptTemplate
import requests
# Download the text file from Project Gutenberg
url = "https://www.gutenberg.org/files/2680/2680-0.txt"
response = requests.get(url)
meditations = response.text
# Define the transformation function
def transform_func(inputs: dict) -> dict:
"""
Extracts specific sections from a given text based on newline separators.
The function assumes the input text is divided into sections or paragraphs separated
by one newline characters (`\n`). It extracts the sections from index 922 to 950
(inclusive) and returns them in a dictionary.
Parameters:
- inputs (dict): A dictionary containing the key "text" with the input text as its value.
Returns:
- dict: A dictionary containing the key "output_text" with the extracted sections as its value.
"""
text = inputs["text"]
shortened_text = "\n".join(text.split("\n")[921:950])
return {"output_text": shortened_text}
# Create the transformation chain
transform_chain = TransformChain(
input_variables=["text"], output_variables=["output_text"], transform=transform_func, verbose=True
)
# Define the prompt template for rephrasing the text
template = """
Rephrase this text:
{output_text}
In the style of a 90s gangster rapper speaking to his homies.
Rephrased:"""
prompt = PromptTemplate(input_variables=["output_text"], template=template)
# Create the LLMChain with OpenAI model and prompt template
llm_chain = LLMChain(llm=OpenAI(), prompt=prompt)
# Combine the chains into a SimpleSequentialChain
sequential_chain = SimpleSequentialChain(chains=[transform_chain, llm_chain], verbose=True)
# Run the overall chain with the input text
result = sequential_chain.run({"text": meditations})
print(result)
> Entering new SimpleSequentialChain chain...
> Entering new TransformChain chain...
> Finished chain.
{
"output_text": "Extracted sections from 'Meditations' by Marcus Aurelius",
"rephrased_text": "Yo, check it, homies. This here wisdom from back in the day. It's like, stay true to yourself, keep your cool, and handle your business like a real OG. Respect the game and keep hustlin' no matter what life throws at you. Peace."
}
Conclusion
LangChain provides developers with a robust framework for constructing sophisticated text processing pipelines. Its diverse chain types, including TransformChain for data preprocessing and LLMChain for creative content generation, empower users to handle various aspects of text processing with precision and flexibility. The integration of SimpleSequentialChain ensures smooth execution of these chains, enabling developers to build scalable and efficient text processing applications with LangChain.