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:
Eduard van Valkenburg
2026-01-28 15:53:53 +01:00
committed by GitHub
Unverified
parent 15b43f2abe
commit a7d924a7d2
255 changed files with 1202 additions and 1290 deletions
@@ -8,7 +8,7 @@ import pytest
from openai.types.beta.assistant import Assistant
from pydantic import BaseModel, Field
from agent_framework import ChatAgent, HostedCodeInterpreterTool, HostedFileSearchTool, ai_function, normalize_tools
from agent_framework import ChatAgent, HostedCodeInterpreterTool, HostedFileSearchTool, normalize_tools, tool
from agent_framework.exceptions import ServiceInitializationError
from agent_framework.openai import OpenAIAssistantProvider
from agent_framework.openai._shared import from_assistant_tools, to_assistant_tools
@@ -244,11 +244,11 @@ class TestOpenAIAssistantProviderCreateAgent:
assert call_kwargs["tools"][0]["type"] == "function"
assert call_kwargs["tools"][0]["function"]["name"] == "get_weather"
async def test_create_agent_with_ai_function(self, mock_async_openai: MagicMock) -> None:
"""Test assistant creation with AIFunction."""
async def test_create_agent_with_tool(self, mock_async_openai: MagicMock) -> None:
"""Test assistant creation with FunctionTool."""
provider = OpenAIAssistantProvider(mock_async_openai)
@ai_function
@tool
def my_function(x: int) -> int:
"""Double a number."""
return x * 2
@@ -537,10 +537,10 @@ class TestOpenAIAssistantProviderAsAgent:
class TestToolConversion:
"""Tests for tool conversion utilities (shared functions)."""
def test_to_assistant_tools_ai_function(self) -> None:
"""Test AIFunction conversion to API format."""
def test_to_assistant_tools_tool(self) -> None:
"""Test FunctionTool conversion to API format."""
@ai_function
@tool
def test_func(x: int) -> int:
"""Test function."""
return x
@@ -555,7 +555,7 @@ class TestToolConversion:
def test_to_assistant_tools_callable(self) -> None:
"""Test raw callable conversion via normalize_tools."""
# normalize_tools converts callables to AIFunction
# normalize_tools converts callables to FunctionTool
normalized = normalize_tools([get_weather])
api_tools = to_assistant_tools(normalized)
@@ -666,12 +666,12 @@ class TestToolValidation:
# Should not raise
provider._validate_function_tools(assistant_tools, None) # type: ignore[reportPrivateUsage]
def test_validate_with_ai_function(self, mock_async_openai: MagicMock) -> None:
"""Test validation with AIFunction."""
def test_validate_with_tool(self, mock_async_openai: MagicMock) -> None:
"""Test validation with FunctionTool."""
provider = OpenAIAssistantProvider(mock_async_openai)
assistant_tools = [create_function_tool("get_weather")]
wrapped = ai_function(get_weather)
wrapped = tool(get_weather)
# Should not raise
provider._validate_function_tools(assistant_tools, [wrapped]) # type: ignore[reportPrivateUsage]
@@ -789,6 +789,7 @@ class TestOpenAIAssistantProviderIntegration:
"""Integration test with function tools."""
provider = OpenAIAssistantProvider()
@tool(approval_mode="never_require")
def get_current_time() -> str:
"""Get the current time."""
from datetime import datetime
@@ -23,7 +23,7 @@ from agent_framework import (
HostedCodeInterpreterTool,
HostedFileSearchTool,
Role,
ai_function,
tool,
)
from agent_framework.exceptions import ServiceInitializationError
from agent_framework.openai import OpenAIAssistantsClient
@@ -709,13 +709,13 @@ def test_prepare_options_basic(mock_async_openai: MagicMock) -> None:
assert tool_results is None
def test_prepare_options_with_ai_function_tool(mock_async_openai: MagicMock) -> None:
"""Test _prepare_options with AIFunction tool."""
def test_prepare_options_with_tool_tool(mock_async_openai: MagicMock) -> None:
"""Test _prepare_options with a FunctionTool."""
chat_client = create_test_openai_assistants_client(mock_async_openai)
# Create a simple function for testing and decorate it
@ai_function
@tool(approval_mode="never_require")
def test_function(query: str) -> str:
"""A test function."""
return f"Result for {query}"
@@ -998,6 +998,7 @@ def test_update_agent_name_and_description_none(mock_async_openai: MagicMock) ->
assert chat_client.assistant_name is None
@tool(approval_mode="never_require")
def get_weather(
location: Annotated[str, Field(description="The location to get the weather for.")],
) -> str:
@@ -19,8 +19,8 @@ from agent_framework import (
Content,
HostedWebSearchTool,
ToolProtocol,
ai_function,
prepare_function_call_results,
tool,
)
from agent_framework.exceptions import ServiceInitializationError, ServiceResponseException
from agent_framework.openai import OpenAIChatClient
@@ -175,7 +175,7 @@ def test_unsupported_tool_handling(openai_unit_test_env: dict[str, str]) -> None
"""Test that unsupported tool types are handled correctly."""
client = OpenAIChatClient()
# Create a mock ToolProtocol that's not an AIFunction
# Create a mock ToolProtocol that's not a FunctionTool
unsupported_tool = MagicMock(spec=ToolProtocol)
unsupported_tool.__class__.__name__ = "UnsupportedAITool"
@@ -189,7 +189,7 @@ def test_unsupported_tool_handling(openai_unit_test_env: dict[str, str]) -> None
assert result["tools"] == [dict_tool]
@ai_function
@tool(approval_mode="never_require")
def get_story_text() -> str:
"""Returns a story about Emily and David."""
return (
@@ -200,7 +200,7 @@ def get_story_text() -> str:
)
@ai_function
@tool(approval_mode="never_require")
def get_weather(location: str) -> str:
"""Get the current weather for a location."""
return f"The weather in {location} is sunny and 72°F."
@@ -40,7 +40,7 @@ from agent_framework import (
HostedMCPTool,
HostedWebSearchTool,
Role,
ai_function,
tool,
)
from agent_framework.exceptions import (
ServiceInitializationError,
@@ -96,7 +96,7 @@ async def delete_vector_store(client: OpenAIResponsesClient, file_id: str, vecto
await client.client.files.delete(file_id=file_id)
@ai_function
@tool(approval_mode="never_require")
async def get_weather(location: Annotated[str, "The location as a city name"]) -> str:
"""Get the current weather in a given location."""
# Implementation of the tool to get weather
@@ -642,7 +642,7 @@ def test_response_content_creation_with_function_call() -> None:
assert function_call.arguments == '{"location": "Seattle"}'
def test_prepare_content_for_openai_function_approval_response() -> None:
def test_prepare_content_for_opentool_approval_response() -> None:
"""Test _prepare_content_for_openai with function approval response content."""
client = OpenAIResponsesClient(model_id="test-model", api_key="test-key")