mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: [Feature Branch] Added more examples and fixes for Azure AI agent (#2077)
* Updated azure-ai-projects package version * Added an example of hosted MCP with approval required * Updated code interpreter example * Added file search example * Update python/samples/getting_started/agents/azure_ai/azure_ai_with_file_search.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Update python/samples/getting_started/agents/azure_ai/azure_ai_with_file_search.py Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com> * Small fix --------- Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
This commit is contained in:
committed by
GitHub
Unverified
parent
c3ef6475a2
commit
26e73756c7
@@ -199,7 +199,7 @@ class AzureAIClient(OpenAIBaseResponsesClient):
|
||||
# Try to use latest version if requested and agent exists
|
||||
if self.use_latest_version:
|
||||
try:
|
||||
existing_agent = await self.project_client.agents.retrieve(agent_name)
|
||||
existing_agent = await self.project_client.agents.get(agent_name)
|
||||
self.agent_name = existing_agent.name
|
||||
self.agent_version = existing_agent.versions.latest.version
|
||||
return {"name": self.agent_name, "version": self.agent_version, "type": "agent_reference"}
|
||||
@@ -314,7 +314,6 @@ class AzureAIClient(OpenAIBaseResponsesClient):
|
||||
if tool.allowed_tools:
|
||||
mcp["allowed_tools"] = list(tool.allowed_tools)
|
||||
|
||||
# TODO (dmytrostruk): Check "always" approval mode
|
||||
if tool.approval_mode:
|
||||
match tool.approval_mode:
|
||||
case str():
|
||||
|
||||
@@ -24,7 +24,7 @@ classifiers = [
|
||||
]
|
||||
dependencies = [
|
||||
"agent-framework-core",
|
||||
"azure-ai-projects >= 2.0.0a20251105001",
|
||||
"azure-ai-projects >= 2.0.0a20251110001",
|
||||
"azure-ai-agents == 1.2.0b5",
|
||||
"aiohttp",
|
||||
]
|
||||
|
||||
@@ -375,13 +375,13 @@ async def test_azure_ai_client_use_latest_version_existing_agent(
|
||||
mock_existing_agent = MagicMock()
|
||||
mock_existing_agent.name = "existing-agent"
|
||||
mock_existing_agent.versions.latest.version = "2.5"
|
||||
mock_project_client.agents.retrieve = AsyncMock(return_value=mock_existing_agent)
|
||||
mock_project_client.agents.get = AsyncMock(return_value=mock_existing_agent)
|
||||
|
||||
run_options = {"model": "test-model"}
|
||||
agent_ref = await client._get_agent_reference_or_create(run_options, None) # type: ignore
|
||||
|
||||
# Verify existing agent was retrieved and used
|
||||
mock_project_client.agents.retrieve.assert_called_once_with("existing-agent")
|
||||
mock_project_client.agents.get.assert_called_once_with("existing-agent")
|
||||
mock_project_client.agents.create_version.assert_not_called()
|
||||
|
||||
assert agent_ref == {"name": "existing-agent", "version": "2.5", "type": "agent_reference"}
|
||||
@@ -398,7 +398,7 @@ async def test_azure_ai_client_use_latest_version_agent_not_found(
|
||||
client = create_test_azure_ai_client(mock_project_client, agent_name="non-existing-agent", use_latest_version=True)
|
||||
|
||||
# Mock ResourceNotFoundError when trying to retrieve agent
|
||||
mock_project_client.agents.retrieve = AsyncMock(side_effect=ResourceNotFoundError("Agent not found"))
|
||||
mock_project_client.agents.get = AsyncMock(side_effect=ResourceNotFoundError("Agent not found"))
|
||||
|
||||
# Mock agent creation response for fallback
|
||||
mock_created_agent = MagicMock()
|
||||
@@ -410,7 +410,7 @@ async def test_azure_ai_client_use_latest_version_agent_not_found(
|
||||
agent_ref = await client._get_agent_reference_or_create(run_options, None) # type: ignore
|
||||
|
||||
# Verify retrieval was attempted and creation was used as fallback
|
||||
mock_project_client.agents.retrieve.assert_called_once_with("non-existing-agent")
|
||||
mock_project_client.agents.get.assert_called_once_with("non-existing-agent")
|
||||
mock_project_client.agents.create_version.assert_called_once()
|
||||
|
||||
assert agent_ref == {"name": "non-existing-agent", "version": "1.0", "type": "agent_reference"}
|
||||
@@ -434,7 +434,7 @@ async def test_azure_ai_client_use_latest_version_false(
|
||||
agent_ref = await client._get_agent_reference_or_create(run_options, None) # type: ignore
|
||||
|
||||
# Verify retrieval was not attempted and creation was used directly
|
||||
mock_project_client.agents.retrieve.assert_not_called()
|
||||
mock_project_client.agents.get.assert_not_called()
|
||||
mock_project_client.agents.create_version.assert_called_once()
|
||||
|
||||
assert agent_ref == {"name": "test-agent", "version": "1.0", "type": "agent_reference"}
|
||||
@@ -451,7 +451,7 @@ async def test_azure_ai_client_use_latest_version_with_existing_agent_version(
|
||||
agent_ref = await client._get_agent_reference_or_create({}, None) # type: ignore
|
||||
|
||||
# Verify neither retrieval nor creation was attempted since version is already set
|
||||
mock_project_client.agents.retrieve.assert_not_called()
|
||||
mock_project_client.agents.get.assert_not_called()
|
||||
mock_project_client.agents.create_version.assert_not_called()
|
||||
|
||||
assert agent_ref == {"name": "test-agent", "version": "3.0", "type": "agent_reference"}
|
||||
|
||||
@@ -10,8 +10,8 @@ This folder contains examples demonstrating different ways to create and use age
|
||||
| [`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 using the `use_latest_version=True` parameter. |
|
||||
| [`azure_ai_with_code_interpreter.py`](azure_ai_with_code_interpreter.py) | Shows how to use the `HostedCodeInterpreterTool` with Azure AI agents to write and execute Python code for mathematical problem solving and data analysis. |
|
||||
| [`azure_ai_with_existing_agent.py`](azure_ai_with_existing_agent.py) | Shows how to work with a pre-existing agent by providing the agent name and version to the Azure AI client. Demonstrates agent reuse patterns for production scenarios. |
|
||||
| [`azure_ai_with_existing_conversation.py`](azure_ai_with_existing_conversation.py) | Shows how to work with a pre-existing conversation by providing the conversation ID to continue existing chat sessions. |
|
||||
| [`azure_ai_with_explicit_settings.py`](azure_ai_with_explicit_settings.py) | Shows how to create an agent with explicitly configured `AzureAIClient` settings, including project endpoint, model deployment, and credentials rather than relying on environment variable defaults. |
|
||||
| [`azure_ai_with_file_search.py`](azure_ai_with_file_search.py) | Shows how to use the `HostedFileSearchTool` with Azure AI agents to upload files, create vector stores, and enable agents to search through uploaded documents to answer user questions. |
|
||||
| [`azure_ai_with_hosted_mcp.py`](azure_ai_with_hosted_mcp.py) | Shows how to integrate hosted Model Context Protocol (MCP) tools with Azure AI Agent. |
|
||||
| [`azure_ai_with_response_format.py`](azure_ai_with_response_format.py) | Shows how to use structured outputs (response format) with Azure AI agents using Pydantic models to enforce specific response schemas. |
|
||||
| [`azure_ai_with_thread.py`](azure_ai_with_thread.py) | Demonstrates thread management with Azure AI agents, including automatic thread creation for stateless conversations and explicit thread management for maintaining conversation context across multiple interactions. |
|
||||
|
||||
@@ -35,11 +35,20 @@ async def main() -> None:
|
||||
isinstance(result.raw_representation, ChatResponse)
|
||||
and isinstance(result.raw_representation.raw_representation, OpenAIResponse)
|
||||
and len(result.raw_representation.raw_representation.output) > 0
|
||||
and isinstance(result.raw_representation.raw_representation.output[0], ResponseCodeInterpreterToolCall)
|
||||
):
|
||||
generated_code = result.raw_representation.raw_representation.output[0].code
|
||||
# Find the first ResponseCodeInterpreterToolCall item
|
||||
code_interpreter_item = next(
|
||||
(
|
||||
item
|
||||
for item in result.raw_representation.raw_representation.output
|
||||
if isinstance(item, ResponseCodeInterpreterToolCall)
|
||||
),
|
||||
None,
|
||||
)
|
||||
|
||||
print(f"Generated code:\n{generated_code}")
|
||||
if code_interpreter_item is not None:
|
||||
generated_code = code_interpreter_item.code
|
||||
print(f"Generated code:\n{generated_code}")
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -0,0 +1,76 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
import os
|
||||
from pathlib import Path
|
||||
|
||||
from agent_framework import ChatAgent, HostedFileSearchTool, HostedVectorStoreContent
|
||||
from agent_framework.azure import AzureAIClient
|
||||
from azure.ai.agents.aio import AgentsClient
|
||||
from azure.ai.agents.models import FileInfo, VectorStore
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
"""
|
||||
The following sample demonstrates how to create a simple, Azure AI agent that
|
||||
uses a file search tool to answer user questions.
|
||||
"""
|
||||
|
||||
|
||||
# Simulate a conversation with the agent
|
||||
USER_INPUTS = [
|
||||
"Who is the youngest employee?",
|
||||
"Who works in sales?",
|
||||
"I have a customer request, who can help me?",
|
||||
]
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
"""Main function demonstrating Azure AI agent with file search capabilities."""
|
||||
file: FileInfo | None = None
|
||||
vector_store: VectorStore | None = None
|
||||
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
AgentsClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as agents_client,
|
||||
AzureAIClient(async_credential=credential) as client,
|
||||
):
|
||||
try:
|
||||
# 1. Upload file and create vector store
|
||||
pdf_file_path = Path(__file__).parent.parent / "resources" / "employees.pdf"
|
||||
print(f"Uploading file from: {pdf_file_path}")
|
||||
|
||||
file = await agents_client.files.upload_and_poll(file_path=str(pdf_file_path), purpose="assistants")
|
||||
print(f"Uploaded file, file ID: {file.id}")
|
||||
|
||||
vector_store = await agents_client.vector_stores.create_and_poll(file_ids=[file.id], name="my_vectorstore")
|
||||
print(f"Created vector store, vector store ID: {vector_store.id}")
|
||||
|
||||
# 2. Create file search tool with uploaded resources
|
||||
file_search_tool = HostedFileSearchTool(inputs=[HostedVectorStoreContent(vector_store_id=vector_store.id)])
|
||||
|
||||
# 3. Create an agent with file search capabilities
|
||||
# The tool_resources are automatically extracted from HostedFileSearchTool
|
||||
async with ChatAgent(
|
||||
chat_client=client,
|
||||
name="EmployeeSearchAgent",
|
||||
instructions=(
|
||||
"You are a helpful assistant that can search through uploaded employee files "
|
||||
"to answer questions about employees."
|
||||
),
|
||||
tools=file_search_tool,
|
||||
) as agent:
|
||||
# 4. Simulate conversation with the agent
|
||||
for user_input in USER_INPUTS:
|
||||
print(f"# User: '{user_input}'")
|
||||
response = await agent.run(user_input)
|
||||
print(f"# Agent: {response.text}")
|
||||
finally:
|
||||
# 5. Cleanup: Delete the vector store and file in case of earlier failure to prevent orphaned resources.
|
||||
if vector_store:
|
||||
await agents_client.vector_stores.delete(vector_store.id)
|
||||
if file:
|
||||
await agents_client.files.delete(file.id)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
@@ -1,8 +1,9 @@
|
||||
# Copyright (c) Microsoft. All rights reserved.
|
||||
|
||||
import asyncio
|
||||
from typing import Any
|
||||
|
||||
from agent_framework import HostedMCPTool
|
||||
from agent_framework import AgentProtocol, AgentThread, ChatMessage, HostedMCPTool
|
||||
from agent_framework.azure import AzureAIClient
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
@@ -13,33 +14,103 @@ This sample demonstrates integrating hosted Model Context Protocol (MCP) tools w
|
||||
"""
|
||||
|
||||
|
||||
async def run_hosted_mcp() -> None:
|
||||
async def handle_approvals_without_thread(query: str, agent: "AgentProtocol"):
|
||||
"""When we don't have a thread, we need to ensure we return with the input, approval request and approval."""
|
||||
|
||||
result = await agent.run(query, store=False)
|
||||
while len(result.user_input_requests) > 0:
|
||||
new_inputs: list[Any] = [query]
|
||||
for user_input_needed in result.user_input_requests:
|
||||
print(
|
||||
f"User Input Request for function from {agent.name}: {user_input_needed.function_call.name}"
|
||||
f" with arguments: {user_input_needed.function_call.arguments}"
|
||||
)
|
||||
new_inputs.append(ChatMessage(role="assistant", contents=[user_input_needed]))
|
||||
user_approval = input("Approve function call? (y/n): ")
|
||||
new_inputs.append(
|
||||
ChatMessage(role="user", contents=[user_input_needed.create_response(user_approval.lower() == "y")])
|
||||
)
|
||||
|
||||
result = await agent.run(new_inputs, store=False)
|
||||
return result
|
||||
|
||||
|
||||
async def handle_approvals_with_thread(query: str, agent: "AgentProtocol", thread: "AgentThread"):
|
||||
"""Here we let the thread deal with the previous responses, and we just rerun with the approval."""
|
||||
|
||||
result = await agent.run(query, thread=thread)
|
||||
while len(result.user_input_requests) > 0:
|
||||
new_input: list[Any] = []
|
||||
for user_input_needed in result.user_input_requests:
|
||||
print(
|
||||
f"User Input Request for function from {agent.name}: {user_input_needed.function_call.name}"
|
||||
f" with arguments: {user_input_needed.function_call.arguments}"
|
||||
)
|
||||
user_approval = input("Approve function call? (y/n): ")
|
||||
new_input.append(
|
||||
ChatMessage(
|
||||
role="user",
|
||||
contents=[user_input_needed.create_response(user_approval.lower() == "y")],
|
||||
)
|
||||
)
|
||||
result = await agent.run(new_input, thread=thread)
|
||||
return result
|
||||
|
||||
|
||||
async def run_hosted_mcp_without_approval() -> None:
|
||||
"""Example showing MCP Tools without approval."""
|
||||
# Since no Agent ID is provided, the agent will be automatically created.
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
AzureAIClient(async_credential=credential).create_agent(
|
||||
name="MyDocsAgent",
|
||||
name="MyLearnDocsAgent",
|
||||
instructions="You are a helpful assistant that can help with Microsoft documentation questions.",
|
||||
tools=HostedMCPTool(
|
||||
name="Microsoft Learn MCP",
|
||||
url="https://learn.microsoft.com/api/mcp",
|
||||
# "always_require" mode is not supported yet
|
||||
approval_mode="never_require",
|
||||
),
|
||||
) as agent,
|
||||
):
|
||||
query = "How to create an Azure storage account using az cli?"
|
||||
print(f"User: {query}")
|
||||
result = await agent.run(query)
|
||||
result = await handle_approvals_without_thread(query, agent)
|
||||
print(f"{agent.name}: {result}\n")
|
||||
|
||||
|
||||
async def run_hosted_mcp_with_approval_and_thread() -> None:
|
||||
"""Example showing MCP Tools with approvals using a thread."""
|
||||
print("=== MCP with approvals and with thread ===")
|
||||
|
||||
# Since no Agent ID is provided, the agent will be automatically created.
|
||||
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
|
||||
# authentication option.
|
||||
async with (
|
||||
AzureCliCredential() as credential,
|
||||
AzureAIClient(async_credential=credential).create_agent(
|
||||
name="MyApiSpecsAgent",
|
||||
instructions="You are a helpful agent that can use MCP tools to assist users.",
|
||||
tools=HostedMCPTool(
|
||||
name="api-specs",
|
||||
url="https://gitmcp.io/Azure/azure-rest-api-specs",
|
||||
approval_mode="always_require",
|
||||
),
|
||||
) as agent,
|
||||
):
|
||||
thread = agent.get_new_thread()
|
||||
query = "Please summarize the Azure REST API specifications Readme"
|
||||
print(f"User: {query}")
|
||||
result = await handle_approvals_with_thread(query, agent, thread)
|
||||
print(f"{agent.name}: {result}\n")
|
||||
|
||||
|
||||
async def main() -> None:
|
||||
print("=== Azure AI Agent with Hosted Mcp Tools Example ===\n")
|
||||
print("=== Azure AI Agent with Hosted MCP Tools Example ===\n")
|
||||
|
||||
await run_hosted_mcp()
|
||||
await run_hosted_mcp_without_approval()
|
||||
await run_hosted_mcp_with_approval_and_thread()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
||||
@@ -71,7 +71,7 @@ async def main() -> None:
|
||||
# Ignore cleanup errors to avoid masking issues
|
||||
pass
|
||||
finally:
|
||||
# 6. Cleanup: Delete the vector store and file in case of eariler failure to prevent orphaned resources.
|
||||
# 6. Cleanup: Delete the vector store and file in case of earlier failure to prevent orphaned resources.
|
||||
|
||||
# Refreshing the client is required since chat agent closes it
|
||||
client = AzureAIAgentClient(async_credential=AzureCliCredential())
|
||||
|
||||
Generated
+4
-4
@@ -252,7 +252,7 @@ requires-dist = [
|
||||
{ name = "agent-framework-core", editable = "packages/core" },
|
||||
{ name = "aiohttp" },
|
||||
{ name = "azure-ai-agents", specifier = "==1.2.0b5" },
|
||||
{ name = "azure-ai-projects", specifier = ">=2.0.0a20251105001", index = "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple" },
|
||||
{ name = "azure-ai-projects", specifier = ">=2.0.0a20251110001", index = "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
@@ -883,7 +883,7 @@ wheels = [
|
||||
|
||||
[[package]]
|
||||
name = "azure-ai-projects"
|
||||
version = "2.0.0a20251105001"
|
||||
version = "2.0.0a20251110001"
|
||||
source = { registry = "https://pkgs.dev.azure.com/azure-sdk/public/_packaging/azure-sdk-for-python/pypi/simple" }
|
||||
dependencies = [
|
||||
{ name = "azure-core", marker = "sys_platform == 'darwin' or sys_platform == 'linux' or sys_platform == 'win32'" },
|
||||
@@ -891,9 +891,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://pkgs.dev.azure.com/azure-sdk/29ec6040-b234-4e31-b139-33dc4287b756/_packaging/3572dbf9-b5ef-433b-9137-fc4d7768e7cc/pypi/download/azure-ai-projects/2a20251105001/azure_ai_projects-2.0.0a20251105001.tar.gz", hash = "sha256:045740a4f9154b2aae5ae76a074a243ea29b26086e00948539b6725249535430" }
|
||||
sdist = { url = "https://pkgs.dev.azure.com/azure-sdk/29ec6040-b234-4e31-b139-33dc4287b756/_packaging/3572dbf9-b5ef-433b-9137-fc4d7768e7cc/pypi/download/azure-ai-projects/2a20251110001/azure_ai_projects-2.0.0a20251110001.tar.gz", hash = "sha256:d241d41507517ec9f438830f116e67539d7e762a9b9f9cff40f0245f1ff13f5b" }
|
||||
wheels = [
|
||||
{ url = "https://pkgs.dev.azure.com/azure-sdk/29ec6040-b234-4e31-b139-33dc4287b756/_packaging/3572dbf9-b5ef-433b-9137-fc4d7768e7cc/pypi/download/azure-ai-projects/2a20251105001/azure_ai_projects-2.0.0a20251105001-py3-none-any.whl", hash = "sha256:4fd25f169137533723ea41d40d08a226f10df63e091829a8d4a30e46f968574b" },
|
||||
{ url = "https://pkgs.dev.azure.com/azure-sdk/29ec6040-b234-4e31-b139-33dc4287b756/_packaging/3572dbf9-b5ef-433b-9137-fc4d7768e7cc/pypi/download/azure-ai-projects/2a20251110001/azure_ai_projects-2.0.0a20251110001-py3-none-any.whl", hash = "sha256:1d5e16affceb7beecfffeb2e77044b5358e8b6d3a8dfbe0a3caa34e0eaafd5a9" },
|
||||
]
|
||||
|
||||
[[package]]
|
||||
|
||||
Reference in New Issue
Block a user