test/tools/test_public_exports.py
Rama main · réplica de lectura del repositorio original.
# Copyright (c) 2026, AG2ai, Inc., AG2ai open-source projects maintainers and core contributors
#
# SPDX-License-Identifier: Apache-2.0
import ag2.tools
import ag2.tools.toolkits
import ag2.tools.toolkits.mcp_server
import ag2.tools.types
from ag2 import Agent
from ag2.tools import Toolkit, tool
from ag2.tools.types import (
ClientTool,
FunctionDefinition,
FunctionParameters,
FunctionTool,
FunctionToolSchema,
Tool,
ToolSchema,
)
# Ready-to-use tools that must stay importable from `ag2.tools`, so the
# re-homing of the abstractions cannot quietly take them with it.
_CONCRETE_TOOLS = ("CodeExecutionTool", "ShellTool", "WebSearchTool", "MCPServerTool")
def test_every_public_name_resolves() -> None:
"""``__all__`` must not advertise a name the module does not expose."""
assert [name for name in ag2.tools.__all__ if not hasattr(ag2.tools, name)] == []
assert [name for name in ag2.tools.types.__all__ if not hasattr(ag2.tools.types, name)] == []
class TestImportPathsStaySeparate:
"""`ag2.tools` is ready-to-use tools; `ag2.tools.types` is the abstractions."""
def test_abstractions_are_importable_from_one_place(self) -> None:
assert set(ag2.tools.types.__all__) == {
"ClientTool",
"FunctionDefinition",
"FunctionParameters",
"FunctionTool",
"FunctionToolSchema",
"Tool",
"ToolSchema",
"Toolkit",
}
def test_abstractions_are_not_mixed_into_ag2_tools(self) -> None:
# Toolkit predates this split and stays exported from both.
assert [name for name in ag2.tools.types.__all__ if name in ag2.tools.__all__] == ["Toolkit"]
def test_concrete_tools_are_untouched(self) -> None:
assert [name for name in _CONCRETE_TOOLS if name not in ag2.tools.__all__] == []
class TestTheMCPAnswerPolicyNamesItsProtocol:
"""`ag2.tools` holds several protocols' names; a policy must say which it answers for."""
def test_it_is_advertised_under_the_protocol_qualified_name(self) -> None:
assert "MCPAnswerPolicy" in ag2.tools.__all__
def test_the_generic_name_is_exported_at_no_level(self) -> None:
for module in (ag2.tools, ag2.tools.toolkits, ag2.tools.toolkits.mcp_server):
assert "AnswerPolicy" not in module.__all__
assert not hasattr(module, "AnswerPolicy")
class TestToolAbstraction:
"""ADR 0002 — one ``Tool`` abstraction; every kind of tool implements it."""
def test_function_tool_is_a_tool(self) -> None:
assert issubclass(FunctionTool, Tool)
def test_client_tool_is_a_tool(self) -> None:
assert issubclass(ClientTool, Tool)
def test_toolkit_is_a_tool(self) -> None:
# The composite: a toolkit *is a* Tool, so an agent accepts it anywhere
# it accepts a tool.
assert issubclass(Toolkit, Tool)
def test_function_tool_schema_is_a_tool_schema(self) -> None:
assert issubclass(FunctionToolSchema, ToolSchema)
class TestPublicReturnTypesAreImportable:
"""Callables AG2 exports must return types a caller can name."""
def test_tool_decorator_returns_function_tool(self) -> None:
@tool
def my_tool(a: str) -> str:
"""Tool description."""
return a
assert isinstance(my_tool, FunctionTool)
def test_agent_as_tool_returns_tool(self) -> None:
child = Agent("child", prompt="You are a child agent.")
assert isinstance(child.as_tool(description="Delegate to the child."), Tool)
def test_toolkit_tool_decorator_returns_function_tool(self) -> None:
toolkit = Toolkit()
@toolkit.tool
def my_tool(a: str) -> str:
"""Tool description."""
return a
assert isinstance(my_tool, FunctionTool)
def test_function_tool_schema_is_built_from_public_parts() -> None:
"""``FunctionDefinition`` / ``FunctionParameters`` describe a function tool."""
parameters: FunctionParameters = {"type": "object", "properties": {}}
schema = FunctionToolSchema(function=FunctionDefinition(name="my_tool", parameters=parameters))
assert schema.type == "function"
assert schema.function.name == "my_tool"