From 8bb175163f49c584ae11a6aa743c326300c0655c Mon Sep 17 00:00:00 2001 From: Safal Gupta <148842438+alphasafal@users.noreply.github.com> Date: Sun, 25 Oct 2026 15:18:13 +0000 Subject: [PATCH] Python: keep existing summaries after the system message in the summarization reducer With use_single_summary=False, ChatHistorySummarizationReducer located the end of the existing summaries from index 0. After the first reduction the preserved system message sits at index 0, so the boundary was always 0: earlier summaries were never kept and were folded into the next summary, as if use_single_summary were True. Look past a leading system message when locating the boundary. --- .../chat_history_summarization_reducer.py | 3 ++ ...test_chat_history_summarization_reducer.py | 36 +++++++++++++++++++ 2 files changed, 39 insertions(+) diff --git a/python/semantic_kernel/contents/history_reducer/chat_history_summarization_reducer.py b/python/semantic_kernel/contents/history_reducer/chat_history_summarization_reducer.py index 3b8d2db82acf..b6a086f54dcd 100644 --- a/python/semantic_kernel/contents/history_reducer/chat_history_summarization_reducer.py +++ b/python/semantic_kernel/contents/history_reducer/chat_history_summarization_reducer.py @@ -107,6 +107,9 @@ async def reduce(self) -> Self | None: # 1. Identify where existing summary messages end insertion_point = locate_summarization_boundary(history) + if not self.use_single_summary and system_message_index == 0: + # Earlier reductions keep the system message in front of their summaries, so look past it + insertion_point = 1 + locate_summarization_boundary(history[1:]) if insertion_point == len(history): # fallback fix: force boundary to something reasonable logger.warning("All messages are summaries, forcing boundary to 0.") diff --git a/python/tests/unit/contents/test_chat_history_summarization_reducer.py b/python/tests/unit/contents/test_chat_history_summarization_reducer.py index 3914128ef4b5..74a4f732d289 100644 --- a/python/tests/unit/contents/test_chat_history_summarization_reducer.py +++ b/python/tests/unit/contents/test_chat_history_summarization_reducer.py @@ -312,3 +312,39 @@ async def test_summarization_preserves_developer_message(mock_service): # Developer message must be preserved assert result.messages[0].role == AuthorRole.DEVELOPER assert result.messages[0].content == "Developer instructions" + + +async def test_summarization_keeps_existing_summaries_after_system_message(mock_service): + """With use_single_summary=False, summaries after a preserved system prompt are kept, not re-summarized.""" + system_message = ChatMessageContent(role=AuthorRole.SYSTEM, content="Important system prompt") + existing_summary = ChatMessageContent( + role=AuthorRole.ASSISTANT, content="Earlier summary.", metadata={SUMMARY_METADATA_KEY: True} + ) + messages = [ + system_message, + existing_summary, + ChatMessageContent(role=AuthorRole.USER, content="User says hello"), + ChatMessageContent(role=AuthorRole.ASSISTANT, content="Assistant responds"), + ChatMessageContent(role=AuthorRole.USER, content="User says more"), + ChatMessageContent(role=AuthorRole.ASSISTANT, content="Assistant responds again"), + ChatMessageContent(role=AuthorRole.USER, content="User says even more"), + ChatMessageContent(role=AuthorRole.ASSISTANT, content="Assistant responds yet again"), + ] + + reducer = ChatHistorySummarizationReducer( + service=mock_service, target_count=2, threshold_count=0, use_single_summary=False + ) + reducer.messages = messages + + new_summary = ChatMessageContent(role=AuthorRole.ASSISTANT, content="New summary.") + mock_service.get_chat_message_content.return_value = new_summary + mock_service.get_prompt_execution_settings_from_settings.return_value = PromptExecutionSettings() + + result = await reducer.reduce() + + assert result is not None + assert result.messages[0] is system_message + assert result.messages[1] is existing_summary + assert result.messages[2] is new_summary + summarized_history = mock_service.get_chat_message_content.call_args.kwargs["chat_history"] + assert all(msg.content != "Earlier summary." for msg in summarized_history.messages)