From 937305c442a87e10e5850735c0f37c755616031f Mon Sep 17 00:00:00 2001 From: godququ5-code <256881196+godququ5-code@users.noreply.github.com> Date: Fri, 3 Jul 2026 22:39:27 +0300 Subject: [PATCH] Fix Foundry reasoning MCP compaction --- .../core/agent_framework/_compaction.py | 6 ++-- .../core/tests/core/test_compaction.py | 31 +++++++++++++++++++ .../_responses.py | 4 +-- .../foundry_hosting/tests/test_responses.py | 4 +++ 4 files changed, 40 insertions(+), 5 deletions(-) diff --git a/python/packages/core/agent_framework/_compaction.py b/python/packages/core/agent_framework/_compaction.py index 3644bf4a9cc..8f00fb813fb 100644 --- a/python/packages/core/agent_framework/_compaction.py +++ b/python/packages/core/agent_framework/_compaction.py @@ -74,8 +74,8 @@ def _has_content_type(message: Message, content_type: str) -> bool: return any(content.type == content_type for content in message.contents) -def _has_function_call(message: Message) -> bool: - return _has_content_type(message, "function_call") +def _has_tool_call(message: Message) -> bool: + return any(content.type in {"function_call", "mcp_server_tool_call"} for content in message.contents) def _has_reasoning(message: Message) -> bool: @@ -83,7 +83,7 @@ def _has_reasoning(message: Message) -> bool: def _is_tool_call_assistant(message: Message) -> bool: - return message.role == "assistant" and _has_function_call(message) + return message.role == "assistant" and _has_tool_call(message) def _is_reasoning_only_assistant(message: Message) -> bool: diff --git a/python/packages/core/tests/core/test_compaction.py b/python/packages/core/tests/core/test_compaction.py index aa5fc79a454..3920d63e9e8 100644 --- a/python/packages/core/tests/core/test_compaction.py +++ b/python/packages/core/tests/core/test_compaction.py @@ -45,6 +45,20 @@ def _assistant_function_call(call_id: str) -> Message: ) +def _assistant_mcp_call(call_id: str) -> Message: + return Message( + role="assistant", + contents=[ + Content.from_mcp_server_tool_call( + call_id=call_id, + tool_name="search", + server_name="test_server", + arguments='{"query":"x"}', + ) + ], + ) + + def _assistant_reasoning_and_function_calls(*call_ids: str) -> Message: contents: list[Content] = [Content.from_text_reasoning(text="thinking")] for call_id in call_ids: @@ -154,6 +168,23 @@ def test_group_annotations_handle_same_message_reasoning_and_function_calls() -> assert _group_has_reasoning(messages[1]) is True +async def test_sliding_window_keeps_reasoning_and_mcp_call_atomic() -> None: + messages = [ + Message(role="system", contents=["system"]), + Message(role="assistant", contents=[Content.from_text_reasoning(id="rs_1", text="thinking")]), + _assistant_mcp_call("mcp_1"), + Message(role="assistant", contents=["answer"]), + Message(role="user", contents=["follow up"]), + ] + annotate_message_groups(messages) + + await SlidingWindowStrategy(keep_last_groups=3)(messages) + + assert messages[1].additional_properties[EXCLUDED_KEY] is False + assert messages[2].additional_properties[EXCLUDED_KEY] is False + assert _group_id(messages[1]) == _group_id(messages[2]) + + def test_annotate_message_groups_with_tokenizer_adds_token_counts() -> None: messages = [ Message(role="user", contents=["hello"]), diff --git a/python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py b/python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py index b0b09558447..717f95340e0 100644 --- a/python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py +++ b/python/packages/foundry_hosting/agent_framework_foundry_hosting/_responses.py @@ -1110,7 +1110,7 @@ async def _item_to_message(item: Item, *, approval_storage: ApprovalStorage | No reason_contents: list[Content] = [] if reasoning.summary: for summary in reasoning.summary: - reason_contents.append(Content.from_text(summary.text)) + reason_contents.append(Content.from_text_reasoning(id=reasoning.id, text=summary.text)) return Message(role="assistant", contents=reason_contents) if item.type == "mcp_call": @@ -1383,7 +1383,7 @@ async def _output_item_to_message(item: OutputItem, *, approval_storage: Approva contents: list[Content] = [] if reasoning.summary: for summary in reasoning.summary: - contents.append(Content.from_text(summary.text)) + contents.append(Content.from_text_reasoning(id=reasoning.id, text=summary.text)) return Message(role="assistant", contents=contents) if item.type == "mcp_call": diff --git a/python/packages/foundry_hosting/tests/test_responses.py b/python/packages/foundry_hosting/tests/test_responses.py index 619ff5c0a56..252f3f324eb 100644 --- a/python/packages/foundry_hosting/tests/test_responses.py +++ b/python/packages/foundry_hosting/tests/test_responses.py @@ -813,6 +813,8 @@ async def test_reasoning(self) -> None: msg = await _output_item_to_message(item) assert msg.role == "assistant" assert len(msg.contents) == 1 + assert msg.contents[0].type == "text_reasoning" + assert msg.contents[0].id == "r-1" assert msg.contents[0].text == "thinking hard" async def test_reasoning_no_summary(self) -> None: @@ -1298,6 +1300,8 @@ async def test_reasoning_with_summary(self) -> None: assert msg is not None assert msg.role == "assistant" assert len(msg.contents) == 1 + assert msg.contents[0].type == "text_reasoning" + assert msg.contents[0].id == "r-1" assert msg.contents[0].text == "thinking hard" async def test_reasoning_no_summary(self) -> None: