diff --git a/CHANGELOG.md b/CHANGELOG.md index c54c5bf19..b50245625 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -10,6 +10,7 @@ Versioning: [Semantic Versioning](https://semver.org/spec/v2.0.0.html) ## [Unreleased] ### Fixed +- **OpenAI Responses reask**: Add a fallback correction message when a `RESPONSES_TOOLS` response contains no tool calls (e.g. reasoning-only output), so retries carry validation feedback instead of resending the identical request. - **Credential redaction**: Hide common OAuth and Google API credential aliases in nested v2 debug logging while preserving non-secret token configuration. ([#2490](https://github.com/567-labs/instructor/issues/2490), [#2491](https://github.com/567-labs/instructor/pull/2491)) - **Retry and message integrity**: Preserve cache keys and caller-owned retry messages, retain empty-content legacy function calls, return Anthropic tool results for every parallel tool call, and handle missing OpenAI/Mistral tool calls as retryable parse failures. ([#2454](https://github.com/567-labs/instructor/issues/2454), [#2455](https://github.com/567-labs/instructor/pull/2455), [#2464](https://github.com/567-labs/instructor/issues/2464), [#2484](https://github.com/567-labs/instructor/pull/2484), [#2485](https://github.com/567-labs/instructor/issues/2485), [#2486](https://github.com/567-labs/instructor/pull/2486), [#2448](https://github.com/567-labs/instructor/pull/2448), [#2453](https://github.com/567-labs/instructor/pull/2453)) - **Streaming and DSL correctness**: Isolate partial-model recursion guards, preserve partial nested models and explicit nulls, harden citation matching, derive useful Iterable union names, and continue scanning JSON streams after non-JSON or multiple balanced values. ([#2422](https://github.com/567-labs/instructor/issues/2422), [#2430](https://github.com/567-labs/instructor/pull/2430), [#2431](https://github.com/567-labs/instructor/issues/2431), [#2452](https://github.com/567-labs/instructor/pull/2452), [#2456](https://github.com/567-labs/instructor/pull/2456), [#2461](https://github.com/567-labs/instructor/issues/2461), [#2463](https://github.com/567-labs/instructor/pull/2463), [#2476](https://github.com/567-labs/instructor/pull/2476), [#2487](https://github.com/567-labs/instructor/pull/2487), [#2489](https://github.com/567-labs/instructor/pull/2489)) diff --git a/instructor/v2/providers/openai/handlers.py b/instructor/v2/providers/openai/handlers.py index bea9d8033..8afc5e8dd 100644 --- a/instructor/v2/providers/openai/handlers.py +++ b/instructor/v2/providers/openai/handlers.py @@ -260,6 +260,21 @@ def reask_responses_tools( } ) + if not reask_messages: + # Model produced no tool calls at all (e.g. a reasoning-only or plain + # message output). Fall back to a plain user correction so the retry + # carries feedback instead of resending the identical request, + # mirroring reask_tools and the Anthropic reask handler. + reask_messages.append( + { + "role": "user", + "content": ( + f"Validation Error found:\n{exception}\n" + "Recall the function correctly, fix the errors" + ), + } + ) + kwargs["messages"].extend(reask_messages) return kwargs diff --git a/tests/test_openai_responses_tools.py b/tests/test_openai_responses_tools.py index 5de9068ca..651e58938 100644 --- a/tests/test_openai_responses_tools.py +++ b/tests/test_openai_responses_tools.py @@ -141,6 +141,38 @@ def test_reask_responses_tools_none_arguments() -> None: assert "MUST populate ALL required fields" in msg +def test_reask_responses_tools_no_tool_calls_adds_fallback_message() -> None: + """Reask must add corrective feedback even when the output has no tool calls. + + Reasoning models can return only reasoning/message items instead of the + forced function call. Without a fallback the retry resends the identical + request with no feedback at all. + """ + reasoning_item = MagicMock() + reasoning_item.type = "reasoning" + + message_item = MagicMock() + message_item.type = "message" + + response = MagicMock() + response.output = [reasoning_item, message_item] + + error = ValueError( + "1 validation error for ResponseToolModel\nname\n Field required" + ) + + result = reask_responses_tools( + {"messages": [{"role": "user", "content": "extract"}]}, response, error + ) + + assert len(result["messages"]) == 2 + fallback = result["messages"][-1] + assert fallback["role"] == "user" + assert "Validation Error found" in fallback["content"] + assert "Field required" in fallback["content"] + assert "Recall the function correctly" in fallback["content"] + + def test_responses_tools_overrides_text_type_format() -> None: _, kwargs = OpenAIResponsesToolsHandler().prepare_request( ResponseToolModel,