mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: Fix Azure AI sample errors (#4021)
* Python: Fix Azure AI sample errors - azure_ai_with_application_endpoint: Add missing name to Agent constructor - azure_ai_with_file_search: Fix resource path (parents[2] -> parents[3]) - azure_ai_with_openapi: Fix resource path (parents[2] -> parents[3]) - azure_ai_with_session: Use get_agent/get_session to reuse existing agent version and preserve conversation context across agent instances * Python: Fix resource paths in azure_ai_agent samples - azure_ai_with_file_search: Fix path to employees.pdf (parent.parent -> parents[3]/shared) - azure_ai_with_openapi_tools: Fix path to weather.json/countries.json (parents[2] -> parents[3]) * fix V1 SDK hosted tools (FileSearchTool, etc.) silently dropped during agent creation * fix: V2 file search sample uses correct SDK (AIProjectClient instead of AgentsClient) The azure_ai/azure_ai_with_file_search.py sample incorrectly used the V1 AgentsClient for file/vector store operations. Replaced with V2 pattern: AIProjectClient + get_openai_client() for file upload and vector store management, matching the official Azure AI Projects SDK samples. * fix: use context manager for file open in V2 file search sample
This commit is contained in:
committed by
GitHub
Unverified
parent
2dd731f90f
commit
28e3fc308b
@@ -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 (
|
||||
@@ -25,7 +25,7 @@ from azure.core.credentials_async import AsyncTokenCredential
|
||||
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
|
||||
@@ -238,13 +238,9 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
# 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] = {}
|
||||
@@ -429,16 +425,10 @@ class AzureAIAgentsProvider(Generic[OptionsCoT]):
|
||||
"""
|
||||
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:
|
||||
|
||||
@@ -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"
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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,
|
||||
),
|
||||
|
||||
@@ -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
|
||||
|
||||
"""
|
||||
@@ -25,27 +25,30 @@ USER_INPUTS = [
|
||||
|
||||
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
|
||||
@@ -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__":
|
||||
|
||||
@@ -20,7 +20,7 @@ Prerequisites:
|
||||
|
||||
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)
|
||||
|
||||
@@ -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}")
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -23,7 +23,7 @@ USER_INPUTS = [
|
||||
|
||||
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)
|
||||
|
||||
Reference in New Issue
Block a user