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
3 changes: 1 addition & 2 deletions python/packages/azure-ai/agent_framework_azure_ai/_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -199,7 +199,7 @@ async def _get_agent_reference_or_create(
# 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"}
Expand Down Expand Up @@ -314,7 +314,6 @@ def get_mcp_tool(self, tool: HostedMCPTool) -> MutableMapping[str, Any]:
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():
Expand Down
2 changes: 1 addition & 1 deletion python/packages/azure-ai/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -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",
]
Expand Down
12 changes: 6 additions & 6 deletions python/packages/azure-ai/tests/test_azure_ai_client.py
Original file line number Diff line number Diff line change
Expand Up @@ -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"}
Expand All @@ -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()
Expand All @@ -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"}
Expand All @@ -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"}
Expand All @@ -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"}
Expand Down
2 changes: 1 addition & 1 deletion python/samples/getting_started/agents/azure_ai/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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. |
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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

print(f"Generated code:\n{generated_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,
)

if code_interpreter_item is not None:
generated_code = code_interpreter_item.code
print(f"Generated code:\n{generated_code}")


if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
@@ -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())
Original file line number Diff line number Diff line change
@@ -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

Expand All @@ -13,33 +14,103 @@
"""


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__":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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())
Expand Down
8 changes: 4 additions & 4 deletions python/uv.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.