mirror of
https://github.com/microsoft/agent-framework.git
synced 2026-06-16 21:04:09 +08:00
Python: [BREAKING] changed AIFunction to FunctionTool and @ai_function to @tool (#3413)
* changed AIFunction to FunctionTool and @ai_function to @tool * test and mypy fixes * mypy fix * switch function tool to always_require * fix noop * fix github copilot imports * test fixes * fix ollama test * fixes for tests * fix tests * reverted change to always_require and extended timeout * fix test
This commit is contained in:
committed by
GitHub
Unverified
parent
15b43f2abe
commit
a7d924a7d2
@@ -6,9 +6,9 @@ from typing import TYPE_CHECKING, Any, Generic, TypedDict, cast
|
||||
|
||||
from agent_framework import (
|
||||
AGENT_FRAMEWORK_USER_AGENT,
|
||||
AIFunction,
|
||||
ChatAgent,
|
||||
ContextProvider,
|
||||
FunctionTool,
|
||||
Middleware,
|
||||
ToolProtocol,
|
||||
normalize_tools,
|
||||
@@ -445,9 +445,9 @@ class AzureAIAgentsProvider(Generic[TOptions_co]):
|
||||
# Add user-provided function tools and MCP tools
|
||||
if provided_tools:
|
||||
for provided_tool in provided_tools:
|
||||
# AIFunction - has implementation for function calling
|
||||
# FunctionTool - has implementation for function calling
|
||||
# MCPTool - ChatAgent handles MCP connection and tool discovery at runtime
|
||||
if isinstance(provided_tool, (AIFunction, MCPTool)):
|
||||
if isinstance(provided_tool, (FunctionTool, MCPTool)):
|
||||
merged.append(provided_tool) # type: ignore[reportUnknownArgumentType]
|
||||
|
||||
return merged
|
||||
@@ -488,7 +488,7 @@ class AzureAIAgentsProvider(Generic[TOptions_co]):
|
||||
provided_names: set[str] = set()
|
||||
if provided_tools:
|
||||
for tool in provided_tools:
|
||||
if isinstance(tool, AIFunction):
|
||||
if isinstance(tool, FunctionTool):
|
||||
provided_names.add(tool.name)
|
||||
|
||||
# Check for missing implementations
|
||||
|
||||
@@ -10,7 +10,6 @@ from typing import Any, ClassVar, Generic, TypedDict
|
||||
|
||||
from agent_framework import (
|
||||
AGENT_FRAMEWORK_USER_AGENT,
|
||||
AIFunction,
|
||||
Annotation,
|
||||
BaseChatClient,
|
||||
ChatAgent,
|
||||
@@ -21,6 +20,7 @@ from agent_framework import (
|
||||
ChatResponseUpdate,
|
||||
Content,
|
||||
ContextProvider,
|
||||
FunctionTool,
|
||||
HostedCodeInterpreterTool,
|
||||
HostedFileSearchTool,
|
||||
HostedMCPTool,
|
||||
@@ -1117,7 +1117,7 @@ class AzureAIAgentClient(BaseChatClient[TAzureAIAgentOptions], Generic[TAzureAIA
|
||||
tool_definitions: list[ToolDefinition | dict[str, Any]] = []
|
||||
for tool in tools:
|
||||
match tool:
|
||||
case AIFunction():
|
||||
case FunctionTool():
|
||||
tool_definitions.append(tool.to_json_schema_spec()) # type: ignore[reportUnknownArgumentType]
|
||||
case HostedWebSearchTool():
|
||||
additional_props = tool.additional_properties or {}
|
||||
|
||||
@@ -6,9 +6,9 @@ from typing import Any, Generic, TypedDict
|
||||
|
||||
from agent_framework import (
|
||||
AGENT_FRAMEWORK_USER_AGENT,
|
||||
AIFunction,
|
||||
ChatAgent,
|
||||
ContextProvider,
|
||||
FunctionTool,
|
||||
Middleware,
|
||||
ToolProtocol,
|
||||
get_logger,
|
||||
@@ -20,10 +20,12 @@ from azure.ai.projects.aio import AIProjectClient
|
||||
from azure.ai.projects.models import (
|
||||
AgentReference,
|
||||
AgentVersionDetails,
|
||||
FunctionTool,
|
||||
PromptAgentDefinition,
|
||||
PromptAgentDefinitionText,
|
||||
)
|
||||
from azure.ai.projects.models import (
|
||||
FunctionTool as AzureFunctionTool,
|
||||
)
|
||||
from azure.core.credentials_async import AsyncTokenCredential
|
||||
from pydantic import ValidationError
|
||||
|
||||
@@ -224,7 +226,7 @@ class AzureAIProjectAgentProvider(Generic[TOptions_co]):
|
||||
|
||||
# Connect MCP tools and discover their functions BEFORE creating the agent
|
||||
# This is required because Azure AI Responses API doesn't accept tools at request time
|
||||
mcp_discovered_functions: list[AIFunction[Any, Any]] = []
|
||||
mcp_discovered_functions: list[FunctionTool] = []
|
||||
for mcp_tool in mcp_tools:
|
||||
if not mcp_tool.is_connected:
|
||||
await mcp_tool.connect()
|
||||
@@ -433,9 +435,9 @@ class AzureAIProjectAgentProvider(Generic[TOptions_co]):
|
||||
# Add user-provided function tools and MCP tools
|
||||
if provided_tools:
|
||||
for provided_tool in provided_tools:
|
||||
# AIFunction - has implementation for function calling
|
||||
# FunctionTool - has implementation for function calling
|
||||
# MCPTool - ChatAgent handles MCP connection and tool discovery at runtime
|
||||
if isinstance(provided_tool, (AIFunction, MCPTool)):
|
||||
if isinstance(provided_tool, (FunctionTool, MCPTool)):
|
||||
merged.append(provided_tool) # type: ignore[reportUnknownArgumentType]
|
||||
|
||||
return merged
|
||||
@@ -452,12 +454,14 @@ class AzureAIProjectAgentProvider(Generic[TOptions_co]):
|
||||
"""Validate that required function tools are provided."""
|
||||
# Normalize and validate function tools
|
||||
normalized_tools = normalize_tools(provided_tools)
|
||||
tool_names = {tool.name for tool in normalized_tools if isinstance(tool, AIFunction)}
|
||||
tool_names = {tool.name for tool in normalized_tools if isinstance(tool, FunctionTool)}
|
||||
|
||||
# If function tools exist in agent definition but were not provided,
|
||||
# we need to raise an error, as it won't be possible to invoke the function.
|
||||
missing_tools = [
|
||||
tool.name for tool in (agent_tools or []) if isinstance(tool, FunctionTool) and tool.name not in tool_names
|
||||
tool.name
|
||||
for tool in (agent_tools or [])
|
||||
if isinstance(tool, AzureFunctionTool) and tool.name not in tool_names
|
||||
]
|
||||
|
||||
if missing_tools:
|
||||
|
||||
@@ -5,8 +5,8 @@ from collections.abc import Mapping, MutableMapping, Sequence
|
||||
from typing import Any, ClassVar, Literal, cast
|
||||
|
||||
from agent_framework import (
|
||||
AIFunction,
|
||||
Content,
|
||||
FunctionTool,
|
||||
HostedCodeInterpreterTool,
|
||||
HostedFileSearchTool,
|
||||
HostedImageGenerationTool,
|
||||
@@ -29,7 +29,6 @@ from azure.ai.projects.models import (
|
||||
ApproximateLocation,
|
||||
CodeInterpreterTool,
|
||||
CodeInterpreterToolAuto,
|
||||
FunctionTool,
|
||||
ImageGenTool,
|
||||
ImageGenToolInputImageMask,
|
||||
MCPTool,
|
||||
@@ -42,6 +41,9 @@ from azure.ai.projects.models import (
|
||||
from azure.ai.projects.models import (
|
||||
FileSearchTool as ProjectsFileSearchTool,
|
||||
)
|
||||
from azure.ai.projects.models import (
|
||||
FunctionTool as AzureFunctionTool,
|
||||
)
|
||||
from pydantic import BaseModel
|
||||
|
||||
logger = get_logger("agent_framework.azure")
|
||||
@@ -141,7 +143,7 @@ def to_azure_ai_agent_tools(
|
||||
tool_definitions: list[ToolDefinition | dict[str, Any]] = []
|
||||
for tool in tools:
|
||||
match tool:
|
||||
case AIFunction():
|
||||
case FunctionTool():
|
||||
tool_definitions.append(tool.to_json_schema_spec()) # type: ignore[reportUnknownArgumentType]
|
||||
case HostedWebSearchTool():
|
||||
additional_props = tool.additional_properties or {}
|
||||
@@ -439,11 +441,11 @@ def to_azure_ai_tools(
|
||||
container = CodeInterpreterToolAuto(file_ids=file_ids if file_ids else None)
|
||||
ci_tool: CodeInterpreterTool = CodeInterpreterTool(container=container)
|
||||
azure_tools.append(ci_tool)
|
||||
case AIFunction():
|
||||
case FunctionTool():
|
||||
params = tool.parameters()
|
||||
params["additionalProperties"] = False
|
||||
azure_tools.append(
|
||||
FunctionTool(
|
||||
AzureFunctionTool(
|
||||
name=tool.name,
|
||||
parameters=params,
|
||||
strict=False,
|
||||
|
||||
@@ -12,7 +12,7 @@ from agent_framework import (
|
||||
HostedFileSearchTool,
|
||||
HostedMCPTool,
|
||||
HostedWebSearchTool,
|
||||
ai_function,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
from azure.ai.agents.models import (
|
||||
@@ -222,7 +222,7 @@ async def test_create_agent_with_tools(
|
||||
|
||||
provider = AzureAIAgentsProvider(agents_client=mock_agents_client)
|
||||
|
||||
@ai_function
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get weather for a city."""
|
||||
return f"Weather in {city}"
|
||||
@@ -366,7 +366,7 @@ async def test_get_agent_with_provided_function_tools(
|
||||
mock_agent.tools = [mock_function_tool]
|
||||
mock_agents_client.get_agent = AsyncMock(return_value=mock_agent)
|
||||
|
||||
@ai_function
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get weather for a city."""
|
||||
return f"Weather in {city}"
|
||||
@@ -483,9 +483,9 @@ def test_to_azure_ai_agent_tools_empty() -> None:
|
||||
|
||||
|
||||
def test_to_azure_ai_agent_tools_function() -> None:
|
||||
"""Test converting AIFunction to Azure tool definition."""
|
||||
"""Test converting FunctionTool to Azure tool definition."""
|
||||
|
||||
@ai_function
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(city: str) -> str:
|
||||
"""Get weather for a city."""
|
||||
return f"Weather in {city}"
|
||||
|
||||
@@ -23,6 +23,7 @@ from agent_framework import (
|
||||
HostedMCPTool,
|
||||
HostedWebSearchTool,
|
||||
Role,
|
||||
tool,
|
||||
)
|
||||
from agent_framework._serialization import SerializationMixin
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
@@ -940,7 +941,7 @@ async def test_azure_ai_chat_client_service_url(mock_agents_client: MagicMock) -
|
||||
assert result == "https://test-endpoint.com/"
|
||||
|
||||
|
||||
async def test_azure_ai_chat_client_prepare_tool_outputs_for_azure_ai_function_result(
|
||||
async def test_azure_ai_chat_client_prepare_tool_outputs_for_azure_tool_result(
|
||||
mock_agents_client: MagicMock,
|
||||
) -> None:
|
||||
"""Test _prepare_tool_outputs_for_azure_ai with FunctionResultContent."""
|
||||
@@ -1336,6 +1337,7 @@ def test_azure_ai_chat_client_extract_file_path_contents_empty_annotations(
|
||||
assert len(file_contents) == 0
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
) -> str:
|
||||
|
||||
@@ -22,6 +22,7 @@ from agent_framework import (
|
||||
HostedMCPTool,
|
||||
HostedWebSearchTool,
|
||||
Role,
|
||||
tool,
|
||||
)
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
@@ -1025,6 +1026,7 @@ def test_from_azure_ai_tools() -> None:
|
||||
# region Integration Tests
|
||||
|
||||
|
||||
@tool(approval_mode="never_require")
|
||||
def get_weather(
|
||||
location: Annotated[str, Field(description="The location to get the weather for.")],
|
||||
) -> str:
|
||||
|
||||
@@ -4,16 +4,18 @@ import os
|
||||
from unittest.mock import AsyncMock, MagicMock, patch
|
||||
|
||||
import pytest
|
||||
from agent_framework import AIFunction, ChatAgent
|
||||
from agent_framework import ChatAgent, FunctionTool
|
||||
from agent_framework._mcp import MCPTool
|
||||
from agent_framework.exceptions import ServiceInitializationError
|
||||
from azure.ai.projects.aio import AIProjectClient
|
||||
from azure.ai.projects.models import (
|
||||
AgentReference,
|
||||
AgentVersionDetails,
|
||||
FunctionTool,
|
||||
PromptAgentDefinition,
|
||||
)
|
||||
from azure.ai.projects.models import (
|
||||
FunctionTool as AzureFunctionTool,
|
||||
)
|
||||
from azure.identity.aio import AzureCliCredential
|
||||
|
||||
from agent_framework_azure_ai import AzureAIProjectAgentProvider
|
||||
@@ -373,7 +375,7 @@ async def test_provider_get_agent_missing_function_tools(mock_project_client: Ma
|
||||
mock_agent_version.description = None
|
||||
mock_agent_version.definition = MagicMock(spec=PromptAgentDefinition)
|
||||
mock_agent_version.definition.tools = [
|
||||
FunctionTool(name="test_tool", parameters=[], strict=True, description="Test tool")
|
||||
AzureFunctionTool(name="test_tool", parameters=[], strict=True, description="Test tool")
|
||||
]
|
||||
|
||||
mock_agent_object = MagicMock()
|
||||
@@ -494,7 +496,7 @@ class MockMCPTool(MCPTool): # pyright: ignore[reportGeneralTypeIssues]
|
||||
unit testing. We only need isinstance(obj, MCPTool) to return True.
|
||||
"""
|
||||
|
||||
def __init__(self, functions: list[AIFunction] | None = None) -> None:
|
||||
def __init__(self, functions: list[FunctionTool] | None = None) -> None:
|
||||
self.name = "MockMCPTool"
|
||||
self.description = "A mock MCP tool for testing"
|
||||
self.is_connected = False
|
||||
@@ -502,7 +504,7 @@ class MockMCPTool(MCPTool): # pyright: ignore[reportGeneralTypeIssues]
|
||||
self._connect_called = False
|
||||
|
||||
@property
|
||||
def functions(self) -> list[AIFunction]:
|
||||
def functions(self) -> list[FunctionTool]:
|
||||
return self._mock_functions
|
||||
|
||||
async def connect(self, *, reset: bool = False) -> None:
|
||||
@@ -520,13 +522,13 @@ def mock_mcp_tool() -> MockMCPTool:
|
||||
return MockMCPTool(functions=mock_functions)
|
||||
|
||||
|
||||
def create_mock_ai_function(name: str, description: str = "A mock function") -> AIFunction:
|
||||
"""Create a real AIFunction for testing."""
|
||||
def create_mock_ai_function(name: str, description: str = "A mock function") -> FunctionTool:
|
||||
"""Create a real FunctionTool for testing."""
|
||||
|
||||
def mock_func(arg: str) -> str:
|
||||
return f"Result from {name}: {arg}"
|
||||
|
||||
return AIFunction(func=mock_func, name=name, description=description)
|
||||
return FunctionTool(func=mock_func, name=name, description=description, approval_mode="never_require")
|
||||
|
||||
|
||||
async def test_provider_create_agent_with_mcp_tool(
|
||||
@@ -593,8 +595,8 @@ async def test_provider_create_agent_with_mcp_and_regular_tools(
|
||||
azure_ai_unit_test_env: dict[str, str],
|
||||
mock_mcp_tool: "MockMCPTool",
|
||||
) -> None:
|
||||
"""Test that create_agent handles both MCP tools and regular AIFunctions."""
|
||||
# Create a regular AIFunction
|
||||
"""Test that create_agent handles both MCP tools and regular FunctionTools."""
|
||||
# Create a regular FunctionTool
|
||||
regular_function = create_mock_ai_function("regular_function", "A regular function")
|
||||
|
||||
# Patch normalize_tools to return tools as-is in a list (avoids callable check)
|
||||
@@ -637,7 +639,7 @@ async def test_provider_create_agent_with_mcp_and_regular_tools(
|
||||
)
|
||||
|
||||
# Verify to_azure_ai_tools was called with:
|
||||
# - The regular AIFunction (1)
|
||||
# - The regular FunctionTool (1)
|
||||
# - The 2 discovered MCP functions
|
||||
mock_to_azure_tools.assert_called_once()
|
||||
tools_passed = mock_to_azure_tools.call_args[0][0]
|
||||
|
||||
Reference in New Issue
Block a user