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