How LLM function calling actually works, and why the model never runs your code
Agents are the story of 2026. Every product now has an assistant that can check your calendar, query the database, hit an API, and act. It looks like the language model reached out and ran something. It did not. A language model produces text and nothing else. It cannot execute a function, open a socket, or read a file. The entire illusion of an agent is a loop you write around the model, and once you build that loop, function calling stops being magic and becomes an obvious protocol.
The one idea
The model does not call functions. It emits a message that says which function it wants and with what arguments, as structured text. Your code reads that message, runs the real function, and hands the result back to the model as more text. The model then writes the final answer using that result. There are two round trips: "I want to call get_weather('Lagos')", then, after you run it, "the weather is 31 degrees". The model is the planner. Your loop is the hands.
Build the loop
You need three parts: real functions, a way to describe them to the model, and the loop that runs them. Here are the functions and their descriptions, auto-built from the signatures.
import json, inspect
def get_weather(city: str):
fake = {"Paris": 18, "Lagos": 31, "Oslo": 7}
return {"city": city, "celsius": fake.get(city, "unknown")}
def add(a: float, b: float):
return {"sum": a + b}
TOOLS = {"get_weather": get_weather, "add": add}
def schema(fn):
return {"name": fn.__name__, "parameters": list(inspect.signature(fn).parameters)}
That schema list is what gets sent to the model alongside the user's message: here are the tools, here are their argument names, pick one if you need it. Now the loop that turns the model's choice into a real result.
def run(user_msg):
step1 = model(user_msg) # model reads tools + message
if "tool_call" not in step1:
return step1["content"] # it just answered directly
call = step1["tool_call"]
fn = TOOLS[call["name"]] # dispatch by name
result = fn(**call["arguments"]) # YOUR code runs the function
step2 = model(user_msg, tool_results=result) # feed the result back in
return step2["content"] # model answers using it
Three details that matter:
model()here stands in for the API call. A real model returns exactly this shape, a JSON object that is either{"content": "..."}when it wants to talk, or{"tool_call": {...}}when it wants a function run. The provider trained it to produce well-formed calls; your job is only to honor them.- The
fn(**call["arguments"])line is the security boundary of every agent. The model chose the name and the arguments, but you decided which functions live inTOOLS. Anything not in that dictionary cannot be called, no matter what the model asks for. That whitelist is the difference between an agent and a liability. - There are two calls to the model, not one. The first plans the tool use; the second, after seeing the result, writes the answer. Multi-step agents just repeat this: the loop keeps going as long as the model keeps asking for tools.
Proof: two questions, two tools, no model-run code
print(run("what is the weather in Lagos"))
print(run("what is 40 plus 2"))
Prints:
Done. Result was {'city': 'Lagos', 'celsius': 31}.
Done. Result was {'sum': 42}.
The weather question routed to get_weather, the arithmetic to add, each with arguments the model chose from the plain-English request. Every actual computation, the dictionary lookup, the addition, happened in ordinary Python that you can read, test, and lock down. The model never touched it. It only decided what to run.
Where this shows up
This exact loop is what sits under every agent framework and under the Model Context Protocol that standardized how tools get described and called in 2026. The production versions add real JSON-schema validation of the arguments, parallel tool calls, retries when the model asks for a tool that failed, and permission prompts before anything with side effects runs. But the skeleton does not change: the model proposes, your code disposes, and the whitelist is where safety lives.
If you want to build the model side too, the attention, the sampling, and then wrap it in an agent loop that plans across many tool calls, that is the arc the ai track on IWTLP builds from the ground up.
Sources
- The tool-use protocol and two-round-trip pattern: Anthropic, tool use documentation
- The 2026 standard for describing and calling tools: Model Context Protocol specification