|$ curl https://forge-ai.dev/api/markdown?path=docs/ai/langchain
$cat docs/langchain-framework.md
updated Recently·40 min read·published

LangChain Framework

AIIntermediate to Advanced🎯Free Tools
Introduction

LangChain is an open-source framework for building applications powered by large language models. It provides composable abstractions for chaining together LLM calls, retrieval systems, tools, and memory — enabling developers to build complex AI workflows from simple, reusable components.

The framework's core philosophy is modularity: each component (model, prompt, retriever, tool) is a building block that can be combined using LangChain Expression Language (LCEL) to create sophisticated pipelines. This guide covers the essential building blocks and patterns for production applications.

Installation & Setup

LangChain is split into multiple packages. The core package provides base abstractions, while partner packages add specific integrations.

install.sh
Bash
1# Core LangChain
2pip install langchain
3
4# Partner packages (install what you need)
5pip install langchain-openai
6pip install langchain-anthropic
7pip install langchain-community
8pip install langchain-core
9
10# Vector stores
11pip install langchain-pinecone langchain-chroma
12
13# Loaders for document ingestion
14pip install langchain-community[document_loaders]
setup.py
Python
1import os
2from langchain_openai import ChatOpenAI
3from langchain_core.messages import HumanMessage
4
5os.environ["OPENAI_API_KEY"] = "sk-..."
6
7llm = ChatOpenAI(model="gpt-4o", temperature=0)
8response = llm.invoke([HumanMessage(content="What is LangChain?")])
9print(response.content)

info

Always set API keys via environment variables, never hardcode them. Use .env files locally and secrets managers in production.
LangChain Expression Language (LCEL)

LCEL is the declarative way to compose LangChain components using the pipe (|) operator. It automatically handles streaming, async, batch operations, and fallbacks.

lcel_basics.py
Python
1from langchain_openai import ChatOpenAI
2from langchain_core.prompts import ChatPromptTemplate
3from langchain_core.output_parsers import StrOutputParser
4
5# Compose a chain using LCEL pipe operator
6prompt = ChatPromptTemplate.from_messages([
7 ("system", "You are a helpful assistant. Answer in {style}."),
8 ("human", "{question}"),
9])
10
11llm = ChatOpenAI(model="gpt-4o", temperature=0.7)
12parser = StrOutputParser()
13
14# Chain using pipe operator — automatically handles
15# streaming, batching, async, and error propagation
16chain = prompt | llm | parser
17
18# Invoke the chain
19result = chain.invoke({
20 "style": "concise",
21 "question": "What is the LangChain Expression Language?"
22})
23print(result)
lcel_parallel.py
Python
1# Parallel branches using RunnableParallel
2from langchain_core.runnables import RunnableParallel
3
4# Run two chains in parallel, merge results
5analysis_chain = prompt | llm | parser
6
7parallel = RunnableParallel(
8 summary=analysis_chain,
9 keywords=ChatPromptTemplate.from_template(
10 "List 5 keywords for: {question}"
11 ) | llm | parser,
12)
13
14result = parallel.invoke({"style": "brief", "question": "Explain RAG"})
15# result["summary"] and result["keywords"] both populated
16print(result)
lcel_streaming.py
Python
1# LCEL with streaming support
2async for chunk in chain.astream({
3 "style": "concise",
4 "question": "What is retrieval-augmented generation?"
5}):
6 print(chunk, end="", flush=True)
7
8# Batch processing — run multiple inputs
9results = chain.batch([
10 {"style": "concise", "question": "What is RAG?"},
11 {"style": "detailed", "question": "What is RAG?"},
12 {"style": "bullet-points", "question": "What is RAG?"},
13])

best practice

Always prefer LCEL over the legacy LLMChain class. LCEL provides native streaming, async support, and better error handling with less code.
Chains

Chains are sequences of calls that pass data from one component to the next. Common patterns include sequential chains, router chains, and retrieval chains.

