TechByteByByte

Tools and Tool Calling

How a tool schema works, how the model's decision becomes a real action, why argument validation matters, and what separates a well-designed tool from a poorly-designed one.

#Agentic AI#AI Agents#Tool Calling#LLM

An LLM can write, “Your order has shipped,” but words do not prove that an order exists. To check the real order, the model needs a controlled doorway into the order system. That doorway is a tool.

User request

Model chooses tool + arguments

Application validates and runs it

Tool result returns to the model

What You Will Learn

  • What a tool is and why the model does not directly execute ordinary text.
  • How a tool name, description, input schema, arguments, and result work together.
  • How structured tool calling becomes a real function or API call.
  • Why validation, permissions, timeouts, retries, and idempotency matter.
  • How clear tool design improves both safety and the model’s tool choice.

A Real Function-Calling Message

In Google’s documented custom-tool flow, Gemini can return structured data similar to {"name":"get_order_status","args":{"order_id":"123"}}. Your application—not Gemini—must check that the order ID is allowed, call the function, and return the result with the matching call ID. (Google Gemini tools)

This is why a tool call is not the same as an action. It is a proposed action in a machine-readable envelope until trusted code accepts and executes it.

Module 3 told you tools were the box where an agent’s capability to do things lives. Module 4 ran a full loop on top of that capability without stopping to explain how it works. This module is where we stop and open that box up properly, because tools are the single most consequential piece of an agent’s design. Get them right and the loop from Module 4 behaves the way you saw it behave — grounded, deliberate, recoverable.

Get them wrong — vague descriptions, unvalidated arguments, ambiguous results — and every failure mode from that module becomes far more likely, no matter how good the underlying model is.

What a tool is

Strip away the terminology and a tool is simple: it’s a function. Something with a name, a defined set of inputs, and a return value — exactly the kind of thing you’ve written a thousand times as a software engineer. What makes it a tool in the agentic sense isn’t anything special about the function itself. It’s that the function is described to the model in a structured way the model can understand and choose to invoke, and that your code — not the model — runs it.

Agent

LLM decides:
"I need payment information"

get_payment_history()

API

Result

LLM

Next decision

This is worth sitting with for a second, because it’s the entire resolution to the problem Module 1 opened with. A plain LLM, asked about a customer’s payment history, can only generate plausible- sounding text about what it might be. A model with get_payment_history available as a tool can go find out. The gap between those two isn’t a matter of the model being smarter — it’s the existence of a real, callable bridge between “the model’s reasoning” and “the real system holding the actual answer.” That bridge is what a tool is.

Why this couldn’t just be free text

It’s worth understanding why tool calling needed to be built as its own structured mechanism, rather than just asking a model to write “I will now call get_payment_history” in plain text and having your code try to parse that out. Early agent experiments — the AutoGPT-style projects from Module 2 — mostly did exactly this, because at the time, it was the only option available. And it was fragile.

A model might phrase the same intention a dozen different ways across a dozen different runs — “let me check the payment history,” “I should look up payments for this customer,” “checking payment records now” — and your code had to somehow reliably extract a function name and arguments out of whatever phrasing showed up, which meant writing increasingly elaborate, increasingly brittle parsing logic that broke the moment the model’s phrasing drifted even slightly from what you’d anticipated.

This exact fragility is precisely why OpenAI, Anthropic, and Google all built native, structured tool-calling directly into their model APIs. Instead of generating loose text you have to parse, the model generates a structured object — a JSON payload with a function name and a set of arguments, produced in a fixed, predictable format your code can parse reliably every single time, with no guesswork about phrasing involved at all. This is a real, concrete engineering upgrade, not a cosmetic one, and it’s worth understanding it as the direct fix for a real, specific problem the field had already run into publicly.

The tool schema, piece by piece

Here’s what a real tool definition looks like, using our recurring example:

{
  "name": "get_payment_history",
  "description": "Returns the last 90 days of payment attempts for a customer account, including status, amount, and decline reason if applicable.",
  "parameters": {
    "type": "object",
    "properties": {
      "customer_id": {
        "type": "string",
        "description": "The unique identifier for the customer, e.g. 'C-4471'."
      },
      "days": {
        "type": "integer",
        "description": "Number of days of history to retrieve. Defaults to 90 if not specified."
      }
    },
    "required": ["customer_id"]
  }
}

Every piece here is doing real work, and it’s worth understanding what each one is for, because a mistake in any of them causes a predictable, specific class of problem later.

