From ea3d149232061234675e7c23b167ec6025818811 Mon Sep 17 00:00:00 2001 From: Joshua Nwachinemere <217677783+dk3yyyy@users.noreply.github.com> Date: Wed, 29 Jul 2026 11:06:20 +0000 Subject: [PATCH 1/2] Python: Ignore excluded tool results during compaction --- .../core/agent_framework/_compaction.py | 5 ++- .../core/tests/core/test_compaction.py | 37 +++++++++++++++++++ 2 files changed, 40 insertions(+), 2 deletions(-) diff --git a/python/packages/core/agent_framework/_compaction.py b/python/packages/core/agent_framework/_compaction.py index 59abb10a468..6398a654dda 100644 --- a/python/packages/core/agent_framework/_compaction.py +++ b/python/packages/core/agent_framework/_compaction.py @@ -894,9 +894,10 @@ async def __call__(self, messages: list[Message]) -> bool: if group_id in keep_ids: continue group_msgs = grouped.get(group_id, []) + included_group_msgs = [msg for msg in group_msgs if not msg.additional_properties.get(EXCLUDED_KEY, False)] # Build a call_id → function_name map from function_call contents. call_id_to_name: dict[str, str] = {} - for msg in group_msgs: + for msg in included_group_msgs: for content in msg.contents: if content.type == "function_call" and content.call_id and content.name: call_id_to_name[content.call_id] = content.name @@ -904,7 +905,7 @@ async def __call__(self, messages: list[Message]) -> bool: call_id_to_name[content.call_id] = content.tool_name # Collect tool results with the function name for context. tool_results: list[str] = [] - for msg in group_msgs: + for msg in included_group_msgs: for content in msg.contents: if content.type == "function_result": result_text = content.result if isinstance(content.result, str) else str(content.result) diff --git a/python/packages/core/tests/core/test_compaction.py b/python/packages/core/tests/core/test_compaction.py index 4507d39946f..daaf6bb9780 100644 --- a/python/packages/core/tests/core/test_compaction.py +++ b/python/packages/core/tests/core/test_compaction.py @@ -772,6 +772,43 @@ async def test_tool_result_compaction_preserves_tool_results_in_summary() -> Non assert "found 3 docs" in summary_msgs[0].text # type: ignore[operator] +async def test_tool_result_compaction_does_not_restore_excluded_results() -> None: + """A summary must use only results that remain in the included context.""" + excluded_payload = "excluded payload " * 2_000 + messages = [ + Message(role="user", contents=["u"]), + Message( + role="assistant", + contents=[ + Content.from_function_call(call_id="c1", name="get_weather", arguments="{}"), + Content.from_function_call(call_id="c2", name="search_docs", arguments="{}"), + ], + ), + _tool_result("c1", "sunny"), + _tool_result("c2", excluded_payload), + Message(role="assistant", contents=["done"]), + ] + strategy = ToolResultCompactionStrategy(keep_last_tool_call_groups=0) + tokenizer = CharacterEstimatorTokenizer() + annotate_message_groups(messages, tokenizer=tokenizer) + original_group = messages[1:4] + excluded_result = messages[3] + excluded_result.additional_properties[EXCLUDED_KEY] = True + original_message_ids = [message.message_id for message in original_group if message.message_id] + token_count_before = included_token_count(messages) + + await strategy(messages) + annotate_message_groups(messages, tokenizer=tokenizer) + + summary = next( + message for message in included_messages(messages) if (message.text or "").startswith("[Tool results:") + ) + assert summary.text == "[Tool results: get_weather: sunny]" + assert included_token_count(messages) < token_count_before + assert _group_unknown_value(summary, SUMMARY_OF_MESSAGE_IDS_KEY) == original_message_ids + assert _group_unknown_value(excluded_result, SUMMARIZED_BY_SUMMARY_ID_KEY) == summary.message_id + + async def test_tool_result_compaction_bidirectional_tracing() -> None: """Summary and originals should link to each other like SummarizationStrategy does.""" messages = [ From e8390cfda806f34099043210ca39164f5dc959e1 Mon Sep 17 00:00:00 2001 From: Joshua Nwachinemere <217677783+dk3yyyy@users.noreply.github.com> Date: Thu, 30 Jul 2026 10:32:58 +0000 Subject: [PATCH 2/2] fix: avoid extra compaction message pass --- python/packages/core/agent_framework/_compaction.py | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/python/packages/core/agent_framework/_compaction.py b/python/packages/core/agent_framework/_compaction.py index 6398a654dda..9d2bb7415b9 100644 --- a/python/packages/core/agent_framework/_compaction.py +++ b/python/packages/core/agent_framework/_compaction.py @@ -894,10 +894,11 @@ async def __call__(self, messages: list[Message]) -> bool: if group_id in keep_ids: continue group_msgs = grouped.get(group_id, []) - included_group_msgs = [msg for msg in group_msgs if not msg.additional_properties.get(EXCLUDED_KEY, False)] # Build a call_id → function_name map from function_call contents. call_id_to_name: dict[str, str] = {} - for msg in included_group_msgs: + for msg in group_msgs: + if msg.additional_properties.get(EXCLUDED_KEY, False): + continue for content in msg.contents: if content.type == "function_call" and content.call_id and content.name: call_id_to_name[content.call_id] = content.name @@ -905,7 +906,9 @@ async def __call__(self, messages: list[Message]) -> bool: call_id_to_name[content.call_id] = content.tool_name # Collect tool results with the function name for context. tool_results: list[str] = [] - for msg in included_group_msgs: + for msg in group_msgs: + if msg.additional_properties.get(EXCLUDED_KEY, False): + continue for content in msg.contents: if content.type == "function_result": result_text = content.result if isinstance(content.result, str) else str(content.result)