fix(openai): add reask fallback when RESPONSES_TOOLS output has no tool calls - #2498
Closed
Kropiunig wants to merge 1 commit into
Closed
fix(openai): add reask fallback when RESPONSES_TOOLS output has no tool calls#2498Kropiunig wants to merge 1 commit into
Kropiunig wants to merge 1 commit into
Conversation
…ol calls When a Responses API completion fails validation but contains no function_call items (e.g. a reasoning-only or plain message output), reask_responses_tools returned the kwargs unchanged, so every retry resent the identical request with no corrective feedback and burned the full retry budget. Append a plain user correction message in that case, mirroring the no-tool-call fallbacks already present in reask_tools (567-labs#2448) and the Anthropic reask handler (567-labs#2485).
jxnl
added a commit
that referenced
this pull request
Aug 3, 2026
## Summary - accumulate declared, nested, and unknown numeric OpenAI/Anthropic usage fields across retries while preserving non-numeric metadata - add corrective feedback when a Responses API retry receives no tool call - preserve raw iterable type hints through sync and async v2 parallel-tool wrappers - strengthen API-key-free coverage for current and future SDK usage counters ## Consolidated and superseded items - closes #2493 - consolidates contributor work from #2498, #2500, and #2501 with original commit authorship preserved - supersedes #2497 because it drops unknown `model_extra` counters - supersedes #2499 because its hand-maintained provider field lists would drift as SDKs evolve ## Validation - focused changed-surface suite: `110 passed` - broad offline v2/coverage suite: `2368 passed, 91 skipped, 73 deselected` - Ruff check and format check: passed - scoped `ty check`: passed - `uv lock --check`: passed - pre-commit hooks and `git diff --check`: passed The 73 deselected tests require live provider credentials. An unfiltered local run confirmed its 22 failures were provider network connections in the restricted environment; GitHub provider jobs remain the authoritative validation for those paths. ## Intentionally skipped - provider additions or expansions: #2436, #2435, #2423, #2409, #2384, #2322, #2306, #2298, #2283, #2168, #2086; issues #2408, #2383, #2365, #2260, #2084, #2076 - broad architecture, product, security, or streaming decisions: #2394, #2392, #2357, #2356, #2355, #2351, #2321, #2307, #2287, #2263; issues #2479, #2403, #2393, #2391, #2316, #2272, #2056 - dependency batch: #2433 - nontrivial examples and editorial/resource additions: #2468, #2405, #2401, #2354, #2346, #2311, #2305; issue #2404 These remain open because they need dedicated product, architecture, provider, security, dependency, or editorial review and are not required for the `1.15.5` patch release. <!-- CURSOR_SUMMARY --> --- > [!NOTE] > **Medium Risk** > Changes retry usage totals and reask message content on failure paths; scope is limited and heavily covered by tests, with no auth or data-store changes. > > **Overview** > Bundles three v2 retry and wrapper fixes for a patch release. > > **Retry usage accounting** replaces hand-maintained token field sums with generic `_accumulate_models` on Pydantic usage objects. Numeric fields (including nested models and `model_extra` counters) add across retries; booleans and other non-numeric metadata are not treated as billable. OpenAI and Anthropic paths share this logic. > > **OpenAI Responses reask** appends a user correction when `RESPONSES_TOOLS` validation fails but the output has no tool calls (e.g. reasoning-only), so retries include feedback instead of repeating the same request. > > **Parallel tools** in `patch_v2` skips `prepare_response_model` and does not replace `response_model` with the handler’s prepared wrapper for parallel modes, keeping raw `Iterable[...]` hints so schemas and parsed results include every member type. > > <sup>Reviewed by [Cursor Bugbot](https://cursor.com/bugbot) for commit bbddca1. Configure [here](https://www.cursor.com/dashboard/bugbot).</sup> <!-- /CURSOR_SUMMARY -->
Collaborator
|
Consolidated in #2502, now merged to main. Your original commit and authorship were preserved in the merge, with the Responses API no-tool-call retry regression and changelog entry included. Closing this source PR as superseded by the consolidation. |
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.
What
reask_responses_toolsbuilds its correction messages only from the tool calls inresponse.output. When a failed Responses API completion contains nofunction_callitems — reasoning models routinely return onlyreasoning/messageoutput items instead of honoring the forced tool call —
reask_messagesends upempty and the handler returns the kwargs unchanged. Every retry then resends the
byte-identical request with no corrective feedback, so the full retry budget (and
its tokens) is burned on responses that can't improve.
Repro sketch:
Why it's a bug, not a choice
response is None or no .outputguard at the top of the same function doesappend a fallback user correction — the invariant "a reask always carries
feedback" just never got applied to the has-output-but-no-tool-calls case.
reask_tools(chat completions) gained exactly this fallback in fix: consolidate small correctness fixes #2495 (fromfix(openai): handle responses with no tool_calls in reask_tools to avoid TypeError #2448), and the Anthropic handler gained it in fix(anthropic): return tool_result for every tool_use on reask #2486 (fix(anthropic): handle_reask drops tool_result for all but the last tool_use block in PARALLEL_TOOLS mode #2485). RESPONSES_TOOLS is
the remaining sibling without it.
Fix
If the tool-call loop produced no messages, append the same plain user correction
used by the stream/None branch. One conditional, no behavior change when tool calls
are present.
Tests
test_reask_responses_tools_no_tool_calls_adds_fallback_message— reasoning +message output items, asserts a user correction containing the validation error is
appended. Fails on
main(assert 1 == 2, messages unchanged), passes with the fix.tests/test_openai_responses_tools.py,tests/test_streaming_reask_bug.py,tests/coverage/test_openai_handlers_coverage.py: 74 passed, 1 skipped.[Unreleased].