diff --git a/python/pyproject.toml b/python/pyproject.toml
index 6f977994dd27..5a8cd960f85c 100644
--- a/python/pyproject.toml
+++ b/python/pyproject.toml
@@ -24,8 +24,8 @@ classifiers = [
]
dependencies = [
# azure agents
- "azure-ai-projects >= 1.0.0b11",
- "azure-ai-agents >= 1.1.0b1",
+ "azure-ai-projects >= 1.0.0b12",
+ "azure-ai-agents >= 1.1.0b4",
"aiohttp ~= 3.8",
"cloudevents ~=1.0",
"pydantic >=2.0,!=2.10.0,!=2.10.1,!=2.10.2,!=2.10.3,<2.12",
@@ -93,7 +93,7 @@ google = [
]
hugging_face = [
"transformers[torch] ~= 4.28",
- "sentence-transformers >= 2.2,< 5.0",
+ "sentence-transformers >= 2.2,< 6.0",
"torch == 2.7.1"
]
mcp = [
diff --git a/python/samples/concepts/README.md b/python/samples/concepts/README.md
index 8daa34c3d0fb..683c37e068f8 100644
--- a/python/samples/concepts/README.md
+++ b/python/samples/concepts/README.md
@@ -21,6 +21,7 @@
- [Azure AI Agent Declarative with OpenAPI Interpreter](./agents/azure_ai_agent/azure_ai_agent_declarative_openapi.py)
- [Azure AI Agent Declarative with Existing Agent ID](./agents/azure_ai_agent/azure_ai_agent_declarative_with_existing_agent_id.py)
- [Azure AI Agent File Manipulation](./agents/azure_ai_agent/azure_ai_agent_file_manipulation.py)
+- [Azure AI Agent MCP Streaming](./agents/azure_ai_agent/azure_ai_agent_mcp_streaming.py)
- [Azure AI Agent Prompt Templating](./agents/azure_ai_agent/azure_ai_agent_prompt_templating.py)
- [Azure AI Agent Message Callback Streaming](./agents/azure_ai_agent/azure_ai_agent_message_callback_streaming.py)
- [Azure AI Agent Message Callback](./agents/azure_ai_agent/azure_ai_agent_message_callback.py)
diff --git a/python/samples/concepts/agents/azure_ai_agent/azure_ai_agent_mcp_streaming.py b/python/samples/concepts/agents/azure_ai_agent/azure_ai_agent_mcp_streaming.py
new file mode 100644
index 000000000000..3e06a87257c0
--- /dev/null
+++ b/python/samples/concepts/agents/azure_ai_agent/azure_ai_agent_mcp_streaming.py
@@ -0,0 +1,121 @@
+# Copyright (c) Microsoft. All rights reserved.
+
+import asyncio
+
+from azure.ai.agents.models import McpTool
+from azure.identity.aio import DefaultAzureCredential
+
+from semantic_kernel.agents import AzureAIAgent, AzureAIAgentSettings, AzureAIAgentThread
+from semantic_kernel.contents import ChatMessageContent, FunctionCallContent, FunctionResultContent
+
+"""
+The following sample demonstrates how to create a simple, Azure AI agent that
+uses the mcp tool to connect to an mcp server with streaming responses.
+"""
+
+TASK = "Please summarize the Azure REST API specifications Readme"
+
+
+async def handle_intermediate_messages(message: ChatMessageContent) -> None:
+ for item in message.items or []:
+ if isinstance(item, FunctionResultContent):
+ print(f"Function Result:> {item.result} for function: {item.name}")
+ elif isinstance(item, FunctionCallContent):
+ print(f"Function Call:> {item.name} with arguments: {item.arguments}")
+ else:
+ print(f"{item}")
+
+
+async def main() -> None:
+ async with (
+ DefaultAzureCredential() as creds,
+ AzureAIAgent.create_client(credential=creds) as client,
+ ):
+ # 1. Define the MCP tool with the server URL
+ mcp_tool = McpTool(
+ server_label="github",
+ server_url="https://gitmcp.io/Azure/azure-rest-api-specs",
+ allowed_tools=[], # Specify allowed tools if needed
+ )
+
+ # Optionally you may configure to require approval
+ # Allowed values are "never" or "always"
+ mcp_tool.set_approval_mode("never")
+
+ # 2. Create an agent with the MCP tool on the Azure AI agent service
+ agent_definition = await client.agents.create_agent(
+ model=AzureAIAgentSettings().model_deployment_name,
+ tools=mcp_tool.definitions,
+ instructions="You are a helpful agent that can use MCP tools to assist users.",
+ )
+
+ # 3. Create a Semantic Kernel agent for the Azure AI agent
+ agent = AzureAIAgent(
+ client=client,
+ definition=agent_definition,
+ )
+
+ # 4. Create a thread for the agent
+ # If no thread is provided, a new thread will be
+ # created and returned with the initial response
+ thread: AzureAIAgentThread | None = None
+
+ try:
+ print(f"# User: '{TASK}'")
+ # 5. Invoke the agent for the specified thread for response
+ async for response in agent.invoke_stream(
+ messages=TASK,
+ thread=thread,
+ on_intermediate_message=handle_intermediate_messages,
+ ):
+ print(f"{response}", end="", flush=True)
+ thread = response.thread
+ finally:
+ # 6. Cleanup: Delete the thread, agent, and file
+ await thread.delete() if thread else None
+ await client.agents.delete_agent(agent.id)
+
+ """
+ Sample Output:
+
+ # User: 'Please summarize the Azure REST API specifications Readme'
+ Function Call:> fetch_azure_rest_api_docs with arguments: {}
+ The Azure REST API specifications Readme provides comprehensive documentation and guidelines for designing,
+ authoring, validating, and evolving Azure REST APIs. It covers key areas including:
+
+ 1. Breaking changes and versioning: Guidelines to manage API changes that break backward compatibility, when to
+ increment API versions, and how to maintain smooth API evolution.
+
+ 2. OpenAPI/Swagger specifications: How to author REST APIs using OpenAPI specification 2.0 (Swagger), including
+ structure, conventions, validation tools, and extensions used by AutoRest for generating client SDKs.
+
+ 3. TypeSpec language: Introduction to TypeSpec, a powerful language for describing and generating REST API
+ specifications and client SDKs with extensibility to other API styles.
+
+ 4. Directory structure and uniform versioning: Organizing service specifications by teams, resource provider
+ namespaces, and following uniform versioning to keep API versions consistent across documentation and SDKs.
+
+ 5. Validation and tooling: Tools and processes like OAV, AutoRest, RESTler, and CI checks used to validate API
+ specs, generate SDKs, detect breaking changes, lint specifications, and test service contract accuracy.
+
+ 6. Authoring best practices: Manual and automated guidelines for quality API spec authoring, including writing
+ effective descriptions, resource modeling, naming conventions, and examples.
+
+ 7. Code generation configurations: How to configure readme files to generate SDKs for various languages
+ including .NET, Java, Python, Go, Typescript, and Azure CLI using AutoRest.
+
+ 8. API Scenarios and testing: Defining API scenario test files for end-to-end REST API workflows, including
+ variables, ARM template integration, and usage of test-proxy for recording traffic.
+
+ 9. SDK automation and release requests: Workflows for SDK generation validation, suppressing breaking change
+ warnings, and requesting official Azure SDK releases.
+
+ Overall, the Readme acts as a central hub providing references, guidelines, examples, and tools for maintaining
+ high-quality Azure REST API specifications and seamless SDK generation across multiple languages and
+ platforms. It ensures consistent API design, versioning, validation, and developer experience in the Azure
+ ecosystem.
+ """
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/python/samples/getting_started_with_agents/azure_ai_agent/step1_azure_ai_agent.py b/python/samples/getting_started_with_agents/azure_ai_agent/step01_azure_ai_agent.py
similarity index 100%
rename from python/samples/getting_started_with_agents/azure_ai_agent/step1_azure_ai_agent.py
rename to python/samples/getting_started_with_agents/azure_ai_agent/step01_azure_ai_agent.py
diff --git a/python/samples/getting_started_with_agents/azure_ai_agent/step2_azure_ai_agent_plugin.py b/python/samples/getting_started_with_agents/azure_ai_agent/step02_azure_ai_agent_plugin.py
similarity index 100%
rename from python/samples/getting_started_with_agents/azure_ai_agent/step2_azure_ai_agent_plugin.py
rename to python/samples/getting_started_with_agents/azure_ai_agent/step02_azure_ai_agent_plugin.py
diff --git a/python/samples/getting_started_with_agents/azure_ai_agent/step3_azure_ai_agent_group_chat.py b/python/samples/getting_started_with_agents/azure_ai_agent/step03_azure_ai_agent_group_chat.py
similarity index 100%
rename from python/samples/getting_started_with_agents/azure_ai_agent/step3_azure_ai_agent_group_chat.py
rename to python/samples/getting_started_with_agents/azure_ai_agent/step03_azure_ai_agent_group_chat.py
diff --git a/python/samples/getting_started_with_agents/azure_ai_agent/step4_azure_ai_agent_code_interpreter.py b/python/samples/getting_started_with_agents/azure_ai_agent/step04_azure_ai_agent_code_interpreter.py
similarity index 100%
rename from python/samples/getting_started_with_agents/azure_ai_agent/step4_azure_ai_agent_code_interpreter.py
rename to python/samples/getting_started_with_agents/azure_ai_agent/step04_azure_ai_agent_code_interpreter.py
diff --git a/python/samples/getting_started_with_agents/azure_ai_agent/step5_azure_ai_agent_file_search.py b/python/samples/getting_started_with_agents/azure_ai_agent/step05_azure_ai_agent_file_search.py
similarity index 100%
rename from python/samples/getting_started_with_agents/azure_ai_agent/step5_azure_ai_agent_file_search.py
rename to python/samples/getting_started_with_agents/azure_ai_agent/step05_azure_ai_agent_file_search.py
diff --git a/python/samples/getting_started_with_agents/azure_ai_agent/step6_azure_ai_agent_openapi.py b/python/samples/getting_started_with_agents/azure_ai_agent/step06_azure_ai_agent_openapi.py
similarity index 100%
rename from python/samples/getting_started_with_agents/azure_ai_agent/step6_azure_ai_agent_openapi.py
rename to python/samples/getting_started_with_agents/azure_ai_agent/step06_azure_ai_agent_openapi.py
diff --git a/python/samples/getting_started_with_agents/azure_ai_agent/step7_azure_ai_agent_retrieval.py b/python/samples/getting_started_with_agents/azure_ai_agent/step07_azure_ai_agent_retrieval.py
similarity index 100%
rename from python/samples/getting_started_with_agents/azure_ai_agent/step7_azure_ai_agent_retrieval.py
rename to python/samples/getting_started_with_agents/azure_ai_agent/step07_azure_ai_agent_retrieval.py
diff --git a/python/samples/getting_started_with_agents/azure_ai_agent/step8_azure_ai_agent_declarative.py b/python/samples/getting_started_with_agents/azure_ai_agent/step08_azure_ai_agent_declarative.py
similarity index 100%
rename from python/samples/getting_started_with_agents/azure_ai_agent/step8_azure_ai_agent_declarative.py
rename to python/samples/getting_started_with_agents/azure_ai_agent/step08_azure_ai_agent_declarative.py
diff --git a/python/samples/getting_started_with_agents/azure_ai_agent/step09_azure_ai_agent_mcp.py b/python/samples/getting_started_with_agents/azure_ai_agent/step09_azure_ai_agent_mcp.py
new file mode 100644
index 000000000000..5317cbf43bbc
--- /dev/null
+++ b/python/samples/getting_started_with_agents/azure_ai_agent/step09_azure_ai_agent_mcp.py
@@ -0,0 +1,119 @@
+# Copyright (c) Microsoft. All rights reserved.
+
+import asyncio
+
+from azure.ai.agents.models import McpTool
+from azure.identity.aio import DefaultAzureCredential
+
+from semantic_kernel.agents import AzureAIAgent, AzureAIAgentSettings, AzureAIAgentThread
+from semantic_kernel.contents import ChatMessageContent, FunctionCallContent, FunctionResultContent
+
+"""
+The following sample demonstrates how to create a simple, Azure AI agent that
+uses the mcp tool to connect to an mcp server.
+"""
+
+TASK = "Please summarize the Azure REST API specifications Readme"
+
+
+async def handle_intermediate_messages(message: ChatMessageContent) -> None:
+ for item in message.items or []:
+ if isinstance(item, FunctionResultContent):
+ print(f"Function Result:> {item.result} for function: {item.name}")
+ elif isinstance(item, FunctionCallContent):
+ print(f"Function Call:> {item.name} with arguments: {item.arguments}")
+ else:
+ print(f"{item}")
+
+
+async def main() -> None:
+ async with (
+ DefaultAzureCredential() as creds,
+ AzureAIAgent.create_client(credential=creds) as client,
+ ):
+ # 1. Define the MCP tool with the server URL
+ mcp_tool = McpTool(
+ server_label="github",
+ server_url="https://gitmcp.io/Azure/azure-rest-api-specs",
+ allowed_tools=[], # Specify allowed tools if needed
+ )
+
+ # Optionally you may configure to require approval
+ # Allowed values are "never" or "always"
+ mcp_tool.set_approval_mode("never")
+
+ # 2. Create an agent with the MCP tool on the Azure AI agent service
+ agent_definition = await client.agents.create_agent(
+ model=AzureAIAgentSettings().model_deployment_name,
+ tools=mcp_tool.definitions,
+ instructions="You are a helpful agent that can use MCP tools to assist users.",
+ )
+
+ # 3. Create a Semantic Kernel agent for the Azure AI agent
+ agent = AzureAIAgent(
+ client=client,
+ definition=agent_definition,
+ )
+
+ # 4. Create a thread for the agent
+ # If no thread is provided, a new thread will be
+ # created and returned with the initial response
+ thread: AzureAIAgentThread | None = None
+
+ try:
+ print(f"# User: '{TASK}'")
+ # 5. Invoke the agent for the specified thread for response
+ async for response in agent.invoke(
+ messages=TASK, thread=thread, on_intermediate_message=handle_intermediate_messages
+ ):
+ print(f"# Agent: {response}")
+ thread = response.thread
+ finally:
+ # 6. Cleanup: Delete the thread, agent, and file
+ await thread.delete() if thread else None
+ await client.agents.delete_agent(agent.id)
+
+ """
+ Sample Output:
+
+ # User: 'Please summarize the Azure REST API specifications Readme'
+ Function Call:> fetch_azure_rest_api_docs with arguments: {}
+ The Azure REST API specifications Readme provides comprehensive documentation and guidelines for designing,
+ authoring, validating, and evolving Azure REST APIs. It covers key areas including:
+
+ 1. Breaking changes and versioning: Guidelines to manage API changes that break backward compatibility, when to
+ increment API versions, and how to maintain smooth API evolution.
+
+ 2. OpenAPI/Swagger specifications: How to author REST APIs using OpenAPI specification 2.0 (Swagger), including
+ structure, conventions, validation tools, and extensions used by AutoRest for generating client SDKs.
+
+ 3. TypeSpec language: Introduction to TypeSpec, a powerful language for describing and generating REST API
+ specifications and client SDKs with extensibility to other API styles.
+
+ 4. Directory structure and uniform versioning: Organizing service specifications by teams, resource provider
+ namespaces, and following uniform versioning to keep API versions consistent across documentation and SDKs.
+
+ 5. Validation and tooling: Tools and processes like OAV, AutoRest, RESTler, and CI checks used to validate API
+ specs, generate SDKs, detect breaking changes, lint specifications, and test service contract accuracy.
+
+ 6. Authoring best practices: Manual and automated guidelines for quality API spec authoring, including writing
+ effective descriptions, resource modeling, naming conventions, and examples.
+
+ 7. Code generation configurations: How to configure readme files to generate SDKs for various languages
+ including .NET, Java, Python, Go, Typescript, and Azure CLI using AutoRest.
+
+ 8. API Scenarios and testing: Defining API scenario test files for end-to-end REST API workflows, including
+ variables, ARM template integration, and usage of test-proxy for recording traffic.
+
+ 9. SDK automation and release requests: Workflows for SDK generation validation, suppressing breaking change
+ warnings, and requesting official Azure SDK releases.
+
+ Overall, the Readme acts as a central hub providing references, guidelines, examples, and tools for maintaining
+ high-quality Azure REST API specifications and seamless SDK generation across multiple languages and
+ platforms. It ensures consistent API design, versioning, validation, and developer experience in the Azure
+ ecosystem.
+ """
+
+
+if __name__ == "__main__":
+ asyncio.run(main())
diff --git a/python/semantic_kernel/agents/azure_ai/agent_content_generation.py b/python/semantic_kernel/agents/azure_ai/agent_content_generation.py
index c2152e894c3d..7e389be21c47 100644
--- a/python/semantic_kernel/agents/azure_ai/agent_content_generation.py
+++ b/python/semantic_kernel/agents/azure_ai/agent_content_generation.py
@@ -15,6 +15,7 @@
MessageTextFilePathAnnotation,
MessageTextUrlCitationAnnotation,
RequiredFunctionToolCall,
+ RequiredMcpToolCall,
RunStep,
RunStepAzureAISearchToolCall,
RunStepBingGroundingToolCall,
@@ -25,6 +26,7 @@
RunStepDeltaFunctionToolCall,
RunStepFileSearchToolCall,
RunStepFunctionToolCall,
+ RunStepMcpToolCall,
RunStepOpenAPIToolCall,
ThreadMessage,
ThreadRun,
@@ -774,3 +776,140 @@ def generate_streaming_annotation_content(
title=title,
citation_type=citation_type,
)
+
+
+@experimental
+def generate_mcp_content(agent_name: str, mcp_tool_call: RunStepMcpToolCall) -> ChatMessageContent:
+ """Generate MCP tool content.
+
+ Args:
+ agent_name: The name of the agent.
+ mcp_tool_call: The MCP tool call.
+
+ Returns:
+ The generated content.
+ """
+ mcp_result = FunctionResultContent(
+ function_name=mcp_tool_call.name,
+ id=mcp_tool_call.id,
+ result=mcp_tool_call.output,
+ )
+
+ return ChatMessageContent(
+ role=AuthorRole.ASSISTANT,
+ name=agent_name,
+ items=[mcp_result],
+ inner_content=mcp_tool_call, # type: ignore
+ )
+
+
+@experimental
+def generate_mcp_call_content(agent_name: str, mcp_tool_calls: list[RequiredMcpToolCall]) -> ChatMessageContent:
+ """Generate MCP tool call content.
+
+ Args:
+ agent_name: The name of the agent.
+ mcp_tool_calls: The MCP tool calls.
+
+ Returns:
+ The generated content.
+ """
+ content_items: list[FunctionCallContent] = []
+ for mcp_call in mcp_tool_calls:
+ content_items.append(
+ FunctionCallContent(
+ id=mcp_call.id,
+ name=mcp_call.name,
+ function_name=mcp_call.name,
+ arguments=mcp_call.arguments,
+ server_label=mcp_call.server_label,
+ )
+ )
+
+ return ChatMessageContent(
+ role=AuthorRole.ASSISTANT,
+ name=agent_name,
+ items=content_items, # type: ignore
+ )
+
+
+@experimental
+def generate_streaming_mcp_call_content(
+ agent_name: str, mcp_tool_calls: list["RequiredMcpToolCall"]
+) -> "StreamingChatMessageContent | None":
+ """Generate streaming MCP content.
+
+ Args:
+ agent_name: The name of the agent.
+ mcp_tool_calls: The mcp tool call details.
+
+ Returns:
+ The generated streaming content.
+ """
+ items: list[FunctionCallContent] = []
+ for index, tool in enumerate(mcp_tool_calls or []):
+ if isinstance(tool, RequiredMcpToolCall):
+ items.append(
+ FunctionCallContent(
+ id=tool.id,
+ index=index,
+ name=tool.name,
+ function_name=tool.name,
+ arguments=tool.arguments,
+ server_label=tool.server_label,
+ )
+ )
+
+ return (
+ StreamingChatMessageContent(
+ role=AuthorRole.ASSISTANT,
+ name=agent_name,
+ items=items, # type: ignore
+ choice_index=0,
+ )
+ if items
+ else None
+ )
+
+
+@experimental
+def generate_streaming_mcp_content(
+ agent_name: str, step_details: "RunStepDeltaToolCallObject"
+) -> StreamingChatMessageContent | None:
+ """Generate MCP tool content.
+
+ Args:
+ agent_name: The name of the agent.
+ step_details: The steps details with mcp tool call.
+
+ Returns:
+ The generated content.
+ """
+ if not step_details.tool_calls:
+ return None
+
+ items: list[FunctionResultContent] = []
+
+ for _, tool in enumerate(step_details.tool_calls):
+ if tool.type == "mcp":
+ mcp_tool_call = cast(RunStepMcpToolCall, tool)
+ if not mcp_tool_call.get("output"):
+ continue
+ mcp_result = FunctionResultContent(
+ function_name=mcp_tool_call.get("name"),
+ id=mcp_tool_call.get("id"),
+ result=mcp_tool_call.get("output"),
+ )
+ items.append(mcp_result)
+
+ return (
+ StreamingChatMessageContent(
+ role=AuthorRole.ASSISTANT,
+ name=agent_name,
+ items=items, # type: ignore
+ inner_content=mcp_tool_call, # type: ignore
+ choice_index=0,
+ )
+ if items
+ else None
+ ) # type: ignore
diff --git a/python/semantic_kernel/agents/azure_ai/agent_thread_actions.py b/python/semantic_kernel/agents/azure_ai/agent_thread_actions.py
index 3ade5ff35282..7f8f900e9395 100644
--- a/python/semantic_kernel/agents/azure_ai/agent_thread_actions.py
+++ b/python/semantic_kernel/agents/azure_ai/agent_thread_actions.py
@@ -12,6 +12,7 @@
AsyncAgentRunStream,
BaseAsyncAgentEventHandler,
FunctionToolDefinition,
+ RequiredMcpToolCall,
ResponseFormatJsonSchemaType,
RunStep,
RunStepAzureAISearchToolCall,
@@ -20,13 +21,16 @@
RunStepDeltaChunk,
RunStepDeltaToolCallObject,
RunStepFileSearchToolCall,
+ RunStepMcpToolCall,
RunStepMessageCreationDetails,
RunStepOpenAPIToolCall,
RunStepToolCallDetails,
RunStepType,
+ SubmitToolApprovalAction,
SubmitToolOutputsAction,
ThreadMessage,
ThreadRun,
+ ToolApproval,
ToolDefinition,
TruncationObject,
)
@@ -40,12 +44,16 @@
generate_function_call_content,
generate_function_call_streaming_content,
generate_function_result_content,
+ generate_mcp_call_content,
+ generate_mcp_content,
generate_message_content,
generate_openapi_content,
generate_streaming_azure_ai_search_content,
generate_streaming_bing_grounding_content,
generate_streaming_code_interpreter_content,
generate_streaming_file_search_content,
+ generate_streaming_mcp_call_content,
+ generate_streaming_mcp_content,
generate_streaming_message_content,
generate_streaming_openapi_content,
get_function_call_contents,
@@ -199,31 +207,73 @@ async def invoke(
)
# Check if function calling is required
- if run.status == "requires_action" and isinstance(run.required_action, SubmitToolOutputsAction):
- logger.debug(f"Run [{run.id}] requires tool action for agent `{agent.name}` and thread `{thread_id}`")
- fccs = get_function_call_contents(run, function_steps)
- if fccs:
+ if run.status == "requires_action":
+ if isinstance(run.required_action, SubmitToolOutputsAction):
logger.debug(
- f"Yielding generate_function_call_content for agent `{agent.name}` and "
- f"thread `{thread_id}`, visibility False"
+ f"Run [{run.id}] requires tool action for agent `{agent.name}` and thread `{thread_id}`"
)
- yield False, generate_function_call_content(agent_name=agent.name, fccs=fccs)
+ fccs = get_function_call_contents(run, function_steps)
+ if fccs:
+ logger.debug(
+ f"Yielding generate_function_call_content for agent `{agent.name}` and "
+ f"thread `{thread_id}`, visibility False"
+ )
+ yield False, generate_function_call_content(agent_name=agent.name, fccs=fccs)
- from semantic_kernel.contents.chat_history import ChatHistory
+ from semantic_kernel.contents.chat_history import ChatHistory
- chat_history = ChatHistory() if kwargs.get("chat_history") is None else kwargs["chat_history"]
- _ = await cls._invoke_function_calls(
- kernel=kernel, fccs=fccs, chat_history=chat_history, arguments=arguments
- )
+ chat_history = ChatHistory() if kwargs.get("chat_history") is None else kwargs["chat_history"]
+ _ = await cls._invoke_function_calls(
+ kernel=kernel, fccs=fccs, chat_history=chat_history, arguments=arguments
+ )
- tool_outputs = cls._format_tool_outputs(fccs, chat_history)
- await agent.client.agents.runs.submit_tool_outputs(
- run_id=run.id,
- thread_id=thread_id,
- tool_outputs=tool_outputs, # type: ignore
+ tool_outputs = cls._format_tool_outputs(fccs, chat_history)
+ await agent.client.agents.runs.submit_tool_outputs(
+ run_id=run.id,
+ thread_id=thread_id,
+ tool_outputs=tool_outputs, # type: ignore
+ )
+ logger.debug(f"Submitted tool outputs for agent `{agent.name}` and thread `{thread_id}`")
+ continue
+
+ # Check if MCP tool approval is required
+ elif isinstance(run.required_action, SubmitToolApprovalAction):
+ logger.debug(
+ f"Run [{run.id}] requires MCP tool approval for agent `{agent.name}` and thread `{thread_id}`"
)
- logger.debug(f"Submitted tool outputs for agent `{agent.name}` and thread `{thread_id}`")
- continue
+ tool_calls = run.required_action.submit_tool_approval.tool_calls
+ if not tool_calls:
+ logger.warning(f"No tool calls provided for MCP approval - cancelling run [{run.id}]")
+ await agent.client.agents.runs.cancel(run_id=run.id, thread_id=thread_id)
+ continue
+
+ mcp_tool_calls = [tc for tc in tool_calls if isinstance(tc, RequiredMcpToolCall)]
+ if mcp_tool_calls:
+ logger.debug(
+ f"Yielding generate_mcp_call_content for agent `{agent.name}` and "
+ f"thread `{thread_id}`, visibility False"
+ )
+ yield False, generate_mcp_call_content(agent_name=agent.name, mcp_tool_calls=mcp_tool_calls)
+
+ # Create tool approvals for MCP calls
+ tool_approvals = []
+ for mcp_call in mcp_tool_calls:
+ tool_approvals.append(
+ ToolApproval(
+ tool_call_id=mcp_call.id,
+ # TODO(evmattso): we don't support manual tool calling yet
+ # so we always approve
+ approve=True,
+ )
+ )
+
+ await agent.client.agents.runs.submit_tool_outputs(
+ run_id=run.id,
+ thread_id=thread_id,
+ tool_approvals=tool_approvals, # type: ignore
+ )
+ logger.debug(f"Submitted MCP tool approvals for agent `{agent.name}` and thread `{thread_id}`")
+ continue
steps: list[RunStep] = []
async for steps_response in agent.client.agents.run_steps.list(thread_id=thread_id, run_id=run.id):
@@ -331,6 +381,16 @@ def sort_key(step: RunStep):
agent_name=agent.name,
openapi_tool_call=openapi_tool_call,
)
+ case AgentsNamedToolChoiceType.MCP:
+ logger.debug(
+ f"Entering tool_calls (mcp) for run [{run.id}], agent "
+ f" `{agent.name}` and thread `{thread_id}`"
+ )
+ mcp_tool_call: RunStepMcpToolCall = cast(RunStepMcpToolCall, tool_call)
+ content = generate_mcp_content(
+ agent_name=agent.name,
+ mcp_tool_call=mcp_tool_call,
+ )
if content:
message_count += 1
@@ -552,6 +612,10 @@ async def _process_stream_events(
content = generate_streaming_openapi_content(
agent_name=agent.name, step_details=details
)
+ case AgentsNamedToolChoiceType.MCP:
+ content = generate_streaming_mcp_content(
+ agent_name=agent.name, step_details=details
+ )
if content:
if output_messages is not None:
output_messages.append(content)
@@ -564,41 +628,95 @@ async def _process_stream_events(
f"thread `{thread_id}` with event data: {event_data}"
)
run = cast(ThreadRun, event_data)
- action_result = await cls._handle_streaming_requires_action(
- agent_name=agent.name,
- kernel=kernel,
- run=run,
- function_steps=function_steps,
- arguments=arguments,
- )
- if action_result is None:
- raise RuntimeError(
- f"Function call required but no function steps found for agent `{agent.name}` "
- f"thread: {thread_id}."
+
+ # Check if this is a function call request
+ if isinstance(run.required_action, SubmitToolOutputsAction):
+ action_result = await cls._handle_streaming_requires_action(
+ agent_name=agent.name,
+ kernel=kernel,
+ run=run,
+ function_steps=function_steps,
+ arguments=arguments,
)
+ if action_result is None:
+ raise RuntimeError(
+ f"Function call required but no function steps found for agent `{agent.name}` "
+ f"thread: {thread_id}."
+ )
- for content in (
- action_result.function_call_streaming_content,
- action_result.function_result_streaming_content,
- ):
- if content and output_messages is not None:
- output_messages.append(content)
-
- handler: BaseAsyncAgentEventHandler = AsyncAgentEventHandler()
- await agent.client.agents.runs.submit_tool_outputs_stream(
- run_id=run.id,
- thread_id=thread_id,
- tool_outputs=action_result.tool_outputs, # type: ignore
- event_handler=handler,
- )
- # Pass the handler to the stream to continue processing
- stream = handler # type: ignore
+ for content in (
+ action_result.function_call_streaming_content,
+ action_result.function_result_streaming_content,
+ ):
+ if content and output_messages is not None:
+ output_messages.append(content)
- logger.debug(
- f"Submitted tool outputs stream for agent `{agent.name}` and "
- f"thread `{thread_id}` and run id `{run.id}`"
- )
- break
+ handler: BaseAsyncAgentEventHandler = AsyncAgentEventHandler()
+ await agent.client.agents.runs.submit_tool_outputs_stream(
+ run_id=run.id,
+ thread_id=thread_id,
+ tool_outputs=action_result.tool_outputs, # type: ignore
+ event_handler=handler,
+ )
+ # Pass the handler to the stream to continue processing
+ stream = handler # type: ignore
+
+ logger.debug(
+ f"Submitted tool outputs stream for agent `{agent.name}` and "
+ f"thread `{thread_id}` and run id `{run.id}`"
+ )
+ break
+
+ # Check if this is an MCP tool approval request
+ elif isinstance(run.required_action, SubmitToolApprovalAction):
+ tool_calls = run.required_action.submit_tool_approval.tool_calls
+ if not tool_calls:
+ logger.warning(f"No tool calls provided for MCP approval - cancelling run [{run.id}]")
+ await agent.client.agents.runs.cancel(run_id=run.id, thread_id=thread_id)
+ break
+
+ mcp_tool_calls = [tc for tc in tool_calls if isinstance(tc, RequiredMcpToolCall)]
+ if mcp_tool_calls:
+ logger.debug(
+ f"Processing MCP tool approvals for agent `{agent.name}` and "
+ f"thread `{thread_id}` and run id `{run.id}`"
+ )
+
+ if output_messages is not None:
+ content = generate_streaming_mcp_call_content(
+ agent_name=agent.name, mcp_tool_calls=mcp_tool_calls
+ )
+ if content:
+ output_messages.append(content)
+
+ # Create tool approvals for MCP calls
+ tool_approvals = []
+ for mcp_call in mcp_tool_calls:
+ tool_approvals.append(
+ ToolApproval(
+ tool_call_id=mcp_call.id,
+ approve=True,
+ # Note: headers would need to be provided by the MCP tool configuration
+ # This is a simplified implementation
+ headers={},
+ )
+ )
+
+ handler: BaseAsyncAgentEventHandler = AsyncAgentEventHandler() # type: ignore
+ await agent.client.agents.runs.submit_tool_outputs_stream(
+ run_id=run.id,
+ thread_id=thread_id,
+ tool_approvals=tool_approvals, # type: ignore
+ event_handler=handler,
+ )
+ # Pass the handler to the stream to continue processing
+ stream = handler # type: ignore
+
+ logger.debug(
+ f"Submitted MCP tool approvals stream for agent `{agent.name}` and "
+ f"thread `{thread_id}` and run id `{run.id}`"
+ )
+ break
elif event_type == AgentStreamEvent.THREAD_RUN_COMPLETED:
logger.debug(
diff --git a/python/tests/unit/connectors/ai/open_ai/services/test_azure_text_to_image.py b/python/tests/unit/connectors/ai/open_ai/services/test_azure_text_to_image.py
index 20e46f27fcc0..5e5ab9a108f8 100644
--- a/python/tests/unit/connectors/ai/open_ai/services/test_azure_text_to_image.py
+++ b/python/tests/unit/connectors/ai/open_ai/services/test_azure_text_to_image.py
@@ -78,10 +78,10 @@ def test_azure_text_to_image_init_with_from_dict(azure_openai_unit_test_env) ->
assert azure_text_to_image.client.default_headers[key] == value
-@patch.object(AsyncImages, "generate", return_value=AsyncMock(spec=ImagesResponse))
+@patch.object(AsyncImages, "generate", new_callable=AsyncMock)
async def test_azure_text_to_image_calls_with_parameters(mock_generate, azure_openai_unit_test_env) -> None:
- mock_generate.return_value.data = [Image(url="abc")]
- mock_generate.return_value.usage = None
+ mock_response = ImagesResponse(created=1, data=[Image(url="abc")], usage=None)
+ mock_generate.return_value = mock_response
prompt = "A painting of a vase with flowers"
width = 512
diff --git a/python/tests/unit/connectors/ai/open_ai/services/test_openai_text_to_image.py b/python/tests/unit/connectors/ai/open_ai/services/test_openai_text_to_image.py
index 18ff4b749d0f..6d5845994fda 100644
--- a/python/tests/unit/connectors/ai/open_ai/services/test_openai_text_to_image.py
+++ b/python/tests/unit/connectors/ai/open_ai/services/test_openai_text_to_image.py
@@ -32,10 +32,11 @@ def test_init(openai_unit_test_env):
assert openai_text_to_image.ai_model_id == openai_unit_test_env["OPENAI_TEXT_TO_IMAGE_MODEL_ID"]
-def test_init_validation_fail() -> None:
+@pytest.mark.parametrize("exclude_list", [["OPENAI_TEXT_TO_IMAGE_MODEL_ID"]], indirect=True)
+def test_init_validation_fail(openai_unit_test_env) -> None:
"""Test that initialization fails when required parameters are missing."""
with pytest.raises(ServiceInitializationError):
- OpenAITextToImage(api_key="34523", ai_model_id=None)
+ OpenAITextToImage(api_key="34523", ai_model_id=None, env_file_path="test.env")
def test_init_to_from_dict(openai_unit_test_env):
@@ -77,11 +78,11 @@ def test_prompt_execution_settings_class(openai_unit_test_env) -> None:
assert openai_text_to_image.get_prompt_execution_settings_class() == OpenAITextToImageExecutionSettings
-@patch.object(AsyncImages, "generate", return_value=AsyncMock(spec=ImagesResponse))
+@patch.object(AsyncImages, "generate", new_callable=AsyncMock)
async def test_generate_calls_with_parameters(mock_generate, openai_unit_test_env) -> None:
"""Test that generate_image calls the OpenAI API with correct parameters."""
- mock_generate.return_value.data = [Image(url="abc")]
- mock_generate.return_value.usage = None
+ mock_response = ImagesResponse(created=1, data=[Image(url="abc")], usage=None)
+ mock_generate.return_value = mock_response
ai_model_id = "test_model_id"
prompt = "painting of flowers in vase"
diff --git a/python/uv.lock b/python/uv.lock
index 46f5f6d4c1d7..4627973d356c 100644
--- a/python/uv.lock
+++ b/python/uv.lock
@@ -350,16 +350,16 @@ wheels = [
[[package]]
name = "azure-ai-agents"
-version = "1.1.0b2"
+version = "1.1.0b4"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "azure-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "isodate", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/18/7b/fa5452b4cf7559ac827140edf38097026da66bfe78df70783057aea68238/azure_ai_agents-1.1.0b2.tar.gz", hash = "sha256:432ce359c3d02e05873d1d670bd91c1393bbe51f734ec4ce3f76fcafd8104c75", size = 302724, upload-time = "2025-06-09T16:53:47.994Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/30/8f/741c57202f4e4b6a7782f5a7ce7b18fd607696a154f5c9f2c716b207fa61/azure_ai_agents-1.1.0b4.tar.gz", hash = "sha256:126007543e3e9b9a4be017287e230e911fa126081f05b1c593e0d75702d01cd5", size = 331198, upload-time = "2025-07-11T19:55:35.776Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/2c/34/3f47d9cb320b672c8482b15b0431fc546aea5b00104e405042e75d2f1397/azure_ai_agents-1.1.0b2-py3-none-any.whl", hash = "sha256:4e48aba6ac2cdb4955adae5a5c94324b51005d2360ef5bc30a4d2ed86d7a9bde", size = 189945, upload-time = "2025-06-09T16:53:49.721Z" },
+ { url = "https://files.pythonhosted.org/packages/3f/40/7a1bd4b98c7c16b863782b07c9c3e98a1b531068a3b5462c935a468db5ce/azure_ai_agents-1.1.0b4-py3-none-any.whl", hash = "sha256:2256cdddd6176ac3855c9b1fb7174e156d1a2f7538cfb6bd80a743e2b205d775", size = 200368, upload-time = "2025-07-11T19:55:37.512Z" },
]
[[package]]
@@ -378,7 +378,7 @@ wheels = [
[[package]]
name = "azure-ai-projects"
-version = "1.0.0b11"
+version = "1.0.0b12"
source = { registry = "https://pypi.org/simple" }
dependencies = [
{ name = "azure-ai-agents", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
@@ -387,9 +387,9 @@ dependencies = [
{ name = "isodate", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
{ name = "typing-extensions", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
]
-sdist = { url = "https://files.pythonhosted.org/packages/01/4b/0a879eb66b5d9a08ab09292ff9a36a4e9c855b458d0e843b5e838fc6f6fd/azure_ai_projects-1.0.0b11.tar.gz", hash = "sha256:68a115c48cde7d5f9c29aee61c7fbf0b6de69aecbd1dc749b847a1e1348216b5", size = 133087, upload-time = "2025-05-16T00:33:32.286Z" }
+sdist = { url = "https://files.pythonhosted.org/packages/a5/57/9a89c1978ec9ce29a3be454b83b66885982261762d7a436cad73c47c9225/azure_ai_projects-1.0.0b12.tar.gz", hash = "sha256:1a3784e4be6af3b0fc76e9e4a64158a38f6679fe3a1f8b9c33f12bc8914ae36c", size = 144358, upload-time = "2025-06-27T04:12:48.334Z" }
wheels = [
- { url = "https://files.pythonhosted.org/packages/3e/2d/5502377ecc07677365a1e86be64d8cb9959eb6e9b605fcc28f1f68d3777a/azure_ai_projects-1.0.0b11-py3-none-any.whl", hash = "sha256:3572f2989627e896ecfebe2fa7326d5b940f920cc581e98809b244af7a38cbf0", size = 130983, upload-time = "2025-05-16T00:33:33.789Z" },
+ { url = "https://files.pythonhosted.org/packages/73/e4/50cd2c3bd5ab745e85a4a1bd591bf4343d6e3470580f1eadceed55fd57c0/azure_ai_projects-1.0.0b12-py3-none-any.whl", hash = "sha256:4e3d3ef275f7409ea8030e474626968848055d4b3717ff7ef03681da809c096f", size = 129783, upload-time = "2025-06-27T04:12:49.837Z" },
]
[[package]]
@@ -5669,14 +5669,14 @@ requires-dist = [
{ name = "aiortc", marker = "extra == 'realtime'", specifier = ">=1.9.0" },
{ name = "anthropic", marker = "extra == 'anthropic'", specifier = "~=0.32" },
{ name = "autogen-agentchat", marker = "extra == 'autogen'", specifier = ">=0.2,<0.4" },
- { name = "azure-ai-agents", specifier = ">=1.1.0b1" },
+ { name = "azure-ai-agents", specifier = ">=1.1.0b4" },
{ name = "azure-ai-inference", marker = "extra == 'azure'", specifier = ">=1.0.0b6" },
- { name = "azure-ai-projects", specifier = ">=1.0.0b11" },
+ { name = "azure-ai-projects", specifier = ">=1.0.0b12" },
{ name = "azure-core-tracing-opentelemetry", marker = "extra == 'azure'", specifier = ">=1.0.0b11" },
{ name = "azure-cosmos", marker = "extra == 'azure'", specifier = "~=4.7" },
{ name = "azure-identity", specifier = ">=1.13" },
{ name = "azure-search-documents", marker = "extra == 'azure'", specifier = ">=11.6.0b4" },
- { name = "boto3", marker = "extra == 'aws'", specifier = ">=1.36.4,<1.39.0" },
+ { name = "boto3", marker = "extra == 'aws'", specifier = ">=1.36.4,<1.40.0" },
{ name = "chromadb", marker = "extra == 'chroma'", specifier = ">=0.5,<1.1" },
{ name = "cloudevents", specifier = "~=1.0" },
{ name = "dapr", marker = "extra == 'dapr'", specifier = ">=1.14.0" },
@@ -5711,7 +5711,7 @@ requires-dist = [
{ name = "pydantic", specifier = ">=2.0,!=2.10.0,!=2.10.1,!=2.10.2,!=2.10.3,<2.12" },
{ name = "pydantic-settings", specifier = "~=2.0" },
{ name = "pymilvus", marker = "extra == 'milvus'", specifier = ">=2.3,<2.6" },
- { name = "pymongo", marker = "extra == 'mongo'", specifier = ">=4.8.0,<4.13" },
+ { name = "pymongo", marker = "extra == 'mongo'", specifier = ">=4.8.0,<4.14" },
{ name = "pyodbc", marker = "extra == 'sql'", specifier = ">=5.2" },
{ name = "qdrant-client", marker = "extra == 'qdrant'", specifier = "~=1.9" },
{ name = "redis", extras = ["hiredis"], marker = "extra == 'redis'", specifier = "~=6.0" },
pFad - Phonifier reborn
Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.
Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.
Alternative Proxies:
Alternative Proxy
pFad Proxy
pFad v3 Proxy
pFad v4 Proxy