Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions python/packages/core/agent_framework/_agents.py
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
41 changes: 41 additions & 0 deletions python/packages/core/tests/core/test_as_tool_kwargs_propagation.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"
1 change: 1 addition & 0 deletions python/samples/getting_started/agents/azure_ai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
| [`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. |
Expand Down
Original file line number Diff line number Diff line change
@@ -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())
1 change: 1 addition & 0 deletions python/samples/getting_started/agents/openai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down
Original file line number Diff line number Diff line change
@@ -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())
Loading