fix(openai): merge additional_params tools into chat completions tool list; record error-envelope coverage - #2294
Merged
Conversation
… list (#1890) The Chat Completions request flattens additional_params into the serialized body via serde_json::to_value, so a raw `tools` array supplied there silently replaced the builder's typed function tools (flattened key wins last). Every sibling path — the Responses API, Anthropic, and Gemini — already merges instead. Function-shaped entries now merge into the typed tool list; non-function entries stay behind for provider prepare_request hooks (Groq's compound_custom folding). Reproduced live and pinned with a recorded cassette: pre-fix, a request with tool_choice=required and a builder tool carried only the additional_params tool on the wire, forcing the model to call the wrong tool. Also adds the first non-2xx recordings in the suite: six error-envelope cassettes (unary + streaming for OpenAI, Anthropic, Gemini) proving provider_response_status()/provider_response_body() preserve real provider error envelopes, including the lazily-surfaced streaming case.
This was referenced Aug 12, 2026
Merged
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Live-traffic bug hunt against the OpenAI, Anthropic, and Gemini providers: new cassette-backed tests were recorded against the real APIs until a genuine defect surfaced; the defect is fixed here with a cassette-backed regression test, and the passing coverage recorded along the way ships too.
Bug found and fixed
OpenAI Chat Completions:
additional_params["tools"]silently replaced builder-registered tools (issue #1890's chat-path half)..tool(...)plusadditional_params(json!({"tools": [...]}))sent only theadditional_paramstools to the wire. Withtool_choice: requiredand a prompt demanding the builder tool, the model was forced to call the wrong tool — the recorded pre-fix traffic shows the request body containing only the rawtoolsarray, and the model answering withlookup_betawhen told to calllookup_alpha.providers/openai/completion/mod.rsflattensadditional_paramsinto the serializedCompletionRequest. The body is built viaserde_json::to_value, where the flattenedtoolskey wins over the typedtoolsfield — a silent last-write-wins clobber. Every sibling path already merges instead: the Responses API (responses_api/mod.rs), Anthropic (build_tool_definitions), and Gemini (extract_tools_from_additional_params). The chat path was the one hole.toolsfromadditional_paramsinTryFrom<OpenAIRequestParams> for CompletionRequestand append it to the typed tool list, mirroring the Responses-path semantics including the loudRequestErroron a malformed payload.tests/providers/openai/cassette/additional_params_tools.rs+tests/cassettes/openai/additional_params_tools/builder_tools_survive_additional_params_tools.yaml— a fresh post-fix recording whose request body carries both tools and whose response callslookup_alpha. The cassette harness matches the outbound body, so any regression back to the clobber fails replay as a mock miss.New coverage recorded (independent of the fix)
Error-envelope preservation had zero non-2xx interactions in any committed cassette. Six new recordings (unary + streaming per provider) now pin that a real provider error keeps its status and raw body recoverable through
provider_response_status()/provider_response_body():openai/error_envelope/*— Responses API, 400model_not_found(note: OpenAI returns 400, not 404, for an unknown model)anthropic/error_envelope/*— Messages API, 404not_found_errorgemini/error_envelope/*— generateContent + streamGenerateContent, 404NOT_FOUND(the streaming error arrives on an SSE-content-type response and still surfaces correctly)These also document that the streaming paths surface the pre-stream HTTP error lazily (from the first poll of the stream), which the tests capture.
Tracker sweep
All 72 open issues and 52 open PRs were read in full (including comments). Classification:
Addressed here
toolsviaadditional_params) was found to already merge correctly on main (responses_api/mod.rsremoves and merges the key); the issue's remaining ask — first-classprovider_tools()consumption — is a feature, not fixed here.In scope, reproduced or verified, left as follow-ups (open fix PRs exist or fix is a larger surface)
Retry-After) structurally dropped on non-success errors; an open PR fixes it. The new error-envelope cassettes confirm bodies/status survive; headers need feat(core)!: preserve response headers on non-success HTTP errors #2211's new API surface.phasefield dropped on message items;compactioninput items rejected. Overlaps the open compaction PR; fixingphasepreservation independently risks conflicting with feat(openai): support server-side Responses compaction #2234's item-model changes.cache_controldiscarded; open PR ships the fix with cassettes.additional_paramsdrop), PR feat(anthropic): add Claude 4.6 adaptive thinking support #1480 (adaptive thinking), PR fix(embeddings): respect token limits in EmbeddingsBuilder batching #1221 (embeddings batch token limit), Standardize Rig GenAI telemetry on OpenTelemetry semantic conventions #1593 (telemetry accounting) — each has an open PR or needs keys/surface outside this run's scope.Adjacent (PRs touching these providers that would benefit from recorded regression tests, not added here to avoid churn against their in-flight diffs): #2243, #2234, #1905, #1867, #1185, #2121, #2190, #1279.
Out of scope: all remaining open items are other providers, features, docs, or infra — full classification list available in the sweep notes; representative: #2291/#2290 (Mistral), #2241/#2263 (Cohere), #2156 (Ollama), #2292 (CI), #2284/#2278 (agent surface), #2229/#2228 (refactors), etc.
Recording matrix
Cassettes: 7 newly recorded, 0 re-recorded (the fix changes the wire body only for requests carrying
additional_params.tools, and no pre-existing cassette does), 0 touched otherwise. Hygiene review done file-by-file: keys/request_ids redacted by the scrubber, no secrets, no unrelated captures, placeholders redact rather than fabricate.Verification
cargo fmt— cleancargo clippy --all-targets --all-features— clean (no errors, no new warnings)cargo test -p rig-core --all-features— 1325 lib tests + doctests pass (includes a new unit test for the mixed function/nativeadditional_params.toolssplit, which also keeps Groq'scompound_customnative-tool folding intact)--test-threads=1): openai 108 passed / anthropic 79 passed / gemini 131 passed (baselines before any change: 105 / 77 / 129 passed, all green — no pre-existing failures)Estimated API spend
Under $0.05 total: six error-envelope recordings are rejected requests (no tokens billed), and the two
gpt-4o-minitool-call recordings used ~100 tokens each.