Introduction to LangChain

LangChain is a powerful framework for building applications with large language models. It provides abstractions for chains, agents, tools, and memory, making it easier to create sophisticated AI systems.

🔗 Chains

Compose LLMs with other components in sequences

🤖 Agents

LLMs that can use tools and make decisions

🧠 Memory

Persist state between chain/agent calls

🔧 Tools

Functions agents can use to interact with the world

📚 Retrievers

Interface with vector stores and databases

📊 Callbacks

Hook into various stages of execution

Building Your First LangChain Agent

Installation & Setup

# Install LangChain and dependencies
pip install langchain langchain-openai langchain-community
pip install langchain-experimental langchainhub
pip install chromadb tiktoken

# For LangGraph (advanced agents)
pip install langgraph

# Environment setup
import os
os.environ["OPENAI_API_KEY"] = "your-api-key"
os.environ["LANGCHAIN_TRACING_V2"] = "true"  # Enable tracing
os.environ["LANGCHAIN_API_KEY"] = "your-langsmith-key"
                

Basic Agent with Tools

from langchain.agents import create_openai_functions_agent, AgentExecutor
from langchain_openai import ChatOpenAI
from langchain.tools import Tool, tool
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_community.tools import DuckDuckGoSearchRun
from langchain.tools.retriever import create_retriever_tool
import requests

# Initialize LLM
llm = ChatOpenAI(model="gpt-4", temperature=0)

# Define custom tools
@tool
def calculate(expression: str) -> str:
    """Evaluate a mathematical expression."""
    try:
        result = eval(expression)
        return f"The result is: {result}"
    except:
        return "Invalid expression"

@tool
def get_weather(city: str) -> str:
    """Get current weather for a city."""
    # In production, use a real weather API
    return f"The weather in {city} is sunny, 22°C"

# Web search tool
search = DuckDuckGoSearchRun()
search_tool = Tool(
    name="web_search",
    func=search.run,
    description="Search the web for current information"
)

# Create agent
tools = [calculate, get_weather, search_tool]

# Agent prompt
prompt = ChatPromptTemplate.from_messages([
    ("system", """You are a helpful AI assistant with access to various tools.
    Use them to answer questions accurately. Think step-by-step."""),
    ("user", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])

# Create the agent
agent = create_openai_functions_agent(llm, tools, prompt)

# Create executor
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,
    return_intermediate_steps=True,
    max_iterations=5,
    early_stopping_method="generate"
)

# Use the agent
result = agent_executor.invoke({
    "input": "What's the weather in Tokyo and calculate 25% of 480"
})

print(result["output"])
                

Advanced Agent Types

1. ReAct Agent

from langchain.agents import create_react_agent
from langchain import hub

# Pull ReAct prompt from hub
react_prompt = hub.pull("hwchase17/react")

# Create ReAct agent
react_agent = create_react_agent(
    llm=llm,
    tools=tools,
    prompt=react_prompt
)

react_executor = AgentExecutor(
    agent=react_agent,
    tools=tools,
    verbose=True,
    handle_parsing_errors=True
)

# ReAct pattern: Thought → Action → Observation → loop
result = react_executor.invoke({
    "input": "Find the latest AI news and summarize the top 3 stories"
})
                

2. Self-Ask with Search

from langchain.agents import create_self_ask_with_search_agent

# This agent decomposes questions into sub-questions
self_ask_agent = create_self_ask_with_search_agent(
    llm=llm,
    search_tool=search_tool,
    prompt=hub.pull("hwchase17/self-ask-with-search")
)

# Example: Complex multi-hop question
result = AgentExecutor(
    agent=self_ask_agent,
    tools=[search_tool],
    verbose=True
).invoke({
    "input": "What is the population of the capital of the country where Tesla's largest factory outside the US is located?"
})
                

3. Plan-and-Execute Agent

from langchain_experimental.plan_and_execute import (
    PlanAndExecute,
    load_agent_executor,
    load_chat_planner
)

# Create planner and executor
planner = load_chat_planner(llm)
executor = load_agent_executor(llm, tools, verbose=True)

# Create plan-and-execute agent
plan_agent = PlanAndExecute(
    planner=planner,
    executor=executor,
    verbose=True
)

