You are viewing a free preview of this lesson.
Subscribe to unlock all 10 lessons in this course and every other course on LearningBro.
Tools are what give agents their power. Without tools, an agent is just a chatbot. This lesson covers how to design tool libraries, implement tool selection strategies, handle tool results and errors gracefully, and dynamically register tools at runtime.
An LLM on its own can only generate text. Tools let agents interact with the real world:
| Capability | Without Tools | With Tools |
|---|---|---|
| Get current data | Hallucinate or say "I don't know" | Search the web, query a database |
| Perform calculations | Approximate (often wrong) | Execute exact calculations |
| Take actions | Describe what to do | Actually do it (send email, create file) |
| Access private data | Cannot | Query APIs with authentication |
A well-designed tool library is the foundation of an effective agent.
from dataclasses import dataclass, field
from typing import Callable, Any
@dataclass
class Tool:
name: str
description: str
parameters: dict # JSON Schema describing expected args
function: Callable # The actual function to execute
requires_confirmation: bool = False
tags: list[str] = field(default_factory=list)
import requests
import json
import subprocess
def web_search(query: str) -> str:
"""Search the web and return top results."""
# In production, use a real search API
response = requests.get(
"https://api.search.example.com/search",
params={"q": query, "limit": 5},
)
return json.dumps(response.json()["results"])
def read_file(path: str) -> str:
"""Read the contents of a file."""
with open(path, "r") as f:
return f.read()
def run_python(code: str) -> str:
"""Execute Python code in a sandboxed environment."""
result = subprocess.run(
["python3", "-c", code],
capture_output=True, text=True, timeout=10,
)
if result.returncode != 0:
return f"Error: {result.stderr}"
return result.stdout
# Build the tool registry
tools = [
Tool(
name="web_search",
description="Search the web for current information.",
parameters={
"type": "object",
"properties": {
"query": {"type": "string", "description": "The search query"}
},
"required": ["query"],
},
function=web_search,
tags=["search", "read-only"],
),
Tool(
name="read_file",
description="Read the contents of a file at the given path.",
parameters={
"type": "object",
"properties": {
"path": {"type": "string", "description": "File path to read"}
},
"required": ["path"],
},
function=read_file,
tags=["filesystem", "read-only"],
),
Tool(
name="run_python",
description="Execute Python code and return the output.",
parameters={
"type": "object",
"properties": {
"code": {"type": "string", "description": "Python code to execute"}
},
"required": ["code"],
},
function=run_python,
requires_confirmation=True,
tags=["code", "dangerous"],
),
]
When an agent has many tools, selecting the right one becomes critical.
Provide all tool descriptions to the model and let it decide:
def build_tool_prompt(tools: list[Tool]) -> str:
lines = ["Available tools:"]
for tool in tools:
params = ", ".join(
f"{k}: {v.get('description', '')}"
for k, v in tool.parameters.get("properties", {}).items()
)
lines.append(f"- {tool.name}({params}): {tool.description}")
return "\n".join(lines)
When you have dozens or hundreds of tools, retrieve only the relevant ones using embeddings:
from openai import OpenAI
client = OpenAI()
def embed_text(text: str) -> list[float]:
response = client.embeddings.create(
model="text-embedding-3-small",
input=text,
)
return response.data[0].embedding
def cosine_similarity(a: list[float], b: list[float]) -> float:
dot = sum(x * y for x, y in zip(a, b))
norm_a = sum(x ** 2 for x in a) ** 0.5
norm_b = sum(x ** 2 for x in b) ** 0.5
return dot / (norm_a * norm_b)
class ToolRetriever:
def __init__(self, tools: list[Tool]):
self.tools = tools
# Pre-compute embeddings for tool descriptions
self.embeddings = [
embed_text(f"{t.name}: {t.description}")
for t in tools
]
Subscribe to continue reading
Get full access to this lesson and all 10 lessons in this course.