sequential_chain.py
Python
1# Sequential chain — output of one step feeds the next
2from langchain_openai import ChatOpenAI
3from langchain_core.prompts import ChatPromptTemplate
4from langchain_core.output_parsers import StrOutputParser
5
6llm = ChatOpenAI(model="gpt-4o", temperature=0)
7
8# Step 1: Generate an outline
9outline_prompt = ChatPromptTemplate.from_template(
10 "Create a brief outline for a blog post about: {topic}"
11)
12outline_chain = outline_prompt | llm | StrOutputParser()
13
14# Step 2: Expand the outline into full content
15content_prompt = ChatPromptTemplate.from_template(
16 "Write a blog post based on this outline:\n{outline}"
17)
18content_chain = content_prompt | llm | StrOutputParser()
19
20# Compose: topic -> outline -> content
21full_chain = (
22 {"outline": outline_chain}
23 | (lambda x: {"outline": x["outline"]})
24 | content_chain
25)
26
27result = full_chain.invoke({"topic": "Building AI Agents with LangChain"})
28print(result)
router_chain.py
Python
1# Router chain — dynamically choose a processing path
2from langchain_core.runnables import RunnableLambda
3
4def route_to_specialist(input_dict):
5 topic = input_dict["topic"].lower()
6 if "python" in topic or "code" in topic:
7 return python_specialist
8 elif "math" in topic or "equation" in topic:
9 return math_specialist
10 return general_specialist
11
12python_specialist = (
13 ChatPromptTemplate.from_template(
14 "You are a Python expert. Answer: {question}"
15 ) | llm | StrOutputParser()
16)
17
18math_specialist = (
19 ChatPromptTemplate.from_template(
20 "You are a math expert. Show your work for: {question}"
21 ) | llm | StrOutputParser()
22)
23
24general_specialist = (
25 ChatPromptTemplate.from_template(
26 "Answer this question clearly: {question}"
27 ) | llm | StrOutputParser()
28)
29
30router = RunnableLambda(route_to_specialist)
31result = router.invoke({"topic": "python async", "question": "Explain async/await"})

info

Use RunnableLambda for custom routing logic. For more complex routing with structured output, consider using LangGraph which provides a graph-based approach to agent workflows.
Output Parsers

Output parsers transform raw LLM text into structured data. LangChain provides parsers for strings, JSON, Pydantic models, and custom schemas — essential for building reliable pipelines that consume LLM output programmatically.

str_parser.py
Python
1# StrOutputParser — simplest parser, returns raw string
2from langchain_openai import ChatOpenAI
3from langchain_core.prompts import ChatPromptTemplate
4from langchain_core.output_parsers import StrOutputParser
5
6llm = ChatOpenAI(model="gpt-4o", temperature=0)
7prompt = ChatPromptTemplate.from_template(
8 "List 3 benefits of {topic}. One per line."
9)
10
11chain = prompt | llm | StrOutputParser()
12result = chain.invoke({"topic": "Docker"})
13# Returns a plain string, not an AIMessage object
14print(type(result)) # <class 'str'>
json_parser.py
Python
1# JsonOutputParser — parse LLM output into a dict
2from langchain_core.output_parsers import JsonOutputParser
3from langchain_core.prompts import ChatPromptTemplate
4
5parser = JsonOutputParser()
6
7prompt = ChatPromptTemplate.from_messages([
8 ("system", "You are a helpful assistant.\n{format_instructions}"),
9 ("human", "{query}"),
10])
11
12chain = prompt | llm | parser
13result = chain.invoke({
14 "query": "Compare Python vs JavaScript for web backends",
15 "format_instructions": parser.get_format_instructions(),
16})
17# Returns a dict with the parsed JSON
18print(result["comparison"])
pydantic_parser.py
Python
1# PydanticOutputParser — type-safe structured output
2from langchain_core.output_parsers import PydanticOutputParser
3from pydantic import BaseModel, Field
4
5class CodeReview(BaseModel):
6 summary: str = Field(description="One-sentence summary")
7 issues: list[str] = Field(description="List of issues found")
8 score: int = Field(description="Quality score 1-10")
9
10parser = PydanticOutputParser(pydantic_object=CodeReview)
11
12prompt = ChatPromptTemplate.from_messages([
13 ("system", "Review code.\n{format_instructions}"),
14 ("human", "{code}"),
15])
16
17chain = prompt | llm | parser
18result = chain.invoke({
19 "code": "def add(a,b): return a+b",
20 "format_instructions": parser.get_format_instructions(),
21})
22# Returns a CodeReview instance with validated fields
23print(result.score) # int
24print(result.issues) # list[str]