# Complex task requiring planning
result = plan_agent.run(
    "Create a comprehensive market analysis report for electric vehicles: "
    "1) Current market size, 2) Top 5 manufacturers, 3) Growth projections, "
    "4) Key challenges, 5) Investment opportunities"
)
                

LangChain Agent Architecture

Agent Execution Flow

User Input
Agent Brain
(LLM)
Tool Selection
Tool Execution
Observation
Decision
(Continue/Stop)

Complete Agent System

from langchain.memory import ConversationBufferWindowMemory
from langchain.callbacks import StdOutCallbackHandler
from langchain_community.vectorstores import Chroma
from langchain_openai import OpenAIEmbeddings
from langchain.text_splitter import RecursiveCharacterTextSplitter

class AdvancedAgent:
    def __init__(self):
        self.llm = ChatOpenAI(model="gpt-4", temperature=0)
        self.memory = ConversationBufferWindowMemory(
            k=10,
            return_messages=True,
            memory_key="chat_history"
        )
        self.tools = self._setup_tools()
        self.agent = self._create_agent()
        
    def _setup_tools(self):
        tools = []
        
        # 1. Vector store tool for RAG
        embeddings = OpenAIEmbeddings()
        vectorstore = Chroma(
            persist_directory="./chroma_db",
            embedding_function=embeddings
        )
        
        retriever_tool = create_retriever_tool(
            vectorstore.as_retriever(),
            "knowledge_base",
            "Search internal knowledge base for information"
        )
        tools.append(retriever_tool)
        
        # 2. Code execution tool
        @tool
        def execute_python(code: str) -> str:
            """Execute Python code and return the result."""
            try:
                # Use exec with restricted globals for safety
                exec_globals = {"__builtins__": {}}
                exec(code, exec_globals)
                return str(exec_globals)
            except Exception as e:
                return f"Error: {e}"
        
        tools.append(execute_python)
        
        # 3. File operations
        @tool
        def read_file(filepath: str) -> str:
            """Read contents of a file."""
            try:
                with open(filepath, 'r') as f:
                    return f.read()
            except Exception as e:
                return f"Error reading file: {e}"
        
        @tool
        def write_file(filepath: str, content: str) -> str:
            """Write content to a file."""
            try:
                with open(filepath, 'w') as f:
                    f.write(content)
                return f"Successfully wrote to {filepath}"
            except Exception as e:
                return f"Error writing file: {e}"
        
        tools.extend([read_file, write_file])
        
        return tools
    
    def _create_agent(self):
        prompt = ChatPromptTemplate.from_messages([
            ("system", """You are an advanced AI assistant with access to:
            - Knowledge base search
            - Python code execution
            - File operations
            - Web search
            
            Always think step-by-step and use tools when needed.
            Maintain context from previous conversations."""),
            MessagesPlaceholder(variable_name="chat_history"),
            ("user", "{input}"),
            MessagesPlaceholder(variable_name="agent_scratchpad"),
        ])
        
        agent = create_openai_functions_agent(
            self.llm,
            self.tools,
            prompt
        )
        
        return AgentExecutor(
            agent=agent,
            tools=self.tools,
            memory=self.memory,
            verbose=True,
            return_intermediate_steps=True,
            max_iterations=10,
            callbacks=[StdOutCallbackHandler()]
        )
    
    def run(self, query: str):
        """Execute agent with query."""
        return self.agent.invoke({"input": query})
    
    def add_to_knowledge_base(self, documents):
        """Add documents to vector store."""
        text_splitter = RecursiveCharacterTextSplitter(
            chunk_size=1000,
            chunk_overlap=200
        )
        texts = text_splitter.split_documents(documents)
        
        embeddings = OpenAIEmbeddings()
        vectorstore = Chroma.from_documents(
            texts,
            embeddings,
            persist_directory="./chroma_db"
        )
        vectorstore.persist()

# Usage
agent_system = AdvancedAgent()
result = agent_system.run(
    "Search the knowledge base for information about LangChain, "
    "then write a Python script that demonstrates its key features"
)
                

LangGraph: Next-Generation Agents

LangGraph enables building stateful, multi-agent applications with cycles, controllability, and persistence.

Basic LangGraph Agent

