When you ask an AI agent to check your order or look up a customer record, something has to happen between the words you typed and the software that holds the answer. That something is ai agent function calling: the mechanism by which a language model asks your code to run a function on its behalf. The model never touches your systems directly — it returns a structured request, a function name and its arguments, your code executes it, and the result goes back to the model as context for its next step. This article explains that mechanism end to end.
What function calling actually is
Function calling, also known as tool calling, is a way for a model to interface with external systems and access data outside its training data — that is how OpenAI’s own documentation describes it. The definition corrects the most common misunderstanding about agents: the model does not “use” software. It produces text, but here the text is a structured JSON object naming a function and supplying its arguments, rather than prose meant for a human to read.
Your application is the part that acts. When the model returns a tool call, your code reads the function name, parses the arguments, runs the matching function — a database query, an API call, a calculation — and sends the result back. The model reads it and decides what to do next: answer the user, or call another tool. This split is the whole security model in one sentence: the model proposes, your code disposes. Nothing runs unless your code chooses to run it, so permissions, authentication, and validation live on your side of the boundary.
That is why function calling turns a language model from a text generator into a system that can act: without it, a model can only tell you what it knows; with it, the model can look up what it does not know, change state in another system, and chain operations together — while still only ever emitting text.
How the model knows which functions exist

A model cannot call a function it has never heard of, so the developer declares the available tools up front as part of the request. Each tool is described with a JSON Schema definition: a name, a natural-language description, and a parameters object listing the arguments, their types, and which are required. OpenAI calls these function tools; Anthropic’s definitions carry the same three ingredients as name, description, and input_schema.
The schema travels with the conversation as part of the model’s input. The description does more work than it looks like: the model decides which tool to call, and how to fill in its arguments, from the descriptions it was given, so a vague description produces wrong choices. Claude relies entirely on the name, description, and input schema to decide whether to call a tool and how to fill in its arguments. Writing those descriptions is therefore interface design, not documentation: precise names, a clear statement of when the tool applies, and required fields that state exactly what the function needs. But a schema only checks shape: a well-formed value can still be wrong — a fabricated order ID satisfies a string field — so required fields establish conformance, not truth.
{
"type": "function",
"function": {
"name": "get_order_status",
"description": "Look up the delivery status of a customer order by its order ID.",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The order ID, e.g. ORD-1042"
}
},
"required": ["order_id"]
}
}
}The same idea, in Anthropic’s shape, is a flat object rather than a nested one:
{
"name": "get_order_status",
"description": "Look up the delivery status of a customer order by its order ID.",
"input_schema": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The order ID, e.g. ORD-1042"
}
},
"required": ["order_id"]
}
}Both describe the same contract. Either way, what the model receives is the description plus the conversation it belongs to — no code, and no credentials unless the application mistakenly supplies them.
The AI agent function calling loop: reason, call, observe, repeat
The pattern is ReAct, short for reasoning and acting, from a 2022 paper by Yao and colleagues: the model writes out its reasoning, takes an action through a tool, reads the observation, and reasons again with the new fact in hand. Thought, action, observation; then thought again.
OpenAI documents the same loop as five steps: request with tools, receive a tool call, execute code with the call’s input, send the tool output back, receive a final response — or more tool calls.
The loop stops when the goal is reached, when no further call is needed, when it hits a step limit your code imposed, or when it meets an error it cannot recover from. The step limit matters: without one, a model that keeps calling tools without progress loops until it runs out of context or budget. Every production agent loop needs a maximum number of steps, set by the application, not the model.
The ReAct paper showed interleaving reasoning and acting outperformed imitation and reinforcement learning methods by 34 percentage points on ALFWorld and 10 percentage points on WebShop with only one or two in-context examples. On knowledge tasks such as HotpotQA and FEVER, pulling facts from the Wikipedia API mid-reasoning reduced the hallucination and error propagation that pure chain-of-thought reasoning suffers from. Grounding the model’s thinking in observations is the point.
From a function call to a real API request
Between the model’s JSON and the outside world sits the execution layer: your code, mapping the model’s arguments onto something real — an HTTP request, an SDK call, or a local function. Authentication, rate limits, and permissions all live here. The model never opens a network connection itself, but what it learns about your systems is whatever the application puts in front of it: the conversation, the tool definitions and descriptions, and the results. Keeping credentials out of that context is the application’s job, not an automatic property of function calling. The result is serialized back into text the model can read, becoming part of the conversation for its next decision.
The two providers package this handoff differently. With OpenAI’s Chat Completions API, the call arrives as a tool_calls array with the arguments as a JSON string to parse; with Anthropic’s Messages API, the response carries a stop_reason of tool_use and a tool_use block whose input is already a parsed JSON object with a unique id. Results differ too: OpenAI uses a message with the tool role, Anthropic nests tool_result blocks inside a user message.
Above the individual call sits a protocol that standardizes how tools are discovered and described. The Model Context Protocol, released by Anthropic in November 2024 and donated to the Agentic AI Foundation in December 2025, defines how AI applications discover and call external tools over JSON-RPC 2.0. A server exposes three primitives — model-controlled tools, application-controlled resources, user-controlled prompts — and clients discover tools with tools/list and invoke them with tools/call. MCP does not replace function calling: the host still uses the model’s own function calling so the model can choose a tool. MCP standardizes the plumbing, so a tool built once works in any compliant application.
A worked example: checking an order end to end

