The model plans, your code runs the tool
“Function calling” is one of those names that slightly exaggerates: the model can’t actually execute functions. The model itself has no internet, no shell, no file system, and no way to execute code. It can’t run Python, query a database, or call an API on its own. The only thing the model can produce is text. The cleanest way to think about what’s going on is in terms of roles:- The model is the planner. It looks at the conversation, reasons about what should happen next, and either writes a reply to the user or asks for a tool to be run. The model itself can’t actually run anything.
- Your code is the worker. It sees the model’s request, decides whether to honor it, and if so, runs the function, handing the result back as the next message in the conversation.
The loop
One complete tool-using interaction usually looks something like this:How to tell the model what tools are available
Tools are declared in the API request alongside the messages. Each tool has a name, a description, and a JSON schema for its arguments:search tool that takes a query argument tends to work better than twenty narrow tools that each cover a single search type.
What the model emits
When the model wants to call a tool, the API response includes atool_calls field instead of (or in addition to) a text answer:
id. When you send the results back to the model, you include the same id so the model knows which call the result belongs to. This matters when the model emits multiple parallel tool calls in a single turn.
After your code has run the tool, you send the result back as a new message with role: "tool":
Multi-step agent loops
Real tasks can rarely be completed with a single tool call. A coding agent task like “fix the failing test inauth.py” unfolds across many rounds:
- The model calls
read_file("auth.py")andread_file("test_auth.py"). - Your code returns the file contents.
- The model calls
run_tests("test_auth.py")to see the failure. - Your code returns the failure output.
- The model reasons about the bug, then calls
edit_file("auth.py", ...). - Your code applies the edit and returns confirmation.
- The model re-runs the tests to verify.
- Your code returns the passing test output.
- The model writes a final summary message to the user.
- Step limit: Cap the number of times the loop will iterate before giving up. Models can sometimes spiral into tool-call loops if they get confused. A ceiling of 20–50 steps is reasonable for most tasks, but coding agents often go higher.
- Tool authorization: A tool request is not authorization. Even when the model asks for a tool to be run, your code does not have to comply. For anything irreversible (sending money, deleting data, force-pushing to main), your code should require human confirmation before honoring the call.
The Model Context Protocol (MCP) has emerged as the standard way to expose tools to any compatible model without redeclaring them per-provider. Tools live as standalone MCP servers, and any MCP-aware model can pick them up via a single connection. Most production coding agents and chat clients now speak MCP, which means you can write a tool once and use it from Claude, ChatGPT, Cursor, and others without changes.
How it goes wrong
There are five common ways tool use breaks in practice:- Hallucinated arguments: The model produces arguments that do not match the schema. There might be a missing field, a wrong type, or a city that does not actually exist. You should always validate the arguments before executing the call, rather than blindly trusting what the model produces.
- Tool-call loops: The model keeps calling the same tool with slightly different arguments and getting back the same kind of answer. The common cause is a tool result that is vague or unhelpful, which leads the model to think it didn’t get what it asked for. The fix is to make tool outputs explicit (“Found 0 results matching ‘cat photos’ uploaded after 2024-01-01”) or to set a step limit on the loop.
- Picked the wrong tool: Two tools have overlapping descriptions and the model picks the wrong one. The fix is to disambiguate the descriptions or merge the two tools into one.
- Skipped a tool when it should have used one: The model answers from its training knowledge when fresh data was needed. The fix is to strengthen the system prompt with something like “Always use
lookup_pricebefore quoting a price”. - Parallel calls when serial was intended: Modern models often emit multiple tool calls in a single turn. Make sure your executor can handle them in parallel and that it matches results back to calls using the
idfield.
Next steps
Structured outputs & JSON mode
The same constrained-decoding plumbing, but for non-tool outputs.
Context engineering
How the system prompt steers tool selection.
Context windows
Agent loops grow the message history fast.