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
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,7 @@
from __future__ import annotations

import sys
from collections.abc import Callable, MutableMapping, Sequence
from collections.abc import Callable, Sequence
from typing import Any, Generic, cast

from agent_framework import (
Expand All @@ -25,7 +25,7 @@
from pydantic import BaseModel

from ._chat_client import AzureAIAgentClient, AzureAIAgentOptions
from ._shared import AzureAISettings, from_azure_ai_agent_tools, to_azure_ai_agent_tools
from ._shared import AzureAISettings, to_azure_ai_agent_tools

if sys.version_info >= (3, 13):
from typing import Self, TypeVar # type: ignore # pragma: no cover
Expand Down Expand Up @@ -238,13 +238,9 @@ async def create_agent(
# Local MCP tools (MCPTool) are handled by Agent at runtime, not stored on the Azure agent
normalized_tools = normalize_tools(tools)
if normalized_tools:
# Only convert non-MCP tools to Azure AI format
non_mcp_tools: list[FunctionTool | MutableMapping[str, Any]] = []
for normalized_tool in normalized_tools:
if isinstance(normalized_tool, MCPTool):
continue
if isinstance(normalized_tool, (FunctionTool, MutableMapping)):
non_mcp_tools.append(normalized_tool)
# Collect all non-MCP tools for Azure AI agent creation.
# to_azure_ai_agent_tools handles FunctionTool, SDK Tool types (FileSearchTool, etc.), and dicts.
non_mcp_tools: list[Any] = [t for t in normalized_tools if not isinstance(t, MCPTool)]
if non_mcp_tools:
# Pass run_options to capture tool_resources (e.g., for file search vector stores)
run_options: dict[str, Any] = {}
Expand Down Expand Up @@ -429,16 +425,10 @@ def _merge_tools(
"""
merged: list[ToolTypes] = []

# Convert hosted tools from agent definition
hosted_tools = from_azure_ai_agent_tools(agent_tools)
for hosted_tool in hosted_tools:
# Skip function tool dicts - they don't have implementations
# Skip OpenAPI tool dicts - they're defined on the agent, not needed at runtime
if isinstance(hosted_tool, dict):
tool_type = hosted_tool.get("type")
if tool_type == "function" or tool_type == "openapi":
continue
merged.append(hosted_tool)
# Hosted tools (file_search, code_interpreter, bing_grounding, openapi, etc.)
# are already defined on the server agent and will be read back by the client
# at run time via agent_definition.tools. We skip them here to avoid sending
# them again at request time (which causes API errors like unknown vector_store_ids).

# Add user-provided function tools and MCP tools
if provided_tools:
Expand Down
7 changes: 4 additions & 3 deletions python/packages/azure-ai/tests/test_agent_provider.py
Original file line number Diff line number Diff line change
Expand Up @@ -437,7 +437,7 @@ def test_as_agent_with_hosted_tools(
azure_ai_unit_test_env: dict[str, str],
mock_agents_client: MagicMock,
) -> None:
"""Test as_agent handles hosted tools correctly."""
"""Test as_agent excludes hosted tools from local tools (they stay on the server agent)."""
mock_code_interpreter = MagicMock()
mock_code_interpreter.type = "code_interpreter"

Expand All @@ -456,9 +456,10 @@ def test_as_agent_with_hosted_tools(
agent = provider.as_agent(mock_agent)

assert isinstance(agent, Agent)
# Should have code_interpreter dict tool in the default_options tools
# Hosted tools (code_interpreter, file_search, etc.) are already on the server agent
# and should NOT be in local tools to avoid re-sending them at run time
tools = agent.default_options.get("tools") or []
assert any(isinstance(t, dict) and t.get("type") == "code_interpreter" for t in tools)
assert not any(isinstance(t, dict) and t.get("type") == "code_interpreter" for t in tools)


def test_as_agent_with_dict_function_tools_validates(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ async def main() -> None:
# /api/projects/<project-name>/applications/<application-name>/protocols
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client,
Agent(
name="ApplicationAgent",
client=AzureAIClient(
project_client=project_client,
),
Expand Down
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
# Copyright (c) Microsoft. All rights reserved.

import asyncio
import contextlib
import os
from pathlib import Path

from agent_framework.azure import AzureAIClient, AzureAIProjectAgentProvider
from azure.ai.agents.aio import AgentsClient
from azure.ai.agents.models import FileInfo, VectorStore
from azure.ai.projects.aio import AIProjectClient
from azure.identity.aio import AzureCliCredential

"""
Expand All @@ -25,27 +25,30 @@

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,
AzureAIProjectAgentProvider(credential=credential) as provider,
AIProjectClient(endpoint=os.environ["AZURE_AI_PROJECT_ENDPOINT"], credential=credential) as project_client,
AzureAIProjectAgentProvider(project_client=project_client) as provider,
):
openai_client = project_client.get_openai_client()

try:
# 1. Upload file and create vector store
pdf_file_path = Path(__file__).parents[2] / "shared" / "resources" / "employees.pdf"
# 1. Upload file and create vector store via OpenAI client
pdf_file_path = Path(__file__).parents[3] / "shared" / "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")
vector_store = await openai_client.vector_stores.create(name="my_vectorstore")
print(f"Created vector store, vector store ID: {vector_store.id}")

# 2. Create a client to access hosted tool factory methods
client = AzureAIClient(credential=credential)
with open(pdf_file_path, "rb") as f:
file = await openai_client.vector_stores.files.upload_and_poll(
vector_store_id=vector_store.id,
file=f,
)
print(f"Uploaded file, file ID: {file.id}")

# 2. Create a file search tool
client = AzureAIClient(project_client=project_client)
file_search_tool = client.get_file_search_tool(vector_store_ids=[vector_store.id])

# 3. Create an agent with file search capabilities using the provider
Expand All @@ -64,11 +67,9 @@ async def main() -> None:
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)
# 5. Cleanup: Delete the vector store (also deletes associated files)
with contextlib.suppress(Exception):
await openai_client.vector_stores.delete(vector_store.id)


