Learnixo
Back to blog
AI Systemsintermediate

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.

Asma Hafeez KhanMay 16, 20265 min read
LangChainLLMChainChainsPromptsOutput ParsingLCEL
Share:š•

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.

Python
# 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:

Python
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

Python
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:

Python
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

Python
# 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:

Python
# 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

Python
# 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})

Enjoyed this article?

Explore the AI Systems learning path for more.

Found this helpful?

Share:š•

Leave a comment

Have a question, correction, or just found this helpful? Leave a note below.