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.