Begin with the problem
Text alone cannot check an order or update a ticket. Tools give the surrounding application controlled ways to read information or perform actions.
tool description + input rules → model selects tool → application checks → tool executes → result
What you will learn
- Define tools, actions, arguments, observations, and tool registries.
- See how a tool turns a model decision into an external operation.
- Design narrow tool interfaces with validation, permissions, and useful error messages.
- Distinguish read-only tools from actions that change the world.
Current real-system grounding: Google’s tool documentation shows the critical difference between provider-executed built-in tools and custom functions executed by your application.
These official links document available product features. They do not reveal every provider’s private implementation, hidden reasoning, default setting, or internal limit.
1. The problem this module solves
Module 5 established that the LLM only decides which tool to use — this module covers everything else: what a tool is, how it’s described to the LLM, how the decision gets validated and executed, and what happens when any part of this chain goes wrong.
2. What Is a Tool?
A tool is a well-defined capability the agent can invoke to affect or observe its environment — a function, an API call, a database query, a file operation — described precisely enough that the LLM can understand when and how to use it.
Tool vs. API: an API is a broader concept -- a way for
software to talk to other software. A TOOL, in agent
terms, is typically a WRAPPER around an API (or a
function, or any capability) SPECIFICALLY packaged with
a description the LLM can reason about.
Tool vs. function: a function is CODE. A tool is that SAME code, plus
a real SCHEMA (name, description, parameters)
that makes it DISCOVERABLE and USABLE by an LLM's
reasoning process.
3. Why Agents Need Tools
Recall Module 1: a one-shot LLM can only generate text from its training knowledge. Tools are precisely what let an agent:
- OBSERVE the real, current world (check an order status, read a
file, query a database)
- ACT on the real world (send an email, process a refund, update a
record)
Without tools, an agent’s loop (Module 4) would have nothing real to observe or act upon beyond its own generated text — the loop would be structurally present but practically empty.
4. Tool Schema — How a Tool Is Described
name: check_shipping_carrier
description: "Looks up a package's delivery status given a tracking
number."
parameters:
tracking_number: string (required)
This schema is what the LLM reasons over when deciding whether and how to use a tool — directly connecting to Module 5, Section 4’s point about tool description quality. A vague description (“checks shipping”) produces worse tool selection than a precise one.
5. The Complete Flow
flowchart TD
U[User Request] --> LLM[LLM]
LLM --> Select[Select Tool]
Select --> Gen[Generate Tool Arguments]
Gen --> Val{Validate<br/>Parameters}
Val -->|Invalid| Err[Return Error<br/>to LLM]
Val -->|Valid| Exec[Tool Execution]
Exec --> Result[Tool Result]
Result --> LLM2[LLM: Next Decision]
Err --> LLM2
6. Tool Errors — A First-Class Concern
A tool call can fail for many reasons:
- Invalid or missing PARAMETERS (caught by validation, Section 5)
- The underlying API is DOWN or times out
- The requested resource doesn't EXIST (e.g., an invalid
order number)
- A PERMISSION error (Section 8)
Directly connecting to Module 4, Section 7: a tool error should become a real OBSERVATION the agent can reason about, not a silent crash. A well-designed tool wrapper catches errors and returns them in a structured form the LLM can understand and react to.
7. Multiple Tools — Choosing Among Several Options
An agent with SEVERAL available tools must have the LLM CHOOSE the right one, given the current situation:
- check_order_status
- check_shipping_carrier
- process_refund
- send_email
The QUALITY of each tool's description directly determines whether
the LLM selects correctly among these options -- Module 18's failure
modes covers "wrong tool selection" as a real, common failure.
8. Tool Permissions and Authentication — A Security Boundary
Boundary
PERMISSIONS: does THIS agent (or this specific user context)
have the RIGHT to call this tool at all? (e.g., a
customer-facing agent should NOT have access to an
"delete_all_records" tool)
AUTHENTICATION: does the TOOL ITSELF have valid credentials to
call the underlying API or service?
This directly previews Module 17’s Agent Security discussion: permission boundaries need to be enforced by the surrounding system (Module 5’s control logic), NOT left to the LLM’s judgment about whether an action seems appropriate. Exactly the same principle from your RAG course’s access-control module, applied here to actions instead of retrieval.
9. A Real Developer Example
TechCorp equips its support agent with four real tools:
| Tool | Purpose | Requires Human Approval? |
|---|---|---|
check_order_status | Read-only lookup | No |
check_shipping_carrier | Read-only lookup | No |
send_email | Sends a message to the customer | No, but logged |
process_refund | Moves real money | Yes (Module 16, Human-in-the-Loop) |
The agent’s control logic (Module 5) enforces this distinction — the LLM might decide to process a refund, but the surrounding system routes that specific decision through a human approval step before actual execution, exactly Module 8’s earlier framing of “LLM decides, application executes” extended with a real permission gate.
10. A Simple Agentic AI Connection
This entire module is the agentic connection — tools are precisely what let an agent’s loop (Module 4) actually do something in the real world, rather than merely reasoning in the abstract. Every subsequent module — planning (Module 8), ReAct (Module 9), multi-agent systems (Module 15) — assumes tools work exactly as this module describes.
11. How Is This Used in AI?
🤖 How Is This Used in AI?
Every production agent framework’s tool-calling mechanism (Module 22) implements exactly this flow — schema-described tools, LLM selection, parameter validation, structured error handling, and permission enforcement — because real agent reliability > depends on getting this entire chain right, not just the LLM’s reasoning step.
12. Real-World Applications
- Customer support agents with database and API access
- Research agents with search and document-retrieval tools
- DevOps agents with infrastructure query and (carefully gated) action tools
13. Common Mistakes
Incorrect idea: Writing vague tool descriptions.
Why it is incorrect: As shown directly in Section 4, this directly degrades tool selection accuracy.
Incorrect idea: Letting tool errors crash the agent silently instead of feeding them back as observations.
Why it is incorrect: As shown directly in Section 6, this prevents the agent from reasoning about and recovering from failures.
Incorrect idea: Giving an agent tools with more permission than necessary.
Why it is incorrect: As shown directly in Section 8, this is a real security risk — Module 17 covers this fully.
14. Limitations
- Even well-validated tool parameters don’t guarantee the underlying action is correct or appropriate for the situation — Module 18 covers this class of failure directly
- Tool description quality has real, real limits — some ambiguous situations will still produce imperfect tool selection regardless of how well the schema is written
15. Quick Reference
flowchart LR
Tool[Tool] --> Name[Name]
Tool --> Desc[Description]
Tool --> Params[Parameters/Schema]
Tool --> Fn[Underlying Function]
Tool --> Perms[Permissions]
16. Code — Implementing a Complete Tool Registry
🎯 Target of this example: implement Section 5’s complete flow directly — a tool registry supporting registration, LLM-facing descriptions, parameter validation, and structured error handling for missing parameters and unknown tools, exactly Section 6’s “errors as observations, not crashes” principle.
Example 1 — Simple
from dataclasses import dataclass
@dataclass
class Tool:
"""A tool schema -- name, description, and a callable that
executes the underlying action (Section 4)."""
name: str
description: str
parameters: dict
function: callable
class ToolRegistry:
"""Implements Section 5's complete flow: register, describe,
validate, execute, return a structured result."""
def __init__(self):
self.tools = {}
def register(self, tool: Tool):
self.tools[tool.name] = tool
def get_descriptions(self) -> list:
return [{"name": t.name, "description": t.description, "parameters": t.parameters}
for t in self.tools.values()]
def execute(self, name: str, **kwargs) -> dict:
if name not in self.tools:
return {"success": False, "error": f"Tool '{name}' not found"}
tool = self.tools[name]
missing = [p for p in tool.parameters if p not in kwargs]
if missing:
return {"success": False, "error": f"Missing required parameters: {missing}"}
try:
result = tool.function(**kwargs)
return {"success": True, "result": result}
except Exception as e:
return {"success": False, "error": str(e)}
def check_shipping_status(tracking_number: str) -> str:
return f"Package {tracking_number} was delivered 2 days ago."
registry = ToolRegistry()
registry.register(Tool(
name="check_shipping_carrier",
description="Looks up a package's delivery status given a tracking number.",
parameters={"tracking_number": "string"},
function=check_shipping_status,
))
print("Available tools:", registry.get_descriptions())
print()
result = registry.execute("check_shipping_carrier", tracking_number="1Z999AA1")
print("Execution result:", result)
bad_result = registry.execute("check_shipping_carrier")
print("Missing param result:", bad_result)
unknown_result = registry.execute("nonexistent_tool")
print("Unknown tool result:", unknown_result)
Expected Output:
Available tools: [{'name': 'check_shipping_carrier', 'description':
"Looks up a package's delivery status given a tracking number.",
'parameters': {'tracking_number': 'string'}}]
Execution result: {'success': True, 'result': 'Package 1Z999AA1 was
delivered 2 days ago.'}
Missing param result: {'success': False, 'error': "Missing required
parameters: ['tracking_number']"}
Unknown tool result: {'success': False, 'error': "Tool
'nonexistent_tool' not found"}
What we conclude from this example: both the missing-parameter and unknown-tool cases return structured, informative errors — exactly Section 6’s principle: these become real observations the agent could reason about (“I’m missing a required parameter, let me find it”), not silent crashes that halt the loop unexpectedly.
Example 2 — Intermediate
from dataclasses import dataclass
@dataclass
class Tool:
name: str
description: str
parameters: dict
function: callable
requires_human_approval: bool = False
class ToolRegistry:
def __init__(self):
self.tools = {}
def register(self, tool: Tool):
self.tools[tool.name] = tool
def execute(self, name: str, **kwargs) -> dict:
"""Extends Example 1 with Section 8-9's PERMISSION gate --
directly implementing TechCorp's requires_human_approval
distinction as ENFORCED logic, not just documentation."""
if name not in self.tools:
return {"success": False, "error": f"Tool '{name}' not found"}
tool = self.tools[name]
if tool.requires_human_approval:
return {"success": False, "requires_approval": True,
"message": f"'{name}' requires human approval before execution."}
missing = [p for p in tool.parameters if p not in kwargs]
if missing:
return {"success": False, "error": f"Missing required parameters: {missing}"}
try:
result = tool.function(**kwargs)
return {"success": True, "result": result}
except Exception as e:
return {"success": False, "error": str(e)}
registry = ToolRegistry()
registry.register(Tool("check_order_status", "Read-only order lookup.", {"order_id": "string"},
lambda order_id: f"Order {order_id}: LATE", requires_human_approval=False))
registry.register(Tool("process_refund", "Processes a monetary refund.", {"order_id": "string", "amount": "number"},
lambda order_id, amount: f"Refunded ${amount} for {order_id}", requires_human_approval=True))
read_result = registry.execute("check_order_status", order_id="4471")
refund_result = registry.execute("process_refund", order_id="4471", amount=50)
print(f"Read-only tool result: {read_result}")
print(f"High-risk tool result: {refund_result}")
Expected Output:
Read-only tool result: {'success': True, 'result': 'Order 4471:
LATE'}
High-risk tool result: {'success': False, 'requires_approval': True,
'message': "'process_refund' requires human approval before
execution."}
What we conclude from this example: the read-only tool executes
normally, while process_refund is correctly blocked from direct
execution, requiring approval instead — exactly Section 9’s table,
now enforced as real, structural logic rather than a policy
description a developer could forget to implement.
Example 3 — Production Grade
from dataclasses import dataclass, field
from enum import Enum
class ExecutionStatus(Enum):
SUCCESS = "success"
VALIDATION_ERROR = "validation_error"
TOOL_ERROR = "tool_error"
NOT_FOUND = "not_found"
REQUIRES_APPROVAL = "requires_approval"
@dataclass
class ToolExecutionResult:
status: ExecutionStatus
tool_name: str
result: object = None
error_message: str = None
@dataclass
class Tool:
name: str
description: str
parameters: dict
function: callable
requires_human_approval: bool = False
class ProductionToolRegistry:
"""A production-style registry returning a STRUCTURED
ToolExecutionResult with an explicit status ENUM -- directly
supporting Module 21's observability, since every execution
outcome is classifiable and loggable, not just a loose
dict."""
def __init__(self):
self.tools = {}
def register(self, tool: Tool):
self.tools[tool.name] = tool
def execute(self, name: str, **kwargs) -> ToolExecutionResult:
if name not in self.tools:
return ToolExecutionResult(ExecutionStatus.NOT_FOUND, name,
error_message=f"Tool '{name}' not registered")
tool = self.tools[name]
if tool.requires_human_approval:
return ToolExecutionResult(ExecutionStatus.REQUIRES_APPROVAL, name,
error_message="Awaiting human approval")
missing = [p for p in tool.parameters if p not in kwargs]
if missing:
return ToolExecutionResult(ExecutionStatus.VALIDATION_ERROR, name,
error_message=f"Missing parameters: {missing}")
try:
result = tool.function(**kwargs)
return ToolExecutionResult(ExecutionStatus.SUCCESS, name, result=result)
except Exception as e:
return ToolExecutionResult(ExecutionStatus.TOOL_ERROR, name, error_message=str(e))
def flaky_shipping_api(tracking_number: str) -> str:
raise ConnectionError("Shipping carrier API timed out")
registry = ProductionToolRegistry()
registry.register(Tool("check_shipping_carrier", "Checks delivery status.",
{"tracking_number": "string"}, flaky_shipping_api))
result = registry.execute("check_shipping_carrier", tracking_number="1Z999AA1")
print(f"Status: {result.status.value}")
print(f"Error: {result.error_message}")
Expected Output:
Status: tool_error
Error: Shipping carrier API timed out
What we conclude from this example: the ExecutionStatus enum
correctly classifies this as a real tool_error (the API itself
failed) rather than a validation_error (parameters were fine) —
exactly the kind of precise, structured classification a real
production system needs to distinguish DIFFERENT failure modes,
directly connecting to Module 18’s full failure-mode taxonomy later in
this course.
17. Interview Questions
Q: What’s the real difference between a “tool” in the agent sense and a plain function or API?
Ans: A function or API is simply code that performs an operation. A tool is that same underlying capability, wrapped with an explicit schema — a name, a natural-language description, and a parameter specification — that makes it discoverable and usable by an LLM’s reasoning process. The schema is what lets the LLM understand when and how to invoke the capability, which a plain function signature alone doesn’t provide.
Q: Why should tool errors be fed back to the agent as observations rather than causing the system to crash?
Ans: A tool error is useful information — it tells the agent something didn’t work as expected, which the agent can reason about and potentially recover from, like retrying, trying an alternative approach, or reporting that it cannot complete the task. If errors instead crash the system silently, the agent loses the opportunity to reason about and adapt to the failure, directly undermining the recovery potential the agent loop is designed to provide.
Q: Why is enforcing tool permissions at the control-logic level more secure than relying on the LLM’s own judgment about whether an action is appropriate?
Ans: The LLM’s judgment about appropriateness is a reasoning process that can be mistaken, manipulated, or simply wrong in an unexpected situation — it’s not a reliable security boundary. Enforcing permissions structurally, in the surrounding control logic, means a high-risk action (like processing a refund) is blocked or gated regardless of what the LLM decides, providing an actual, enforceable guarantee rather than depending on the model consistently making the right judgment call every time.
Q: Design a tool registry feature that would help diagnose whether a production agent’s poor performance stems from tool selection errors versus tool execution errors.
Ans: I’d implement structured execution results with an explicit status classification — distinguishing validation errors (wrong or missing parameters, suggesting the LLM generated incorrect arguments), tool errors (the underlying function or API failed, unrelated to what the LLM decided), and not-found errors (the LLM selected a tool that doesn’t exist, a real selection mistake). Logging this classification for every tool call across many production requests would let a team see whether failures cluster around selection mistakes (pointing to tool description quality) or execution failures (pointing to underlying API reliability), rather than treating every failure identically.
18. What You Should Remember
- A tool is a capability wrapped with a real, LLM-readable schema — name, description, parameters — distinct from the plain function or API underneath it.
- Tool errors should become observations the agent reasons about, not silent crashes — verified directly through a registry returning structured, informative errors for missing parameters and unknown tools.
- Permissions must be enforced structurally, by control logic — not left to the LLM’s judgment — verified directly through a registry that blocks a high-risk tool from direct execution regardless of what the LLM decides.
19. Quick Practice
Design a tool schema (name, description, parameters) for a new capability relevant to your own domain of interest, and identify whether it should require human approval before execution, using this module’s read-only vs. high-risk distinction.
20. Next Step
Next: Module 7 — Function Calling Deep Dive — closing Level 3: the precise mechanics of how an LLM’s structured decision becomes an executable function call, and the critical clarification that the LLM never literally executes anything itself.
- Author
- TechByteByByte Editorial Team
- Reviewed by
- TechByteByByte Admin
- Published
- Last reviewed