Skip to content

Commit f8f90ff

Browse files
dmytrostrukCopilot
andcommitted
Python: [Feature Branch] Added more examples and fixes for Azure AI agent (microsoft#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>
1 parent 2b4704f commit f8f90ff

File tree

9 files changed

+178
-23
lines changed

9 files changed

+178
-23
lines changed

python/packages/azure-ai/agent_framework_azure_ai/_client.py

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -199,7 +199,7 @@ async def _get_agent_reference_or_create(
199199
# Try to use latest version if requested and agent exists
200200
if self.use_latest_version:
201201
try:
202-
existing_agent = await self.project_client.agents.retrieve(agent_name)
202+
existing_agent = await self.project_client.agents.get(agent_name)
203203
self.agent_name = existing_agent.name
204204
self.agent_version = existing_agent.versions.latest.version
205205
return {"name": self.agent_name, "version": self.agent_version, "type": "agent_reference"}
@@ -314,7 +314,6 @@ def get_mcp_tool(self, tool: HostedMCPTool) -> MutableMapping[str, Any]:
314314
if tool.allowed_tools:
315315
mcp["allowed_tools"] = list(tool.allowed_tools)
316316

317-
# TODO (dmytrostruk): Check "always" approval mode
318317
if tool.approval_mode:
319318
match tool.approval_mode:
320319
case str():

python/packages/azure-ai/pyproject.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -24,7 +24,7 @@ classifiers = [
2424
]
2525
dependencies = [
2626
"agent-framework-core",
27-
"azure-ai-projects >= 2.0.0a20251105001",
27+
"azure-ai-projects >= 2.0.0a20251110001",
2828
"azure-ai-agents == 1.2.0b5",
2929
"aiohttp",
3030
]

python/packages/azure-ai/tests/test_azure_ai_client.py

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -375,13 +375,13 @@ async def test_azure_ai_client_use_latest_version_existing_agent(
375375
mock_existing_agent = MagicMock()
376376
mock_existing_agent.name = "existing-agent"
377377
mock_existing_agent.versions.latest.version = "2.5"
378-
mock_project_client.agents.retrieve = AsyncMock(return_value=mock_existing_agent)
378+
mock_project_client.agents.get = AsyncMock(return_value=mock_existing_agent)
379379

380380
run_options = {"model": "test-model"}
381381
agent_ref = await client._get_agent_reference_or_create(run_options, None) # type: ignore
382382

383383
# Verify existing agent was retrieved and used
384-
mock_project_client.agents.retrieve.assert_called_once_with("existing-agent")
384+
mock_project_client.agents.get.assert_called_once_with("existing-agent")
385385
mock_project_client.agents.create_version.assert_not_called()
386386

387387
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(
398398
client = create_test_azure_ai_client(mock_project_client, agent_name="non-existing-agent", use_latest_version=True)
399399

400400
# Mock ResourceNotFoundError when trying to retrieve agent
401-
mock_project_client.agents.retrieve = AsyncMock(side_effect=ResourceNotFoundError("Agent not found"))
401+
mock_project_client.agents.get = AsyncMock(side_effect=ResourceNotFoundError("Agent not found"))
402402

403403
# Mock agent creation response for fallback
404404
mock_created_agent = MagicMock()
@@ -410,7 +410,7 @@ async def test_azure_ai_client_use_latest_version_agent_not_found(
410410
agent_ref = await client._get_agent_reference_or_create(run_options, None) # type: ignore
411411

412412
# Verify retrieval was attempted and creation was used as fallback
413-
mock_project_client.agents.retrieve.assert_called_once_with("non-existing-agent")
413+
mock_project_client.agents.get.assert_called_once_with("non-existing-agent")
414414
mock_project_client.agents.create_version.assert_called_once()
415415

416416
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(
434434
agent_ref = await client._get_agent_reference_or_create(run_options, None) # type: ignore
435435

436436
# Verify retrieval was not attempted and creation was used directly
437-
mock_project_client.agents.retrieve.assert_not_called()
437+
mock_project_client.agents.get.assert_not_called()
438438
mock_project_client.agents.create_version.assert_called_once()
439439

440440
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(
451451
agent_ref = await client._get_agent_reference_or_create({}, None) # type: ignore
452452

453453
# Verify neither retrieval nor creation was attempted since version is already set
454-
mock_project_client.agents.retrieve.assert_not_called()
454+
mock_project_client.agents.get.assert_not_called()
455455
mock_project_client.agents.create_version.assert_not_called()
456456

457457
assert agent_ref == {"name": "test-agent", "version": "3.0", "type": "agent_reference"}

python/samples/getting_started/agents/azure_ai/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,8 +10,8 @@ This folder contains examples demonstrating different ways to create and use age
1010
| [`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. |
1111
| [`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. |
1212
| [`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. |
13-
| [`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. |
1413
| [`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. |
14+
| [`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. |
1515
| [`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. |
1616
| [`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. |
1717
| [`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. |

python/samples/getting_started/agents/azure_ai/azure_ai_with_code_interpreter.py

Lines changed: 13 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,20 @@ async def main() -> None:
3535
isinstance(result.raw_representation, ChatResponse)
3636
and isinstance(result.raw_representation.raw_representation, OpenAIResponse)
3737
and len(result.raw_representation.raw_representation.output) > 0
38-
and isinstance(result.raw_representation.raw_representation.output[0], ResponseCodeInterpreterToolCall)
3938
):
40-
generated_code = result.raw_representation.raw_representation.output[0].code
41-
42-
print(f"Generated code:\n{generated_code}")
39+
# Find the first ResponseCodeInterpreterToolCall item
40+
code_interpreter_item = next(
41+
(
42+
item
43+
for item in result.raw_representation.raw_representation.output
44+
if isinstance(item, ResponseCodeInterpreterToolCall)
45+
),
46+
None,
47+
)
48+
49+
if code_interpreter_item is not None:
50+
generated_code = code_interpreter_item.code
51+
print(f"Generated code:\n{generated_code}")
4352

4453

4554
if __name__ == "__main__":
Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
# Copyright (c) Microsoft. All rights reserved.
2+
3+
import asyncio
4+
import os
5+
from pathlib import Path
6+
7+
from agent_framework import ChatAgent, HostedFileSearchTool, HostedVectorStoreContent
8+
from agent_framework.azure import AzureAIClient
9+
from azure.ai.agents.aio import AgentsClient
10+
from azure.ai.agents.models import FileInfo, VectorStore
11+
from azure.identity.aio import AzureCliCredential
12+
13+
"""
14+
The following sample demonstrates how to create a simple, Azure AI agent that
15+
uses a file search tool to answer user questions.
16+
"""
17+
18+
19+
# Simulate a conversation with the agent
20+
USER_INPUTS = [
21+
"Who is the youngest employee?",
22+
"Who works in sales?",
23+
"I have a customer request, who can help me?",
24+
]
25+
26+
27+
async def main() -> None:
28+
"""Main function demonstrating Azure AI agent with file search capabilities."""
29+
file: FileInfo | None = None
30+
vector_store: VectorStore | None = None
31+
32+
async with (
33+
AzureCliCredential() as credential,
34+
AgentsClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as agents_client,
35+
AzureAIClient(async_credential=credential) as client,
36+
):
37+
try:
38+
# 1. Upload file and create vector store
39+
pdf_file_path = Path(__file__).parent.parent / "resources" / "employees.pdf"
40+
print(f"Uploading file from: {pdf_file_path}")
41+
42+
file = await agents_client.files.upload_and_poll(file_path=str(pdf_file_path), purpose="assistants")
43+
print(f"Uploaded file, file ID: {file.id}")
44+
45+
vector_store = await agents_client.vector_stores.create_and_poll(file_ids=[file.id], name="my_vectorstore")
46+
print(f"Created vector store, vector store ID: {vector_store.id}")
47+
48+
# 2. Create file search tool with uploaded resources
49+
file_search_tool = HostedFileSearchTool(inputs=[HostedVectorStoreContent(vector_store_id=vector_store.id)])
50+
51+
# 3. Create an agent with file search capabilities
52+
# The tool_resources are automatically extracted from HostedFileSearchTool
53+
async with ChatAgent(
54+
chat_client=client,
55+
name="EmployeeSearchAgent",
56+
instructions=(
57+
"You are a helpful assistant that can search through uploaded employee files "
58+
"to answer questions about employees."
59+
),
60+
tools=file_search_tool,
61+
) as agent:
62+
# 4. Simulate conversation with the agent
63+
for user_input in USER_INPUTS:
64+
print(f"# User: '{user_input}'")
65+
response = await agent.run(user_input)
66+
print(f"# Agent: {response.text}")
67+
finally:
68+
# 5. Cleanup: Delete the vector store and file in case of earlier failure to prevent orphaned resources.
69+
if vector_store:
70+
await agents_client.vector_stores.delete(vector_store.id)
71+
if file:
72+
await agents_client.files.delete(file.id)
73+
74+
75+
if __name__ == "__main__":
76+
asyncio.run(main())

python/samples/getting_started/agents/azure_ai/azure_ai_with_hosted_mcp.py

Lines changed: 78 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,9 @@
11
# Copyright (c) Microsoft. All rights reserved.
22

33
import asyncio
4+
from typing import Any
45

5-
from agent_framework import HostedMCPTool
6+
from agent_framework import AgentProtocol, AgentThread, ChatMessage, HostedMCPTool
67
from agent_framework.azure import AzureAIClient
78
from azure.identity.aio import AzureCliCredential
89

@@ -13,33 +14,103 @@
1314
"""
1415

1516

16-
async def run_hosted_mcp() -> None:
17+
async def handle_approvals_without_thread(query: str, agent: "AgentProtocol"):
18+
"""When we don't have a thread, we need to ensure we return with the input, approval request and approval."""
19+
20+
result = await agent.run(query, store=False)
21+
while len(result.user_input_requests) > 0:
22+
new_inputs: list[Any] = [query]
23+
for user_input_needed in result.user_input_requests:
24+
print(
25+
f"User Input Request for function from {agent.name}: {user_input_needed.function_call.name}"
26+
f" with arguments: {user_input_needed.function_call.arguments}"
27+
)
28+
new_inputs.append(ChatMessage(role="assistant", contents=[user_input_needed]))
29+
user_approval = input("Approve function call? (y/n): ")
30+
new_inputs.append(
31+
ChatMessage(role="user", contents=[user_input_needed.create_response(user_approval.lower() == "y")])
32+
)
33+
34+
result = await agent.run(new_inputs, store=False)
35+
return result
36+
37+
38+
async def handle_approvals_with_thread(query: str, agent: "AgentProtocol", thread: "AgentThread"):
39+
"""Here we let the thread deal with the previous responses, and we just rerun with the approval."""
40+
41+
result = await agent.run(query, thread=thread)
42+
while len(result.user_input_requests) > 0:
43+
new_input: list[Any] = []
44+
for user_input_needed in result.user_input_requests:
45+
print(
46+
f"User Input Request for function from {agent.name}: {user_input_needed.function_call.name}"
47+
f" with arguments: {user_input_needed.function_call.arguments}"
48+
)
49+
user_approval = input("Approve function call? (y/n): ")
50+
new_input.append(
51+
ChatMessage(
52+
role="user",
53+
contents=[user_input_needed.create_response(user_approval.lower() == "y")],
54+
)
55+
)
56+
result = await agent.run(new_input, thread=thread)
57+
return result
58+
59+
60+
async def run_hosted_mcp_without_approval() -> None:
61+
"""Example showing MCP Tools without approval."""
1762
# Since no Agent ID is provided, the agent will be automatically created.
1863
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
1964
# authentication option.
2065
async with (
2166
AzureCliCredential() as credential,
2267
AzureAIClient(async_credential=credential).create_agent(
23-
name="MyDocsAgent",
68+
name="MyLearnDocsAgent",
2469
instructions="You are a helpful assistant that can help with Microsoft documentation questions.",
2570
tools=HostedMCPTool(
2671
name="Microsoft Learn MCP",
2772
url="https://learn.microsoft.com/api/mcp",
28-
# "always_require" mode is not supported yet
2973
approval_mode="never_require",
3074
),
3175
) as agent,
3276
):
3377
query = "How to create an Azure storage account using az cli?"
3478
print(f"User: {query}")
35-
result = await agent.run(query)
79+
result = await handle_approvals_without_thread(query, agent)
80+
print(f"{agent.name}: {result}\n")
81+
82+
83+
async def run_hosted_mcp_with_approval_and_thread() -> None:
84+
"""Example showing MCP Tools with approvals using a thread."""
85+
print("=== MCP with approvals and with thread ===")
86+
87+
# Since no Agent ID is provided, the agent will be automatically created.
88+
# For authentication, run `az login` command in terminal or replace AzureCliCredential with preferred
89+
# authentication option.
90+
async with (
91+
AzureCliCredential() as credential,
92+
AzureAIClient(async_credential=credential).create_agent(
93+
name="MyApiSpecsAgent",
94+
instructions="You are a helpful agent that can use MCP tools to assist users.",
95+
tools=HostedMCPTool(
96+
name="api-specs",
97+
url="https://gitmcp.io/Azure/azure-rest-api-specs",
98+
approval_mode="always_require",
99+
),
100+
) as agent,
101+
):
102+
thread = agent.get_new_thread()
103+
query = "Please summarize the Azure REST API specifications Readme"
104+
print(f"User: {query}")
105+
result = await handle_approvals_with_thread(query, agent, thread)
36106
print(f"{agent.name}: {result}\n")
37107

38108

39109
async def main() -> None:
40-
print("=== Azure AI Agent with Hosted Mcp Tools Example ===\n")
110+
print("=== Azure AI Agent with Hosted MCP Tools Example ===\n")
41111

42-
await run_hosted_mcp()
112+
await run_hosted_mcp_without_approval()
113+
await run_hosted_mcp_with_approval_and_thread()
43114

44115

45116
if __name__ == "__main__":

python/samples/getting_started/agents/azure_ai_agent/azure_ai_with_file_search.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ async def main() -> None:
7171
# Ignore cleanup errors to avoid masking issues
7272
pass
7373
finally:
74-
# 6. Cleanup: Delete the vector store and file in case of eariler failure to prevent orphaned resources.
74+
# 6. Cleanup: Delete the vector store and file in case of earlier failure to prevent orphaned resources.
7575

7676
# Refreshing the client is required since chat agent closes it
7777
client = AzureAIAgentClient(async_credential=AzureCliCredential())

python/uv.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

0 commit comments

Comments
 (0)