HOW-TO · RAG
How to Implement Tool Use in AI Agents
Target environment
Ubuntu 24.04 · Ollama 0.4.x
PREREQUISITES
Agent framework set up, tool functions defined, Python 3.10+
What this does
Tool use enables agents to go beyond text generation by calling external functions — APIs, databases, calculators — and incorporating the results into their reasoning. This guide integrates tools into the agent decision loop.
Steps
- Define tools as callable functions with metadata. Each tool needs a name, description, and parameter schema.
def search_web(query: str, max_results: int = 5) -> list[dict]:
"""Search the web for a query."""
# Implementation: call search API
return [{"title": "Result 1", "url": "https://example.com"}]
def calculate(expression: str) -> str:
"""Evaluate a mathematical expression."""
return str(eval(expression))
TOOL_SCHEMAS = [
{
"type": "function",
"function": {
"name": "search_web",
"description": "Search the web for information",
"parameters": {
"type": "object",
"properties": {
"query": {"type": "string", "description": "Search query"},
"max_results": {"type": "integer", "default": 5}
},
"required": ["query"]
}
}
},
{
"type": "function",
"function": {
"name": "calculate",
"description": "Evaluate a math expression",
"parameters": {
"type": "object",
"properties": {
"expression": {"type": "string"}
},
"required": ["expression"]
}
}
}
]
- Map tool names to functions.
TOOL_MAP = {
"search_web": search_web,
"calculate": calculate,
}
- Process tool calls in the agent loop. After the LLM responds, check for tool calls.
def process_tool_calls(message, messages):
for tc in message.tool_calls:
func = TOOL_MAP.get(tc.function.name)
if not func:
result = {"error": f"Unknown tool: {tc.function.name}"}
else:
args = json.loads(tc.function.arguments)
try:
result = func(**args)
except Exception as e:
result = {"error": str(e)}
messages.append({
"role": "tool",
"tool_call_id": tc.id,
"content": json.dumps(result)
})
- Add tool output formatting. Convert complex results to text the LLM can understand.
def format_tool_result(result) -> str:
if isinstance(result, list):
items = "\n".join(f"- {r['title']}: {r['url']}" for r in result[:5])
return f"Search results:\n{items}"
return str(result)
- Handle parallel tool calls. The LLM may request multiple tools in one response. Execute them concurrently.
import asyncio
async def process_parallel_tool_calls(tool_calls):
async def call(tc):
func = TOOL_MAP[tc.function.name]
args = json.loads(tc.function.arguments)
return tc.id, await asyncio.to_thread(func, **args)
tasks = [call(tc) for tc in tool_calls]
results = await asyncio.gather(*tasks)
return [{"tool_call_id": tid, "content": str(r)} for tid, r in results]
Verification
python -c "
from langchain.tools import tool
@tool
def double(n: int) -> int:
'''Double a number.'''
return n * 2
print(double.invoke({'n': 21}))
# Expected: 42
"
Common failures
- Tool function signature mismatch. The LLM may pass string arguments when the function expects integers. Add type coercion.
- No error handling in tool execution. A tool that raises an unhandled exception breaks the agent loop. Wrap all tool bodies in try/except.
- Side effects without confirmation. Tools that modify state (delete, write) should require explicit user confirmation before execution.
- Version mismatch - The installed package or runtime differs from the command shown; check the version first and rerun the smallest verification command.
- Local environment drift - Another service, virtual environment, model, or path is being used; print the active binary path and configuration before changing the guide steps.
Related guides
- How to Build Core AI Agent Architecture
- How to Create Agent Decision-Making Logic