Skip to content

604: LangChain - Agents

Chapter Overview

While [[603-LangChain-Chains|Chains]] follow a predetermined path, Agents use an LLM as a "reasoning engine" to dynamically decide a sequence of actions to take. Agents can interact with their environment using a set of Tools.

An agent is the core component for building applications that can solve complex problems that require external information or actions.


The ReAct Framework: Reason + Act

Most LangChain agents are based on the ReAct (Reason + Act) framework. The agent operates in a loop where it "thinks" about what to do next, takes an action, observes the result, and then repeats the process until the task is complete.

flowchart TD
    A[User Goal:<br/>"What was the high temperature in Paris yesterday?"] --> B{1. Think}
    B -- "I need today's date and a weather API" --> C[2. Act<br/>Tool: Search Engine<br/>Input: "yesterday's date"]
    C --> D{3. Observe}
    D -- "Result: 'November 15, 2023'" --> B
    B -- "Okay, now I need the weather for Paris on that date" --> E[2. Act<br/>Tool: Weather API<br/>Input: "Paris, 2023-11-15"]
    E --> F{3. Observe}
    F -- "Result: 'High: 12°C'" --> B
    B -- "I have the final answer." --> G[Final Answer:<br/>"The high temperature in Paris yesterday was 12°C."]

    style B fill:#fff3e0,stroke:#f57c00
    style C fill:#e3f2fd,stroke:#1976d2
    style E fill:#e3f2fd,stroke:#1976d2
    style G fill:#c8e6c9,stroke:#1B5E20
    style A fill:#f3e5f5,stroke:#7b1fa2
    style D fill:#fce4ec,stroke:#c2185b
    style F fill:#fce4ec,stroke:#c2185b

Agent Components

An agent system consists of three main components:

1. The Agent (Brain)

The LLM that decides what actions to take based on the current state and available tools.

2. Tools (Hands)

External functions or APIs that the agent can use to interact with the world.

3. Executor (Body)

The runtime that manages the agent's execution loop, handles tool calls, and maintains state.

flowchart LR
    A[Agent<br/>LLM Brain] --> B[Tool Selection]
    B --> C[Tool 1<br/>Web Search]
    B --> D[Tool 2<br/>Calculator]
    B --> E[Tool 3<br/>Database]
    B --> F[Tool N<br/>...]

    C --> G[Executor<br/>Action Loop]
    D --> G
    E --> G
    F --> G

    G --> H[Result Processing]
    H --> I[Continue or Stop?]
    I -- Continue --> A
    I -- Stop --> J[Final Answer]

    style A fill:#fff3e0,stroke:#f57c00
    style G fill:#e8f5e8,stroke:#388e3c
    style J fill:#c8e6c9,stroke:#1B5E20

Creating Your First Agent

Step 1: Define Tools

from langchain.agents import Tool
from langchain.utilities import SerpAPIWrapper
from langchain.utilities import OpenWeatherMapAPIWrapper

# Web search tool
search = SerpAPIWrapper()
search_tool = Tool(
    name="Search",
    description="Useful for finding current information on the internet",
    func=search.run
)

# Weather tool
weather = OpenWeatherMapAPIWrapper()
weather_tool = Tool(
    name="Weather",
    description="Useful for getting weather information for a specific location",
    func=weather.run
)

# Calculator tool
def calculate(expression):
    """Safely evaluate mathematical expressions"""
    try:
        return str(eval(expression))
    except:
        return "Error: Invalid mathematical expression"

calculator_tool = Tool(
    name="Calculator",
    description="Useful for mathematical calculations",
    func=calculate
)

tools = [search_tool, weather_tool, calculator_tool]

Step 2: Create the Agent

from langchain.agents import initialize_agent, AgentType
from langchain.llms import OpenAI

# Initialize the LLM
llm = OpenAI(temperature=0)

# Create the agent
agent = initialize_agent(
    tools=tools,
    llm=llm,
    agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True
)

# Use the agent
result = agent.run(
    "What's the weather like in Paris today? If it's above 20°C, calculate 20 * 1.8 + 32"
)

Agent Types

LangChain provides several pre-built agent types:

1. Zero-Shot ReAct Agent

Makes decisions based solely on the current input and available tools.

agent = initialize_agent(
    tools=tools,
    llm=llm,
    agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True
)

2. Conversational ReAct Agent

Maintains conversation history and can reference previous interactions.

from langchain.memory import ConversationBufferMemory

memory = ConversationBufferMemory(memory_key="chat_history")

agent = initialize_agent(
    tools=tools,
    llm=llm,
    agent_type=AgentType.CONVERSATIONAL_REACT_DESCRIPTION,
    memory=memory,
    verbose=True
)

3. Self-Ask with Search Agent

Specialized for breaking down complex questions into sub-questions.

agent = initialize_agent(
    [search_tool],
    llm=llm,
    agent_type=AgentType.SELF_ASK_WITH_SEARCH,
    verbose=True
)

Custom Tools

You can create custom tools for specific use cases:

Simple Function Tool

from langchain.tools import tool

@tool
def word_count(text: str) -> int:
    """Count the number of words in a text"""
    return len(text.split())

# Use the tool
tools = [word_count]
agent = initialize_agent(tools, llm, agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION)

Class-Based Tool

from langchain.tools import BaseTool
from typing import Optional

class DatabaseQueryTool(BaseTool):
    name = "database_query"
    description = "Execute SQL queries on the database"

    def _run(self, query: str) -> str:
        # Implement your database query logic
        return f"Executed query: {query}"

    def _arun(self, query: str) -> str:
        # Async version
        raise NotImplementedError("Async not implemented")

