# The Responses API tool loop in Python
Function calling on the Responses API is an explicit loop you own. Verified
2026-09-26.
## The loop
1. Call `client.responses.create(model=..., input=messages, tools=[{...}])`.
Function tools are declared with fields at the top level of the tool object
(type "function", name, description, parameters), not nested under a
"function" key.
2. Scan `response.output` for items with `type == "function_call"`. Each has
`call_id`, `name`, and `arguments` (a JSON string).
3. `json.loads(item.arguments)` inside try/except. Malformed arguments happen;
on failure, send back a function_call_output whose output describes the parse
error so the model can retry with valid JSON.
4. Append to the input list:
- the original function_call items, and
- `{"type": "function_call_output", "call_id": item.call_id, "output": json.dumps(result)}`.
The `call_id` linkage is mandatory; a mismatched or missing call_id 400s.
5. Call `responses.create` again with the extended input. Repeat until no
function_call items remain.
## Guards
- Cap iterations (for example 8). A model stuck calling a failing tool will
otherwise bill you in a circle. When the cap hits, return the partial result
with a note instead of looping.
- Set `parallel_tool_calls=False` when your functions have ordering dependencies;
the default lets the model fire several at once.
- Validate `name` against your registry before dispatching. The model can invent
tool names; never `getattr` a module on a model-supplied string.
## Check before you ship
- Unit-test with a canned function_call item: assert the output item carries the
same call_id.
- Log every iteration count; alert if production traffic ever hits the cap.