From 02b65f3da2f5d72e43301552ab9e54b0a1aba85b Mon Sep 17 00:00:00 2001 From: njbrake <33383515+njbrake@users.noreply.github.com> Date: Mon, 21 Sep 2026 15:48:37 +0000 Subject: [PATCH] fix: repair unit and mypy failures caused by upstream drift All of these became visible once the mcp cap let the suite collect again. - openai 2.x made `cache_write_tokens` required on `InputTokensDetails`. `PromptTokensDetails` carries the field in both openai 2.x and any-llm, and any-llm's Anthropic provider populates it, so mirror the existing getattr chain rather than reporting a constant zero. - any-llm 1.28 retired the `gateway` provider key in favour of `otari`, so `split_model_provider` raises before tinyagent reaches its strip. That strip lives in the tinyagent package and `otari:` cannot stand in here because the otari SDK is not a dependency, so the two `gateway:` params are dropped and the test renamed to what it now covers. Filed as mozilla-ai/tinyagent#23. - `google.adk.tools` resolves names through a lazy importlib loader with a TYPE_CHECKING block, so mypy treats them as imported rather than re-exported. Import from the concrete submodules; runtime behavior is unchanged. Two `patch()` targets in test_google.py were retargeted to match. - any-llm 1.28 widened `split_model_provider` to return `str | LLMProvider`, so coerce through `LLMProvider` in the smolagents assertion. mypy on 3.13 reports no issues across 127 source files. Co-Authored-By: Claude Opus 5 (1M context) --- src/any_agent/frameworks/openai.py | 13 ++++++++++++- src/any_agent/tools/wrappers.py | 5 +++-- tests/integration/tools/test_wrap_tools.py | 2 +- tests/unit/frameworks/test_google.py | 6 +++--- tests/unit/frameworks/test_smolagents.py | 5 ++++- tests/unit/frameworks/test_tinyagent.py | 12 +++++++----- tests/unit/tools/test_unit_wrappers.py | 7 ++++--- 7 files changed, 34 insertions(+), 16 deletions(-) diff --git a/src/any_agent/frameworks/openai.py b/src/any_agent/frameworks/openai.py index c2899883..c7cfe4b0 100644 --- a/src/any_agent/frameworks/openai.py +++ b/src/any_agent/frameworks/openai.py @@ -145,7 +145,18 @@ async def get_response( response_usage.prompt_tokens_details, "cached_tokens", 0 ) ) - or 0 + or 0, + # Required since openai 2.x. Older versions of the model allow + # extra fields, so passing it stays compatible with those. + cache_write_tokens=( + getattr(response_usage, "prompt_tokens_details", None) + and getattr( + response_usage.prompt_tokens_details, + "cache_write_tokens", + 0, + ) + ) + or 0, ), output_tokens_details=OutputTokensDetails( reasoning_tokens=( diff --git a/src/any_agent/tools/wrappers.py b/src/any_agent/tools/wrappers.py index b9d3e87c..35fcd0fd 100644 --- a/src/any_agent/tools/wrappers.py +++ b/src/any_agent/tools/wrappers.py @@ -9,7 +9,7 @@ if TYPE_CHECKING: from agents import Tool as AgentTool - from google.adk.tools import BaseTool as GoogleTool + from google.adk.tools.base_tool import BaseTool as GoogleTool from langchain_core.tools import BaseTool as LangchainTool from llama_index.core.tools import FunctionTool as LlamaIndexTool from smolagents import Tool as SmolagentsTool @@ -87,7 +87,8 @@ def _wrap_tool_llama_index(tool: "Tool | LlamaIndexTool") -> "LlamaIndexTool": def _wrap_tool_google(tool: "Tool | GoogleTool") -> "GoogleTool": - from google.adk.tools import BaseTool, FunctionTool + from google.adk.tools.base_tool import BaseTool + from google.adk.tools.function_tool import FunctionTool if isinstance(tool, BaseTool): return tool diff --git a/tests/integration/tools/test_wrap_tools.py b/tests/integration/tools/test_wrap_tools.py index e0505a9a..6e41e43b 100644 --- a/tests/integration/tools/test_wrap_tools.py +++ b/tests/integration/tools/test_wrap_tools.py @@ -4,7 +4,7 @@ import pytest from agents.tool import Tool as OpenaiClass from any_llm.utils.aio import run_async_in_sync -from google.adk.tools import FunctionTool as GoogleClass +from google.adk.tools.function_tool import FunctionTool as GoogleClass from langchain_core.tools import BaseTool as LangchainClass from llama_index.core.tools import FunctionTool as LlamaindexClass from smolagents.tools import Tool as SmolagentsClass diff --git a/tests/unit/frameworks/test_google.py b/tests/unit/frameworks/test_google.py index 813adbfb..2a7c7cb4 100644 --- a/tests/unit/frameworks/test_google.py +++ b/tests/unit/frameworks/test_google.py @@ -7,7 +7,7 @@ def test_load_google_default() -> None: - from google.adk.tools import FunctionTool + from google.adk.tools.function_tool import FunctionTool mock_agent = MagicMock() mock_model = MagicMock() @@ -20,7 +20,7 @@ def __new__(cls, *args: Any, **kwargs: Any) -> MagicMock: with ( patch("any_agent.frameworks.google.LlmAgent", mock_agent), patch("any_agent.frameworks.google.DEFAULT_MODEL_TYPE", mock_model), - patch("google.adk.tools.FunctionTool", MockedFunctionTool), + patch("google.adk.tools.function_tool.FunctionTool", MockedFunctionTool), ): AnyAgent.create( AgentFramework.GOOGLE, AgentConfig(model_id="mistral:mistral-small-latest") @@ -65,7 +65,7 @@ def test_run_google_custom_args() -> None: patch("any_agent.frameworks.google.LlmAgent", mock_agent), patch("any_agent.frameworks.google.InMemoryRunner", mock_runner), patch("any_agent.frameworks.google.DEFAULT_MODEL_TYPE"), - patch("google.adk.tools.FunctionTool"), + patch("google.adk.tools.function_tool.FunctionTool"), ): agent = AnyAgent.create( AgentFramework.GOOGLE, AgentConfig(model_id="mistral:mistral-small-latest") diff --git a/tests/unit/frameworks/test_smolagents.py b/tests/unit/frameworks/test_smolagents.py index af97f352..ffaf89fd 100644 --- a/tests/unit/frameworks/test_smolagents.py +++ b/tests/unit/frameworks/test_smolagents.py @@ -2,6 +2,8 @@ import pytest +from any_llm import LLMProvider + from any_agent import AgentConfig, AgentFramework, AnyAgent from any_agent.frameworks.smolagents import AnyLLMModel @@ -131,7 +133,8 @@ def test_parses_model_id_correctly(self) -> None: api_base="https://api.example.com", ) - assert model._provider.value == "openai" + # any-llm 1.28 widened split_model_provider to return `str | LLMProvider`. + assert LLMProvider(model._provider) is LLMProvider.OPENAI assert model._anyllm_completion_kwargs["model"] == "gpt-4o" assert model._api_key == "test-key" assert model._api_base == "https://api.example.com" diff --git a/tests/unit/frameworks/test_tinyagent.py b/tests/unit/frameworks/test_tinyagent.py index 48baebdf..f25d8454 100644 --- a/tests/unit/frameworks/test_tinyagent.py +++ b/tests/unit/frameworks/test_tinyagent.py @@ -250,18 +250,20 @@ def create_mock_response(content: str, is_structured: bool = False) -> MagicMock assert second_call_args["response_format"] == SampleOutput +# The `gateway:` cases that used to live here are gone. any-llm retired the +# `gateway` provider key in favour of `otari`, so `split_model_provider` raises +# before tinyagent reaches its `if provider_name == "gateway"` strip, and `otari:` +# cannot stand in because the otari SDK is not a dependency here. The strip lives +# in the `tinyagent` package, so that behavior belongs to its test suite. +# See https://github.com/mozilla-ai/tinyagent/issues/23 @pytest.mark.parametrize( ("model_id", "expected_uses_openai"), [ - ("gateway:openai:gpt-4.1-mini", True), - ("gateway:anthropic:claude-3", False), ("openai:gpt-4", True), ("anthropic:claude-3", False), ], ) -def test_uses_openai_handles_gateway_provider( - model_id: str, expected_uses_openai: bool -) -> None: +def test_uses_openai(model_id: str, expected_uses_openai: bool) -> None: config = AgentConfig(model_id=model_id) agent: TinyAgent = AnyAgent.create(AgentFramework.TINYAGENT, config) # type: ignore[assignment] diff --git a/tests/unit/tools/test_unit_wrappers.py b/tests/unit/tools/test_unit_wrappers.py index b551dbfd..5618c1c5 100644 --- a/tests/unit/tools/test_unit_wrappers.py +++ b/tests/unit/tools/test_unit_wrappers.py @@ -114,7 +114,7 @@ def test_wrap_tool_smolagents_builtin_tools() -> None: def test_wrap_tool_google() -> None: - from google.adk.tools import FunctionTool + from google.adk.tools.function_tool import FunctionTool wrapper = MagicMock() wrapper.return_value = None @@ -125,7 +125,7 @@ def test_wrap_tool_google() -> None: def test_wrap_tool_google_already_wrapped() -> None: - from google.adk.tools import FunctionTool + from google.adk.tools.function_tool import FunctionTool wrapper = MagicMock() wrapper.return_value = None @@ -137,7 +137,8 @@ def test_wrap_tool_google_already_wrapped() -> None: def test_wrap_tool_google_builtin_tools() -> None: - from google.adk.tools import FunctionTool, google_search + from google.adk.tools.function_tool import FunctionTool + from google.adk.tools.google_search_tool import google_search wrapper = MagicMock() wrapper.return_value = None