From c019056de09ea81d0a029d8653d9ba75a92ce532 Mon Sep 17 00:00:00 2001 From: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com> Date: Sat, 17 Jan 2026 01:58:34 -0800 Subject: [PATCH 1/5] Filter conversation_id when passing kwargs to agent as tool --- .../packages/core/agent_framework/_agents.py | 4 +- .../core/test_as_tool_kwargs_propagation.py | 41 +++++++++++ .../getting_started/agents/azure_ai/README.md | 1 + .../azure_ai/azure_ai_with_agent_as_tool.py | 70 +++++++++++++++++++ .../getting_started/agents/openai/README.md | 1 + ...nai_responses_client_with_agent_as_tool.py | 67 ++++++++++++++++++ 6 files changed, 182 insertions(+), 2 deletions(-) create mode 100644 python/samples/getting_started/agents/azure_ai/azure_ai_with_agent_as_tool.py create mode 100644 python/samples/getting_started/agents/openai/openai_responses_client_with_agent_as_tool.py diff --git a/python/packages/core/agent_framework/_agents.py b/python/packages/core/agent_framework/_agents.py index 628ac7fb17..e54fef5aeb 100644 --- a/python/packages/core/agent_framework/_agents.py +++ b/python/packages/core/agent_framework/_agents.py @@ -470,8 +470,8 @@ async def agent_wrapper(**kwargs: Any) -> str: # Extract the input from kwargs using the specified arg_name input_text = kwargs.get(arg_name, "") - # Forward all kwargs except the arg_name to support runtime context propagation - forwarded_kwargs = {k: v for k, v in kwargs.items() if k != arg_name} + # Forward runtime context kwargs, excluding arg_name and conversation_id. + forwarded_kwargs = {k: v for k, v in kwargs.items() if k not in (arg_name, "conversation_id")} if stream_callback is None: # Use non-streaming mode diff --git a/python/packages/core/tests/core/test_as_tool_kwargs_propagation.py b/python/packages/core/tests/core/test_as_tool_kwargs_propagation.py index 8669ecc3d6..6bd52975c2 100644 --- a/python/packages/core/tests/core/test_as_tool_kwargs_propagation.py +++ b/python/packages/core/tests/core/test_as_tool_kwargs_propagation.py @@ -313,3 +313,44 @@ async def capture_middleware( # Verify second call had its own kwargs (not leaked from first) assert second_call_kwargs.get("session_id") == "session-2" assert second_call_kwargs.get("api_token") == "token-2" + + async def test_as_tool_excludes_conversation_id_from_forwarded_kwargs(self, chat_client: MockChatClient) -> None: + """Test that conversation_id is not forwarded to sub-agent.""" + captured_kwargs: dict[str, Any] = {} + + @agent_middleware + async def capture_middleware( + context: AgentRunContext, next: Callable[[AgentRunContext], Awaitable[None]] + ) -> None: + captured_kwargs.update(context.kwargs) + await next(context) + + # Setup mock response + chat_client.responses = [ + ChatResponse(messages=[ChatMessage(role="assistant", text="Response from sub-agent")]), + ] + + sub_agent = ChatAgent( + chat_client=chat_client, + name="sub_agent", + middleware=[capture_middleware], + ) + + tool = sub_agent.as_tool(name="delegate", arg_name="task") + + # Invoke tool with conversation_id in kwargs (simulating parent's conversation state) + await tool.invoke( + arguments=tool.input_model(task="Test delegation"), + conversation_id="conv-parent-456", + api_token="secret-xyz-123", + user_id="user-456", + ) + + # Verify conversation_id was NOT forwarded to sub-agent + assert "conversation_id" not in captured_kwargs, ( + f"conversation_id should not be forwarded, but got: {captured_kwargs}" + ) + + # Verify other kwargs were still forwarded + assert captured_kwargs.get("api_token") == "secret-xyz-123" + assert captured_kwargs.get("user_id") == "user-456" diff --git a/python/samples/getting_started/agents/azure_ai/README.md b/python/samples/getting_started/agents/azure_ai/README.md index f60b64cf18..efc0afdbba 100644 --- a/python/samples/getting_started/agents/azure_ai/README.md +++ b/python/samples/getting_started/agents/azure_ai/README.md @@ -9,6 +9,7 @@ This folder contains examples demonstrating different ways to create and use age | [`azure_ai_basic.py`](azure_ai_basic.py) | The simplest way to create an agent using `AzureAIProjectAgentProvider`. Demonstrates both streaming and non-streaming responses with function tools. Shows automatic agent creation and basic weather functionality. | | [`azure_ai_provider_methods.py`](azure_ai_provider_methods.py) | Comprehensive guide to `AzureAIProjectAgentProvider` methods: `create_agent()` for creating new agents, `get_agent()` for retrieving existing agents (by name, reference, or details), and `as_agent()` for wrapping SDK objects without HTTP calls. | | [`azure_ai_use_latest_version.py`](azure_ai_use_latest_version.py) | Demonstrates how to reuse the latest version of an existing agent instead of creating a new agent version on each instantiation by using `provider.get_agent()` to retrieve the latest version. | +| [`azure_ai_with_agent_as_tool.py`](azure_ai_with_agent_as_tool.py) | Shows how to use the agent-as-tool pattern with Azure AI agents, where one agent delegates work to specialized sub-agents wrapped as tools using `as_tool()`. Demonstrates hierarchical agent architectures with streaming. | | [`azure_ai_with_agent_to_agent.py`](azure_ai_with_agent_to_agent.py) | Shows how to use Agent-to-Agent (A2A) capabilities with Azure AI agents to enable communication with other agents using the A2A protocol. Requires an A2A connection configured in your Azure AI project. | | [`azure_ai_with_azure_ai_search.py`](azure_ai_with_azure_ai_search.py) | Shows how to use Azure AI Search with Azure AI agents to search through indexed data and answer user questions with proper citations. Requires an Azure AI Search connection and index configured in your Azure AI project. | | [`azure_ai_with_bing_grounding.py`](azure_ai_with_bing_grounding.py) | Shows how to use Bing Grounding search with Azure AI agents to search the web for current information and provide grounded responses with citations. Requires a Bing connection configured in your Azure AI project. | diff --git a/python/samples/getting_started/agents/azure_ai/azure_ai_with_agent_as_tool.py b/python/samples/getting_started/agents/azure_ai/azure_ai_with_agent_as_tool.py new file mode 100644 index 0000000000..6db8aafdfc --- /dev/null +++ b/python/samples/getting_started/agents/azure_ai/azure_ai_with_agent_as_tool.py @@ -0,0 +1,70 @@ +# Copyright (c) Microsoft. All rights reserved. + +import asyncio +from collections.abc import Awaitable, Callable + +from agent_framework import FunctionInvocationContext +from agent_framework.azure import AzureAIProjectAgentProvider +from azure.identity.aio import AzureCliCredential + +""" +Azure AI Agent-as-Tool Example + +Demonstrates hierarchical agent architectures where one agent delegates +work to specialized sub-agents wrapped as tools using as_tool(). + +This pattern is useful when you want a coordinator agent to orchestrate +multiple specialized agents, each focusing on specific tasks. +""" + + +async def logging_middleware( + context: FunctionInvocationContext, + next: Callable[[FunctionInvocationContext], Awaitable[None]], +) -> None: + """Middleware that logs tool invocations to show the delegation flow.""" + print(f"[Calling tool: {context.function.name}]") + print(f"[Request: {context.arguments}]") + + await next(context) + + print(f"[Response: {context.result}]") + + +async def main() -> None: + print("=== Azure AI Agent-as-Tool Pattern ===") + + async with ( + AzureCliCredential() as credential, + AzureAIProjectAgentProvider(credential=credential) as provider, + ): + # Create a specialized writer agent + writer = await provider.create_agent( + name="WriterAgent", + instructions="You are a creative writer. Write short, engaging content. ", + ) + + # Convert writer agent to a tool using as_tool() + writer_tool = writer.as_tool( + name="creative_writer", + description="Generate creative content like taglines, slogans, or short copy", + arg_name="request", + arg_description="What to write", + ) + + # Create coordinator agent with writer as a tool + coordinator = await provider.create_agent( + name="CoordinatorAgent", + instructions="You coordinate with specialized agents. Delegate writing tasks to the creative_writer tool. ", + tools=[writer_tool], + middleware=[logging_middleware], + ) + + query = "Create a tagline for a coffee shop" + print(f"User: {query}") + result = await coordinator.run(query) + print(f"Coordinator: {result}\n") + + +if __name__ == "__main__": + asyncio.run(main()) diff --git a/python/samples/getting_started/agents/openai/README.md b/python/samples/getting_started/agents/openai/README.md index d744531845..4feff05d22 100644 --- a/python/samples/getting_started/agents/openai/README.md +++ b/python/samples/getting_started/agents/openai/README.md @@ -27,6 +27,7 @@ This folder contains examples demonstrating different ways to create and use age | [`openai_responses_client_image_generation.py`](openai_responses_client_image_generation.py) | Demonstrates how to use image generation capabilities with OpenAI agents to create images based on text descriptions. Requires PIL (Pillow) for image display. | | [`openai_responses_client_reasoning.py`](openai_responses_client_reasoning.py) | Demonstrates how to use reasoning capabilities with OpenAI agents, showing how the agent can provide detailed reasoning for its responses. | | [`openai_responses_client_streaming_image_generation.py`](openai_responses_client_streaming_image_generation.py) | Demonstrates streaming image generation with partial images for real-time image creation feedback and improved user experience. | +| [`openai_responses_client_with_agent_as_tool.py`](openai_responses_client_with_agent_as_tool.py) | Shows how to use the agent-as-tool pattern with OpenAI Responses Client, where one agent delegates work to specialized sub-agents wrapped as tools using `as_tool()`. Demonstrates hierarchical agent architectures. | | [`openai_responses_client_with_code_interpreter.py`](openai_responses_client_with_code_interpreter.py) | Shows how to use the HostedCodeInterpreterTool with OpenAI agents to write and execute Python code. Includes helper methods for accessing code interpreter data from response chunks. | | [`openai_responses_client_with_explicit_settings.py`](openai_responses_client_with_explicit_settings.py) | Shows how to initialize an agent with a specific responses client, configuring settings explicitly including API key and model ID. | | [`openai_responses_client_with_file_search.py`](openai_responses_client_with_file_search.py) | Demonstrates how to use file search capabilities with OpenAI agents, allowing the agent to search through uploaded files to answer questions. | diff --git a/python/samples/getting_started/agents/openai/openai_responses_client_with_agent_as_tool.py b/python/samples/getting_started/agents/openai/openai_responses_client_with_agent_as_tool.py new file mode 100644 index 0000000000..550ec647ac --- /dev/null +++ b/python/samples/getting_started/agents/openai/openai_responses_client_with_agent_as_tool.py @@ -0,0 +1,67 @@ +# Copyright (c) Microsoft. All rights reserved. + +import asyncio +from collections.abc import Awaitable, Callable + +from agent_framework import FunctionInvocationContext +from agent_framework.openai import OpenAIResponsesClient + +""" +OpenAI Responses Client Agent-as-Tool Example + +Demonstrates hierarchical agent architectures where one agent delegates +work to specialized sub-agents wrapped as tools using as_tool(). + +This pattern is useful when you want a coordinator agent to orchestrate +multiple specialized agents, each focusing on specific tasks. +""" + + +async def logging_middleware( + context: FunctionInvocationContext, + next: Callable[[FunctionInvocationContext], Awaitable[None]], +) -> None: + """Middleware that logs tool invocations to show the delegation flow.""" + print(f"[Calling tool: {context.function.name}]") + print(f"[Request: {context.arguments}]") + + await next(context) + + print(f"[Response: {context.result}]") + + +async def main() -> None: + print("=== OpenAI Responses Client Agent-as-Tool Pattern ===") + + client = OpenAIResponsesClient() + + # Create a specialized writer agent + writer = client.as_agent( + name="WriterAgent", + instructions="You are a creative writer. Write short, engaging content. ", + ) + + # Convert writer agent to a tool using as_tool() + writer_tool = writer.as_tool( + name="creative_writer", + description="Generate creative content like taglines, slogans, or short copy", + arg_name="request", + arg_description="What to write", + ) + + # Create coordinator agent with writer as a tool + coordinator = client.as_agent( + name="CoordinatorAgent", + instructions="You coordinate with specialized agents. Delegate writing tasks to the creative_writer tool.", + tools=[writer_tool], + middleware=[logging_middleware], + ) + + query = "Create a tagline for a coffee shop" + print(f"User: {query}") + result = await coordinator.run(query) + print(f"Coordinator: {result}\n") + + +if __name__ == "__main__": + asyncio.run(main()) From edec41afbd27cc344b98347165698e7414562b95 Mon Sep 17 00:00:00 2001 From: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com> Date: Sat, 17 Jan 2026 02:14:45 -0800 Subject: [PATCH 2/5] Small fix --- .../agents/azure_ai/azure_ai_with_agent_as_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/samples/getting_started/agents/azure_ai/azure_ai_with_agent_as_tool.py b/python/samples/getting_started/agents/azure_ai/azure_ai_with_agent_as_tool.py index 6db8aafdfc..e25b538b33 100644 --- a/python/samples/getting_started/agents/azure_ai/azure_ai_with_agent_as_tool.py +++ b/python/samples/getting_started/agents/azure_ai/azure_ai_with_agent_as_tool.py @@ -55,7 +55,7 @@ async def main() -> None: # Create coordinator agent with writer as a tool coordinator = await provider.create_agent( name="CoordinatorAgent", - instructions="You coordinate with specialized agents. Delegate writing tasks to the creative_writer tool. ", + instructions="You coordinate with specialized agents. Delegate writing tasks to the creative_writer tool.", tools=[writer_tool], middleware=[logging_middleware], ) From 03b1a1069fbcb65147aef90205d9aabc215d42dd Mon Sep 17 00:00:00 2001 From: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com> Date: Sat, 17 Jan 2026 02:49:25 -0800 Subject: [PATCH 3/5] Update python/samples/getting_started/agents/azure_ai/README.md Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- python/samples/getting_started/agents/azure_ai/README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/samples/getting_started/agents/azure_ai/README.md b/python/samples/getting_started/agents/azure_ai/README.md index efc0afdbba..c949476ca0 100644 --- a/python/samples/getting_started/agents/azure_ai/README.md +++ b/python/samples/getting_started/agents/azure_ai/README.md @@ -9,7 +9,7 @@ This folder contains examples demonstrating different ways to create and use age | [`azure_ai_basic.py`](azure_ai_basic.py) | The simplest way to create an agent using `AzureAIProjectAgentProvider`. Demonstrates both streaming and non-streaming responses with function tools. Shows automatic agent creation and basic weather functionality. | | [`azure_ai_provider_methods.py`](azure_ai_provider_methods.py) | Comprehensive guide to `AzureAIProjectAgentProvider` methods: `create_agent()` for creating new agents, `get_agent()` for retrieving existing agents (by name, reference, or details), and `as_agent()` for wrapping SDK objects without HTTP calls. | | [`azure_ai_use_latest_version.py`](azure_ai_use_latest_version.py) | Demonstrates how to reuse the latest version of an existing agent instead of creating a new agent version on each instantiation by using `provider.get_agent()` to retrieve the latest version. | -| [`azure_ai_with_agent_as_tool.py`](azure_ai_with_agent_as_tool.py) | Shows how to use the agent-as-tool pattern with Azure AI agents, where one agent delegates work to specialized sub-agents wrapped as tools using `as_tool()`. Demonstrates hierarchical agent architectures with streaming. | +| [`azure_ai_with_agent_as_tool.py`](azure_ai_with_agent_as_tool.py) | Shows how to use the agent-as-tool pattern with Azure AI agents, where one agent delegates work to specialized sub-agents wrapped as tools using `as_tool()`. Demonstrates hierarchical agent architectures. | | [`azure_ai_with_agent_to_agent.py`](azure_ai_with_agent_to_agent.py) | Shows how to use Agent-to-Agent (A2A) capabilities with Azure AI agents to enable communication with other agents using the A2A protocol. Requires an A2A connection configured in your Azure AI project. | | [`azure_ai_with_azure_ai_search.py`](azure_ai_with_azure_ai_search.py) | Shows how to use Azure AI Search with Azure AI agents to search through indexed data and answer user questions with proper citations. Requires an Azure AI Search connection and index configured in your Azure AI project. | | [`azure_ai_with_bing_grounding.py`](azure_ai_with_bing_grounding.py) | Shows how to use Bing Grounding search with Azure AI agents to search the web for current information and provide grounded responses with citations. Requires a Bing connection configured in your Azure AI project. | From cae73ded231d819a4bd834afa9d8eb989440fd70 Mon Sep 17 00:00:00 2001 From: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com> Date: Sat, 17 Jan 2026 02:49:33 -0800 Subject: [PATCH 4/5] Update python/samples/getting_started/agents/openai/openai_responses_client_with_agent_as_tool.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../agents/openai/openai_responses_client_with_agent_as_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/samples/getting_started/agents/openai/openai_responses_client_with_agent_as_tool.py b/python/samples/getting_started/agents/openai/openai_responses_client_with_agent_as_tool.py index 550ec647ac..13b472e2a3 100644 --- a/python/samples/getting_started/agents/openai/openai_responses_client_with_agent_as_tool.py +++ b/python/samples/getting_started/agents/openai/openai_responses_client_with_agent_as_tool.py @@ -38,7 +38,7 @@ async def main() -> None: # Create a specialized writer agent writer = client.as_agent( name="WriterAgent", - instructions="You are a creative writer. Write short, engaging content. ", + instructions="You are a creative writer. Write short, engaging content.", ) # Convert writer agent to a tool using as_tool() From 0bc733c1c337421d3dda21c42d61d888c147b32d Mon Sep 17 00:00:00 2001 From: Dmytro Struk <13853051+dmytrostruk@users.noreply.github.com> Date: Sat, 17 Jan 2026 02:49:39 -0800 Subject: [PATCH 5/5] Update python/samples/getting_started/agents/azure_ai/azure_ai_with_agent_as_tool.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> --- .../agents/azure_ai/azure_ai_with_agent_as_tool.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/python/samples/getting_started/agents/azure_ai/azure_ai_with_agent_as_tool.py b/python/samples/getting_started/agents/azure_ai/azure_ai_with_agent_as_tool.py index e25b538b33..041f632d2f 100644 --- a/python/samples/getting_started/agents/azure_ai/azure_ai_with_agent_as_tool.py +++ b/python/samples/getting_started/agents/azure_ai/azure_ai_with_agent_as_tool.py @@ -41,7 +41,7 @@ async def main() -> None: # Create a specialized writer agent writer = await provider.create_agent( name="WriterAgent", - instructions="You are a creative writer. Write short, engaging content. ", + instructions="You are a creative writer. Write short, engaging content.", ) # Convert writer agent to a tool using as_tool()