# Function calling (`type: "function"`) — OpenAI **Status:** DOCUMENTED · LIVE_VERIFIED (2026-09-18, `gpt-5.4-nano` on `POST /v1/responses`; `gpt-4.1-nano` on `POST /v1/chat/completions`) **Sources:** https://developers.openai.com/api/docs/guides/function-calling · https://developers.openai.com/api/docs/guides/async-tool-calling · https://developers.openai.com/api/reference/resources/responses/methods/create · openapi-master.yaml `FunctionTool`, `FunctionToolCall`, `FunctionCallOutputItemParam`, `ToolChoiceParam`, `ParallelToolCalls` **Last verified:** 2026-09-18 ## Definition (Responses API) | Field | Type | Req. | Notes | |---|---|---|---| | `type` | `"function"` | yes | | | `name` | string | yes | `^[a-zA-Z0-9_-]+$`, ≤ 128 chars (NamespaceTool inner definition) | | `description` | string \| null | no | drives *when* the model calls it | | `parameters` | JSON Schema object \| null | yes (spec) | injected into the system message → billed as input tokens | | `strict` | boolean \| null | yes (spec, nullable) | Responses: omitted = best-effort strict; Chat Completions: default `false` | | `output_schema` | JSON Schema \| null | no | describes the JSON encoded in string outputs (used by programmatic tool calling) | | `async` | boolean | no | GPT-6 Astra+: model keeps working while the tool runs (pair with a *wait* tool + `task_handle`) | | `defer_loading` | boolean | no | load through `tool_search` | | `allowed_callers` | `["direct"\|"programmatic"]` | no | who may invoke: model directly and/or a `program` item | Chat Completions shape: `{"type":"function","function":{"name","description","parameters","strict"}}`; forced choice `{"type":"function","function":{"name":…}}`. ## Strict mode (live) `additionalProperties:false` + every property in `required` are mandatory; sending a strict schema without `additionalProperties` → **HTTP 400** `invalid_request_error`, `code: invalid_function_parameters`, `param: tools[0].parameters` (message: *"'additionalProperties' is required to be supplied and to be false"*). Strict schemas are cached and not ZDR-eligible; fine-tuned models lose strict mode when several functions are called in one turn. ## Items Output item `function_call`: ```json {"id":"fc_…","type":"function_call","status":"completed","call_id":"call_…","name":"get_weather","arguments":"{\"city\":\"Paris\"}"} ``` Optional fields: `namespace` (when defined inside a `namespace` tool — observed live), `caller` (`{type:"direct"}` | `{type:"program", caller_id}`), `async`. Input item `function_call_output`: `{"type":"function_call_output","call_id":"call_…","output": "" | [content parts]}` (optionally `name`, `namespace`, `caller`). Reasoning models: pass the `reasoning` items back with the outputs when not using `previous_response_id`. ## `tool_choice` | Shape | Effect | Live | |---|---|---| | `"none"` / `"auto"` (default) / `"required"` | no tool / model decides / ≥ 1 tool call | `required` verified | | `{"type":"function","name":"get_weather"}` | force this function | verified | | `{"type":"allowed_tools","mode":"auto"\|"required","tools":[{"type":"function","name":…}, {"type":"mcp","server_label":…}, …]}` | restrict to a subset without changing `tools` (keeps prompt cache) | verified (`required`, 2 tools → only `get_weather` called) | | `{"type":"custom","name":…}`, `{"type":"mcp","server_label", "name"}`, `{"type":""}`, `{"type":"shell"}`, `{"type":"apply_patch"}`, `{"type":"programmatic_tool_calling"}` | other tool families | see their pages | `parallel_tool_calls` (default `true`): set `false` for ≤ 1 call per turn. GPT-5+ can batch several *function* calls in one turn even when built-in tools are present, but built-in tools are never part of that batch. ## Streaming (live sequence, forced call) `response.created` → `response.in_progress` → `response.output_item.added` (item `function_call`, `arguments: ""`) → `response.function_call_arguments.delta` × N (`delta`, `item_id`, `output_index`, `obfuscation`) → `response.function_call_arguments.done` (`arguments`) → `response.output_item.done` → `response.completed`. ## Namespaces `{"type":"namespace","name":"weather","description":"…","tools":[…]}` groups tools; resulting calls carry `"namespace":"weather"`. Deferred loading (`defer_loading`) applies to the inner tools. Verified live on `gpt-5.4-nano` (`tool_choice: "required"`). ## Limits & guidance (docs) - Soft target < 20 functions available at turn start; combine sequential functions; don't ask the model for values you already know. - Definitions count against context and are billed as input tokens; consider fine-tuning to shrink them. - GPT-6 Astra requires the Responses API for tool calling. - Async tool calling: GPT-6 Astra and later; don't combine with parallel tool calls in multi-agent mode. ## Live evidence (sanitized, `tmp-live/tools/`) | Probe | Status | Result | |---|---|---| | forced call | 200 | `function_call` `{"city":"Paris"}`; usage 50 in / 18 out | | round trip (`previous_response_id` + `function_call_output`) | 200 | `message` | | stream | 200 | 11 events, sequence above | | strict invalid schema | 400 | `invalid_function_parameters` | | `allowed_tools` + `parallel_tool_calls:false` | 200 | 1 call | | namespace | 200 | `function_call.namespace = "weather"` | | chat completions (`gpt-4.1-nano`) | 200 | `choices[0].message.tool_calls[0].function`, `finish_reason: "stop"` (forced choice) | Examples: `examples/openai/tools/function-calling/` (sh/py/ts, all LIVE_VERIFIED). Tests: `tests/openai/test_tools.py`. Loop patterns: [tool-loop](../../openai/tool-loop.md).