Foundations

LangChain Architecture and Core Concepts

Lesson 1 of 4 Estimated Time 50 min

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:

  1. prompt receives the input dict
  2. Formatted prompt goes to model
  3. Model’s response goes to output_parser
  4. 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

  1. Simple chain: Build a chain that takes a topic and generates a haiku. Use ChatPromptTemplate and StrOutputParser.

  2. Structured output: Build a chain that extracts person information (name, age) from text and returns a Pydantic model.

  3. Multi-step chain: Create a chain that first summarizes text, then extracts key concepts from the summary.

  4. Streaming: Modify a chain to stream output character-by-character instead of returning all at once.

  5. Error handling: Wrap a chain in try/except. Test with invalid inputs and handle errors gracefully.

  6. Async chain: Convert a synchronous chain to async. Use asyncio to run it.