info

Start with StrOutputParser for simple use cases, then graduate to PydanticOutputParser when you need validated, typed data. Use with_structured_output() on the LLM directly for the cleanest approach — it handles parsing internally.
Memory

Memory components store conversation history and context between invocations. LangChain provides several memory strategies from simple buffer memory to sophisticated summarization.

memory_buffer.py
Python
1# Conversation history with buffer memory
2from langchain_openai import ChatOpenAI
3from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
4from langchain_core.messages import HumanMessage, AIMessage
5from langchain_core.output_parsers import StrOutputParser
6
7llm = ChatOpenAI(model="gpt-4o", temperature=0.7)
8
9# In-memory conversation store
10chat_history = []
11
12prompt = ChatPromptTemplate.from_messages([
13 ("system", "You are a helpful coding assistant."),
14 MessagesPlaceholder(variable_name="history"),
15 ("human", "{input}"),
16])
17
18chain = prompt | llm | StrOutputParser()
19
20def chat(user_input: str) -> str:
21 response = chain.invoke({
22 "history": chat_history,
23 "input": user_input,
24 })
25 # Store the exchange in history
26 chat_history.append(HumanMessage(content=user_input))
27 chat_history.append(AIMessage(content=response))
28 return response
29
30# Multi-turn conversation
31print(chat("What is a closure in JavaScript?"))
32print(chat("Show me an example")) # Context retained
33print(chat("How is that different from a scope?")) # Full history used
memory_advanced.py
Python
1# Summary memory — compress long conversations
2from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
3
4summary = ""
5
6summarize_prompt = ChatPromptTemplate.from_template(
7 "Summarize this conversation so far:\n{history}\n"
8 "New message: {new_message}\n"
9 "Provide a concise summary."
10)
11
12def summarize_and_append(history, new_message):
13 global summary
14 result = (summarize_prompt | llm | StrOutputParser()).invoke({
15 "history": history,
16 "new_message": new_message,
17 })
18 summary = result
19 return summary
20
21# RAG-backed memory — store in vector database
22from langchain_chroma import Chroma
23from langchain_openai import OpenAIEmbeddings
24
25vectorstore = Chroma(
26 collection_name="chat_memory",
27 embedding_function=OpenAIEmbeddings(),
28)
29
30def remember(key: str, value: str):
31 vectorstore.add_texts([value], metadatas=[{"key": key}])
32
33def recall(query: str, k: int = 3):
34 docs = vectorstore.similarity_search(query, k=k)
35 return [doc.page_content for doc in docs]

warning

Buffer memory grows unbounded — always implement a sliding window or summarization strategy for production use. Conversation history in memory is lost on restart unless persisted to a database.
Tools & Function Calling

Tools are functions that an LLM can call to interact with external systems. LangChain provides a decorator-based interface for defining tools with type-safe schemas.

