Your First Agent with LangChain

Building Your First Chain

12m read

Building Your First Chain

A chain in LangChain is a sequence of components connected together, where the output of one becomes the input of the next. In this lesson, you'll build a real chain from scratch: a research summarizer that takes a topic, generates a structured analysis, and formats it as a markdown report.

The LCEL Pipe Operator

LangChain Expression Language (LCEL) uses the | operator to compose components. This is equivalent to function composition: f | g | h means "pass input through f, then g, then h."

Every LCEL component implements the Runnable interface with these key methods:

  • .invoke(input) — run synchronously with a single input
  • .batch(inputs) — run on multiple inputs in parallel
  • .stream(input) — stream output tokens as they arrive

Building a Research Summarizer Chain

# agent/chains/research_summarizer.py
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

# Step 1: Define the model
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0.3)

# Step 2: Create a prompt template
research_prompt = ChatPromptTemplate.from_messages([
    ("system", """You are a research assistant. When given a topic, provide:
1. A 2-sentence definition
2. Three key concepts (as bullet points)
3. Two practical applications
4. One open challenge in the field

Format your response in clean markdown."""),
    ("human", "Research topic: {topic}")
])

# Step 3: Create an output parser
output_parser = StrOutputParser()

# Step 4: Chain them together with LCEL
research_chain = research_prompt | llm | output_parser

# Usage
if __name__ == "__main__":
    result = research_chain.invoke({"topic": "transformer attention mechanisms"})
    print(result)

Adding Multiple Prompts (Sequential Chains)

Real pipelines often need multiple LLM calls. Here's a two-step chain: first research, then critique:

from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.runnables import RunnablePassthrough

llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)

# First chain: generate research
research_prompt = ChatPromptTemplate.from_template(
    "Provide a 3-paragraph overview of: {topic}"
)
research_chain = research_prompt | llm | StrOutputParser()

# Second chain: critique the research
critique_prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a critical editor. Identify gaps and improvements."),
    ("human", "Original topic: {topic}\n\nDraft content:\n{research}\n\nProvide 3 specific improvements.")
])
critique_chain = critique_prompt | llm | StrOutputParser()

# Combine: pass topic to research, then both topic and research to critique
full_pipeline = (
    RunnablePassthrough.assign(research=research_chain)
    | critique_chain
)

result = full_pipeline.invoke({"topic": "vector databases"})
print(result)

Streaming Output

For interactive applications, stream tokens as they generate instead of waiting for the full response:

# Stream tokens in real-time
for chunk in research_chain.stream({"topic": "reinforcement learning"}):
    print(chunk, end="", flush=True)
print()  # Final newline

Adding Structured Output Parsing

Instead of free-form text, parse the LLM output into a typed Python object:

from langchain_core.output_parsers import JsonOutputParser
from pydantic import BaseModel, Field
from typing import List

class ResearchResult(BaseModel):
    topic: str = Field(description="The researched topic")
    summary: str = Field(description="A 2-sentence summary")
    key_concepts: List[str] = Field(description="3-5 key concepts")
    applications: List[str] = Field(description="2-3 practical applications")

# Tell the model to output JSON matching the schema
json_parser = JsonOutputParser(pydantic_object=ResearchResult)

structured_prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a research assistant. Respond with valid JSON only."),
    ("human", "Research topic: {topic}\n\n{format_instructions}")
]).partial(format_instructions=json_parser.get_format_instructions())

structured_chain = structured_prompt | llm | json_parser

result: ResearchResult = structured_chain.invoke({"topic": "RAG systems"})
print(f"Topic: {result['topic']}")
print(f"Key concepts: {result['key_concepts']}")

Inspecting Chain Execution

LangChain provides callbacks for debugging:

from langchain_core.callbacks import StdOutCallbackHandler

# Print all LLM inputs/outputs during execution
result = research_chain.invoke(
    {"topic": "AI safety"},
    config={"callbacks": [StdOutCallbackHandler()]}
)

You now have a working chain that takes a topic, runs it through a structured prompt, and returns formatted research output. In the next lesson, you'll attach real tools to turn this chain into a true agent.