from langgraph.graph import StateGraph, END
from typing import TypedDict, Annotated, Sequence
import operator
from langchain_core.messages import BaseMessage, HumanMessage, AIMessage

# Define state
class AgentState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], operator.add]
    next_step: str

# Create graph
workflow = StateGraph(AgentState)

# Define nodes
def researcher(state):
    """Research node that gathers information."""
    messages = state["messages"]
    last_message = messages[-1].content
    
    # Simulate research
    research_result = f"Research on: {last_message}\nFound: [relevant information]"
    
    return {
        "messages": [AIMessage(content=research_result)],
        "next_step": "analyzer"
    }

def analyzer(state):
    """Analyze research results."""
    messages = state["messages"]
    research = messages[-1].content
    
    analysis = f"Analysis: The research shows... [detailed analysis]"
    
    return {
        "messages": [AIMessage(content=analysis)],
        "next_step": "writer"
    }

def writer(state):
    """Generate final output."""
    messages = state["messages"]
    analysis = messages[-1].content
    
    final_output = f"Final Report:\n{analysis}\n[Formatted output]"
    
    return {
        "messages": [AIMessage(content=final_output)],
        "next_step": "end"
    }

# Add nodes to graph
workflow.add_node("researcher", researcher)
workflow.add_node("analyzer", analyzer)
workflow.add_node("writer", writer)

# Define edges
workflow.set_entry_point("researcher")
workflow.add_edge("researcher", "analyzer")
workflow.add_edge("analyzer", "writer")
workflow.add_edge("writer", END)

# Compile graph
app = workflow.compile()

# Run the graph
initial_state = {
    "messages": [HumanMessage(content="Analyze the AI agent market")],
    "next_step": "researcher"
}

result = app.invoke(initial_state)
print(result["messages"][-1].content)
                

Advanced LangGraph: Multi-Agent Collaboration

from langgraph.graph import StateGraph, END
from langgraph.checkpoint import MemorySaver
from langchain_openai import ChatOpenAI

class MultiAgentState(TypedDict):
    messages: Annotated[Sequence[BaseMessage], operator.add]
    current_agent: str
    task_completed: bool
    results: dict

class MultiAgentSystem:
    def __init__(self):
        self.llm = ChatOpenAI(model="gpt-4")
        self.workflow = StateGraph(MultiAgentState)
        self._setup_agents()
        self._setup_edges()
        
    def _setup_agents(self):
        # Coordinator Agent
        def coordinator(state):
            """Decides which agent should act next."""
            prompt = f"""
            Current state: {state['results']}
            Messages: {state['messages'][-1].content if state['messages'] else 'Start'}
            
            Decide next agent: researcher, coder, reviewer, or end
            """
            
            response = self.llm.invoke(prompt)
            next_agent = self._parse_next_agent(response.content)
            
            return {
                "current_agent": next_agent,
                "messages": [AIMessage(content=f"Routing to {next_agent}")]
            }
        
        # Researcher Agent
        def researcher(state):
            """Researches information."""
            task = state['messages'][-2].content if len(state['messages']) > 1 else ""
            
            research = self.llm.invoke(
                f"Research this topic and provide findings: {task}"
            )
            
            state['results']['research'] = research.content
            
            return {
                "messages": [AIMessage(content=research.content)],
                "current_agent": "coordinator"
            }
        
        # Coder Agent
        def coder(state):
            """Writes code based on requirements."""
            context = state['results'].get('research', '')
            
            code = self.llm.invoke(
                f"Write code based on: {context}"
            )
            
            state['results']['code'] = code.content
            
            return {
                "messages": [AIMessage(content=code.content)],
                "current_agent": "coordinator"
            }
        
        # Reviewer Agent
        def reviewer(state):
            """Reviews and provides feedback."""
            code = state['results'].get('code', '')
            
            review = self.llm.invoke(
                f"Review this code and suggest improvements: {code}"
            )
            
            state['results']['review'] = review.content
            
            return {
                "messages": [AIMessage(content=review.content)],
                "current_agent": "coordinator",
                "task_completed": True
            }
        
        # Add all nodes
        self.workflow.add_node("coordinator", coordinator)
        self.workflow.add_node("researcher", researcher)
        self.workflow.add_node("coder", coder)
        self.workflow.add_node("reviewer", reviewer)
    
    def _setup_edges(self):
        # Conditional routing
        def route_next(state):
            if state.get('task_completed', False):
                return END
            return state['current_agent']
        
        self.workflow.set_entry_point("coordinator")
        
        # Add conditional edges from coordinator
        self.workflow.add_conditional_edges(
            "coordinator",
            route_next,
            {
                "researcher": "researcher",
                "coder": "coder",
                "reviewer": "reviewer",
                END: END
            }
        )
        
        # All agents return to coordinator
        self.workflow.add_edge("researcher", "coordinator")
        self.workflow.add_edge("coder", "coordinator")
        self.workflow.add_edge("reviewer", "coordinator")
    
    def compile(self):
        # Add memory for persistence
        memory = MemorySaver()
        return self.workflow.compile(checkpointer=memory)