tools_basics.py
Python
1from langchain_core.tools import tool
2from langchain_openai import ChatOpenAI
3from langchain_core.messages import HumanMessage
4import math
5
6# Define tools using the @tool decorator
7@tool
8def calculate(expression: str) -> str:
9 """Evaluate a mathematical expression. Supports basic arithmetic,
10 trigonometry, and common math functions."""
11 allowed = {
12 "sqrt": math.sqrt, "abs": abs, "round": round,
13 "sin": math.sin, "cos": math.cos, "pi": math.pi,
14 }
15 try:
16 result = eval(expression, {"__builtins__": {}}, allowed)
17 return str(result)
18 except Exception as e:
19 return f"Error: {e}"
20
21@tool
22def get_weather(city: str) -> str:
23 """Get current weather for a given city."""
24 # In production, call a real weather API
25 weather_data = {
26 "New York": "72F, sunny",
27 "London": "58F, cloudy",
28 "Tokyo": "75F, clear",
29 }
30 return weather_data.get(city, f"Weather data unavailable for {city}")
31
32# Bind tools to LLM
33llm = ChatOpenAI(model="gpt-4o", temperature=0)
34llm_with_tools = llm.bind_tools([calculate, get_weather])
35
36# LLM decides which tool to call
37response = llm_with_tools.invoke([
38 HumanMessage(content="What is sqrt(144) + cos(pi)?")
39])
40print(response.tool_calls)
41# [{'name': 'calculate', 'args': {'expression': 'sqrt(144) + cos(pi)'}}]
agent_tools.py
Python
1# Agent that uses tools autonomously
2from langgraph.prebuilt import create_react_agent
3
4# Create a ReAct agent with tools
5agent = create_react_agent(
6 model=llm,
7 tools=[calculate, get_weather],
8 prompt="You are a helpful assistant. Use tools when needed.",
9)
10
11# Agent will reason, decide which tool to call, and respond
12result = agent.invoke({
13 "messages": [HumanMessage(content="What is 42 * 3.14159 and what's the weather in Tokyo?")]
14})
15
16for msg in result["messages"]:
17 if hasattr(msg, "tool_calls") and msg.tool_calls:
18 for tc in msg.tool_calls:
19 print(f"Tool called: {tc['name']}({tc['args']})")
20 elif msg.content:
21 print(f"Response: {msg.content}")

best practice

Always write clear, descriptive docstrings for your tools — the LLM uses them to decide when and how to call each tool. Test tools independently before binding them to agents.
Document Loaders

Document loaders ingest data from external sources — files, databases, APIs, web pages — into LangChain Document objects. Each loader handles parsing, metadata extraction, and chunking differently depending on the source format.

file_loaders.py
Python
1# Text and file loaders
2from langchain_community.document_loaders import (
3 TextLoader,
4 CSVLoader,
5 JSONLoader,
6 DirectoryLoader,
7)
8
9# Load a single text file
10loader = TextLoader("README.md", encoding="utf-8")
11docs = loader.load()
12print(docs[0].metadata) # {'source': 'README.md'}
13
14# Load CSV rows — each row becomes a Document
15csv_loader = CSVLoader("data.csv", csv_args={"delimiter": ","})
16csv_docs = csv_loader.load()
17# csv_docs[0].page_content = "Name: Alice\nAge: 30\n..."
18
19# Load all .md files from a directory
20dir_loader = DirectoryLoader(
21 "./docs",
22 glob="**/*.md",
23 loader_cls=TextLoader,
24 show_progress=True,
25)
26all_docs = dir_loader.load()
27print(f"Loaded {len(all_docs)} documents")
web_loaders.py
Python
1# Web and API loaders
2from langchain_community.document_loaders import WebBaseLoader
3from langchain_community.document_loaders import GitbookLoader
4
5# Scrape a web page
6web_loader = WebBaseLoader("https://example.com/article")
7web_docs = web_loader.load()
8print(web_docs[0].page_content[:200]) # Extracted text content
9
10# Load from Gitbook or Notion
11notion_loader = NotionDatabaseLoader(
12 integration_token="ntn_...",
13 database_id="abc123",
14)
15notion_docs = notion_loader.load()
16
17# PDF loader — requires pip install pypdf
18from langchain_community.document_loaders import PyPDFLoader
19pdf_loader = PyPDFLoader("paper.pdf")
20pages = pdf_loader.load() # One Document per page

info

Use DirectoryLoader with glob patterns to load entire folders. For production RAG, combine loaders with RecursiveCharacterTextSplitter to chunk documents before embedding.
Retrieval Chains

Retrieval chains combine document retrieval with LLM generation — the foundation of RAG (Retrieval-Augmented Generation). LangChain simplifies document loading, splitting, embedding, and retrieval.

