From 896e079ecfe40c95a75ab0f7dcdae8376c53361c Mon Sep 17 00:00:00 2001 From: jiangjiang248 Date: Sat, 26 Sep 2026 13:34:48 +0800 Subject: [PATCH] Python: isolate structured output transform history per invocation --- .../agents/orchestration/tools.py | 8 +++--- .../orchestration/test_orchestration_tools.py | 26 +++++++++++++++++++ 2 files changed, 29 insertions(+), 5 deletions(-) diff --git a/python/semantic_kernel/agents/orchestration/tools.py b/python/semantic_kernel/agents/orchestration/tools.py index 1ab38f9ef9cc..36d4758ea534 100644 --- a/python/semantic_kernel/agents/orchestration/tools.py +++ b/python/semantic_kernel/agents/orchestration/tools.py @@ -42,15 +42,13 @@ def structured_outputs_transform( raise ValueError("The service must support structured output.") settings.response_format = target_structure - chat_history = ChatHistory( - system_message=( - "Try your best to summarize the conversation into structured format:\n" - f"{target_structure.model_json_schema()}." - ), + system_message = ( + f"Try your best to summarize the conversation into structured format:\n{target_structure.model_json_schema()}." ) async def output_transform(output: DefaultTypeAlias) -> BaseModel: """Transform the output of the chat completion service into the target structure.""" + chat_history = ChatHistory(system_message=system_message) if isinstance(output, ChatMessageContent): chat_history.add_message(output) elif isinstance(output, list) and all(isinstance(item, ChatMessageContent) for item in output): diff --git a/python/tests/unit/agents/orchestration/test_orchestration_tools.py b/python/tests/unit/agents/orchestration/test_orchestration_tools.py index ec1a34f258f7..0ded533e1ee6 100644 --- a/python/tests/unit/agents/orchestration/test_orchestration_tools.py +++ b/python/tests/unit/agents/orchestration/test_orchestration_tools.py @@ -157,6 +157,32 @@ async def test_structured_outputs_transform_invoke_with_messages(): assert len(mock_get_chat_message_content.call_args[0][0].messages) == 3 +async def test_structured_outputs_transform_keeps_invocations_isolated(): + """Each invocation should send only its own messages to the chat service.""" + mock_model = MockModel(name="John Doe", age=30) + + with patch.object( + MockChatCompletionService, "get_chat_message_content", new_callable=AsyncMock + ) as mock_get_chat_message_content: + mock_get_chat_message_content.return_value = ChatMessageContent( + role="assistant", content=mock_model.model_dump_json() + ) + service = MockChatCompletionService(ai_model_id="test_model") + transform = structured_outputs_transform(target_structure=MockModel, service=service) + + first = ChatMessageContent(role="user", content="first invocation") + second = ChatMessageContent(role="user", content="second invocation") + assert await transform(first) == mock_model + assert await transform(second) == mock_model + + histories = [call.args[0] for call in mock_get_chat_message_content.call_args_list] + assert len(histories) == 2 + assert histories[0] is not histories[1] + assert histories[0].messages == [histories[0].messages[0], first] + assert histories[1].messages == [histories[1].messages[0], second] + assert histories[0].messages[0] == histories[1].messages[0] + + async def test_structured_outputs_transform_invoke_unsupported_type(): """Test the structured_outputs_transform function and invoke the transform with messages of unsupported type."""