~13 min
A language model can't query a database, hit an API, or send an email — underneath all the tooling, it's still just predicting the next token in a sequence. Tool calling (also called function calling) gives it a structured way to ask your application to do those things on its behalf, instead of trying to describe the action in prose and hoping you parse it correctly.
The full loop has five steps, and it's the same shape on every provider: you send a request describing the tools available (name, description, and an input schema); the model may respond not with prose but with a request to call one of those tools, naming which one and with what arguments; your application executes that call — the model never runs any code itself; you send the result back to the model as a new message; and the model either replies with a final answer or asks for another tool call. Nothing about this loop is automatic unless you build it yourself or use a framework that runs it for you.
python
from openai import OpenAI
import json
client = OpenAI()
tools = [{
"type": "function",
"function": {
"name": "get_weather",
"description": "Get current weather for a city.",
"parameters": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
"additionalProperties": False,
},
},
}]
messages = [{"role": "user", "content": "What's the weather in Lisbon?"}]
response = client.chat.completions.create(model="gpt-5.6", messages=messages, tools=tools)
call = response.choices[0].message.tool_calls[0]
args = json.loads(call.function.arguments)
result = get_weather(args["city"])
messages.append(response.choices[0].message)
messages.append({"role": "tool", "tool_call_id": call.id, "content": json.dumps(result)})
final = client.chat.completions.create(model="gpt-5.6", messages=messages, tools=tools)
print(final.choices[0].message.content)OpenAI's Chat Completions API nests a function's definition
inside a function object: {"type": "function", "function": {"name": ..., "description": ..., "parameters": ...}}. The
newer Responses API flattens that same definition — type,
name, description, and parameters sit directly on the tool
object, with no nested wrapper. The two APIs also differ in how
a call comes back: Chat Completions puts it on
message.tool_calls, each with an id; Responses returns a
top-level function_call item with a call_id. Both shapes
describe the same underlying capability, so picking one is
mostly about which endpoint the rest of your integration already
uses.
Anthropic's Messages API skips the nested-function wrapper
entirely: a tool is {"name": ..., "description": ..., "input_schema": ...}, and a call comes back as a tool_use
content block in the response, carrying its own id, the
tool's name, and the parsed input object.
python
import anthropic
client = anthropic.Anthropic()
tools = [{
"name": "get_weather",
"description": "Get current weather for a city.",
"input_schema": {
"type": "object",
"properties": {"city": {"type": "string"}},
"required": ["city"],
},
}]
messages = [{"role": "user", "content": "What's the weather in Lisbon?"}]
response = client.messages.create(model="claude-opus-5-5", max_tokens=1024, tools=tools, messages=messages)
call = next(b for b in response.content if b.type == "tool_use")
result = get_weather(call.input["city"])
messages.append({"role": "assistant", "content": response.content})
messages.append({"role": "user", "content": [
{"type": "tool_result", "tool_use_id": call.id, "content": str(result)}
]})
final = client.messages.create(model="claude-opus-5-5", max_tokens=1024, tools=tools, messages=messages)
print(final.content)No — the model only ever produces a request naming the tool and
its arguments. Your application code decides whether and how to
actually execute send_refund; that's true on every major
provider, and it's exactly the seam where your own validation
and authorization checks belong.
Sending the result back uses a different shape on each
provider, and mismatching it is a common source of broken
loops. OpenAI's Chat Completions API expects a new message with
role: "tool" and a tool_call_id matching the call you're
responding to; its Responses API expects a function_call_output
item carrying the matching call_id. Anthropic expects a
tool_result content block with a tool_use_id matching the
tool_use block's id, sent inside the next user message.
Each provider also gives you a distinct signal that a tool call
is what came back instead of a final answer — OpenAI's
finish_reason is "tool_calls", Anthropic's stop_reason is
"tool_use" — and checking that signal, rather than assuming,
is what makes a loop reliable across turns where the model
sometimes just answers in prose instead.