rag_chain.py
Python
1# Complete RAG pipeline
2from langchain_openai import ChatOpenAI, OpenAIEmbeddings
3from langchain_chroma import Chroma
4from langchain_core.prompts import ChatPromptTemplate
5from langchain_core.output_parsers import StrOutputParser
6from langchain_core.runnables import RunnablePassthrough
7
8# 1. Load and split documents
9from langchain_community.document_loaders import TextLoader
10from langchain_text_splitters import RecursiveCharacterTextSplitter
11
12loader = TextLoader("docs.txt")
13docs = loader.load()
14
15splitter = RecursiveCharacterTextSplitter(
16 chunk_size=1000,
17 chunk_overlap=200,
18 separators=["\n\n", "\n", ". ", " "],
19)
20splits = splitter.split_documents(docs)
21
22# 2. Create vector store
23vectorstore = Chroma.from_documents(
24 documents=splits,
25 embedding=OpenAIEmbeddings(),
26 persist_directory="./chroma_db",
27)
28
29# 3. Create retriever
30retriever = vectorstore.as_retriever(
31 search_type="similarity",
32 search_kwargs={"k": 4},
33)
34
35# 4. Build RAG chain
36rag_prompt = ChatPromptTemplate.from_template("""
37Answer the question based on the context below.
38If the answer is not in the context, say "I don't have enough information."
39
40Context: {context}
41Question: {question}
42""")
43
44def format_docs(docs):
45 return "\n\n".join(doc.page_content for doc in docs)
46
47rag_chain = (
48 {"context": retriever | format_docs, "question": RunnablePassthrough()}
49 | rag_prompt
50 | ChatOpenAI(model="gpt-4o")
51 | StrOutputParser()
52)
53
54answer = rag_chain.invoke("What are the main features?")
55print(answer)

info

Use RunnablePassthrough to pass through the original input alongside transformed data. This pattern keeps your chain composable and testable.
Agents with LangGraph

LangGraph is the recommended way to build stateful, multi-step agents. It models agent workflows as graphs with nodes (operations) and edges (transitions), supporting cycles, human-in-the-loop, and persistence.

langgraph_agent.py
Python
1from typing import TypedDict, Annotated
2from langgraph.graph import StateGraph, END
3from langgraph.graph.message import add_messages
4from langchain_openai import ChatOpenAI
5from langchain_core.messages import HumanMessage
6
7class AgentState(TypedDict):
8 messages: Annotated[list, add_messages]
9
10llm = ChatOpenAI(model="gpt-4o", temperature=0)
11
12def chatbot(state: AgentState):
13 return {"messages": [llm.invoke(state["messages"])]}
14
15def should_continue(state: AgentState):
16 last_message = state["messages"][-1]
17 if hasattr(last_message, "tool_calls") and last_message.tool_calls:
18 return "tools"
19 return END
20
21# Build the graph
22graph = StateGraph(AgentState)
23graph.add_node("chatbot", chatbot)
24graph.set_entry_point("chatbot")
25graph.add_conditional_edges("chatbot", should_continue)
26graph.compile()
27
28# Run the agent
29from langgraph.checkpoint.memory import MemorySaver
30memory = MemorySaver()
31app = graph.compile(checkpointer=memory)
32
33config = {"configurable": {"thread_id": "1"}}
34result = app.invoke(
35 {"messages": [HumanMessage(content="What is 2+2?")]},
36 config=config,
37)
38print(result["messages"][-1].content)

best practice

Use LangGraph for any agent that needs cycles, conditional branching, or state persistence. The create_react_agentshortcut works for simple cases, but LangGraph gives you full control over the agent's decision-making flow.
Callbacks & Tracing

Callbacks hook into every step of a chain or agent execution — prompt formatting, LLM calls, tool invocations — giving you observability without modifying your pipeline. LangSmith provides hosted tracing, dashboards, and evaluation.

