LangChain Basics
Build powerful AI applications by connecting LLMs with tools, data sources, and workflows
Understanding LangChain
LangChain is a framework designed to simplify the development of applications powered by language models. It provides a standardised interface for chains, integrations with other tools, and end-to-end chains for common applications.
Key Insight
LangChain isn't just a library—it's an ecosystem that allows you to build complex AI applications by connecting LLMs with external data, tools, and environments.
Why LangChain Matters for AI Agents
LangChain addresses several critical challenges in building AI agents:
- Context Limitations: Helps overcome LLM context window constraints by connecting to external data sources
- Tool Integration: Provides standardised interfaces for LLMs to use external tools
- Memory Management: Offers built-in solutions for short and long-term memory
- Complex Workflows: Enables the creation of multi-step reasoning and action processes
- Reusability: Promotes modular components that can be combined in different ways
LangChain Architecture Overview
LangChain is organised around several key components:
- Models: Interfaces to language models (LLMs, chat models, text embedding models)
- Prompts: Templates and management for model inputs
- Indexes: Tools for structuring and accessing external data
- Memory: State persistence between chain runs
- Chains: Sequences of operations on inputs
- Agents: Chains that use LLMs to determine which tools to use based on user input
When to Use LangChain
| Use Case | LangChain Benefit | Alternative Approach |
|---|---|---|
| Building agents that use external tools | Pre-built agent frameworks with tool integration | Custom implementation with function calling APIs |
| Creating applications with external data sources | Document loaders, text splitters, and retrievers | Custom vector database integration |
| Implementing conversational memory | Ready-to-use memory classes | Manual conversation history management |
| Orchestrating complex workflows | Chain composition and sequencing | Custom workflow management |
| Rapid prototyping | High-level abstractions for common patterns | Building from scratch with basic APIs |
LangChain Core Components
Understanding the core components of LangChain is essential for effective implementation. Let's explore each component in detail:
1. Models
LangChain provides standardised interfaces for working with different types of language models:
Model Types in LangChain:
- LLMs: Models that take a string prompt and return a string completion (e.g., text-davinci-003)
- Chat Models: Models that take a series of messages and return a message response (e.g., gpt-4, claude-3)
- Text Embedding Models: Models that convert text into vector representations
# Ensure required libraries are installed
# pip install langchain openai tiktoken faiss-cpu
import os
from dotenv import load_dotenv
# Load environment variables (especially OPENAI_API_KEY)
load_dotenv()
# Check if the API key is loaded (optional but good practice)
if not os.getenv("OPENAI_API_KEY"):
print("Warning: OPENAI_API_KEY environment variable not set.")
from langchain_openai import OpenAI, ChatOpenAI, OpenAIEmbeddings
# Initialize an LLM (use model_name if needed)
# llm = OpenAI(temperature=0.7, model_name="gpt-3.5-turbo-instruct")
llm = OpenAI(temperature=0.7)
# Initialize a chat model
chat_model = ChatOpenAI(temperature=0.7, model="gpt-4o") # Using a newer model
# Initialize an embedding model
embeddings = OpenAIEmbeddings()
# Using an LLM
try:
llm_result = llm.invoke("Explain what an AI agent is.")
print(f"LLM Result:\n{llm_result}\n")
except Exception as e:
print(f"LLM Error: {e}")
# Using a chat model
from langchain_core.messages import HumanMessage, SystemMessage
messages = [
SystemMessage(content="You are a helpful AI assistant."),
HumanMessage(content="Explain what an AI agent is.")
]
try:
chat_result = chat_model.invoke(messages)
print(f"Chat Model Result:\n{chat_result.content}\n")
except Exception as e:
print(f"Chat Model Error: {e}")
# Using embeddings
text = "This is a sample text to embed."
try:
embedding_vector = embeddings.embed_query(text)
# print(f"Embedding Vector (first 10 dims): {embedding_vector[:10]}...")
print(f"Generated embedding vector of dimension {len(embedding_vector)}\n")
except Exception as e:
print(f"Embedding Error: {e}")
Model Selection Strategy
LangChain makes it easy to switch between different models. Consider implementing a model selection strategy based on:
- Task Complexity: Use more powerful models for complex reasoning
- Cost Sensitivity: Use cheaper models for routine tasks
- Response Time: Use faster models when speed is critical
- Provider Diversity: Implement fallbacks across different providers
2. Prompts
LangChain's prompt management system helps create, manage, and optimise prompts:
Key Prompt Components:
- PromptTemplates: Parameterised prompt strings
- Example Selectors: Methods for choosing examples for few-shot learning
- Output Parsers: Tools for structuring model outputs
from langchain_core.prompts import PromptTemplate, ChatPromptTemplate
from langchain_core.prompts.example_selector import SemanticSimilarityExampleSelector
from langchain_community.vectorstores import FAISS
from langchain_core.output_parsers import StructuredOutputParser, ResponseSchema
from langchain_openai import OpenAIEmbeddings # Ensure embeddings model is imported
# Simple prompt template
template = "Write a {tone} email about {subject}."
prompt = PromptTemplate.from_template(template)
# Generate a prompt
formatted_prompt = prompt.format(tone="professional", subject="meeting agenda")
print(f"Formatted Prompt:\n{formatted_prompt}\n")
# Chat prompt template
chat_template = ChatPromptTemplate.from_messages([
("system", "You are a helpful assistant."),
("human", "Tell me about {topic}.")
])
formatted_chat_prompt = chat_template.format_messages(topic="AI Agents")
print(f"Formatted Chat Prompt:\n{formatted_chat_prompt}\n")
# Few-shot learning with semantic example selection
examples = [
{"input": "happy", "output": "joyful"},
{"input": "sad", "output": "melancholy"},
{"input": "angry", "output": "furious"}
]
# Ensure embeddings is initialized (e.g., embeddings = OpenAIEmbeddings())
try:
example_selector = SemanticSimilarityExampleSelector.from_examples(
examples,
embeddings, # Use the initialized embeddings object
FAISS, # Use FAISS for in-memory vector store
k=1 # Select 1 example
)
# Select examples similar to a query
selected_examples = example_selector.select_examples({"input": "unhappy"})
print(f"Selected Examples:\n{selected_examples}\n")
except Exception as e:
print(f"Example Selector Error: {e}")
# Structured output parsing
response_schemas = [
ResponseSchema(name="name", description="The person's name"),
ResponseSchema(name="age", description="The person's age", type="integer"), # Added type
ResponseSchema(name="occupation", description="The person's occupation")
]
output_parser = StructuredOutputParser.from_response_schemas(response_schemas)
format_instructions = output_parser.get_format_instructions()
structured_prompt_template = PromptTemplate(
template="Extract information from the text below.\n{format_instructions}\nText: {text}",
input_variables=["text"],
partial_variables={"format_instructions": format_instructions}
)
# Example usage with a model (assuming chat_model is initialized)
text_to_parse = "John Doe is a 30 year old software engineer."
formatted_structured_prompt = structured_prompt_template.format(text=text_to_parse)
# This would typically be sent to the model:
# try:
# response = chat_model.invoke(formatted_structured_prompt)
# parsed_output = output_parser.parse(response.content)
# print(f"Parsed Output:\n{parsed_output}")
# except Exception as e:
# print(f"Structured Output Error: {e}")
print(f"Format Instructions (for model):\n{format_instructions}\n")
3. Memory
LangChain's memory components help maintain state between chain runs, essential for conversational agents:
Memory Types:
- ConversationBufferMemory: Stores messages and returns them as a buffer
- ConversationBufferWindowMemory: Keeps a sliding window of the most recent messages
- ConversationSummaryMemory / ConversationSummaryBufferMemory: Summarises the conversation as it goes
- VectorStoreRetrieverMemory: Stores embeddings of messages for semantic retrieval
from langchain.memory import ConversationBufferMemory, ConversationSummaryBufferMemory
from langchain.chains import ConversationChain
from langchain_openai import ChatOpenAI # Ensure ChatOpenAI is imported
# Ensure chat_model is initialized (e.g., chat_model = ChatOpenAI(temperature=0, model="gpt-4o"))
chat_model = ChatOpenAI(temperature=0, model="gpt-4o") # Example initialization
# Simple conversation buffer memory
memory = ConversationBufferMemory()
memory.save_context({"input": "Hi, my name is John."}, {"output": "Hello John, how can I help you today?"})
# Retrieve the messages
memory_state = memory.load_memory_variables({})
print(f"Buffer Memory State:\n{memory_state}\n")
# Conversation chain with buffer memory
conversation = ConversationChain(
llm=chat_model,
memory=ConversationBufferMemory(),
verbose=True # Show chain steps
)
# First interaction
try:
response1 = conversation.invoke(input="Hi, my name is John.")
print(f"Response 1: {response1['response']}\n")
except Exception as e:
print(f"Conversation Error 1: {e}")
# Second interaction (memory of the first interaction is preserved)
try:
response2 = conversation.invoke(input="What's my name?")
print(f"Response 2: {response2['response']}\n")
except Exception as e:
print(f"Conversation Error 2: {e}")
# Summary buffer memory for longer conversations (keeps recent interactions + summary)
summary_memory = ConversationSummaryBufferMemory(llm=chat_model, max_token_limit=100)
summary_conversation = ConversationChain(
llm=chat_model,
memory=summary_memory,
verbose=True
)
# Add some interactions to summary memory
try:
summary_conversation.invoke(input="My favourite colour is blue.")
summary_conversation.invoke(input="I live in London.")
summary_conversation.invoke(input="What is my favourite colour?")
summary_state = summary_memory.load_memory_variables({})
print(f"Summary Memory State:\n{summary_state}")
except Exception as e:
print(f"Summary Conversation Error: {e}")
Memory Selection Strategy
Choose the appropriate memory type based on your agent's needs:
- Short, simple conversations: ConversationBufferMemory
- Medium-length conversations: ConversationBufferWindowMemory
- Long conversations needing context compression: ConversationSummaryBufferMemory
- Retrieving relevant past interactions: VectorStoreRetrieverMemory
4. Indexes
Indexes help structure documents for efficient interaction with LLMs, crucial for Retrieval-Augmented Generation (RAG):
Key Indexing Components:
- Document Loaders: Load data from various sources (files, web, databases)
- Text Splitters: Break large documents into smaller chunks
- Vector Stores: Store text embeddings and perform similarity searches
- Retrievers: Interface for fetching relevant documents based on queries
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain_openai import OpenAIEmbeddings # Ensure embeddings imported
# Assume embeddings is initialized: embeddings = OpenAIEmbeddings()
# 1. Load Documents (Example: loading this file - needs path adjustment)
try:
# Create a dummy text file for demonstration
with open("dummy_document.txt", "w") as f:
f.write("LangChain is a framework for AI applications.\nIt connects LLMs to data sources.\nAgents use tools via LangChain.")
loader = TextLoader('./dummy_document.txt')
documents = loader.load()
print(f"Loaded {len(documents)} document(s).\n")
except Exception as e:
print(f"Document Loading Error: {e}")
documents = [] # Assign empty list if loading fails
if documents: # Proceed only if documents were loaded
# 2. Split Documents
text_splitter = CharacterTextSplitter(chunk_size=100, chunk_overlap=20)
texts = text_splitter.split_documents(documents)
print(f"Split into {len(texts)} chunks.\n")
# 3. Create Vector Store
try:
vectorstore = FAISS.from_documents(texts, embeddings)
print("Created FAISS vector store.\n")
# 4. Create Retriever
retriever = vectorstore.as_retriever(search_kwargs={"k": 2}) # Retrieve top 2 results
print("Created retriever.\n")
# Example retrieval
query = "What are LangChain agents?"
retrieved_docs = retriever.invoke(query)
print(f"Retrieved {len(retrieved_docs)} docs for query: '{query}'")
# for i, doc in enumerate(retrieved_docs):
# print(f"Doc {i+1}: {doc.page_content[:100]}...")
except Exception as e:
print(f"Vector Store/Retrieval Error: {e}")
# Clean up dummy file
if os.path.exists("dummy_document.txt"):
os.remove("dummy_document.txt")
5. Chains
Chains are sequences of calls to components like models, prompts, or other chains.
Common Chain Types:
- LLMChain: The most basic chain combining a model and a prompt template
- Sequential Chains: Run chains in sequence, passing outputs as inputs
- Router Chains: Dynamically select the next chain based on input
- RetrievalQA Chain: Combines a retriever and an LLM chain for question answering over documents
from langchain.chains import LLMChain, SimpleSequentialChain
from langchain_core.prompts import PromptTemplate
from langchain_openai import ChatOpenAI # Ensure model imported
# Assume chat_model is initialized: chat_model = ChatOpenAI(temperature=0, model="gpt-4o")
# Simple LLMChain
prompt_template = PromptTemplate.from_template(
"Explain the concept of {concept} in one sentence."
)
llm_chain = LLMChain(prompt=prompt_template, llm=chat_model)
try:
response = llm_chain.invoke({"concept": "AI agents"})
print(f"LLMChain Response:\n{response['text']}\n")
except Exception as e:
print(f"LLMChain Error: {e}")
# Simple Sequential Chain
# Chain 1: Generate a company name
name_prompt = PromptTemplate.from_template("Suggest a creative name for a company that makes {product}.")
name_chain = LLMChain(llm=chat_model, prompt=name_prompt)
# Chain 2: Generate a slogan for that company name
slogan_prompt = PromptTemplate.from_template("Write a catchy slogan for a company named {company_name}.")
slogan_chain = LLMChain(llm=chat_model, prompt=slogan_prompt)
# Combine chains
# Note: SimpleSequentialChain takes single input/output chains
# For more complex sequencing, use SequentialChain
sequential_chain = SimpleSequentialChain(chains=[name_chain, slogan_chain], verbose=True)
try:
result = sequential_chain.invoke("colourful socks") # Corrected UK spelling
print(f"Sequential Chain Result:\n{result['output']}\n")
except Exception as e:
print(f"Sequential Chain Error: {e}")
# RetrievalQA Chain (requires retriever from Indexing section)
from langchain.chains import RetrievalQA
# Assume retriever is initialized from the Indexing section example
# if 'retriever' in locals(): # Check if retriever exists
# try:
# qa_chain = RetrievalQA.from_chain_type(
# llm=chat_model,
# chain_type="stuff", # Method to stuff docs into context
# retriever=retriever,
# return_source_documents=True
# )
# query = "How do agents use tools?"
# qa_result = qa_chain.invoke({"query": query})
# print(f"RetrievalQA Result for '{query}':\n{qa_result['result']}")
# # print(f"Source Documents: {qa_result['source_documents']}")
# except Exception as e:
# print(f"RetrievalQA Error: {e}")
# else:
# print("Skipping RetrievalQA example as retriever is not defined.")
print("Skipping RetrievalQA example as it depends on previous steps.")
6. Agents
Agents use an LLM to decide a sequence of actions to take, often involving external tools.
Key Agent Concepts:
- Tools: Functions the agent can call (web search, calculator, database lookup)
- Agent Executor: Runs the agent loop, calling the LLM and tools
- Agent Types: Different strategies for reasoning and tool use (Zero-shot ReAct, OpenAI Functions, etc.)
from langchain.agents import load_tools, initialize_agent, AgentType
from langchain_openai import ChatOpenAI # Ensure model imported
# Assume chat_model is initialized: chat_model = ChatOpenAI(temperature=0, model="gpt-4o")
# Load some common tools (ensure necessary libraries like wikipedia, llm-math are installed)
# pip install wikipedia langchain_experimental
tools = load_tools(["llm-math", "wikipedia"], llm=chat_model)
# Initialize an agent (Zero-shot ReAct)
try:
agent_executor = initialize_agent(
tools,
chat_model,
agent=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
verbose=True,
handle_parsing_errors=True # Handle cases where LLM output is not parsable
)
# Run the agent
query = "Who won the FIFA World Cup in 2018? What was the score?"
agent_response = agent_executor.invoke({"input": query})
print(f"Agent Response:\n{agent_response['output']}")
except ImportError as e:
print(f"Agent Tool Dependency Error: {e}. Skipping agent example.")
except Exception as e:
print(f"Agent Initialization/Run Error: {e}")
# OpenAI Functions Agent (often more reliable)
# try:
# # Ensure model supports function calling (like gpt-4o)
# function_agent_executor = initialize_agent(
# tools,
# chat_model,
# agent=AgentType.OPENAI_FUNCTIONS,
# verbose=True
# )
# query_func = "What is the square root of the population of France in 2023?"
# func_agent_response = function_agent_executor.invoke({"input": query_func})
# print(f"Function Agent Response:\n{func_agent_response['output']}")
# except ImportError as e:
# print(f"Agent Tool Dependency Error: {e}. Skipping function agent example.")
# except Exception as e:
# print(f"Function Agent Error: {e}")
print("Skipping Function Agent example as it depends on tool libraries.")
Building a Simple LangChain Application
Let's combine these components to build a simple application: a conversational agent that can answer questions about a specific document.
Objective: Document Q&A Agent
An agent that:
- Loads a text document
- Splits it into chunks
- Creates a vector store for retrieval
- Uses a conversational chain with memory to answer questions based on the document
import os
from dotenv import load_dotenv
from langchain_openai import ChatOpenAI, OpenAIEmbeddings
from langchain_community.document_loaders import TextLoader
from langchain.text_splitter import CharacterTextSplitter
from langchain_community.vectorstores import FAISS
from langchain.memory import ConversationBufferMemory
from langchain.chains import ConversationalRetrievalChain
# --- Setup ---
load_dotenv()
# Create a dummy document
doc_content = """
The Solar System consists of the Sun and the astronomical objects bound to it by gravity.
Mercury is the closest planet to the Sun.
Venus is the second planet from the Sun.
Earth is the third planet, known for its life.
Mars is the fourth planet, often called the Red Planet.
Jupiter is the fifth planet, the largest in the Solar System.
Saturn is the sixth planet, famous for its rings.
Uranus is the seventh planet, tilted on its side.
Neptune is the eighth and farthest known planet from the Sun.
"""
with open("solar_system.txt", "w") as f:
f.write(doc_content)
# --- LangChain Implementation ---
try:
# 1. Load Document
loader = TextLoader("./solar_system.txt")
documents = loader.load()
# 2. Split Document
text_splitter = CharacterTextSplitter(chunk_size=200, chunk_overlap=20)
texts = text_splitter.split_documents(documents)
# 3. Create Vector Store & Retriever
embeddings = OpenAIEmbeddings()
vectorstore = FAISS.from_documents(texts, embeddings)
retriever = vectorstore.as_retriever()
# 4. Setup Memory
memory = ConversationBufferMemory(memory_key="chat_history", return_messages=True)
# 5. Create Conversational Chain
qa_chain = ConversationalRetrievalChain.from_llm(
llm=ChatOpenAI(temperature=0, model="gpt-4o"),
retriever=retriever,
memory=memory
)
# --- Interact with the Chain ---
print("Ask questions about the Solar System document (type 'quit' to exit).")
while True:
query = input("Your Question: ")
if query.lower() == 'quit':
break
result = qa_chain.invoke({"question": query})
print(f"Answer: {result['answer']}\n")
except Exception as e:
print(f"An error occurred: {e}")
finally:
# Clean up dummy file
if os.path.exists("solar_system.txt"):
os.remove("solar_system.txt")
Next Steps: RAG Systems Essentials
LangChain provides powerful tools for building Retrieval-Augmented Generation (RAG) systems, which combine LLMs with external knowledge sources. Understanding RAG is crucial for building agents that can access and reason over up-to-date or proprietary information.
Key Takeaways from This Section:
- LangChain simplifies building complex AI applications by connecting components
- Core components include Models, Prompts, Memory, Indexes, Chains, and Agents
- LangChain helps manage context, tool use, memory, and workflows
- Understanding these components allows for building sophisticated conversational agents and RAG systems
In the next section, we delve into RAG Systems Essentials, exploring how to build AI agents that can retrieve and synthesize information from external knowledge bases.
Continue to RAG Systems Essentials →