From b47f147df154ead40673b0839bb8989197d9040b Mon Sep 17 00:00:00 2001 From: Evan Mattson Date: Fri, 6 Mar 2026 12:37:23 +0900 Subject: [PATCH 1/3] Python: Exclude conversation_id from chat completions options (#4315) When a session with service_session_id is passed to an agent using the Chat Completions client, conversation_id leaked through _prepare_options() into AsyncCompletions.create(), causing an 'unexpected keyword argument' error. The Responses client already excluded conversation_id but the Chat Completions client did not. Added conversation_id to the exclusion set in _prepare_options(). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../agent_framework/openai/_chat_client.py | 6 +- .../tests/azure/test_azure_chat_client.py | 67 +++++++++++++++++++ .../tests/openai/test_openai_chat_client.py | 15 +++++ 3 files changed, 87 insertions(+), 1 deletion(-) diff --git a/python/packages/core/agent_framework/openai/_chat_client.py b/python/packages/core/agent_framework/openai/_chat_client.py index 0c3d346129..7f78068388 100644 --- a/python/packages/core/agent_framework/openai/_chat_client.py +++ b/python/packages/core/agent_framework/openai/_chat_client.py @@ -322,7 +322,11 @@ def _prepare_options(self, messages: Sequence[Message], options: Mapping[str, An messages = prepend_instructions_to_messages(list(messages), instructions, role="system") # Start with a copy of options - run_options = {k: v for k, v in options.items() if v is not None and k not in {"instructions", "tools"}} + run_options = { + k: v + for k, v in options.items() + if v is not None and k not in {"instructions", "tools", "conversation_id"} + } # messages if messages and "messages" not in run_options: diff --git a/python/packages/core/tests/azure/test_azure_chat_client.py b/python/packages/core/tests/azure/test_azure_chat_client.py index 3e88504493..b6809d097d 100644 --- a/python/packages/core/tests/azure/test_azure_chat_client.py +++ b/python/packages/core/tests/azure/test_azure_chat_client.py @@ -626,6 +626,73 @@ async def test_streaming_with_none_delta( assert any(msg.contents for msg in results) +@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock) +async def test_cmc_with_conversation_id( + mock_create: AsyncMock, + azure_openai_unit_test_env: dict[str, str], + chat_history: list[Message], + mock_chat_completion_response: ChatCompletion, +) -> None: + """Test that conversation_id is excluded from the completions create call.""" + mock_create.return_value = mock_chat_completion_response + chat_history.append(Message(text="hello world", role="user")) + + azure_chat_client = AzureOpenAIChatClient() + await azure_chat_client.get_response( + messages=chat_history, + options={"conversation_id": "12345"}, + ) + + call_kwargs = mock_create.call_args.kwargs + assert "conversation_id" not in call_kwargs + + +@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock) +async def test_cmc_streaming_with_conversation_id( + mock_create: AsyncMock, + azure_openai_unit_test_env: dict[str, str], + chat_history: list[Message], + mock_streaming_chat_completion_response: AsyncStream[ChatCompletionChunk], +) -> None: + """Test that conversation_id is excluded from the streaming completions create call.""" + mock_create.return_value = mock_streaming_chat_completion_response + chat_history.append(Message(text="hello world", role="user")) + + azure_chat_client = AzureOpenAIChatClient() + async for _ in azure_chat_client.get_response( + messages=chat_history, + options={"conversation_id": "12345"}, + stream=True, + ): + pass + + call_kwargs = mock_create.call_args.kwargs + assert "conversation_id" not in call_kwargs + + +@patch.object(AsyncChatCompletions, "create", new_callable=AsyncMock) +async def test_cmc_agent_with_service_session_id( + mock_create: AsyncMock, + azure_openai_unit_test_env: dict[str, str], + mock_chat_completion_response: ChatCompletion, +) -> None: + """Test that agent.run() with a session containing service_session_id works correctly.""" + mock_create.return_value = mock_chat_completion_response + + azure_chat_client = AzureOpenAIChatClient() + agent = azure_chat_client.as_agent( + name="TestAgent", + instructions="You are a helpful assistant.", + ) + + session = agent.get_session(service_session_id="12345") + response = await agent.run("hello", session=session) + + assert response is not None + call_kwargs = mock_create.call_args.kwargs + assert "conversation_id" not in call_kwargs + + @tool(approval_mode="never_require") def get_story_text() -> str: """Returns a story about Emily and David.""" diff --git a/python/packages/core/tests/openai/test_openai_chat_client.py b/python/packages/core/tests/openai/test_openai_chat_client.py index 58faac42a3..04321b0883 100644 --- a/python/packages/core/tests/openai/test_openai_chat_client.py +++ b/python/packages/core/tests/openai/test_openai_chat_client.py @@ -1161,6 +1161,21 @@ def test_prepare_options_removes_parallel_tool_calls_when_no_tools(openai_unit_t assert "parallel_tool_calls" not in prepared_options +def test_prepare_options_excludes_conversation_id(openai_unit_test_env: dict[str, str]) -> None: + """Test that conversation_id is excluded from prepared options for chat completions.""" + client = OpenAIChatClient() + + messages = [Message(role="user", text="test")] + options = {"conversation_id": "12345", "temperature": 0.7} + + prepared_options = client._prepare_options(messages, options) + + # conversation_id is not a valid parameter for AsyncCompletions.create() + assert "conversation_id" not in prepared_options + # Other options should still be present + assert prepared_options["temperature"] == 0.7 + + async def test_streaming_exception_handling(openai_unit_test_env: dict[str, str]) -> None: """Test that streaming errors are properly handled.""" client = OpenAIChatClient() From cc572b3f1e0384df7d2f5ba75740482b3dc4e477 Mon Sep 17 00:00:00 2001 From: Evan Mattson Date: Fri, 6 Mar 2026 12:37:42 +0900 Subject: [PATCH 2/3] Apply pre-commit auto-fixes --- REPRODUCTION_REPORT.md | 33 +++++++ .../agent_framework/openai/_chat_client.py | 4 +- .../tests/workflow/test_agent_executor.py | 1 + .../core/tests/workflow/test_agent_utils.py | 27 +++++- .../packages/core/tests/workflow/test_edge.py | 3 +- .../core/tests/workflow/test_executor.py | 3 +- .../tests/workflow/test_workflow_agent.py | 36 +++++++- .../tests/workflow/test_workflow_kwargs.py | 90 ++++++++++++++++--- .../tests/workflow/test_workflow_states.py | 8 +- 9 files changed, 180 insertions(+), 25 deletions(-) create mode 100644 REPRODUCTION_REPORT.md diff --git a/REPRODUCTION_REPORT.md b/REPRODUCTION_REPORT.md new file mode 100644 index 0000000000..d6e4e59812 --- /dev/null +++ b/REPRODUCTION_REPORT.md @@ -0,0 +1,33 @@ +# Reproduction Report — Issue #4315 +**Python: [Bug]: Azure open ai agent session error** + +*Generated: 2026-03-06 03:33:44 UTC* + +## Result + +| Field | Value | +|-------|-------| +| Status | `reproduced` | +| Failure observed | `yes` | +| Evidence strength | `high` | +| Confidence | `high` | +| Failing test | `python/packages/core/tests/azure/test_azure_chat_client_conversation_id_bug.py` | +| Gate passed | Yes | + +## Files Examined + +python/packages/core/agent_framework/azure/_chat_client.py, python/packages/core/agent_framework/openai/_chat_client.py, python/packages/core/agent_framework/openai/_responses_client.py, python/packages/core/agent_framework/_agents.py, python/packages/core/tests/azure/conftest.py, python/packages/core/tests/azure/test_azure_chat_client.py + +## Tests Run + +packages/core/tests/azure/test_azure_chat_client_conversation_id_bug.py::test_conversation_id_not_passed_to_completions_create, packages/core/tests/azure/test_azure_chat_client_conversation_id_bug.py::test_conversation_id_not_passed_to_streaming_completions_create, packages/core/tests/azure/test_azure_chat_client_conversation_id_bug.py::test_agent_run_with_session_service_session_id + +## Finding + +The bug is confirmed. In `RawOpenAIChatClient._prepare_options()` (line 325 of `_chat_client.py`), `conversation_id` is not excluded from the options dict before it's passed to `AsyncCompletions.create()`. The `OpenAIResponsesClient` correctly excludes it at line 902, but the chat completions client does not. When `agent.run()` is called with a session that has `service_session_id`, the agent sets `conversation_id` in options (line 1067 of `_agents.py`), and it leaks through to the OpenAI API call. + +## Full Analysis + +Now let me look at the key files - the Azure chat client and how `conversation_id` flows through.I can see the bug. In `_prepare_options` at line 325, `conversation_id` is NOT excluded from the options dict that gets passed to `AsyncCompletions.create()`. Let me verify this is handled differently in the responses client:Confirmed. The `OpenAIResponsesClient` explicitly excludes `conversation_id` (line 902) and handles it separately, but `RawOpenAIChatClient._prepare_options` does NOT exclude it, so it gets passed straight through to `AsyncCompletions.create()`. + +Let me now look at existing tests and write a reproduction test.Now I have everything I need. Let me write a test that reproduces the bug.All 3 tests fail, confirming the bug. Let me fix the streaming test's minor issue (wrong method name) so all failures are purely about the bug:I see — streaming uses `get_response(stream=True)`. Let me fix the streaming test:All 3 tests fail cleanly, confirming the bug. The `conversation_id` leaks through to `AsyncCompletions.create()` in all paths. diff --git a/python/packages/core/agent_framework/openai/_chat_client.py b/python/packages/core/agent_framework/openai/_chat_client.py index 7f78068388..a00806b957 100644 --- a/python/packages/core/agent_framework/openai/_chat_client.py +++ b/python/packages/core/agent_framework/openai/_chat_client.py @@ -323,9 +323,7 @@ def _prepare_options(self, messages: Sequence[Message], options: Mapping[str, An # Start with a copy of options run_options = { - k: v - for k, v in options.items() - if v is not None and k not in {"instructions", "tools", "conversation_id"} + k: v for k, v in options.items() if v is not None and k not in {"instructions", "tools", "conversation_id"} } # messages diff --git a/python/packages/core/tests/workflow/test_agent_executor.py b/python/packages/core/tests/workflow/test_agent_executor.py index 788e96e61e..16190129ba 100644 --- a/python/packages/core/tests/workflow/test_agent_executor.py +++ b/python/packages/core/tests/workflow/test_agent_executor.py @@ -5,6 +5,7 @@ from typing import TYPE_CHECKING, Any, Literal, overload import pytest + from agent_framework import ( AgentExecutor, AgentResponse, diff --git a/python/packages/core/tests/workflow/test_agent_utils.py b/python/packages/core/tests/workflow/test_agent_utils.py index 07d1e64c08..633ba1072c 100644 --- a/python/packages/core/tests/workflow/test_agent_utils.py +++ b/python/packages/core/tests/workflow/test_agent_utils.py @@ -16,10 +16,31 @@ def __init__(self, agent_id: str, name: str | None = None) -> None: self.description: str | None = None @overload - def run(self, messages: AgentRunInputs | None = ..., *, stream: Literal[False] = ..., session: AgentSession | None = ..., **kwargs: Any) -> Awaitable[AgentResponse[Any]]: ... + def run( + self, + messages: AgentRunInputs | None = ..., + *, + stream: Literal[False] = ..., + session: AgentSession | None = ..., + **kwargs: Any, + ) -> Awaitable[AgentResponse[Any]]: ... @overload - def run(self, messages: AgentRunInputs | None = ..., *, stream: Literal[True], session: AgentSession | None = ..., **kwargs: Any) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... - def run(self, messages: AgentRunInputs | None = None, *, stream: bool = False, session: AgentSession | None = None, **kwargs: Any) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... + def run( + self, + messages: AgentRunInputs | None = ..., + *, + stream: Literal[True], + session: AgentSession | None = ..., + **kwargs: Any, + ) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... + def run( + self, + messages: AgentRunInputs | None = None, + *, + stream: bool = False, + session: AgentSession | None = None, + **kwargs: Any, + ) -> Awaitable[AgentResponse[Any]] | ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... def create_session(self, **kwargs: Any) -> AgentSession: """Creates a new conversation session for the agent.""" diff --git a/python/packages/core/tests/workflow/test_edge.py b/python/packages/core/tests/workflow/test_edge.py index ecaa341726..422d530631 100644 --- a/python/packages/core/tests/workflow/test_edge.py +++ b/python/packages/core/tests/workflow/test_edge.py @@ -4,9 +4,8 @@ from typing import Any from unittest.mock import patch -from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter - import pytest +from opentelemetry.sdk.trace.export.in_memory_span_exporter import InMemorySpanExporter from agent_framework import ( Executor, diff --git a/python/packages/core/tests/workflow/test_executor.py b/python/packages/core/tests/workflow/test_executor.py index 77827c0634..137e308d8c 100644 --- a/python/packages/core/tests/workflow/test_executor.py +++ b/python/packages/core/tests/workflow/test_executor.py @@ -3,6 +3,8 @@ from dataclasses import dataclass import pytest +from typing_extensions import Never + from agent_framework import ( Executor, Message, @@ -14,7 +16,6 @@ handler, response_handler, ) -from typing_extensions import Never # Module-level types for string forward reference tests diff --git a/python/packages/core/tests/workflow/test_workflow_agent.py b/python/packages/core/tests/workflow/test_workflow_agent.py index b5a8bb9902..eacf70c6db 100644 --- a/python/packages/core/tests/workflow/test_workflow_agent.py +++ b/python/packages/core/tests/workflow/test_workflow_agent.py @@ -717,9 +717,23 @@ def get_session(self, *, service_session_id: str, **kwargs: Any) -> AgentSession return AgentSession() @overload - def run(self, messages: str | Content | Message | Sequence[str | Content | Message] | None = ..., *, stream: Literal[False] = ..., session: AgentSession | None = ..., **kwargs: Any) -> Awaitable[AgentResponse[Any]]: ... + def run( + self, + messages: str | Content | Message | Sequence[str | Content | Message] | None = ..., + *, + stream: Literal[False] = ..., + session: AgentSession | None = ..., + **kwargs: Any, + ) -> Awaitable[AgentResponse[Any]]: ... @overload - def run(self, messages: str | Content | Message | Sequence[str | Content | Message] | None = ..., *, stream: Literal[True], session: AgentSession | None = ..., **kwargs: Any) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... + def run( + self, + messages: str | Content | Message | Sequence[str | Content | Message] | None = ..., + *, + stream: Literal[True], + session: AgentSession | None = ..., + **kwargs: Any, + ) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... def run( self, @@ -813,9 +827,23 @@ def get_session(self, *, service_session_id: str, **kwargs: Any) -> AgentSession return AgentSession() @overload - def run(self, messages: str | Content | Message | Sequence[str | Content | Message] | None = ..., *, stream: Literal[False] = ..., session: AgentSession | None = ..., **kwargs: Any) -> Awaitable[AgentResponse[Any]]: ... + def run( + self, + messages: str | Content | Message | Sequence[str | Content | Message] | None = ..., + *, + stream: Literal[False] = ..., + session: AgentSession | None = ..., + **kwargs: Any, + ) -> Awaitable[AgentResponse[Any]]: ... @overload - def run(self, messages: str | Content | Message | Sequence[str | Content | Message] | None = ..., *, stream: Literal[True], session: AgentSession | None = ..., **kwargs: Any) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... + def run( + self, + messages: str | Content | Message | Sequence[str | Content | Message] | None = ..., + *, + stream: Literal[True], + session: AgentSession | None = ..., + **kwargs: Any, + ) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... def run( self, diff --git a/python/packages/core/tests/workflow/test_workflow_kwargs.py b/python/packages/core/tests/workflow/test_workflow_kwargs.py index 0850c6b060..d315f75f85 100644 --- a/python/packages/core/tests/workflow/test_workflow_kwargs.py +++ b/python/packages/core/tests/workflow/test_workflow_kwargs.py @@ -52,9 +52,23 @@ def __init__(self, name: str = "test_agent") -> None: self.captured_kwargs = [] @overload - def run(self, messages: AgentRunInputs | None = ..., *, stream: Literal[False] = ..., session: AgentSession | None = ..., **kwargs: Any) -> Awaitable[AgentResponse[Any]]: ... + def run( + self, + messages: AgentRunInputs | None = ..., + *, + stream: Literal[False] = ..., + session: AgentSession | None = ..., + **kwargs: Any, + ) -> Awaitable[AgentResponse[Any]]: ... @overload - def run(self, messages: AgentRunInputs | None = ..., *, stream: Literal[True], session: AgentSession | None = ..., **kwargs: Any) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... + def run( + self, + messages: AgentRunInputs | None = ..., + *, + stream: Literal[True], + session: AgentSession | None = ..., + **kwargs: Any, + ) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... def run( self, @@ -90,9 +104,23 @@ def __init__(self, name: str = "options_agent") -> None: self.captured_kwargs = [] @overload - def run(self, messages: AgentRunInputs | None = ..., *, stream: Literal[False] = ..., session: AgentSession | None = ..., **kwargs: Any) -> Awaitable[AgentResponse[Any]]: ... + def run( + self, + messages: AgentRunInputs | None = ..., + *, + stream: Literal[False] = ..., + session: AgentSession | None = ..., + **kwargs: Any, + ) -> Awaitable[AgentResponse[Any]]: ... @overload - def run(self, messages: AgentRunInputs | None = ..., *, stream: Literal[True], session: AgentSession | None = ..., **kwargs: Any) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... + def run( + self, + messages: AgentRunInputs | None = ..., + *, + stream: Literal[True], + session: AgentSession | None = ..., + **kwargs: Any, + ) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... def run( self, @@ -475,9 +503,23 @@ def __init__(self) -> None: self._asked = False @overload - def run(self, messages: AgentRunInputs | None = ..., *, stream: Literal[False] = ..., session: AgentSession | None = ..., **kwargs: Any) -> Awaitable[AgentResponse[Any]]: ... + def run( + self, + messages: AgentRunInputs | None = ..., + *, + stream: Literal[False] = ..., + session: AgentSession | None = ..., + **kwargs: Any, + ) -> Awaitable[AgentResponse[Any]]: ... @overload - def run(self, messages: AgentRunInputs | None = ..., *, stream: Literal[True], session: AgentSession | None = ..., **kwargs: Any) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... + def run( + self, + messages: AgentRunInputs | None = ..., + *, + stream: Literal[True], + session: AgentSession | None = ..., + **kwargs: Any, + ) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... def run( self, @@ -538,9 +580,23 @@ def __init__(self) -> None: self._asked = False @overload - def run(self, messages: AgentRunInputs | None = ..., *, stream: Literal[False] = ..., session: AgentSession | None = ..., **kwargs: Any) -> Awaitable[AgentResponse[Any]]: ... + def run( + self, + messages: AgentRunInputs | None = ..., + *, + stream: Literal[False] = ..., + session: AgentSession | None = ..., + **kwargs: Any, + ) -> Awaitable[AgentResponse[Any]]: ... @overload - def run(self, messages: AgentRunInputs | None = ..., *, stream: Literal[True], session: AgentSession | None = ..., **kwargs: Any) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... + def run( + self, + messages: AgentRunInputs | None = ..., + *, + stream: Literal[True], + session: AgentSession | None = ..., + **kwargs: Any, + ) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... def run( self, @@ -605,9 +661,23 @@ def __init__(self) -> None: self._asked = False @overload - def run(self, messages: AgentRunInputs | None = ..., *, stream: Literal[False] = ..., session: AgentSession | None = ..., **kwargs: Any) -> Awaitable[AgentResponse[Any]]: ... + def run( + self, + messages: AgentRunInputs | None = ..., + *, + stream: Literal[False] = ..., + session: AgentSession | None = ..., + **kwargs: Any, + ) -> Awaitable[AgentResponse[Any]]: ... @overload - def run(self, messages: AgentRunInputs | None = ..., *, stream: Literal[True], session: AgentSession | None = ..., **kwargs: Any) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... + def run( + self, + messages: AgentRunInputs | None = ..., + *, + stream: Literal[True], + session: AgentSession | None = ..., + **kwargs: Any, + ) -> ResponseStream[AgentResponseUpdate, AgentResponse[Any]]: ... def run( self, diff --git a/python/packages/core/tests/workflow/test_workflow_states.py b/python/packages/core/tests/workflow/test_workflow_states.py index 34c7e8c93f..bf2e277d10 100644 --- a/python/packages/core/tests/workflow/test_workflow_states.py +++ b/python/packages/core/tests/workflow/test_workflow_states.py @@ -38,7 +38,9 @@ async def test_executor_failed_and_workflow_failed_events_streaming(): events.append(ev) # executor_failed event (type='executor_failed') should be emitted before workflow failed event - executor_failed_events: list[WorkflowEvent[Any]] = [e for e in events if isinstance(e, WorkflowEvent) and e.type == "executor_failed"] + executor_failed_events: list[WorkflowEvent[Any]] = [ + e for e in events if isinstance(e, WorkflowEvent) and e.type == "executor_failed" + ] assert executor_failed_events, "executor_failed event should be emitted when start executor fails" assert executor_failed_events[0].executor_id == "f" assert executor_failed_events[0].origin is WorkflowEventSource.FRAMEWORK @@ -96,7 +98,9 @@ async def test_executor_failed_event_from_second_executor_in_chain(): events.append(ev) # executor_failed event should be emitted for the failing executor - executor_failed_events: list[WorkflowEvent[Any]] = [e for e in events if isinstance(e, WorkflowEvent) and e.type == "executor_failed"] + executor_failed_events: list[WorkflowEvent[Any]] = [ + e for e in events if isinstance(e, WorkflowEvent) and e.type == "executor_failed" + ] assert executor_failed_events, "executor_failed event should be emitted when second executor fails" assert executor_failed_events[0].executor_id == "failing" assert executor_failed_events[0].origin is WorkflowEventSource.FRAMEWORK From 424461fd929145f210a883e7bbef7ecc9241b2ed Mon Sep 17 00:00:00 2001 From: Evan Mattson Date: Fri, 6 Mar 2026 12:40:25 +0900 Subject: [PATCH 3/3] Remove reproduction report artifact Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- REPRODUCTION_REPORT.md | 33 --------------------------------- 1 file changed, 33 deletions(-) delete mode 100644 REPRODUCTION_REPORT.md diff --git a/REPRODUCTION_REPORT.md b/REPRODUCTION_REPORT.md deleted file mode 100644 index d6e4e59812..0000000000 --- a/REPRODUCTION_REPORT.md +++ /dev/null @@ -1,33 +0,0 @@ -# Reproduction Report — Issue #4315 -**Python: [Bug]: Azure open ai agent session error** - -*Generated: 2026-03-06 03:33:44 UTC* - -## Result - -| Field | Value | -|-------|-------| -| Status | `reproduced` | -| Failure observed | `yes` | -| Evidence strength | `high` | -| Confidence | `high` | -| Failing test | `python/packages/core/tests/azure/test_azure_chat_client_conversation_id_bug.py` | -| Gate passed | Yes | - -## Files Examined - -python/packages/core/agent_framework/azure/_chat_client.py, python/packages/core/agent_framework/openai/_chat_client.py, python/packages/core/agent_framework/openai/_responses_client.py, python/packages/core/agent_framework/_agents.py, python/packages/core/tests/azure/conftest.py, python/packages/core/tests/azure/test_azure_chat_client.py - -## Tests Run - -packages/core/tests/azure/test_azure_chat_client_conversation_id_bug.py::test_conversation_id_not_passed_to_completions_create, packages/core/tests/azure/test_azure_chat_client_conversation_id_bug.py::test_conversation_id_not_passed_to_streaming_completions_create, packages/core/tests/azure/test_azure_chat_client_conversation_id_bug.py::test_agent_run_with_session_service_session_id - -## Finding - -The bug is confirmed. In `RawOpenAIChatClient._prepare_options()` (line 325 of `_chat_client.py`), `conversation_id` is not excluded from the options dict before it's passed to `AsyncCompletions.create()`. The `OpenAIResponsesClient` correctly excludes it at line 902, but the chat completions client does not. When `agent.run()` is called with a session that has `service_session_id`, the agent sets `conversation_id` in options (line 1067 of `_agents.py`), and it leaks through to the OpenAI API call. - -## Full Analysis - -Now let me look at the key files - the Azure chat client and how `conversation_id` flows through.I can see the bug. In `_prepare_options` at line 325, `conversation_id` is NOT excluded from the options dict that gets passed to `AsyncCompletions.create()`. Let me verify this is handled differently in the responses client:Confirmed. The `OpenAIResponsesClient` explicitly excludes `conversation_id` (line 902) and handles it separately, but `RawOpenAIChatClient._prepare_options` does NOT exclude it, so it gets passed straight through to `AsyncCompletions.create()`. - -Let me now look at existing tests and write a reproduction test.Now I have everything I need. Let me write a test that reproduces the bug.All 3 tests fail, confirming the bug. Let me fix the streaming test's minor issue (wrong method name) so all failures are purely about the bug:I see — streaming uses `get_response(stream=True)`. Let me fix the streaming test:All 3 tests fail cleanly, confirming the bug. The `conversation_id` leaks through to `AsyncCompletions.create()` in all paths.