From 495e1dad6bf3c62b14929805cfd5f0409c897876 Mon Sep 17 00:00:00 2001 From: Giles Odigwe <79032838+giles17@users.noreply.github.com> Date: Sun, 19 Apr 2026 20:30:54 -0700 Subject: [PATCH] Python: Fix CopilotStudioAgent to reuse conversation ID from existing session (#5299) * Fix CopilotStudioAgent to reuse existing conversation on session (#5285) CopilotStudioAgent unconditionally called _start_new_conversation() in both _run_impl and _run_stream_impl, ignoring any existing service_session_id on the session. Add a guard to only start a new conversation when there is no existing service_session_id, matching the pattern used by other agents. Also fix pre-existing pyright reportMissingImports errors for orjson in file_history_provider samples. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> * Revert out-of-scope sample file changes Remove unrelated orjson type-ignore comment changes from sample files that were outside the scope of the conversation-ID reuse fix. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --------- Co-authored-by: Copilot Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../agent_framework_copilotstudio/_agent.py | 6 ++- .../copilotstudio/tests/test_copilot_agent.py | 41 +++++++++++++++++++ 2 files changed, 45 insertions(+), 2 deletions(-) diff --git a/python/packages/copilotstudio/agent_framework_copilotstudio/_agent.py b/python/packages/copilotstudio/agent_framework_copilotstudio/_agent.py index 56a9c89081..333e75470f 100644 --- a/python/packages/copilotstudio/agent_framework_copilotstudio/_agent.py +++ b/python/packages/copilotstudio/agent_framework_copilotstudio/_agent.py @@ -244,7 +244,8 @@ class CopilotStudioAgent(BaseAgent): """Non-streaming implementation of run.""" if not session: session = self.create_session() - session.service_session_id = await self._start_new_conversation() + if not session.service_session_id: + session.service_session_id = await self._start_new_conversation() input_messages = normalize_messages(messages) @@ -271,7 +272,8 @@ class CopilotStudioAgent(BaseAgent): nonlocal session if not session: session = self.create_session() - session.service_session_id = await self._start_new_conversation() + if not session.service_session_id: + session.service_session_id = await self._start_new_conversation() input_messages = normalize_messages(messages) diff --git a/python/packages/copilotstudio/tests/test_copilot_agent.py b/python/packages/copilotstudio/tests/test_copilot_agent.py index 77e370ab1e..49da1d7208 100644 --- a/python/packages/copilotstudio/tests/test_copilot_agent.py +++ b/python/packages/copilotstudio/tests/test_copilot_agent.py @@ -245,6 +245,47 @@ class TestCopilotStudioAgent: assert response_count == 1 assert session.service_session_id == "test-conversation-id" + async def test_run_reuses_existing_conversation( + self, mock_copilot_client: MagicMock, mock_activity: MagicMock + ) -> None: + """Test run method reuses an existing conversation ID from the session.""" + agent = CopilotStudioAgent(client=mock_copilot_client) + session = AgentSession() + session.service_session_id = "existing-conversation-id" + + mock_copilot_client.ask_question.return_value = create_async_generator([mock_activity]) + + response = await agent.run("test message", session=session) + + assert isinstance(response, AgentResponse) + assert session.service_session_id == "existing-conversation-id" + mock_copilot_client.start_conversation.assert_not_called() + mock_copilot_client.ask_question.assert_called_once_with("test message", "existing-conversation-id") + + async def test_run_streaming_reuses_existing_conversation(self, mock_copilot_client: MagicMock) -> None: + """Test run(stream=True) method reuses an existing conversation ID from the session.""" + agent = CopilotStudioAgent(client=mock_copilot_client) + session = AgentSession() + session.service_session_id = "existing-conversation-id" + + typing_activity = MagicMock() + typing_activity.text = "Streaming response" + typing_activity.type = "typing" + typing_activity.id = "test-typing-id" + typing_activity.from_property.name = "Test Bot" + + mock_copilot_client.ask_question.return_value = create_async_generator([typing_activity]) + + response_count = 0 + async for response in agent.run("test message", session=session, stream=True): + assert isinstance(response, AgentResponseUpdate) + response_count += 1 + + assert response_count == 1 + assert session.service_session_id == "existing-conversation-id" + mock_copilot_client.start_conversation.assert_not_called() + mock_copilot_client.ask_question.assert_called_once_with("test message", "existing-conversation-id") + async def test_run_streaming_no_typing_activity(self, mock_copilot_client: MagicMock) -> None: """Test run(stream=True) method with non-typing activity.""" agent = CopilotStudioAgent(client=mock_copilot_client)