custom_callback.py
Python
1# Custom callback handler — log every LLM call
2from langchain_core.callbacks import BaseCallbackHandler
3from langchain_core.outputs import LLMResult
4import time
5
6class TimingCallback(BaseCallbackHandler):
7 def __init__(self):
8 self.calls = []
9
10 def on_llm_start(self, serialized, prompts, **kwargs):
11 self._start_time = time.time()
12 print(f"LLM call started — {len(prompts)} prompt(s)")
13
14 def on_llm_end(self, response: LLMResult, **kwargs):
15 elapsed = time.time() - self._start_time
16 tokens = response.llm_output.get("token_usage", {})
17 self.calls.append({
18 "elapsed": round(elapsed, 2),
19 "input_tokens": tokens.get("prompt_tokens", 0),
20 "output_tokens": tokens.get("completion_tokens", 0),
21 })
22 print(f"LLM call done in {elapsed:.2f}s — {tokens}")
23
24handler = TimingCallback()
25result = chain.invoke(
26 {"question": "Explain recursion"},
27 config={"callbacks": [handler]},
28)
29print(f"Total calls: {len(handler.calls)}")
langsmith_tracing.py
Python
1# LangSmith tracing — one env var enables full tracing
2import os
3os.environ["LANGSMITH_TRACING"] = "true"
4os.environ["LANGSMITH_API_KEY"] = "ls_..."
5
6# All chains, agents, and tools are now traced automatically
7# View traces at smith.langchain.com
8
9# Add tags and metadata for filtering in the dashboard
10from langchain_core.runnables import RunnableConfig
11
12config = RunnableConfig(
13 tags=["production", "customer-support"],
14 metadata={"user_id": "u_12345", "session_id": "s_67890"},
15 run_name="Customer Query Handler",
16)
17
18result = chain.invoke(
19 {"question": "Where is my order?"},
20 config=config,
21)
22
23# Custom project per chain for organized dashboards
24os.environ["LANGCHAIN_PROJECT"] = "forgelearn-prod"

best practice

Enable LangSmith tracing during development — it's the fastest way to debug chain failures and understand token usage patterns. Use RunnableConfig tags to filter traces by environment, feature, or user segment.
Production Patterns

Building production-ready LangChain applications requires attention to error handling, observability, cost control, and deployment patterns.

production.py
Python
1# Production-ready chain with error handling and observability
2from langchain_openai import ChatOpenAI
3from langchain_core.prompts import ChatPromptTemplate
4from langchain_core.output_parsers import StrOutputParser
5from langchain_core.runnables import RunnableConfig
6import logging
7
8logger = logging.getLogger(__name__)
9
10llm = ChatOpenAI(model="gpt-4o", temperature=0, max_retries=2, request_timeout=30)
11
12# Add retries and callbacks
13config = RunnableConfig(
14 callbacks=[logging_handler], # Your custom callback handler
15 tags=["production", "v2"],
16 metadata={"environment": "production"},
17)
18
19prompt = ChatPromptTemplate.from_template(
20 "Answer concisely: {question}"
21)
22chain = prompt | llm | StrOutputParser()
23
24# Try/except with fallback
25try:
26 result = chain.invoke(
27 {"question": "What is Docker?"},
28 config=config,
29 )
30except Exception as e:
31 logger.error(f"Chain failed: {e}")
32 result = "I apologize, but I'm unable to answer right now."
33
34# Streaming for long responses
35async def stream_answer(question: str):
36 full_response = ""
37 async for chunk in chain.astream(
38 {"question": question},
39 config=config,
40 ):
41 full_response += chunk
42 yield chunk
43 logger.info(f"Response length: {len(full_response)} chars")
cost_control.py
Python
1# Cost control with token counting
2from langchain_core.callbacks import get_usage_metadata
3
4def get_token_usage(chain, input_data):
5 """Invoke chain and return token usage metrics."""
6 result = chain.invoke(input_data)
7 usage = get_usage_metadata()
8 return {
9 "result": result,
10 "input_tokens": usage.get("input_tokens", 0),
11 "output_tokens": usage.get("output_tokens", 0),
12 "total_cost_usd": (
13 usage.get("input_tokens", 0) * 0.0025 / 1000
14 + usage.get("output_tokens", 0) * 0.01 / 1000
15 ),
16 }

warning

Never use temperature=0 for agent tool-calling — it can cause the model to skip tool calls. Use temperature=0 only for pure text generation tasks where determinism is critical.
$Blueprint — Engineering Documentation·Section ID: AI-LC-01·Revision: 1.0