The name is what the model references when it decides to call this tool — it needs to be unambiguous and distinct from any other tool available to this agent. The description is arguably the most important field in this entire schema, because it’s the model’s only source of information about when this tool is appropriate to use. A vague description — “gets customer data” — makes it harder for the model to distinguish this tool from a similarly-named one, like get_customer_profile.

A precise description — specifying exactly what this returns and in what timeframe — gives the model a reliable basis for deciding whether this is the right tool for the current situation.

The parameters section, following what’s called JSON Schema, defines exactly what arguments the tool expects, their types, and which ones are required versus optional. This is what lets your code validate an incoming tool call before running it — if the model’s generated call is missing a required field, or has a customer_id that’s a number instead of a string, that’s a mechanical, checkable violation your code can catch immediately, rather than something that only surfaces as a confusing runtime error deep inside your actual API call.

How the model decides, and what happens next

When the agent’s loop reaches a point where it needs information, the model is given the full list of available tool schemas alongside its current context, and it generates a structured tool-call request rather than plain text — something functionally equivalent to:

{
  "tool_call": {
    "name": "get_payment_history",
    "arguments": { "customer_id": "C-4471" }
  }
}

Your application code receives this, and — critically — this is where your responsibility as the engineer begins. The model has made a request. It has not executed anything. A minimal version of what your code needs to do looks like this:

def handle_tool_call(tool_call, tool_registry):
    tool = tool_registry.get(tool_call.name)

    if tool is None:
        return error_result(f"Unknown tool: {tool_call.name}")

    validation_error = validate_arguments(tool_call.arguments, tool.schema)
    if validation_error:
        return error_result(validation_error)

    try:
        result = tool.execute(**tool_call.arguments)
        return success_result(result)
    except Exception as e:
        return error_result(str(e))

Notice this function checks three distinct things before anything real happens: is this a tool this agent has access to at all, do the supplied arguments match what the schema requires, and — only after both of those pass — does the real function run, wrapped in error handling so a failure comes back as a clear result rather than crashing the whole loop. Whatever comes back — success or failure — gets fed back to the model as the next observation, exactly as covered in Module 4, and the loop continues.

Why argument validation matters

It’s tempting to think of this validation step as boilerplate, but it’s protecting against something real: the model can generate a syntactically perfect tool call with a hallucinated argument. Nothing about the JSON looks wrong — customer_id: "C-9999" is a perfectly valid string in exactly the right field — but if that customer ID doesn’t correspond to a real account, executing the call anyway means feeding the agent a misleading result, or worse, causing a real error deep inside a downstream system that’s harder to diagnose than if you’d caught the problem at the boundary.

Good validation goes beyond just checking types and required fields — where it’s feasible, it’s worth checking arguments against known-real values before the call proceeds. If your system already knows which customer this conversation is about, and the model’s generated customer_id doesn’t match, that’s a real, catchable signal worth surfacing back to the model as an error — “the provided customer_id does not match the active session” — rather than silently executing a lookup for the wrong account.

This is a direct, practical application of the incorrect-assumptions failure mode from Module 4: catching a hallucinated argument here, at the boundary, is far cheaper than discovering three steps later that the entire investigation was built on a wrong premise.

Tool selection: why descriptions decide more than you’d expect

When an agent has several tools available, the model’s choice among them is driven almost entirely by how well each tool’s description matches its current understanding of what it needs. This sounds obvious, and it’s also where a common, easy-to-miss mistake happens: two tools with overlapping or ambiguous descriptions can cause the model to reliably pick the wrong one, not because the model reasoned poorly, but because the tool set itself didn’t give it enough signal to distinguish them.

Imagine our support agent also had a tool called check_account alongside check_account_status, with descriptions that were both vaguely “returns information about the customer’s account.” A model facing a need to check whether the account is flagged has no reliable way to know which of these two nearly-identical-sounding tools returns that specific information. The fix here isn’t a smarter model — it’s a smaller, more distinctly-described toolset.

This connects directly to the wrong-tool-selection failure mode you’ll see covered properly in Module 10, and it’s worth planting the diagnosis here, where the actual root cause usually lives: in the schema, not in the model’s reasoning.

Tool results: format is not a cosmetic detail

The result a tool returns becomes the model’s next observation, and how that result is structured affects how well the model can use it. Compare these two possible results from a payment check:

Result A: "OK"

Result B: {
  "status": "declined",
  "reason": "insufficient_funds",
  "attempts": 2,
  "last_attempt": "2026-08-20T14:32:00Z"
}

Result A tells the model almost nothing — it can’t distinguish “the API call succeeded and returned an empty result” from “the payment succeeded” from “something ambiguous happened that technically didn’t error.” A model facing a result this thin often has no better option than to guess at what it means, or — worse — to call the same tool again, hoping for a clearer answer, which is precisely the “agent keeps calling the same tool” problem engineers run into.

