LangChain Architecture and Core Concepts
LangChain Architecture and Core Concepts
LangChain is a framework for building applications with language models. Instead of making raw API calls, you build components that compose together. This lesson covers the core building blocks and how they fit together.
What Is LangChain?
LangChain provides abstractions for:
- Models: Interfaces to LLMs (OpenAI, Anthropic, etc.)
- Prompts: Templates for crafting prompts
- Output Parsers: Extracting structured data from responses
- Chains: Combining components in sequence
- Memory: Managing conversation history
- Tools: Making models capable of taking actions
The power is in composition—build complex systems from simple, reusable pieces.
LCEL: LangChain Expression Language
LCEL (pronounced “el-cel”) is LangChain’s syntax for composing chains. It uses the pipe operator (|) to connect components:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# Define components
model = ChatOpenAI(model="gpt-3.5-turbo")
prompt = ChatPromptTemplate.from_template(
"Answer this question: {question}"
)
output_parser = StrOutputParser()
# Compose with LCEL
chain = prompt | model | output_parser
# Use it
result = chain.invoke({"question": "What is AI?"})
print(result)
The chain flows like this:
promptreceives the input dict- Formatted prompt goes to
model - Model’s response goes to
output_parser - Parser returns clean string
LCEL is powerful because components can be anything—models, functions, other chains.
The Core Components
Models
LangChain wraps different model APIs with a common interface:
from langchain_openai import ChatOpenAI
from langchain_anthropic import ChatAnthropic
from langchain_core.messages import HumanMessage
# OpenAI
openai_model = ChatOpenAI(
model="gpt-4-turbo",
temperature=0.7,
api_key="sk-..."
)
# Anthropic
anthropic_model = ChatAnthropic(
model="claude-3-opus-20240229",
max_tokens=1000,
api_key="sk-ant-..."
)
# Both use the same interface
response = openai_model.invoke([
HumanMessage(content="What is machine learning?")
])
print(response.content)
Prompts
Prompts are templates for constructing messages:
from langchain_core.prompts import ChatPromptTemplate, PromptTemplate
# Simple string prompt
simple_prompt = PromptTemplate(
input_variables=["topic"],
template="Write a haiku about {topic}"
)
output = simple_prompt.format(topic="coding")
print(output)
# Chat prompt with multiple messages
chat_prompt = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant."),
("user", "What is {subject}?")
])
messages = chat_prompt.format_messages(subject="machine learning")
print(messages)
Output Parsers
Parsers extract structured data from model responses:
from langchain_core.output_parsers import StrOutputParser, JsonOutputParser
from pydantic import BaseModel
# Simple string parser
str_parser = StrOutputParser()
# JSON parser with schema
class Person(BaseModel):
name: str
age: int
occupation: str
json_parser = JsonOutputParser(pydantic_object=Person)
# Use in chain
model = ChatOpenAI()
chain = model | json_parser
# If model returns JSON string, parser extracts object
Building Simple Chains
A chain is components connected with LCEL:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
# Create the chain
model = ChatOpenAI(model="gpt-3.5-turbo")
prompt = ChatPromptTemplate.from_template(
"""You are a helpful assistant. Answer this question concisely:
{question}"""
)
chain = prompt | model | StrOutputParser()
# Use it
response = chain.invoke({"question": "What is photosynthesis?"})
print(response)
# You can also use stream for real-time output
for chunk in chain.stream({"question": "Explain quantum computing"}):
print(chunk, end="", flush=True)
The LangChain Ecosystem
Understanding what else LangChain provides:
Embeddings
Convert text to vectors:
from langchain_openai import OpenAIEmbeddings
embeddings = OpenAIEmbeddings()
# Embed a document
doc_embedding = embeddings.embed_query("machine learning is important")
# Embed multiple documents
texts = ["AI is powerful", "ML is a subset of AI", "Deep learning is ML"]
embeddings_list = embeddings.embed_documents(texts)
print(f"Embedding dimension: {len(doc_embedding)}")
Vector Stores
Store and search embeddings:
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
# Create vector store
embeddings = OpenAIEmbeddings()
texts = ["Photosynthesis is how plants make food", "Respiration releases energy"]
vectorstore = Chroma.from_texts(texts, embeddings)
# Search
results = vectorstore.similarity_search("How do plants get energy?", k=1)
print(results[0].page_content)
Loaders
Load data from various sources:
from langchain_community.document_loaders import TextLoader, WebBaseLoader, CSVLoader
# Load from text file
loader = TextLoader("myfile.txt")
docs = loader.load()
# Load from URL
web_loader = WebBaseLoader("https://example.com")
web_docs = web_loader.load()
# Load from CSV
csv_loader = CSVLoader("data.csv")
csv_docs = csv_loader.load()
Memory
Maintain conversation history:
from langchain.memory import ConversationBufferMemory
from langchain_openai import ChatOpenAI
from langchain.chains import LLMChain
from langchain_core.prompts import ChatPromptTemplate
memory = ConversationBufferMemory()
model = ChatOpenAI()
prompt = ChatPromptTemplate.from_messages([
("system", "You are helpful"),
("human", "{input}"),
])
# Create chain with memory
chain = LLMChain(llm=model, prompt=prompt, memory=memory)
# Each call remembers previous context
chain.run(input="My name is Alice")
chain.run(input="What's my name?")
Async and Streaming
LangChain supports async for performance:
import asyncio
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
async def async_example():
model = ChatOpenAI(model="gpt-3.5-turbo")
prompt = ChatPromptTemplate.from_template("What is {topic}?")
chain = prompt | model | StrOutputParser()
# Async invoke
result = await chain.ainvoke({"topic": "machine learning"})
print(result)
# Async stream
async for chunk in chain.astream({"topic": "AI"}):
print(chunk, end="", flush=True)
# Run async code
asyncio.run(async_example())
Error Handling in LangChain
Handle errors gracefully:
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
from langchain_core.output_parsers import StrOutputParser
from langchain_core.exceptions import OutputParserException
model = ChatOpenAI()
prompt = ChatPromptTemplate.from_template("What is {topic}?")
chain = prompt | model | StrOutputParser()
try:
result = chain.invoke({"topic": "AI"})
print(result)
except OutputParserException as e:
print(f"Parse error: {e}")
except Exception as e:
print(f"Error: {e}")
Debugging Chains
LangChain provides debugging tools:
import langchain
from langchain_openai import ChatOpenAI
from langchain_core.prompts import ChatPromptTemplate
# Enable debug mode
langchain.debug = True
model = ChatOpenAI()
prompt = ChatPromptTemplate.from_template("What is {topic}?")
chain = prompt | model
# Now all steps are logged
result = chain.invoke({"topic": "machine learning"})
# Disable when done
langchain.debug = False
Key Takeaway
LangChain abstracts away complexity by providing components (models, prompts, parsers, memory) that compose together with LCEL. The pipe operator connects components into chains. This lets you build complex AI applications from simple, reusable pieces. Understand the core components—model, prompt, parser—and how they flow together.
Exercises
-
Simple chain: Build a chain that takes a topic and generates a haiku. Use ChatPromptTemplate and StrOutputParser.
-
Structured output: Build a chain that extracts person information (name, age) from text and returns a Pydantic model.
-
Multi-step chain: Create a chain that first summarizes text, then extracts key concepts from the summary.
-
Streaming: Modify a chain to stream output character-by-character instead of returning all at once.
-
Error handling: Wrap a chain in try/except. Test with invalid inputs and handle errors gracefully.
-
Async chain: Convert a synchronous chain to async. Use asyncio to run it.