# Usage
system = MultiAgentSystem()
app = system.compile()

# Run with streaming
config = {"configurable": {"thread_id": "main-thread"}}

for output in app.stream({
    "messages": [HumanMessage(content="Create a web scraping tool")],
    "current_agent": "coordinator",
    "results": {}
}, config):
    print(output)
                

Production Best Practices

Performance Optimization:
  • Use streaming for long-running agents
  • Implement caching with Redis or in-memory stores
  • Batch tool calls when possible
  • Use async operations for I/O-bound tasks
  • Implement proper error handling and retries
  • Monitor token usage and costs

Error Handling & Fallbacks

from langchain.agents import AgentExecutor
from tenacity import retry, stop_after_attempt, wait_exponential

class RobustAgent:
    def __init__(self):
        self.primary_llm = ChatOpenAI(model="gpt-4")
        self.fallback_llm = ChatOpenAI(model="gpt-3.5-turbo")
        
    @retry(
        stop=stop_after_attempt(3),
        wait=wait_exponential(multiplier=1, min=4, max=10)
    )
    def execute_with_retry(self, query):
        try:
            # Try with primary LLM
            return self.run_agent(self.primary_llm, query)
        except Exception as e:
            print(f"Primary failed: {e}, using fallback")
            return self.run_agent(self.fallback_llm, query)
    
    def run_agent(self, llm, query):
        agent_executor = AgentExecutor(
            agent=create_openai_functions_agent(llm, tools, prompt),
            tools=tools,
            handle_parsing_errors=True,
            max_iterations=3,
            early_stopping_method="generate"
        )
        
        return agent_executor.invoke({"input": query})
                

Monitoring & Observability

# LangSmith Integration
import langsmith

client = langsmith.Client()

# Custom callbacks for monitoring
from langchain.callbacks.base import BaseCallbackHandler

class MonitoringCallback(BaseCallbackHandler):
    def __init__(self):
        self.token_count = 0
        self.tool_calls = []
        
    def on_llm_start(self, serialized, prompts, **kwargs):
        print(f"LLM Start: {prompts}")
        
    def on_llm_end(self, response, **kwargs):
        # Track token usage
        if hasattr(response, 'llm_output'):
            tokens = response.llm_output.get('token_usage', {})
            self.token_count += tokens.get('total_tokens', 0)
    
    def on_tool_start(self, serialized, input_str, **kwargs):
        self.tool_calls.append({
            'tool': serialized.get('name'),
            'input': input_str,
            'timestamp': datetime.now()
        })
    
    def get_metrics(self):
        return {
            'total_tokens': self.token_count,
            'tool_calls': len(self.tool_calls),
            'tools_used': list(set(t['tool'] for t in self.tool_calls))
        }

# Use in agent
monitor = MonitoringCallback()
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    callbacks=[monitor],
    verbose=True
)

result = agent_executor.invoke({"input": "Complex task"})
print(monitor.get_metrics())
                

Comparison: LangChain vs Others

Feature LangChain CrewAI AutoGen
Learning Curve Moderate-High Low-Moderate High
Flexibility Very High Moderate High
Multi-Agent Via LangGraph Native Native
Tool Ecosystem Extensive Growing Moderate
Production Ready Yes Yes Yes
Best For Complex chains, RAG Multi-agent teams Research, Complex reasoning