Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -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))
Expand Down
15 changes: 15 additions & 0 deletions instructor/v2/providers/openai/handlers.py
Original file line number Diff line number Diff line change
Expand Up @@ -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

Expand Down
32 changes: 32 additions & 0 deletions tests/test_openai_responses_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down