# Use the custom tool
db_tool = DatabaseQueryTool()
tools = [db_tool]

Tool with Parameters

from langchain.tools import Tool
from langchain.utilities import Requests

def make_api_request(url: str) -> str:
    """Make a GET request to the specified URL"""
    response = requests.get(url)
    return response.text

api_tool = Tool(
    name="API_Request",
    description="Make HTTP GET requests to APIs",
    func=make_api_request
)

Agent Memory

Agents can maintain different types of memory:

Conversation Buffer Memory

from langchain.memory import ConversationBufferMemory

memory = ConversationBufferMemory(memory_key="chat_history")

agent = initialize_agent(
    tools=tools,
    llm=llm,
    agent_type=AgentType.CONVERSATIONAL_REACT_DESCRIPTION,
    memory=memory,
    verbose=True
)

Summary Memory

from langchain.memory import ConversationSummaryMemory

memory = ConversationSummaryMemory(
    llm=llm,
    memory_key="chat_history"
)

Entity Memory

from langchain.memory import ConversationEntityMemory

memory = ConversationEntityMemory(
    llm=llm,
    memory_key="chat_history",
    entity_key="entities"
)

Advanced Agent Patterns

Multi-Agent Systems

# Create specialized agents
research_agent = initialize_agent(
    [search_tool],
    llm,
    agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True
)

analysis_agent = initialize_agent(
    [calculator_tool],
    llm,
    agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True
)

# Coordinate between agents
def multi_agent_workflow(query):
    # Research phase
    research_result = research_agent.run(f"Research information about: {query}")

    # Analysis phase
    analysis_result = analysis_agent.run(f"Analyze this data: {research_result}")

    return analysis_result

Agent with Custom Prompts

from langchain.agents import ZeroShotAgent, AgentExecutor

# Custom prompt template
prompt = ZeroShotAgent.create_prompt(
    tools,
    prefix="You are a helpful assistant that always thinks step by step.",
    suffix="Begin! Remember to think carefully about each step.\n{agent_scratchpad}",
    input_variables=["input", "agent_scratchpad"]
)

# Create agent with custom prompt
llm_chain = LLMChain(llm=llm, prompt=prompt)
agent = ZeroShotAgent(llm_chain=llm_chain, tools=tools)
agent_executor = AgentExecutor.from_agent_and_tools(
    agent=agent,
    tools=tools,
    verbose=True
)

Error Handling and Debugging

Handling Tool Errors

from langchain.tools import Tool

def safe_calculator(expression):
    try:
        result = eval(expression)
        return f"Result: {result}"
    except ZeroDivisionError:
        return "Error: Division by zero"
    except Exception as e:
        return f"Error: {str(e)}"

calculator_tool = Tool(
    name="Calculator",
    description="Performs mathematical calculations",
    func=safe_calculator
)

Agent Timeouts

import signal
from contextlib import contextmanager

@contextmanager
def timeout(duration):
    def timeout_handler(signum, frame):
        raise TimeoutError(f"Operation timed out after {duration} seconds")

    signal.signal(signal.SIGALRM, timeout_handler)
    signal.alarm(duration)
    try:
        yield
    finally:
        signal.alarm(0)

# Use with agent
try:
    with timeout(30):  # 30 second timeout
        result = agent.run("Complex query that might take a while")
except TimeoutError:
    print("Agent execution timed out")

Debugging Agent Behavior

# Enable detailed logging
import logging

logging.basicConfig(level=logging.DEBUG)

# Use verbose mode
agent = initialize_agent(
    tools=tools,
    llm=llm,
    agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    verbose=True,
    return_intermediate_steps=True
)

# Get detailed execution trace
result = agent("What's 2 + 2?")
print("Intermediate steps:", result["intermediate_steps"])

Best Practices

1. Tool Design

  • Keep tools focused on single responsibilities
  • Provide clear, descriptive names and descriptions
  • Handle errors gracefully
  • Validate inputs

2. Agent Configuration

  • Choose the right agent type for your use case
  • Use appropriate memory types
  • Set reasonable timeouts
  • Implement proper error handling

3. Prompt Engineering

  • Be specific about expected behavior
  • Provide examples when possible
  • Use clear, unambiguous language
  • Test with various inputs

4. Performance Optimization

  • Limit the number of tools to avoid confusion
  • Use caching for expensive operations
  • Monitor token usage
  • Implement result validation

Common Pitfalls

1. Tool Overload

Don't give agents too many tools - it can lead to confusion and poor performance.

2. Infinite Loops

Agents can get stuck in loops. Implement safeguards:

agent = initialize_agent(
    tools=tools,
    llm=llm,
    agent_type=AgentType.ZERO_SHOT_REACT_DESCRIPTION,
    max_iterations=10,  # Limit iterations
    verbose=True
)

3. Unclear Tool Descriptions

Poor tool descriptions lead to misuse. Be specific about what each tool does.

4. Lack of Error Handling

Always handle potential errors in both tools and agent execution.


Key Takeaways

  • Agents use LLMs as reasoning engines to dynamically decide actions
  • ReAct Framework combines reasoning and acting in a loop
  • Tools are the agent's interface to the external world
  • Memory allows agents to maintain context across interactions
  • Agent Types provide different patterns for different use cases
  • Custom Tools enable domain-specific functionality
  • Error Handling is crucial for robust agent applications

Agents are powerful but require careful design and testing to work reliably in production environments.