From 9c3fddec7025e3157520d3252461a6d305cc72fd Mon Sep 17 00:00:00 2001 From: eavanvalkenburg Date: Fri, 19 Jun 2026 13:28:41 +0200 Subject: [PATCH 1/4] Add FoundryAgent conversation session helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- python/packages/foundry/README.md | 24 ++++++++ .../foundry/agent_framework_foundry/_agent.py | 35 +++++++++++ .../tests/foundry/test_foundry_agent.py | 58 +++++++++++++++++++ 3 files changed, 117 insertions(+) diff --git a/python/packages/foundry/README.md b/python/packages/foundry/README.md index b253d2d000b..e651ca6de63 100644 --- a/python/packages/foundry/README.md +++ b/python/packages/foundry/README.md @@ -106,6 +106,30 @@ Generally available factories: `get_code_interpreter_tool`, | `get_browser_automation_tool(connection_id)` | `BrowserAutomationPreviewTool` | | `get_bing_custom_search_tool(connection_id, instance_name, ...)` | `BingCustomSearchPreviewTool` | | `get_a2a_tool(base_url=..., project_connection_id=..., ...)` | `A2APreviewTool` | + +## Creating Foundry conversation sessions + +`FoundryAgent.create_conversation_session()` creates a server-side Foundry +project conversation and returns an `AgentSession` that can be passed to +`agent.run(...)` without reaching into the raw OpenAI client. + +```python +from agent_framework.foundry import FoundryAgent + +agent = FoundryAgent( + project_endpoint=project_endpoint, + agent_name="travel-agent", + credential=credential, +) + +session = await agent.create_conversation_session() +response = await agent.run("Help me plan a trip to Seattle.", session=session) +``` + +This is separate from hosted-agent `isolation_key` sessions: the created +conversation ID is stored on `AgentSession.service_session_id`, while the local +`session_id` remains available for application/session storage. + ## Publishing an agent as a Foundry prompt agent > **Experimental — `ExperimentalFeature.TO_PROMPT_AGENT`.** `to_prompt_agent` diff --git a/python/packages/foundry/agent_framework_foundry/_agent.py b/python/packages/foundry/agent_framework_foundry/_agent.py index 370e034bcac..af0cba1e554 100644 --- a/python/packages/foundry/agent_framework_foundry/_agent.py +++ b/python/packages/foundry/agent_framework_foundry/_agent.py @@ -754,6 +754,41 @@ async def _create_service_session_id( return agent_session_id + async def create_conversation_session(self, *, session_id: str | None = None) -> AgentSession: + """Create a project-level Foundry conversation session. + + This creates a server-side conversation through the Foundry project's OpenAI + client and returns an ``AgentSession`` configured to continue that + conversation. + + Keyword Args: + session_id: Optional local session ID (generated if not provided). + + Returns: + A new ``AgentSession`` whose ``service_session_id`` is the created + Foundry conversation ID. + + Raises: + TypeError: If the agent does not use a Foundry agent chat client. + RuntimeError: If the Foundry project client does not expose project conversations. + ValueError: If conversation creation does not return a non-empty ID. + """ + if not isinstance(self.client, RawFoundryAgentChatClient): + raise TypeError("create_conversation_session requires a RawFoundryAgentChatClient-based client.") + + openai_client = self.client.project_client.get_openai_client() + conversations = getattr(openai_client, "conversations", None) + create_conversation = getattr(conversations, "create", None) + if create_conversation is None: + raise RuntimeError("The Foundry project OpenAI client does not expose project conversation creation.") + + conversation = await create_conversation() + conversation_id = getattr(conversation, "id", None) + if not isinstance(conversation_id, str) or not conversation_id: + raise ValueError("Foundry conversation creation did not return a non-empty id.") + + return self.get_session(conversation_id, session_id=session_id) + @override async def _prepare_run_context( self, diff --git a/python/packages/foundry/tests/foundry/test_foundry_agent.py b/python/packages/foundry/tests/foundry/test_foundry_agent.py index f14c757d7c3..5995f3f3b70 100644 --- a/python/packages/foundry/tests/foundry/test_foundry_agent.py +++ b/python/packages/foundry/tests/foundry/test_foundry_agent.py @@ -913,6 +913,64 @@ async def test_raw_foundry_agent_prepare_run_context_requires_preview_for_hosted ) +async def test_foundry_agent_create_conversation_session_returns_agent_session() -> None: + """Test that FoundryAgent creates a project conversation and returns a session.""" + + openai_client = MagicMock() + openai_client.conversations.create = AsyncMock(return_value=SimpleNamespace(id="conv_123")) + mock_project = MagicMock() + mock_project.get_openai_client.return_value = openai_client + agent = FoundryAgent(project_client=mock_project, agent_name="test-agent") + + session = await agent.create_conversation_session() + + assert isinstance(session, AgentSession) + assert session.service_session_id == "conv_123" + mock_project.get_openai_client.assert_called() + openai_client.conversations.create.assert_awaited_once_with() + + +async def test_foundry_agent_create_conversation_session_accepts_local_session_id() -> None: + """Test that project conversation sessions can use a caller-provided local session ID.""" + + openai_client = MagicMock() + openai_client.conversations.create = AsyncMock(return_value=SimpleNamespace(id="conv_123")) + mock_project = MagicMock() + mock_project.get_openai_client.return_value = openai_client + agent = FoundryAgent(project_client=mock_project, agent_name="test-agent") + + session = await agent.create_conversation_session(session_id="local-session") + + assert session.session_id == "local-session" + assert session.service_session_id == "conv_123" + + +async def test_foundry_agent_create_conversation_session_requires_conversations_client() -> None: + """Test that project conversation creation fails clearly when the SDK client lacks support.""" + + mock_project = MagicMock() + openai_client = MagicMock() + openai_client.conversations = None + mock_project.get_openai_client.return_value = openai_client + agent = FoundryAgent(project_client=mock_project, agent_name="test-agent") + + with pytest.raises(RuntimeError, match="project conversation creation"): + await agent.create_conversation_session() + + +async def test_foundry_agent_create_conversation_session_requires_conversation_id() -> None: + """Test that project conversation creation validates the returned conversation ID.""" + + openai_client = MagicMock() + openai_client.conversations.create = AsyncMock(return_value=SimpleNamespace(id="")) + mock_project = MagicMock() + mock_project.get_openai_client.return_value = openai_client + agent = FoundryAgent(project_client=mock_project, agent_name="test-agent") + + with pytest.raises(ValueError, match="non-empty id"): + await agent.create_conversation_session() + + def test_foundry_agent_init() -> None: """Test construction of the full-middleware agent.""" From 9b704bd63a8c23a2057b56f0b6f97c81fa7a07f1 Mon Sep 17 00:00:00 2001 From: eavanvalkenburg Date: Fri, 19 Jun 2026 13:33:32 +0200 Subject: [PATCH 2/4] Simplify Foundry conversation session helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- .../foundry/agent_framework_foundry/_agent.py | 23 +++------------- .../tests/foundry/test_foundry_agent.py | 26 ------------------- 2 files changed, 3 insertions(+), 46 deletions(-) diff --git a/python/packages/foundry/agent_framework_foundry/_agent.py b/python/packages/foundry/agent_framework_foundry/_agent.py index af0cba1e554..8459786d6c1 100644 --- a/python/packages/foundry/agent_framework_foundry/_agent.py +++ b/python/packages/foundry/agent_framework_foundry/_agent.py @@ -767,27 +767,10 @@ async def create_conversation_session(self, *, session_id: str | None = None) -> Returns: A new ``AgentSession`` whose ``service_session_id`` is the created Foundry conversation ID. - - Raises: - TypeError: If the agent does not use a Foundry agent chat client. - RuntimeError: If the Foundry project client does not expose project conversations. - ValueError: If conversation creation does not return a non-empty ID. """ - if not isinstance(self.client, RawFoundryAgentChatClient): - raise TypeError("create_conversation_session requires a RawFoundryAgentChatClient-based client.") - - openai_client = self.client.project_client.get_openai_client() - conversations = getattr(openai_client, "conversations", None) - create_conversation = getattr(conversations, "create", None) - if create_conversation is None: - raise RuntimeError("The Foundry project OpenAI client does not expose project conversation creation.") - - conversation = await create_conversation() - conversation_id = getattr(conversation, "id", None) - if not isinstance(conversation_id, str) or not conversation_id: - raise ValueError("Foundry conversation creation did not return a non-empty id.") - - return self.get_session(conversation_id, session_id=session_id) + client = cast(RawFoundryAgentChatClient, self.client) + conversation = await client.project_client.get_openai_client().conversations.create() + return self.get_session(conversation.id, session_id=session_id) @override async def _prepare_run_context( diff --git a/python/packages/foundry/tests/foundry/test_foundry_agent.py b/python/packages/foundry/tests/foundry/test_foundry_agent.py index 5995f3f3b70..e01105b542a 100644 --- a/python/packages/foundry/tests/foundry/test_foundry_agent.py +++ b/python/packages/foundry/tests/foundry/test_foundry_agent.py @@ -945,32 +945,6 @@ async def test_foundry_agent_create_conversation_session_accepts_local_session_i assert session.service_session_id == "conv_123" -async def test_foundry_agent_create_conversation_session_requires_conversations_client() -> None: - """Test that project conversation creation fails clearly when the SDK client lacks support.""" - - mock_project = MagicMock() - openai_client = MagicMock() - openai_client.conversations = None - mock_project.get_openai_client.return_value = openai_client - agent = FoundryAgent(project_client=mock_project, agent_name="test-agent") - - with pytest.raises(RuntimeError, match="project conversation creation"): - await agent.create_conversation_session() - - -async def test_foundry_agent_create_conversation_session_requires_conversation_id() -> None: - """Test that project conversation creation validates the returned conversation ID.""" - - openai_client = MagicMock() - openai_client.conversations.create = AsyncMock(return_value=SimpleNamespace(id="")) - mock_project = MagicMock() - mock_project.get_openai_client.return_value = openai_client - agent = FoundryAgent(project_client=mock_project, agent_name="test-agent") - - with pytest.raises(ValueError, match="non-empty id"): - await agent.create_conversation_session() - - def test_foundry_agent_init() -> None: """Test construction of the full-middleware agent.""" From 95dd747759d035cdb43fefb6adb0138076dc5b8a Mon Sep 17 00:00:00 2001 From: eavanvalkenburg Date: Fri, 19 Jun 2026 13:34:37 +0200 Subject: [PATCH 3/4] Rename Foundry conversation helper Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com> --- python/packages/foundry/README.md | 4 ++-- python/packages/foundry/agent_framework_foundry/_agent.py | 2 +- .../packages/foundry/tests/foundry/test_foundry_agent.py | 8 ++++---- 3 files changed, 7 insertions(+), 7 deletions(-) diff --git a/python/packages/foundry/README.md b/python/packages/foundry/README.md index e651ca6de63..bcaa83f616f 100644 --- a/python/packages/foundry/README.md +++ b/python/packages/foundry/README.md @@ -109,7 +109,7 @@ Generally available factories: `get_code_interpreter_tool`, ## Creating Foundry conversation sessions -`FoundryAgent.create_conversation_session()` creates a server-side Foundry +`FoundryAgent.create_conversation()` creates a server-side Foundry project conversation and returns an `AgentSession` that can be passed to `agent.run(...)` without reaching into the raw OpenAI client. @@ -122,7 +122,7 @@ agent = FoundryAgent( credential=credential, ) -session = await agent.create_conversation_session() +session = await agent.create_conversation() response = await agent.run("Help me plan a trip to Seattle.", session=session) ``` diff --git a/python/packages/foundry/agent_framework_foundry/_agent.py b/python/packages/foundry/agent_framework_foundry/_agent.py index 8459786d6c1..1bb693595ee 100644 --- a/python/packages/foundry/agent_framework_foundry/_agent.py +++ b/python/packages/foundry/agent_framework_foundry/_agent.py @@ -754,7 +754,7 @@ async def _create_service_session_id( return agent_session_id - async def create_conversation_session(self, *, session_id: str | None = None) -> AgentSession: + async def create_conversation(self, *, session_id: str | None = None) -> AgentSession: """Create a project-level Foundry conversation session. This creates a server-side conversation through the Foundry project's OpenAI diff --git a/python/packages/foundry/tests/foundry/test_foundry_agent.py b/python/packages/foundry/tests/foundry/test_foundry_agent.py index e01105b542a..aa15c024be2 100644 --- a/python/packages/foundry/tests/foundry/test_foundry_agent.py +++ b/python/packages/foundry/tests/foundry/test_foundry_agent.py @@ -913,7 +913,7 @@ async def test_raw_foundry_agent_prepare_run_context_requires_preview_for_hosted ) -async def test_foundry_agent_create_conversation_session_returns_agent_session() -> None: +async def test_foundry_agent_create_conversation_returns_agent_session() -> None: """Test that FoundryAgent creates a project conversation and returns a session.""" openai_client = MagicMock() @@ -922,7 +922,7 @@ async def test_foundry_agent_create_conversation_session_returns_agent_session() mock_project.get_openai_client.return_value = openai_client agent = FoundryAgent(project_client=mock_project, agent_name="test-agent") - session = await agent.create_conversation_session() + session = await agent.create_conversation() assert isinstance(session, AgentSession) assert session.service_session_id == "conv_123" @@ -930,7 +930,7 @@ async def test_foundry_agent_create_conversation_session_returns_agent_session() openai_client.conversations.create.assert_awaited_once_with() -async def test_foundry_agent_create_conversation_session_accepts_local_session_id() -> None: +async def test_foundry_agent_create_conversation_accepts_local_session_id() -> None: """Test that project conversation sessions can use a caller-provided local session ID.""" openai_client = MagicMock() @@ -939,7 +939,7 @@ async def test_foundry_agent_create_conversation_session_accepts_local_session_i mock_project.get_openai_client.return_value = openai_client agent = FoundryAgent(project_client=mock_project, agent_name="test-agent") - session = await agent.create_conversation_session(session_id="local-session") + session = await agent.create_conversation(session_id="local-session") assert session.session_id == "local-session" assert session.service_session_id == "conv_123" From 59233d28c119677f1f48cf22464fa75b3d289cc9 Mon Sep 17 00:00:00 2001 From: eavanvalkenburg Date: Fri, 19 Jun 2026 13:35:08 +0200 Subject: [PATCH 4/4] use named kw --- python/packages/foundry/agent_framework_foundry/_agent.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/packages/foundry/agent_framework_foundry/_agent.py b/python/packages/foundry/agent_framework_foundry/_agent.py index 1bb693595ee..3d3735606e6 100644 --- a/python/packages/foundry/agent_framework_foundry/_agent.py +++ b/python/packages/foundry/agent_framework_foundry/_agent.py @@ -770,7 +770,7 @@ async def create_conversation(self, *, session_id: str | None = None) -> AgentSe """ client = cast(RawFoundryAgentChatClient, self.client) conversation = await client.project_client.get_openai_client().conversations.create() - return self.get_session(conversation.id, session_id=session_id) + return self.get_session(service_session_id=conversation.id, session_id=session_id) @override async def _prepare_run_context(