Using Tools and Agents in LangChain: Autonomous LLM Agents
Agents are LLM-powered systems that decide which tools to use and iterate until they solve a problem. Unlike chains, which execute a fixed sequence, agents reason about which action to take next based on the problem. Tools are functions the agent can invoke—APIs, search, calculators, databases. Together, they enable autonomous reasoning loops where the LLM is the decision-maker.
I was manually routing queries to different functions until I built an LangChain agent. The agent learned to route itself—ask it anything and it decides whether to search the web, run code, or query a database. Zero hardcoded rules; it just reasons its way through.
Defining Tools
Tools are functions wrapped with metadata so the agent understands their purpose. Define them with the @tool decorator:
from langchain_core.tools import tool
import math
@tool
def calculate_area(radius: float) -> float:
"""Calculate the area of a circle given the radius."""
return math.pi * radius ** 2
@tool
def get_weather(location: str) -> str:
"""Get the current weather for a location."""
# In production, call a real weather API
return f"Sunny, 72°F in {location}"
# Tools are now callable by the agent
tools = [calculate_area, get_weather]
The docstring is crucial—the agent reads it to understand what the tool does. Docstrings should be concise and describe parameters and return values.
For more control, use the Tool class directly:
from langchain_core.tools import Tool
def multiply(a: float, b: float) -> float:
return a * b
multiply_tool = Tool(
name="multiply",
func=multiply,
description="Multiply two numbers together. Returns the product."
)
Creating an Agent with LangChain
Use create_react_agent() to build a ReAct (Reasoning and Acting) agent—the most common pattern:
from langchain_openai import ChatOpenAI
from langchain.agents import create_react_agent, AgentExecutor
from langchain import hub
# Create the model and tools
model = ChatOpenAI(model="gpt-4o-mini", temperature=0)
tools = [calculate_area, get_weather]
# Load a prompt template designed for ReAct agents
prompt = hub.pull("hwchase17/react")
# Create the agent
agent = create_react_agent(model, tools, prompt)
# Wrap it in an executor
agent_executor = AgentExecutor.from_agent_and_tools(
agent=agent,
tools=tools,
verbose=True, # Print reasoning steps
max_iterations=10 # Prevent infinite loops
)
# Run the agent
result = agent_executor.invoke({
"input": "What's the area of a circle with radius 5?"
})
print(result["output"])
The agent reasons through the problem:
- Thought: "I need to calculate the area of a circle. I have a calculate_area tool."
- Action: Calls
calculate_area(radius=5) - Observation: Receives
78.54 - Thought: "The area is about 78.54 square units."
- Final Answer: Returns the result
Multi-Step Agent Reasoning
Agents shine when problems require multiple steps:
@tool
def search_web(query: str) -> str:
"""Search the web for information."""
return f"Search results for '{query}': ... [mock result]"
@tool
def summarize_text(text: str) -> str:
"""Summarize a block of text."""
return f"Summary of {len(text)} chars: [mock summary]"
tools = [search_web, summarize_text]
agent_executor = AgentExecutor.from_agent_and_tools(
agent=agent,
tools=tools,
verbose=True
)
# Agent will search, then summarize without explicit orchestration
result = agent_executor.invoke({
"input": "Find information about Python async/await and summarize it."
})
The agent iterates:
- Search the web for "Python async/await"
- Receive search results
- Summarize the results
- Return the summary
No hardcoded orchestration—pure reasoning.
Handling Tool Errors and Retries
Agents can fail if tools error or they misuse them. Handle gracefully:
from langchain.agents import Tool
from langchain_core.tools import ToolException
@tool
def divide(a: float, b: float) -> float:
"""Divide two numbers. Raises an error if b is 0."""
if b == 0:
raise ToolException("Division by zero is undefined.")
return a / b
# Pass handle_tool_error=True to let the agent retry on errors
tool = Tool(
name="divide",
func=divide,
description="Divide a by b.",
handle_tool_error=True
)
When the agent triggers an error, it sees the error message and tries a different approach.
Tool Input Validation
Validate inputs before the agent calls a tool:
from pydantic import BaseModel, Field
class CalculateAreaInput(BaseModel):
radius: float = Field(gt=0, description="The radius, must be positive")
@tool(args_schema=CalculateAreaInput)
def calculate_area(radius: float) -> float:
"""Calculate circle area."""
return math.pi * radius ** 2
The agent sees the schema and knows not to pass negative radii.
Streaming Agent Output
For long-running agents, stream reasoning steps:
# Use stream() instead of invoke()
for event in agent_executor.stream({"input": "..."}):
print(event)
# Prints each Thought, Action, Observation as it happens
Great for user-facing applications where showing work-in-progress builds confidence.
Comparing Agent Types
| Type | Reasoning | Flexibility | Complexity |
|---|---|---|---|
| ReAct | Thought → Action → Observation | Very high | Medium |
| OpenAI Functions | Direct tool selection | Moderate | Low |
| Plan-and-Execute | Create plan, then execute | High | High |
| Conversational Agent | Multi-turn with memory | Moderate | Medium |
Agent Debugging
When agents misbehave, enable full debugging:
import langchain
langchain.debug = True
agent_executor.invoke({"input": "..."})
# Prints LLM calls, tool invocations, and reasoning
Look for:
- Wrong tool choice: Agent is misunderstanding the task
- Tool errors: Tools are throwing exceptions
- Max iterations hit: Agent is stuck in a loop
Key Takeaways
- Tools are functions wrapped with metadata so agents understand their purpose
- ReAct agents reason (Thought), act (call tool), and observe (receive result) iteratively
- Agents decide which tool to call; no hardcoded routing
- Multi-step problems are solved without explicit orchestration
- Use
verbose=Trueandlangchain.debug = Trueto debug agent reasoning - Stream agent outputs for user-facing applications
- Validate tool inputs with Pydantic schemas to prevent misuse
Frequently Asked Questions
How does the agent decide which tool to use?
The LLM reads the tool descriptions and reasons which is most relevant. It's not magic—it depends on clear descriptions and the model's capability.
Can agents use tools that aren't predefined?
No, but you can define a generic "run_python_code" tool and let the agent write Python to solve problems dynamically. This is powerful but requires careful sandboxing.
What if the agent gets stuck in a loop?
Set max_iterations to a reasonable number (default 10). If the agent hits the limit, it returns whatever progress it made.
Do agents work with local models?
Yes, with ChatOllama or similar. Local models reason less effectively than GPT-4, but they still work for simple tool-calling tasks.
Can I make the agent remember previous interactions?
Yes, add memory to the agent: AgentExecutor(..., memory=ConversationBufferMemory()). The agent will reference past conversations.