Skip to content

Python: fix: preserve reasoning with hosted MCP calls#6139

Open
he-yufeng wants to merge 1 commit into
microsoft:mainfrom
he-yufeng:fix/openai-mcp-reasoning-pair
Open

Python: fix: preserve reasoning with hosted MCP calls#6139
he-yufeng wants to merge 1 commit into
microsoft:mainfrom
he-yufeng:fix/openai-mcp-reasoning-pair

Conversation

@he-yufeng
Copy link
Copy Markdown
Contributor

Summary

  • keep text_reasoning items when a storage-off replay also keeps a hosted MCP mcp_call
  • leave standalone reasoning and service-side-storage continuation stripping unchanged
  • add a regression test for the reasoning + hosted MCP call pair

Closes #6074.

To verify

  • PYTHONPATH=python\packages\core;python\packages\openai python -m pytest python\packages\openai\tests\openai\test_openai_chat_client.py -q -k "keeps_reasoning_with_mcp_call or serializes_mcp_server_tool_call or coalesces_mcp_call or strips_mcp_items" --basetemp .tmp\pytest-6074-2
  • PYTHONPATH=python\packages\core;python\packages\openai python -m pytest python\packages\openai\tests\openai\test_openai_chat_client.py -q -k "prepare_messages_for_openai" --basetemp .tmp\pytest-6074-prepare
  • python -m py_compile python\packages\openai\agent_framework_openai\_chat_client.py python\packages\openai\tests\openai\test_openai_chat_client.py
  • python -m ruff check python\packages\openai\agent_framework_openai\_chat_client.py python\packages\openai\tests\openai\test_openai_chat_client.py
  • git diff --check

Copilot AI review requested due to automatic review settings May 28, 2026 10:12
@github-actions github-actions Bot changed the title fix: preserve reasoning with hosted MCP calls Python: fix: preserve reasoning with hosted MCP calls May 28, 2026
@moonbox3
Copy link
Copy Markdown
Contributor

@he-yufeng You currently have 38 open PRs. As maintainers, this is a lot for us to keep on top of. Can you please hold off on opening new PRs until you've gotten some of the existing ones merged (comments addressed, checks passing)? Thank you.

@he-yufeng
Copy link
Copy Markdown
Contributor Author

Thanks, understood. I closed six superseded Agent Framework PRs to reduce the queue: #5945, #6033, #6128, #6110, #5809, and #6011. I’ll avoid opening new PRs in this repo while the current queue is being reviewed, and will focus on keeping existing PRs deduplicated, rebased, and with checks/comments addressed.

@moonbox3
Copy link
Copy Markdown
Contributor

Python Test Coverage

Python Test Coverage Report •
FileStmtsMissCoverMissing
packages/openai/agent_framework_openai
   _chat_client.py108914886%276, 289, 631–635, 643–646, 652–656, 706–713, 715–717, 724–726, 772, 780, 803, 921, 1020, 1079, 1081, 1083, 1085, 1151, 1165, 1245, 1255, 1260, 1303, 1414–1415, 1430, 1649, 1654, 1658–1660, 1664–1665, 1748, 1758, 1785, 1791, 1801, 1807, 1812, 1818, 1823–1824, 1843, 1846–1849, 1863, 1865, 1873–1874, 1886, 1928, 2018, 2040–2041, 2056–2057, 2075–2076, 2119, 2285, 2323–2324, 2342, 2422–2430, 2460, 2570, 2605, 2620, 2640–2650, 2663, 2674–2678, 2692, 2706–2717, 2726, 2758–2761, 2771–2772, 2783–2785, 2799–2801, 2811–2812, 2818, 2833
TOTAL36870431388% 

Python Unit Test Overview

Tests Skipped Failures Errors Time
7386 34 💤 0 ❌ 0 🔥 1m 58s ⏱️

# without its required following item"), so the reasoning branch always drops.
# without its required following item"). Hosted MCP calls are the exception: if we keep
# the mcp_call item without storage, keep its paired reasoning item too.
has_hosted_mcp_call = any(content.type == "mcp_server_tool_call" for content in message.contents)
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.

Should retention be per-pair rather than per-message? has_hosted_mcp_call keeps every text_reasoning in the message as soon as any mcp_call is present, but the API needs each reasoning item immediately followed by its required item. Shapes like [reasoning, mcp_call, reasoning] (trailing reasoning) or [reasoning_1, reasoning_2, mcp_call] emit a reasoning item not immediately followed by a valid one -> the exact "reasoning was provided without its required following item" this fix targets. coalesce doesn't reorder, and plain text is appended last, so neither rescues these. Wondering if the gate should check the next surviving content is the mcp_call, not just "mcp_call somewhere in the message".

storage_on = client._prepare_messages_for_openai(messages, request_uses_service_side_storage=True)
assert "reasoning" not in [item.get("type") for item in storage_on]

storage_off = client._prepare_messages_for_openai(messages, request_uses_service_side_storage=False)
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.

Are we covering the orderings where reasoning isn't adjacent to the mcp_call? This pins only [reasoning, mcp_call], the one case least likely to break. Could we add [reasoning_1, reasoning_2, mcp_call] (all kept, in order) and a non-adjacent / trailing-reasoning shape, since that's where the message-level gate could emit an orphan reasoning item?

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]: ] Orphaned mcp_call items cause HTTP 400 in multi-turn reasoning model flows

2 participants