if __name__ == "__main__":
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@

async def main() -> None:
# Load the OpenAPI specification
resources_path = Path(__file__).parents[2] / "shared" / "resources" / "countries.json"
resources_path = Path(__file__).parents[3] / "shared" / "resources" / "countries.json"

with open(resources_path) as f:
openapi_countries = json.load(f)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -133,15 +133,14 @@ async def example_with_existing_session_id() -> None:
if existing_session_id:
print("\n--- Continuing with the same session ID in a new agent instance ---")

# Create a new agent instance from the same provider
second_agent = await provider.create_agent(
# Retrieve the same agent (reuses existing agent version on the service)
second_agent = await provider.get_agent(
name="BasicWeatherAgent",
instructions="You are a helpful weather agent.",
tools=get_weather,
)

# Create a session with the existing ID
session = second_agent.create_session(service_session_id=existing_session_id)
# Attach the existing service session ID so conversation context is preserved
session = second_agent.get_session(service_session_id=existing_session_id)

second_query = "What was the last city I asked about?"
print(f"User: {second_query}")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ async def main() -> None:
):
try:
# 1. Upload file and create vector store
pdf_file_path = Path(__file__).parent.parent / "resources" / "employees.pdf"
pdf_file_path = Path(__file__).parents[3] / "shared" / "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")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@

def load_openapi_specs() -> tuple[dict[str, Any], dict[str, Any]]:
"""Load OpenAPI specification files."""
resources_path = Path(__file__).parents[2] / "shared" / "resources"
resources_path = Path(__file__).parents[3] / "shared" / "resources"

with open(resources_path / "weather.json") as weather_file:
weather_spec = json.load(weather_file)
Expand Down