Skip to content

fix(openai): add reask fallback when RESPONSES_TOOLS output has no tool calls - #2498

Closed
Kropiunig wants to merge 1 commit into
567-labs:mainfrom
Kropiunig:fix-responses-reask-no-tool-call-fallback
Closed

fix(openai): add reask fallback when RESPONSES_TOOLS output has no tool calls#2498
Kropiunig wants to merge 1 commit into
567-labs:mainfrom
Kropiunig:fix-responses-reask-no-tool-call-fallback

Conversation

@Kropiunig

Copy link
Copy Markdown
Contributor

What

reask_responses_tools builds its correction messages only from the tool calls in
response.output. When a failed Responses API completion contains no
function_call items — reasoning models routinely return only reasoning/message
output items instead of honoring the forced tool call — reask_messages ends up
empty 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:

client = instructor.from_openai(OpenAI(), mode=instructor.Mode.RESPONSES_TOOLS)
# model answers with a reasoning/message output instead of the forced tool call
# -> parse raises ResponseParsingError (retryable)
# -> reask_responses_tools(kwargs, response, exc) returns kwargs UNCHANGED
# -> attempts 2..n are identical requests, then InstructorRetryException

Why it's a bug, not a choice

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.
  • Full tests/test_openai_responses_tools.py, tests/test_streaming_reask_bug.py,
    tests/coverage/test_openai_handlers_coverage.py: 74 passed, 1 skipped.
  • CHANGELOG entry added under [Unreleased].

…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 -->
@jxnl

jxnl commented Aug 3, 2026

Copy link
Copy Markdown
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.

@jxnl jxnl closed this Aug 3, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants