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)