Result B gives the model exactly what it needs to reason correctly and move forward with confidence. Designing a tool well means thinking just as carefully about what comes back from it as about what goes into it.

Tool failure: designing for it, not just handling it

Module 4 covered how an agent’s loop should respond to a tool failure. This module is about designing the tool itself so that a failure is recognizable as a failure, rather than something that looks like an ambiguous, silent non-answer. A tool that returns an empty string on error, indistinguishable from an empty-but-successful result, forces the model to guess at what happened — and a guess in either direction can be wrong.

A well-designed tool returns a distinct, structured error — {"error": "gateway_timeout", "retryable": true} — that tells the calling code (and, through it, the model) exactly what kind of failure occurred, and whether retrying is even a reasonable idea. That retryable field, specifically, is doing real work: it’s the tool itself giving the agent’s loop the information it needs to make a correct retry decision, rather than leaving that judgment entirely to inference from an ambiguous result.

Permissions: what a tool is allowed to be given access to

The last piece of the schema worth covering here, even though it gets its own full treatment in Module 11, is that a tool’s existence in an agent’s available toolset is itself a real decision with real consequences. Giving our support agent access to retry_payment() and send_email() is reasonable for its job.

Giving it access to a hypothetical delete_customer_account() tool would not be, regardless of how unlikely misuse might seem — because the moment that tool exists in the agent’s available set, it’s a capability that could be invoked, whether through a reasoning error, a misunderstanding of the situation, or a manipulation of the kind Module 11 will cover in depth. The principle worth internalizing now: a tool’s scope should match exactly what the task requires, not what might conceivably be convenient to have around.

The main categories of tools you’ll build

Tools aren’t limited to REST API calls, even though that’s the most common example. It’s worth having a real sense of the range, because recognizing which category a given need falls into helps you design the schema appropriately.

Database tools query or update structured records directly — get_customer(customer_id) reading from a customer table. REST API tools call external or internal HTTP services — check_payment_gateway() hitting a payment provider’s status endpoint. Search tools retrieve relevant unstructured knowledge — this is where RAG, which you’ve already studied, shows up as a tool the agent can invoke mid-task, the same way it invokes any other.

Calculator or computation tools handle precise math a language model shouldn’t be trusted to do purely by generation — computing an exact refund amount with tax and currency conversion applied is a bad candidate for “let the model figure out the number,” and a good candidate for a tool that computes it deterministically. File system tools read, write, or search files — central to how coding agents work, which we’ll touch on below.

Internal business system tools wrap whatever proprietary systems a company runs on — a CRM update, an internal ticketing system, a fraud-flagging service. And code execution tools let the agent run actual code to compute or verify something rather than reasoning about it in text — increasingly common in coding-focused agents specifically.

Modern coding agents are a useful real-world illustration of tool design, because their toolsets are public and instructive: a typical agentic coding tool exposes something close to a bash execution tool, a file-read tool, a file-edit tool, and a search/grep tool — a deliberately small, well-described set, rather than dozens of narrow, overlapping ones. That’s a real, current design choice worth noticing directly: fewer, more general, clearly-described tools tend to produce more reliable tool selection than many narrow, overlapping ones — directly the lesson from the tool-selection section above, visible in how real production agents are in practice built.

You may have already come across the idea of a standardized protocol for exposing tools to models consistently across different systems — that’s a real, important development, and it gets its own dedicated module later in this learning path. For now, everything you’ve learned here about schemas, validation, selection, and results applies regardless of how the tool is ultimately delivered to the model.

Real tool sets from real agentic products

Everything in this module so far has been principle. It’s worth seeing those principles in an actual, currently-shipping toolset, because the gap between “here’s how a tool schema should work” and “here’s what a real, heavily-used agent’s tools look like” is smaller than you might expect — and the differences that do exist are instructive.

Anthropic publishes the complete list of built-in tools Claude Code ships with, and it’s a direct, real illustration of the tool-selection problem covered above.

Rather than one general-purpose “run a shell command” tool for everything, Claude Code exposes distinct, narrowly- scoped tools for the most common operations: Read for reading a file, Write for creating one, Edit for making a targeted change to existing content, Grep for searching file contents, Glob for finding file paths by pattern, and Bash reserved for everything that needs a shell — package managers, test runners, git operations — rather than for jobs the dedicated tools already handle better. ([Claude Code, Tools reference](https://code.

claude. com/docs/en/tools-reference))

