Advanced

Agents & Tools

Agents are LLM-powered systems that decide which tools to use, in what order, based on user input. They reason step-by-step, take actions, observe results, and iterate until the task is complete.

How Agents Work

Unlike chains (which follow a fixed sequence), agents dynamically choose their next action:

Agent Loop
User: "What's the weather in Tokyo and convert it to Fahrenheit?"

1. THINK: I need to get the weather in Tokyo first.
2. ACT:   Use weather_tool("Tokyo") → "22°C, sunny"
3. THINK: Now I need to convert 22°C to Fahrenheit.
4. ACT:   Use calculator("22 * 9/5 + 32") → "71.6"
5. ANSWER: "It's 22°C (71.6°F) and sunny in Tokyo."

Agent Types

Agent Type How It Works Best For
ReAct Reason + Act loop in text General-purpose, any model
OpenAI Functions Uses OpenAI's function calling API OpenAI models, structured tool use
XML Uses XML tags for tool calls Anthropic Claude models
Tool Calling Native tool/function calling Models with tool calling support

Built-in Tools

LangChain ships with many ready-to-use tools:

Python
# Search the web
from langchain_community.tools import TavilySearchResults
search = TavilySearchResults(max_results=3)

# Wikipedia
from langchain_community.tools import WikipediaQueryRun
from langchain_community.utilities import WikipediaAPIWrapper
wiki = WikipediaQueryRun(api_wrapper=WikipediaAPIWrapper())

# Python REPL (execute Python code)
from langchain_experimental.tools import PythonREPLTool
python_repl = PythonREPLTool()

# Calculator
from langchain_community.tools import LLMMathChain
# Or simply use PythonREPLTool for math

Custom Tools with @tool

Create your own tools with the @tool decorator:

Python
from langchain_core.tools import tool

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

@tool
def get_current_time() -> str:
    """Get the current date and time."""
    from datetime import datetime
    return datetime.now().strftime("%Y-%m-%d %H:%M:%S")

@tool
def lookup_user(user_id: str) -> dict:
    """Look up user information by their ID."""
    # In production, this would query a database
    users = {
        "123": {"name": "Alice", "role": "admin"},
        "456": {"name": "Bob", "role": "user"},
    }
    return users.get(user_id, {"error": "User not found"})

# The docstring becomes the tool description for the LLM
print(get_word_count.name)         # "get_word_count"
print(get_word_count.description)  # "Count the number of words..."

Building an Agent

Create an agent that uses tools to answer questions:

Python
from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

# 1. Define tools
tools = [get_word_count, get_current_time, lookup_user]

# 2. Create prompt with agent scratchpad
prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant. Use tools when needed."),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])

# 3. Create the agent
llm = ChatOpenAI(model="gpt-4o-mini")
agent = create_tool_calling_agent(llm, tools, prompt)

# 4. Wrap in AgentExecutor (manages the loop)
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    verbose=True,       # Print reasoning steps
    max_iterations=5,  # Safety limit
)

# 5. Run the agent
result = agent_executor.invoke({
    "input": "Look up user 123 and tell me the current time"
})
print(result["output"])

Agent with Memory

Add conversation memory so the agent remembers previous interactions:

Python
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder

prompt = ChatPromptTemplate.from_messages([
    ("system", "You are a helpful assistant."),
    MessagesPlaceholder(variable_name="chat_history"),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])

agent = create_tool_calling_agent(llm, tools, prompt)
agent_executor = AgentExecutor(agent=agent, tools=tools)

# Maintain history manually
chat_history = []

result = agent_executor.invoke({
    "input": "Look up user 123",
    "chat_history": chat_history,
})

# Add to history for next turn
from langchain_core.messages import HumanMessage, AIMessage
chat_history.append(HumanMessage(content="Look up user 123"))
chat_history.append(AIMessage(content=result["output"]))

Error Handling

Handle tool errors gracefully to prevent the agent from crashing:

Python
# Option 1: handle_tool_error in AgentExecutor
agent_executor = AgentExecutor(
    agent=agent,
    tools=tools,
    handle_parsing_errors=True,  # Retry on parse errors
    max_iterations=10,
)

# Option 2: Error handling in the tool itself
@tool
def safe_lookup(user_id: str) -> str:
    """Safely look up a user by ID."""
    try:
        # Database query here
        return f"User {user_id} found: Alice"
    except Exception as e:
        return f"Error looking up user: {str(e)}"

Complete Agent Example

A full research agent that can search the web and answer questions:

Python
from langchain_openai import ChatOpenAI
from langchain.agents import create_tool_calling_agent, AgentExecutor
from langchain_core.prompts import ChatPromptTemplate, MessagesPlaceholder
from langchain_core.tools import tool
from langchain_community.tools import TavilySearchResults

# Tools
search = TavilySearchResults(max_results=3)

@tool
def calculate(expression: str) -> str:
    """Evaluate a mathematical expression. Example: '2 + 2' or '100 * 0.15'"""
    try:
        return str(eval(expression))
    except:
        return "Invalid expression"

tools = [search, calculate]

# Agent prompt
prompt = ChatPromptTemplate.from_messages([
    ("system", """You are a research assistant. Use the search tool
to find information and the calculator for math.
Always cite your sources."""),
    MessagesPlaceholder(variable_name="chat_history", optional=True),
    ("human", "{input}"),
    MessagesPlaceholder(variable_name="agent_scratchpad"),
])

# Create and run
llm = ChatOpenAI(model="gpt-4o-mini", temperature=0)
agent = create_tool_calling_agent(llm, tools, prompt)
executor = AgentExecutor(agent=agent, tools=tools, verbose=True)

result = executor.invoke({
    "input": "What is the population of Japan and what is 15% of it?"
})
print(result["output"])
Moving forward: For production agent applications, consider using LangGraph instead of AgentExecutor. LangGraph gives you more control over the agent loop, supports cycles, branching, human-in-the-loop, and persistence. See the next lesson.

What's Next?

The next lesson covers LangGraph - the recommended way to build sophisticated, stateful, multi-step agents with graph-based orchestration.

Ready to Go Deeper?

Live instructor-led courses from our partners. Affiliate disclosure.