Skip to content

Python: fix(python/openai): use full-history mode by default to fix tool call streaming with OpenRouter#5111

Open
Bash892 wants to merge 1 commit intomicrosoft:mainfrom
Bash892:fix/openrouter-tool-result-5105
Open

Python: fix(python/openai): use full-history mode by default to fix tool call streaming with OpenRouter#5111
Bash892 wants to merge 1 commit intomicrosoft:mainfrom
Bash892:fix/openrouter-tool-result-5105

Conversation

@Bash892
Copy link
Copy Markdown

@Bash892 Bash892 commented Apr 5, 2026

Summary

Fixes #5105 — streaming crashes after tool execution when using OpenRouter or any provider that doesn't support previous_response_id.

Root cause: _get_conversation_id returned response.id by default (when store was not explicitly False). This caused the executor to enter server-managed continuation mode — clearing the full conversation history and sending only the tool result in the next request. Providers like OpenRouter ignore previous_response_id and received a bare function_call_output at position 0 → HTTP 400.

Fix: _get_conversation_id now returns None unless store=True is explicitly set. Server-managed conversation mode is opt-in; the default is full-history mode, which is universally compatible.

Users who want the server-managed optimization (OpenAI native only) should set store=True in their chat options.

Changes

  • python/packages/openai/agent_framework_openai/_chat_client.py: changed if store is Falseif store is not True in _get_conversation_id
  • python/packages/openai/tests/openai/test_openai_chat_client.py: updated streaming event tests to cover both store=True and store=None; added test_parse_response_with_store_none_returns_none as a regression test

Test plan

  • uv run pytest packages/openai/tests/ -m "not integration" — 270 passed, 0 failures
  • Verify streaming with OpenRouter no longer raises HTTP 400 after tool execution
  • Verify store=True still enables server-managed conversation mode for OpenAI native

…aming with OpenRouter

_get_conversation_id now returns None unless store=True is explicitly set,
making server-managed conversation mode opt-in. This ensures compatibility
with providers that don't support previous_response_id (e.g. OpenRouter),
where sending only a tool result without full conversation context caused
an HTTP 400 error.

Fixes microsoft#5105
@github-actions github-actions bot changed the title fix(python/openai): use full-history mode by default to fix tool call streaming with OpenRouter Python: fix(python/openai): use full-history mode by default to fix tool call streaming with OpenRouter Apr 5, 2026
@Bash892
Copy link
Copy Markdown
Author

Bash892 commented Apr 5, 2026

@microsoft-github-policy-service agree

Copy link
Copy Markdown
Contributor

@chetantoshniwal chetantoshniwal left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated Code Review

Reviewers: 4 | Confidence: 90%

✓ Correctness

This PR fixes a correctness issue where _get_conversation_id would return a conversation ID when store=None (the default), causing providers that don't support previous_response_id (e.g. OpenRouter) to fail. The fix changes the guard from if store is False to if store is not True, so only an explicit store=True opt-in enables server-managed conversation mode. The logic change is correct and the tests are well-structured. One note: the same _get_conversation_id method in _responses_client.py (line 448) still uses the old if store is False guard, but this may be intentional since the Responses API is OpenAI-native and always supports previous_response_id.

✓ Security Reliability

This PR fixes a reliability issue (GitHub #5105) where _get_conversation_id would return a conversation ID when store=None (the default), causing failures with OpenAI-compatible providers (e.g., OpenRouter) that don't support previous_response_id. The fix correctly changes the guard from if store is False to if store is not True, ensuring only explicit opt-in via store=True enables server-managed conversation mode. The code change is minimal and well-tested. No security issues are introduced. Note that the sibling _responses_client.py still uses the old if store is False check, but this is likely intentional since the Responses API is OpenAI-native and always supports conversation IDs.

✓ Test Coverage

The test coverage for this behavioral change is solid. The new test test_parse_response_with_store_none_returns_none directly validates the regression fix (issue #5105) by asserting that both store=None and store=False return None from _get_conversation_id. The streaming tests (test_streaming_response_created_type and test_streaming_response_in_progress_type) were properly updated to verify both store=True (returns conversation_id) and store=None (returns None) paths. One minor gap: the non-streaming _get_conversation_id path lacks a direct store=True with-conversation positive test in this diff, though this is likely covered by a pre-existing test. A store=False case for streaming is untested but is functionally identical to store=None under the new is not True guard. Overall the tests are meaningful — they assert specific return values rather than just exercising code paths.

✓ Design Approach

The change correctly inverts the semantics of _get_conversation_id from opt-out (return None only when store=False) to opt-in (return non-None only when store=True). Previously, store=None (the default when a caller omits the field) would fall through the guard and return a previous_response_id-based conversation ID, which breaks providers like OpenRouter that don't support that parameter. The fix properly treats store=None the same as store=False, making server-managed conversation mode strictly opt-in. The change is minimal, addresses the root cause rather than masking it, and the updated tests adequately cover all three store states. No blocking design issues found.


Automated review by chetantoshniwal's agents

assert client._get_conversation_id(mock_response, store=False) is None


def test_parse_response_uses_response_id_when_no_conversation() -> None:
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This test covers the store=None and store=False negative cases well, but doesn't verify the positive case (store=True returning conv_456). Adding assert client._get_conversation_id(mock_response, store=True) == 'conv_456' would make this a complete tri-state test and guard against future regressions that might accidentally break the store=True path.

Suggested change
def test_parse_response_uses_response_id_when_no_conversation() -> None:
assert client._get_conversation_id(mock_response, store=None) is None
assert client._get_conversation_id(mock_response, store=False) is None
assert client._get_conversation_id(mock_response, store=True) == "conv_456"

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Python: [Bug]: Streaming fails with OpenRouter 400: tool role without preceding tool_calls

3 participants