Notice what this demonstrates: Grep and Glob could technically both be described as “find things in the codebase,” which is exactly the kind of overlap this module warned would confuse tool selection. What keeps them distinguishable is precision in the description — one searches inside file contents, the other matches file paths — a real, live example of the tool-description discipline covered earlier in this module, not a hypothetical one.

It’s also worth noting the security layering: Claude Code restricts what Bash is allowed to do for common file operations specifically so that a model reaching for a shortcut like cat or sed gets redirected toward the dedicated, more predictable tool instead — a direct, production illustration of the least-privilege principle from earlier in this module, and a preview of the permission-boundary material coming in Module 11.

OpenAI’s agent products follow a different pattern for a different job. Where Claude Code’s tools operate on a local filesystem and shell, OpenAI’s ChatGPT agent (the successor to Operator, covered in Module 4) exposes a much smaller, more general tool surface centered on a virtual browser — screenshot, click, type, scroll — plus a code-execution tool for anything that needs real computation rather than UI interaction.

Fewer tools, each covering broad ground, because the job (operating arbitrary websites that were never designed with an agent in mind) doesn’t benefit from narrow specialization the way a codebase, with its predictable file and shell operations, does.

And it’s worth circling back to AutoGPT one more time here, because its original toolset — web search, browse a page, read a file, write a file, execute code, add or query memory — was broader and less disciplined than either of the examples above, with far less guidance in each tool’s description about exactly when it should be used relative to the others. That looseness is a real, direct contributor to the reliability problems Module 2 and Module 4 already told you about.

Comparing all three side by side is the clearest possible illustration of this module’s central argument: a smaller, more precisely-described, well-scoped toolset produces a more reliable agent than a larger, looser one — regardless of how capable the underlying model is.

When to Expose a Tool—and When Not To

Expose a tool when the agent needs fresh information, deterministic computation, or a controlled real-world action. Give it the narrowest inputs and permissions that satisfy that task.

Do not expose a broad administrative tool merely because it is convenient. If the model only needs order status, provide get_order_status(order_id) rather than unrestricted database access.

Common Misconception

Incorrect idea: A valid JSON tool call is safe to execute.

Why it is incorrect: Valid JSON proves only that the structure can be parsed. Trusted code must still authenticate the user, authorize the action, validate values, and check business rules.

Key Takeaways

  • A tool is fundamentally a function — a name, defined inputs, and a return value — made callable by the model through a structured schema, with your application code, never the model, executing it.
  • Structured tool calling replaced free-text parsing specifically because free-text parsing was fragile — the same intention phrased differently across runs broke brittle parsing logic, which is why major providers built native, JSON-based function calling directly into their APIs.
  • A tool’s description is the model’s only real signal for when to use it — vague or overlapping descriptions are a common, root cause of wrong-tool-selection failures, not a sign of poor model reasoning.
  • Argument validation exists specifically to catch hallucinated or malformed arguments before they reach a real system — checking types and required fields is the baseline; checking arguments against known- real values where feasible catches more.
  • A tool’s result format directly shapes how well the model can use it — a thin, ambiguous result forces the model to guess, which is a direct, common cause of an agent repeatedly calling the same tool.
  • A well-designed tool returns a distinct, structured error rather than an ambiguous non-answer, ideally indicating whether the failure is worth retrying — giving the agent’s loop the information it needs to make a correct decision rather than inferring one.
  • A tool’s mere existence in an agent’s available set is itself a real permission decision — scope every tool to exactly what the task requires, never to what might conceivably be convenient.

Think Like an AI Engineer

  • Write a tool description for a hypothetical cancel_subscription() tool. Now write a second tool, pause_subscription(), with a description vague enough that a model might struggle to tell the two apart. What specifically would you change in the second description to fix that?

  • Design the result format for a hypothetical check_inventory(sku) tool. What fields would you include so the calling agent can reliably tell the difference between “this item is out of stock,” “this SKU doesn’t exist,” and “the inventory system is temporarily unreachable” — three different situations that call for three different next steps?

  • A teammate proposes giving a coding agent unrestricted bash access because “it’s more flexible than a dozen narrow tools.” Based on this module, what’s the argument in favor of that approach, and what’s the argument against it? Where would you land, and why?

  • Go back to the get_payment_history schema at the start of this module. If you added a new, optional include_refunds boolean parameter, what would you need to update — just the schema, or something else too — to make sure the model uses it correctly?

Module 6 goes deep on what happens before a tool gets called at all — how an agent reasons about a situation, when it’s worth building an explicit plan versus reacting step by step, and the , important difference between the model’s own reasoning capability and the orchestration logic your code provides around it.

Author
TechByteByByte Editorial Team
Reviewed by
TechByteByByte Admin
Published
Last reviewed