feat(tools): move ToolProvider out of experimental namespace (#1567)

Co-authored-by: Strands Agent <217235299+strands-agent@users.noreply.github.com>
This commit is contained in:
Nick Clegg
2026-01-27 17:54:51 -05:00
committed by GitHub
co-authored by Strands Agent
parent 27b9bc3acc
commit 4d0ffe84b9
11 changed files with 114 additions and 17 deletions
+2 -2
View File
@@ -55,6 +55,7 @@ strands-agents/
│ ├── tools/ # Tool system
│ │ ├── decorator.py # @tool decorator
│ │ ├── tools.py # Tool base classes
│ │ ├── tool_provider.py # ToolProvider interface
│ │ ├── registry.py # Tool registration
│ │ ├── loader.py # Dynamic tool loading
│ │ ├── watcher.py # Hot reload
@@ -139,8 +140,7 @@ strands-agents/
│ │ │ ├── context_providers/
│ │ │ ├── core/
│ │ │ └── handlers/
│ │ └── tools/ # Experimental tools
│ │ └── tool_provider.py
│ │ └── tools/ # Experimental tools (deprecation shims)
│ │
│ ├── __init__.py # Public API exports
│ ├── interrupt.py # Interrupt handling
+1 -1
View File
@@ -31,7 +31,7 @@ from ..event_loop.event_loop import INITIAL_DELAY, MAX_ATTEMPTS, MAX_DELAY, even
from ..tools._tool_helpers import generate_missing_tool_result_content
if TYPE_CHECKING:
from ..experimental.tools import ToolProvider
from ..tools import ToolProvider
from ..handlers.callback_handler import PrintingCallbackHandler, null_callback_handler
from ..hooks import (
AfterInvocationEvent,
+1 -1
View File
@@ -25,11 +25,11 @@ from ....tools._caller import _ToolCaller
from ....tools.executors import ConcurrentToolExecutor
from ....tools.executors._executor import ToolExecutor
from ....tools.registry import ToolRegistry
from ....tools.tool_provider import ToolProvider
from ....tools.watcher import ToolWatcher
from ....types.content import Message, Messages
from ....types.tools import AgentTool
from ...hooks.events import BidiAgentInitializedEvent, BidiMessageAddedEvent
from ...tools import ToolProvider
from .._async import _TaskGroup, stop_all
from ..models.model import BidiModel
from ..types.agent import BidiAgentInput
+19 -2
View File
@@ -1,5 +1,22 @@
"""Experimental tools package."""
from .tool_provider import ToolProvider
import warnings
from typing import Any
__all__ = ["ToolProvider"]
_DEPRECATED_NAMES = {"ToolProvider"}
def __getattr__(name: str) -> Any:
if name in _DEPRECATED_NAMES:
from ...tools import ToolProvider
warnings.warn(
f"{name} has been moved to production. Use {name} from strands.tools instead.",
DeprecationWarning,
stacklevel=2,
)
return ToolProvider
raise AttributeError(f"module {__name__!r} has no attribute {name!r}")
__all__: list[str] = []
+2
View File
@@ -5,6 +5,7 @@ This module provides the core functionality for creating, managing, and executin
from .decorator import tool
from .structured_output import convert_pydantic_to_tool_spec
from .tool_provider import ToolProvider
from .tools import InvalidToolUseNameException, PythonAgentTool, normalize_schema, normalize_tool_spec
__all__ = [
@@ -14,4 +15,5 @@ __all__ = [
"normalize_schema",
"normalize_tool_spec",
"convert_pydantic_to_tool_spec",
"ToolProvider",
]
+2 -6
View File
@@ -40,11 +40,11 @@ from mcp.types import TextContent as MCPTextContent
from pydantic import AnyUrl
from typing_extensions import Protocol, TypedDict
from ...experimental.tools import ToolProvider
from ...types import PaginatedList
from ...types.exceptions import MCPClientInitializationError, ToolProviderException
from ...types.media import ImageFormat
from ...types.tools import AgentTool, ToolResultContent, ToolResultStatus
from ..tool_provider import ToolProvider
from .mcp_agent_tool import MCPAgentTool
from .mcp_instrumentation import mcp_instrumentation
from .mcp_types import MCPToolResult, MCPTransport
@@ -106,10 +106,6 @@ class MCPClient(ToolProvider):
The connection runs in a background thread to avoid blocking the main application thread
while maintaining communication with the MCP service. When structured content is available
from MCP tools, it will be returned as the last item in the content array of the ToolResult.
Warning:
This class implements the experimental ToolProvider interface and its methods
are subject to change.
"""
def __init__(
@@ -207,7 +203,7 @@ class MCPClient(ToolProvider):
raise MCPClientInitializationError("the client initialization failed") from e
return self
# ToolProvider interface methods (experimental, as ToolProvider is experimental)
# ToolProvider interface methods
async def load_tools(self, **kwargs: Any) -> Sequence[AgentTool]:
"""Load and return tools from the MCP server.
+1 -1
View File
@@ -19,9 +19,9 @@ from typing import Any, cast
from typing_extensions import TypedDict
from .._async import run_async
from ..experimental.tools import ToolProvider
from ..tools.decorator import DecoratedFunctionTool
from ..types.tools import AgentTool, ToolSpec
from . import ToolProvider
from .loader import load_tool_from_string, load_tools_from_module
from .tools import _COMPOSITION_KEYWORDS, PythonAgentTool, normalize_schema, normalize_tool_spec
@@ -5,7 +5,7 @@ from collections.abc import Sequence
from typing import TYPE_CHECKING, Any
if TYPE_CHECKING:
from ...types.tools import AgentTool
from ..types.tools import AgentTool
class ToolProvider(ABC):
@@ -0,0 +1,83 @@
"""Tests to verify that experimental ToolProvider alias works with deprecation warning.
This test module ensures that the experimental ToolProvider alias maintains
backwards compatibility and can be used interchangeably with the actual
ToolProvider type from strands.tools.
"""
import sys
import pytest
from strands.tools import ToolProvider
def test_experimental_alias_is_same_type():
"""Verify that experimental ToolProvider alias is identical to the actual type."""
from strands.experimental.tools import ToolProvider as ExperimentalToolProvider
assert ExperimentalToolProvider is ToolProvider
def test_deprecation_warning_on_import(captured_warnings):
"""Verify that importing ToolProvider from experimental emits deprecation warning."""
# Clear the module from cache to trigger fresh import
if "strands.experimental.tools" in sys.modules:
del sys.modules["strands.experimental.tools"]
# Clear any existing warnings
captured_warnings.clear()
# Import from experimental - this should trigger the warning
from strands.experimental import tools
_ = tools.ToolProvider
assert len(captured_warnings) >= 1
warning = captured_warnings[0]
assert issubclass(warning.category, DeprecationWarning)
assert "ToolProvider" in str(warning.message)
assert "strands.tools" in str(warning.message)
def test_deprecation_warning_on_direct_import(captured_warnings):
"""Verify that direct import from experimental.tools emits deprecation warning."""
# Clear the module from cache to trigger fresh import
if "strands.experimental.tools" in sys.modules:
del sys.modules["strands.experimental.tools"]
# Clear any existing warnings
captured_warnings.clear()
# Direct import - this should trigger the warning
from strands.experimental.tools import ToolProvider as _ # noqa: F401
assert len(captured_warnings) >= 1
warning = captured_warnings[0]
assert issubclass(warning.category, DeprecationWarning)
assert "ToolProvider" in str(warning.message)
assert "strands.tools" in str(warning.message)
def test_attribute_error_on_unknown_attribute():
"""Verify that accessing unknown attributes raises AttributeError."""
import strands.experimental.tools as tools_module
with pytest.raises(AttributeError, match="has no attribute"):
_ = tools_module.NonExistentClass
def test_no_warning_on_production_import(captured_warnings):
"""Verify that importing from strands.tools does not emit deprecation warning."""
# Clear any existing warnings
captured_warnings.clear()
# Import from production - should NOT trigger warning
from strands.tools import ToolProvider as _ # noqa: F401
# Filter for ToolProvider-related deprecation warnings
tool_provider_warnings = [
w for w in captured_warnings if "ToolProvider" in str(w.message) and issubclass(w.category, DeprecationWarning)
]
assert len(tool_provider_warnings) == 0
+1 -2
View File
@@ -7,8 +7,7 @@ from unittest.mock import AsyncMock, MagicMock
import pytest
import strands
from strands.experimental.tools import ToolProvider
from strands.tools import PythonAgentTool
from strands.tools import PythonAgentTool, ToolProvider
from strands.tools.decorator import DecoratedFunctionTool, tool
from strands.tools.mcp import MCPClient
from strands.tools.registry import ToolRegistry
@@ -4,7 +4,7 @@ from unittest.mock import patch
import pytest
from strands.experimental.tools.tool_provider import ToolProvider
from strands.tools import ToolProvider
from strands.tools.registry import ToolRegistry
from tests.fixtures.mock_agent_tool import MockAgentTool