A customer writes: “Where is my order ORD-1042?” The application sends that message to the model with two tools declared — get_order_status, defined above, and issue_replacement, which replaces a lost order given an order ID and a reason:
{
"type": "function",
"function": {
"name": "issue_replacement",
"description": "Issue a replacement shipment for a lost order. Only call this after a lookup has confirmed the order is lost.",
"parameters": {
"type": "object",
"properties": {
"order_id": {
"type": "string",
"description": "The order ID of the lost order, e.g. ORD-1042"
},
"reason": {
"type": "string",
"description": "Why a replacement is needed, e.g. order lost in transit"
}
},
"required": ["order_id", "reason"]
}
}
}The model returns a call, not an answer:
{
"id": "call_9f2c41",
"type": "function",
"function": {
"name": "get_order_status",
"arguments": "{\"order_id\": \"ORD-1042\"}"
}
}Your code reads the function name, parses the arguments string into JSON, and executes the matching function — an ordinary lookup against the orders system. Illustratively, that lookup is an HTTP request to the shop’s API, authenticated by the application, not the model:
GET /orders/ORD-1042 HTTP/1.1
Host: api.example-shop.test
Authorization: Bearer <shop API key, held by the application>The lookup returns a result, which your code sends back to the model as a tool message — the envelope the model actually sees, correlated with the call by its id:
{
"role": "tool",
"tool_call_id": "call_9f2c41",
"content": "{\"order_id\": \"ORD-1042\", \"status\": \"lost\", \"carrier\": \"DHL\", \"eta\": null}"
}The order was lost in transit — a fact the model did not have before, and one that opens a second move: it calls issue_replacement, with the order id and a reason drawn from the first result.
{
"id": "call_9f2c42",
"type": "function",
"function": {
"name": "issue_replacement",
"arguments": "{\"order_id\": \"ORD-1042\", \"reason\": \"order lost in transit\"}"
}
}Your code runs the replacement only after its own checks — a replacement is an action with a real side effect, so the application confirms the order is actually lost and the request is authorized before executing. It returns its confirmation:
{
"order_id": "ORD-1042",
"replacement_id": "ORD-1044",
"status": "replacement shipped"
}Now the model can answer with both facts: the original order was lost, and a replacement is on its way. The first result became context, so each call’s output shapes the next decision. The model never saw the orders database or held the API credentials — it proposed two calls, and your code ran both.
What can go wrong
First, malformed arguments. Without strict schema enforcement, a model can return arguments that do not match the declared schema: a missing required field, a string where a number was expected, a hallucinated value. OpenAI’s guidance is direct: with strict mode on, the arguments a model generates are guaranteed to match the JSON Schema provided; without it, the structure is not guaranteed, so validate arguments with a library such as Pydantic before using them. Validation is the step that stops a malformed call from reaching your systems — but it only checks shape. A schema-valid value can still be invented or wrong, so the application must also check whether the requested action is appropriate before executing it.
Second, ambiguous schemas. If two tools overlap, or a description is vague about when a tool applies, the model picks the wrong one, or fills the right tool with the wrong argument. The fix is at the schema level: precise descriptions, distinct names, and required fields that force the model to supply what the function needs.
Third, API errors, timeouts, and partial results. When the downstream system fails, the model needs to know: return the error as a tool result rather than crashing the loop, so the model can react — retry, try a different tool, or tell the user what happened. The Model Context Protocol makes the same distinction: a failed tool call is still a result, and the caller must check the isError flag before trusting the content. Only protocol-level failures, such as an unknown tool or a timeout, are thrown as errors.
Fourth, runaway loops. A model that misunderstands a result can call the same tool repeatedly, or bounce between two tools, without making progress. The guardrail is a step limit set by the application, plus clear success criteria in the instructions so the model knows what “done” looks like. Without those, the loop has no reason to stop.
Rules for building reliable tool-using agents
Write schemas like documentation: give every tool a precise name, a description that states exactly when it applies, and required fields that match what the function actually needs.
Validate every call before executing it. Never trust model output as safe input: check the arguments against the schema — strict mode where the provider offers it, your own validation regardless — and reject anything that does not match before it reaches your systems. Then check the values themselves: a schema-valid argument can still be invented or unauthorized, so confirm the requested action is appropriate before running it. A rejected call is recoverable; an executed hallucination is a real side effect.
Scope tool permissions to the task. Every tool the model can call is a deliberate permission decision: give the agent the least privilege the task needs, keep credentials and network access on the application side, and log every call so a bad one is diagnosable after the fact.
Give the model a way to stop: state the success criteria, set a maximum number of steps, and define what happens when it cannot make progress — hand off to a human, or report the failure honestly, rather than looping. Control the choice when it matters: both providers let you steer tool use — leave it to the model, force a specific tool, or forbid tool calls entirely — and both support parallel calls. Use those controls deliberately instead of leaving them at their defaults.
Function calling is a small idea with large consequences: a model that can only write text becomes a system that can act. The loop is simple — reason, call, observe, repeat — but its reliability comes from the parts around it: precise schemas, validation before execution, least-privilege tools, and a clear stopping condition. Get those right and the agent does useful work; get them wrong and it does confident work on invented inputs, which is worse.












