TechByteByByte

Tools and Actions

The complete mechanics of what a tool actually is, how an agent selects and executes one, and everything that can go wrong — schema, validation, errors, permissions — starting Level 3.

#AI Agents#AI#Tools#Level 3

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:

ToolPurposeRequires Human Approval?
check_order_statusRead-only lookupNo
check_shipping_carrierRead-only lookupNo
send_emailSends a message to the customerNo, but logged
process_refundMoves real moneyYes (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