🔧 Function Calling: The Agent's Hands

Function Calling: The Agent's Hands: Function calling is like a call center operator who can only fill out a request form ("please transfer $50 from account X to account Y") and

Function calling is like a call center operator who can only fill out a request form ("please transfer $50 from account X to account Y") and hand it to a human teller — the operator NEVER touches the vault themselves, no matter how confidently they wrote the form. The mechanism is exact: the LLM, given a tool's name and expected parameters, produces a structured request (typically JSON) naming the tool and filling in argument values — that is the ENTIRE extent of what "the LLM does" in function calling; a separate piece of code (yours) reads that structured request and is the only thing that actually executes anything. Here is the question worth sitting with: if the model can write a perfectly formed request to delete a file, why is that fundamentally different from the model actually deleting the file? Because the model's output is still just TEXT (structured text, but text) — it has zero ability to touch a file system, a database, or a network socket on its own; the gap between "wrote a request" and "the file is deleted" is entirely bridged by code you wrote and chose to run, which means YOU decide whether the model's request is trustworthy enough to execute, with what permissions, and whether to ask for confirmation first. Java comparison: this is precisely an interface/implementation split — the LLM sees an interface (a tool's name, parameter types, and a description of what it should do) the same way calling code sees a Java interface, but the LLM never provides the implementation; your code is the concrete class that implements what actually happens when the "method" is called, and the model has no visibility into or control over that implementation. The QA stake: this distinction is the entire safety model behind the permission modes discussed on the Claude AI page — because the model can only ever REQUEST a tool call, never force its execution, every genuine safety boundary lives entirely in the code that decides whether to honor that request, not in the model.

The Model Requests, Your Code Executes

The split has exactly two steps: (1) you register a tool by describing its name, its parameters and what it does — usually as a JSON schema — so the model knows it exists and how to ask for it; (2) when the model decides that tool is needed, it does not run anything — it outputs a structured object naming the tool and the argument values it wants to use. Your code then reads that object, decides whether to actually call the real function, executes it if so, and feeds the real result back to the model as the next observation in the loop from the previous tab.

Reasoning: why go through the trouble of a rigid JSON schema instead of just letting the model describe in plain English what it wants to do? Because plain English is ambiguous and not machine-parseable at the reliability level code requires — "check the flaky test log" could mean a dozen different function calls with different parameters. A schema forces the model to commit to an exact, parseable, executable request (tool name + typed parameters) the same way a strongly-typed method signature forces a caller to commit to specific argument types instead of a vague natural-language description of intent.

A Tool Definition, in JSON Schema

Step by Step: From Tool Request to Real Execution

Your code describes a tool's name, parameters and purpose to the model.

Given a task, the model decides this tool is needed.

The model outputs a structured request naming the tool + arguments — this is still just text.

Your code reads the request and actually calls the real function.

The real result becomes the model's next observation.

Arrange the flow from a tool request to real execution in the correct order.

Code describes the tool's name and parameters to the model

The model decides this tool is needed