Function calling is the mechanism by which a language model requests that your application perform an action — check a database, hit an API, send an email — by emitting a structured, machine-readable request instead of free text. The model never executes anything itself. Your code reads that request, runs the actual operation, and hands the result back into the conversation.
Quick Answer: Function calling (also called tool calling) lets an LLM output a structured JSON request naming a tool and its arguments. Your application code — not the model — validates and executes that request, then feeds the result back into context so the model can respond using real data.
What Function Calling Actually Is
Function calling is a contract: you describe a set of tools to the model in a schema, the model decides when one is relevant and emits a call matching that schema, and your application is the only thing that ever runs code. Nothing about this changes what an LLM fundamentally is — a next-token predictor, as covered in the complete guide to LLM fundamentals.
The word "calling" is doing some misleading work here. The model doesn't call anything in the traditional programming sense — it doesn't have a runtime, a network stack, or credentials. It produces text that looks like a function call, formatted to a schema you provided. That's the entire trick, and it's worth sitting with because so much confusion about AI agents traces back to skipping this distinction.
Why This Matters for PMs Building AI Products
If you're scoping an "AI agent" feature, this distinction is the single most load-bearing fact in your spec. The model proposes, your system disposes. Every safety review, every permission model, every audit trail you'll need starts from that sentence.
- The model cannot bypass your authorization checks — it doesn't have access to them.
- The model cannot see whether its request actually succeeded unless you tell it.
- The model can hallucinate a plausible-looking call to a tool that doesn't exist, or with malformed arguments — your code must handle that as a normal, expected case, not an edge case.
The Loop, Step by Step
The function-calling loop has four stages that repeat: describe the tools, receive a structured request, execute it in your code, and feed the result back as new context. Each turn of this loop is a fresh round-trip to the model — nothing persists in the model's "mind" between turns except what's in the conversation transcript.
- You describe available tools. In the request you send to the model API, you include a schema — typically JSON Schema — naming each tool, describing what it does in plain language, and specifying its parameters and types.
- The model emits a structured request. Instead of (or alongside) a normal text reply, the model returns something like
{"name": "check_order_status", "arguments": {"order_id": "A1029"}}. - Your code executes it. Your application parses that structured output, validates the arguments, checks permissions, and actually calls the relevant internal function or API.
- The result goes back into context. You append the tool's output to the conversation as a new message, and send the whole thing back to the model, which now has real data to reason over and respond with.
Quick Answer: The four-stage loop — describe, propose, execute, return — repeats every time the model needs external information. Nothing is skipped: even a trivial lookup goes through all four steps, because the model has no other way to reach outside its own context window.
A Concrete Example: Check Order Status
Say a customer asks a support chatbot, "Where's my order?" The model has no order database — it has never seen this customer's data and never will, unless your code puts it there. Here's what the transcript actually looks like under the hood.
| Turn | Actor | Content |
|---|---|---|
| 1 | User | "Where's my order? It's order A1029." |
| 2 | Model | Structured call: check_order_status(order_id="A1029") |
| 3 | Your app | Executes the real lookup against your order system |
| 4 | Your app | Appends result: {"status": "shipped", "eta": "July 14"} |
| 5 | Model | "Your order A1029 shipped and is expected July 14." |
Notice that turn 5 only sounds informed because turn 4 physically inserted the answer into the model's context window. The model isn't "checking" anything — it's summarizing data you handed it, the same way it would summarize a pasted paragraph. This is the same context-assembly discipline behind retrieval and embeddings: the model reasons over what's in the window, not over some external live connection.
Why the Model Only Proposes, Never Executes
The model only proposes a call because it has no execution environment of its own — it is, architecturally, a stateless function that maps input tokens to output tokens, with no side-channel to your database, filesystem, or network. This isn't a safety feature bolted on afterward; it's a structural fact about what the model is.
That structural fact is actually good news for anyone building serious PM-facing AI features, because it means your application retains full control at the one point that matters: execution. You decide which tools exist, what arguments are valid, whether this user is authorized, and whether to actually run the call at all.
What Your App Is Responsible For
- Schema validation — reject calls with missing or malformed arguments before they touch real systems.
- Authorization — check that this user, in this session, is allowed to run this specific action against this specific resource.
- Idempotency and side-effect guards — a model can (rarely, but observably) emit the same call twice; destructive actions need confirmation steps or dedupe logic.
- Fallback handling — the model may request a tool that doesn't exist or that's temporarily unavailable; return an error message as the tool result rather than crashing.
- Logging — every proposed call and every executed result is an audit trail; keep both, because they can diverge.
Quick Answer: Your application owns every consequential decision in the loop — validation, authorization, and execution — because the model has no independent path to your systems. Treat every emitted tool call as untrusted input, structurally identical to a user-submitted form.
Reliability and Safety Implications
Function calling introduces a new failure surface because the model's output is a prediction of what a well-formed call looks like, not a guaranteed-correct one — and that prediction inherits the same non-determinism you'd see in any other LLM output. Two identical prompts can, on rare occasions, produce different tool choices or slightly different arguments.
Practical implications worth building around:
| Risk | What it looks like | Mitigation |
|---|---|---|
| Wrong tool selected | Model calls cancel_order when it meant check_order_status | Tight, non-overlapping tool descriptions; confirm before destructive actions |
| Malformed arguments | order_id sent as a full sentence instead of an ID | Strict schema validation, typed fields, reject-and-retry loop |
| Hallucinated tool | Model invents a tool name never provided | Treat unknown tool names as errors, never silently no-op |
| Over-permissioned tool | A single tool exposes broader capability than the task needs | Scope tools narrowly — one tool per action, not one mega-tool |
| Prompt injection via tool results | Malicious text in a fetched webpage instructs the model to call another tool | Sanitize and clearly delimit tool outputs before re-inserting into context |
Design tools the way you'd design an API for a slightly overconfident junior engineer — narrow scope, unambiguous naming, strict types, no destructive action without a confirmation step. Anthropic's own guidance on tool use and OpenAI's function-calling documentation both converge on the same principle: the clearer and more atomic the tool definition, the more reliably the model selects and fills it correctly. Simon Willison, who has written extensively and publicly about prompt injection risk in tool-using LLM systems, has repeatedly flagged that the highest-risk pattern is a tool whose output can itself contain instructions the model then follows — a distinct threat from a wrong call, and one that needs its own sanitization step, separate from argument validation.
Mapping This Onto a Product Requirement
If you're writing the PRD for an AI feature that touches real systems, the JTBD framing helps here — the "job" isn't "call an API," it's "resolve the customer's actual question safely," which is the kind of outcome-first framing covered in the complete guide to jobs-to-be-done. Tools are a means, not the job itself, and that reframing usually surfaces the confirmation steps and guardrails a naive spec would skip.
It also helps to map the tool-calling loop onto the actual customer journey it sits inside — a "check order status" tool feels low-risk in isolation, but if it's one step inside a returns-and-refunds flow, the emotional stakes of a wrong or delayed answer are much higher than the technical risk analysis alone would suggest.
Designing the Tool Layer: Where Prodinja Fits
Before a model can call anything, someone has to decide what's callable, write the contract, and make sure engineering and product agree on the shape of every endpoint. Prodinja's API Designing tool is built for exactly that step — it turns the endpoints you'd expose to a model into concrete specs and runnable curl examples, so the tool schema you hand the LLM traces back to a spec your engineering team actually reviewed, not a description improvised in a prompt. That specification discipline is what keeps the loop above — describe, propose, execute, return — grounded in a contract both humans and the model can trust.
Key Takeaways
- The model only proposes — it emits a structured request describing an intended action; it never executes code, queries a database, or calls an API directly.
- The loop has four fixed stages: describe available tools, receive a structured call, execute it in your application, and feed the result back into context.
- Your application owns every consequential decision — validation, authorization, idempotency, and error handling all sit outside the model's control.
- Tool calls are untrusted input — schema-validate and authorize every proposed call exactly as you would a user-submitted form.
- Narrow, atomic tool design improves reliability — one clearly-scoped tool per action beats a single broad, ambiguous one.
- Tool outputs can carry injected instructions — sanitize and clearly delimit any external content before it re-enters the model's context.
- Specification discipline upstream (like Prodinja's API Designing tool) pays off downstream, since a clean, reviewed endpoint spec is what a reliable tool schema is built from.
Frequently Asked Questions
What is function calling in AI?
Function calling is a feature of modern LLM APIs where the model can emit a structured, schema-matching request naming a tool and its arguments instead of (or alongside) plain text. Your application code then executes the requested action and returns the result to the model.
Is tool calling the same as an AI agent?
Tool calling is one building block of an agent, not the whole thing. An agent typically wraps the function-calling loop in an outer control loop that decides when to call tools, when to stop, and how to handle multi-step plans — but the underlying propose-execute-return mechanic is identical.
Can the AI model actually run code itself?
No — the model has no execution environment, network access, or filesystem of its own. It predicts and outputs text formatted as a structured tool call; your application is solely responsible for interpreting and running any actual operation.
How do I make function calling safe for destructive actions?
Add an explicit confirmation step between the model's proposed call and your system's execution for any action with real-world consequences (refunds, deletions, sends). Scope each tool narrowly, validate arguments strictly, and log both the proposed call and the executed result separately.
What happens if the model calls a tool that doesn't exist?
Your application should treat this as an ordinary error case: return a clear error message as the tool result rather than crashing or silently ignoring the call, so the model can recover gracefully in its next turn.