Structuring Tool Call IDs
When sending multi-turn conversations that include tool calls, everytool_call ID and its matching tool_call_id must only contain letters, numbers, underscores, and hyphens ([a-zA-Z0-9_-]).
What causes the error
Using IDs with dots, colons, or other special characters. This commonly happens when you construct IDs yourself or copy them from provider-internal formats:The correct way
Always use theid from the model’s response exactly as returned. If you are generating your own IDs (for example, when replaying a conversation), follow the pattern call_<alphanumeric>:
Pairing Every Tool Call with a Tool Result
When the model responds with one or moretool_calls, every tool call must have a corresponding tool message with a matching tool_call_id immediately after the assistant message.
What causes the error
Sending an assistant message that containstool_calls without providing a tool result for each one in the next turn. This commonly happens when you only handle the first tool call and ignore the rest, or when you skip the tool result entirely and send another user message instead:
The correct way
Loop through alltool_calls and send a tool message for each one:
If a tool call fails on your side, still send back a
tool message for it — set the content to a JSON error like {"error": "service unavailable"}. The model can use this to adjust its response. Never skip a tool result.Preserving Reasoning Blocks in Multi-Turn Requests
When using reasoning models with tool calling, the model returnsreasoning_details alongside its response. If you send those reasoning blocks back in a follow-up request, they must be exactly as the model returned them — including any signature fields.
What causes the error
Modifying, reordering, or stripping fields from thereasoning_details before sending them back. This commonly happens when you serialize the response to a database and a field gets dropped, or when you manually rebuild the assistant message and forget the signature:
signature was originally a cryptographic string like "erUBMkiJvNVMxLa..." but was set to null during serialization. The provider rejects the entire request because the signature no longer matches.
The correct way
Pass the entirereasoning_details array back without touching it:
reasoning_details field from the assistant message. The model will start fresh reasoning for the next turn, which avoids signature validation entirely.
Avoiding Invalid Thinking Signatures
When using models with extended thinking, the model returnsthinking blocks with a signature field that cryptographically validates the thinking content. If you send these blocks back in a multi-turn conversation, the signature must be intact or the API will reject the request with:
What causes the error
Thesignature field gets corrupted during serialization, storage, or reconstruction of the conversation history. The most common cause is null coercion — your database, ORM, or serialization layer converts the signature string to null.
Common scenarios that corrupt signatures:
| Scenario | What happens | Result |
|---|---|---|
ORM/DB stores signature as nullable column | Value becomes NULL on read | 400 error |
JSON.parse → modify → JSON.stringify drops field | signature key missing entirely | Silently accepted (but no continuity) |
Manual reconstruction with signature: null | Explicit null value | 400 error |
| Signature string truncated (e.g. column length limit) | Partial signature | 400 error |
| Thinking blocks stripped entirely | No thinking in history | Silently accepted (fresh reasoning) |
How to reproduce
There are three ways to trigger this error. All examples below use the Blackbox AI endpoint — get a valid response first, then corrupt the signature before sending it back. Setup (shared across all examples):null:
VARCHAR(255) column that can’t hold the full signature (often 500–1500 characters):
Omitting
reasoning_details entirely (or stripping thinking blocks completely) does not cause an error — the model simply starts fresh reasoning. The error only occurs when you include a thinking block with a corrupted signature value.The fix
Option 1 (recommended): Pass thinking blocks back exactly as received. Do not serialize individual fields — store and return the entirereasoning_details array as an opaque blob:
reasoning_details from previous assistant turns. The model will start fresh reasoning each turn:
Thinking signatures work across providers (e.g. a response from
blackboxai/anthropic/claude-opus-4.6 can be sent to blackboxai/vertex_ai/claude-opus-4.6 and vice versa). You do not need to pin the provider — only the content integrity matters.GPT-5.4 Parameter Compatibility
GPT-5.4 (and other GPT-5 reasoning models) restrict certain parameters based on thereasoning_effort setting. Sending unsupported parameters will return a 400 error like:
Which parameters are restricted
The following parameters are only supported whenreasoning_effort is set to none:
| Parameter | With reasoning_effort=none | With reasoning_effort=low/medium/high |
|---|---|---|
temperature (values other than 1) | Supported | Rejected |
top_p | Supported | Rejected |
logprobs | Supported | Rejected |
temperature=1 is always allowed regardless of reasoning effort.
For full details, see OpenAI’s GPT-5.4 parameter documentation.
What causes the error
Sendingtemperature, top_p, or logprobs with a reasoning effort other than none:
The fix
Option 1: Setreasoning_effort to none when you need sampling parameters. This disables reasoning and allows full control over temperature, top_p, and logprobs:
temperature, top_p, or logprobs:
Avoiding Thinking with Forced Tool Choice
When using extended thinking (reasoning), you cannot force the model to use a specific tool viatool_choice. Anthropic-based models require tool_choice to be "auto" (or omitted) when thinking/reasoning is enabled.
What causes the error
Settingtool_choice to "required", "any", or {"type": "function", "name": "..."} while also enabling reasoning:
tool_choice values are rejected when reasoning is enabled:
"required"— forces the model to call any tool"any"— same as required{"type": "function", "function": {"name": "..."}}— forces a specific tool
The fix
Usetool_choice: "auto" (or omit it entirely) when reasoning is enabled. The model will still call tools when appropriate — thinking models are highly capable of deciding when to use tools on their own:
If you absolutely need to force a specific tool, disable reasoning for that request. You can re-enable reasoning on subsequent turns once the forced tool call is complete.