LLMChain: The Building Block
Understand LLMChain ā LangChain's foundational chain. Learn prompt formatting, output parsing, variable injection, and how LLMChain became the basis for LCEL.
What is LLMChain?
LLMChain was LangChain's original building block ā a combination of a prompt template and a model. In modern LangChain (v0.2+), LLMChain is deprecated in favor of LCEL. Understanding it helps you read older code and appreciate why LCEL was designed.
# Legacy LLMChain (deprecated but still common in older codebases)
from langchain.chains import LLMChain
from langchain_openai import ChatOpenAI
from langchain_core.prompts import PromptTemplate
llm = ChatOpenAI(model="gpt-4o", temperature=0)
prompt = PromptTemplate(
input_variables=["drug", "condition"],
template="What is the recommended dose of {drug} for {condition}?",
)
chain = LLMChain(llm=llm, prompt=prompt)
result = chain.invoke({"drug": "warfarin", "condition": "atrial fibrillation"})
# Returns: {"drug": "warfarin", "condition": "...", "text": "Warfarin dose is..."}The LCEL Equivalent
Every LLMChain can be rewritten as a simple LCEL expression:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# LCEL equivalent ā the modern way
prompt = ChatPromptTemplate.from_template(
"What is the recommended dose of {drug} for {condition}?"
)
model = ChatOpenAI(model="gpt-4o", temperature=0)
parser = StrOutputParser()
chain = prompt | model | parser
# Invoke
result = chain.invoke({"drug": "warfarin", "condition": "atrial fibrillation"})
# Returns: str directly (no nested dict)
print(result)The key difference: LCEL chains return the final parser's output type directly; LLMChain.invoke() returned a dict with all inputs plus the output under "text".
Prompt Templates in Depth
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate
# String template (for completion models)
string_prompt = PromptTemplate(
input_variables=["drug", "patient_age"],
template="Patient age: {patient_age}. Drug: {drug}. Provide dosing recommendation.",
)
# Chat prompt (for chat models ā preferred)
chat_prompt = ChatPromptTemplate.from_messages([
("system", "You are a clinical pharmacist with 15 years of experience."),
("human", "Patient age: {patient_age}. Drug: {drug}. Provide dosing recommendation."),
])
# From template string shorthand
quick_prompt = ChatPromptTemplate.from_template(
"Explain the mechanism of {drug} in simple terms."
)
# Format prompts manually (without invoking a model)
formatted = chat_prompt.format_messages(drug="warfarin", patient_age="75 years")
print(formatted) # [SystemMessage(...), HumanMessage(...)]
# Input variables are automatically detected from {}
print(chat_prompt.input_variables) # ["drug", "patient_age"]Output Parsers
Output parsers transform the model's string output into structured data:
from langchain_core.output_parsers import (
StrOutputParser,
JsonOutputParser,
CommaSeparatedListOutputParser,
)
from langchain_core.pydantic_v1 import BaseModel, Field
# StrOutputParser: AIMessage ā str (most common)
str_parser = StrOutputParser()
# JsonOutputParser: AIMessage ā dict (parse JSON from model output)
json_parser = JsonOutputParser()
json_chain = (
ChatPromptTemplate.from_template("Return JSON with drug info for {drug}. Format: {{name: ..., class: ...}}")
| ChatOpenAI(model="gpt-4o")
| json_parser
)
drug_info = json_chain.invoke({"drug": "warfarin"})
print(drug_info["name"]) # dict access
# PydanticOutputParser: parse into a Pydantic model
class DrugInfo(BaseModel):
name: str = Field(description="Drug generic name")
drug_class: str = Field(description="Drug class/category")
mechanism: str = Field(description="Mechanism of action in one sentence")
monitoring: list[str] = Field(description="Required monitoring parameters")
from langchain_core.output_parsers import PydanticOutputParser
pydantic_parser = PydanticOutputParser(pydantic_object=DrugInfo)
pydantic_chain = (
ChatPromptTemplate.from_messages([
("system", "Extract drug information. {format_instructions}"),
("human", "Drug: {drug}"),
]).partial(format_instructions=pydantic_parser.get_format_instructions())
| ChatOpenAI(model="gpt-4o")
| pydantic_parser
)
drug = pydantic_chain.invoke({"drug": "warfarin"})
print(drug.name) # str attribute access
print(drug.monitoring) # list attribute access
# CommaSeparatedListOutputParser
list_parser = CommaSeparatedListOutputParser()
list_chain = (
ChatPromptTemplate.from_template("List 5 drugs in the {drug_class} class, comma-separated.")
| ChatOpenAI(model="gpt-4o-mini")
| list_parser
)
drugs = list_chain.invoke({"drug_class": "beta-blocker"})
print(drugs) # ["metoprolol", "atenolol", "propranolol", "carvedilol", "bisoprolol"]Variable Injection Patterns
# Pattern 1: All variables in .invoke()
chain = prompt | model | parser
result = chain.invoke({"drug": "warfarin", "patient_age": "65"})
# Pattern 2: Partial variables (fixed values baked in)
elderly_chain = chain.partial(patient_age="65+ years (elderly)")
result = elderly_chain.invoke({"drug": "metformin"})
# "patient_age" is already set; only "drug" needed at runtime
# Pattern 3: Assign computed values with RunnablePassthrough.assign
from langchain_core.runnables import RunnablePassthrough
enriched_chain = (
RunnablePassthrough.assign(
# Add "drug_class" derived from another chain
drug_class=(
ChatPromptTemplate.from_template("What drug class is {drug}? One word.")
| ChatOpenAI(model="gpt-4o-mini")
| parser
)
)
| ChatPromptTemplate.from_template(
"{drug} is a {drug_class}. Describe its dosing."
)
| ChatOpenAI(model="gpt-4o")
| parser
)
result = enriched_chain.invoke({"drug": "warfarin"})
# Pattern 4: ConfigurableField for runtime parameter control
from langchain_core.runnables import ConfigurableField
configurable_chain = (
prompt
| ChatOpenAI(model="gpt-4o", temperature=0).configurable_fields(
temperature=ConfigurableField(id="temperature")
)
| parser
)
# Change temperature at invoke time
creative = configurable_chain.invoke(
{"drug": "warfarin", "patient_age": "adult"},
config={"configurable": {"temperature": 0.8}},
)Chain Output Keys: Legacy vs LCEL
This is a common source of confusion when migrating:
# Legacy LLMChain output ā a dict with all keys
old_chain = LLMChain(llm=ChatOpenAI(model="gpt-4o"), prompt=prompt)
old_result = old_chain.invoke({"drug": "warfarin", "condition": "AFib"})
# old_result = {"drug": "warfarin", "condition": "AFib", "text": "The dose is..."}
response_text = old_result["text"] # Buried under "text" key
# LCEL chain output ā just the final parser's output
new_chain = prompt | ChatOpenAI(model="gpt-4o") | StrOutputParser()
new_result = new_chain.invoke({"drug": "warfarin", "condition": "AFib"})
# new_result = "The dose is..." (str directly)
response_text = new_result # Direct access
# If you need both inputs and output in LCEL:
from langchain_core.runnables import RunnablePassthrough
chain_with_all = RunnablePassthrough.assign(
answer=new_chain
)
result = chain_with_all.invoke({"drug": "warfarin", "condition": "AFib"})
# {"drug": "warfarin", "condition": "AFib", "answer": "The dose is..."}Migration Pattern
# Old code pattern (LLMChain)
from langchain.chains import LLMChain
def get_drug_info(drug: str, condition: str) -> str:
chain = LLMChain(
llm=ChatOpenAI(model="gpt-4o"),
prompt=PromptTemplate(
template="Dose of {drug} for {condition}?",
input_variables=["drug", "condition"],
),
)
return chain.run(drug=drug, condition=condition)
# New code pattern (LCEL)
_chain = (
ChatPromptTemplate.from_template("Dose of {drug} for {condition}?")
| ChatOpenAI(model="gpt-4o", temperature=0)
| StrOutputParser()
)
def get_drug_info(drug: str, condition: str) -> str:
return _chain.invoke({"drug": drug, "condition": condition})Found this helpful?
Leave a comment
Have a question, correction, or just found this helpful? Leave a note below.