Real-World Tool Integrations
Part 9 covered the general, real pattern for wrapping an external API as a tool. This part builds four genuinely distinct, real tool types — each with its own specific, honest safety and design consideration, directly setting up parts 11 through 15's real agent projects.
Real web search as a tool
def web_search(query: str) -> list[dict]:
response = requests.get(
"https://api.search-provider.example.com/search",
params={"q": query}, headers={"Authorization": f"Bearer {SEARCH_API_KEY}"},
timeout=10,
)
results = response.json()["results"][:5] # a real, deliberate limit
return [{"title": r["title"], "url": r["url"], "snippet": r["snippet"]} for r in results]The real, deliberate [:5] limit matters directly — an unbounded real search result set adds real, unnecessary token cost (the AI Fundamentals series' own cost coverage) with diminishing real value past the first several results, exactly the same discipline part 9 applied to translating verbose API responses down to what's genuinely needed.
A real, read-only SQL query tool — with a genuine, hard safety boundary
import sqlite3
def query_sales_database(sql_query: str) -> list[dict]:
if not sql_query.strip().upper().startswith("SELECT"):
return {"error": "Only SELECT queries are permitted"}
conn = sqlite3.connect("sales.db")
conn.row_factory = sqlite3.Row
try:
rows = conn.execute(sql_query).fetchall()
return [dict(row) for row in rows]
finally:
conn.close()This explicit SELECT-only check is a genuinely critical, real safety boundary, not a minor detail — a real agent with an unrestricted SQL tool could, in principle, be reasoned (or manipulated, per part 20's security coverage) into executing a real DELETE or DROP TABLE statement. Restricting a database tool to read-only queries by construction, in code, is a far more reliable real safeguard than a prompt instruction alone asking the model to "only read data."
A real, scoped file-system tool
import os
ALLOWED_DIRECTORY = "/app/agent_workspace"
def read_file(filename: str) -> str:
full_path = os.path.join(ALLOWED_DIRECTORY, filename)
real_resolved_path = os.path.realpath(full_path)
if not real_resolved_path.startswith(os.path.realpath(ALLOWED_DIRECTORY)):
return "Error: access outside the allowed directory is not permitted"
with open(real_resolved_path) as f:
return f.read()The os.path.realpath check here is a real, deliberate defense against a genuine, well-known vulnerability class — a filename like "../../etc/passwd" could otherwise escape ALLOWED_DIRECTORY entirely; resolving the real, actual final path and verifying it genuinely stays within bounds closes that real gap.
A real, deliberately gated email-sending tool
def send_email(to: str, subject: str, body: str) -> dict:
# directly following the AI Projects series' own email-generator
# safeguard — queue for real, human approval, never send directly
pending = save_to_review_queue(to=to, subject=subject, body=body)
return {"status": "queued_for_review", "review_id": pending.id}This directly reuses the AI Projects series' own real safeguard — an email-sending tool available to an agent should genuinely queue for real, human review by default, not send immediately, for exactly the same reasoning that series established.
A real, practical pattern across all four: safety lives in the tool, not the prompt
Every one of these four real tools enforces its own actual safety
boundary in CODE — read-only SQL, a scoped file path, a gated
email send — rather than relying solely on a system prompt
instruction telling the model to "be careful"This is the same real, structural principle the LLM & Advanced AI series established for permission scoping — a real safety boundary belongs in the actual execution code, which cannot be reasoned around, rather than in a prompt instruction, which genuinely can be.
Next: building a real AI web research agent — assembling the search tool from this part into a complete, working competitive-research project for Bright Leaf Coffee's marketing team.