Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions python/packages/core/agent_framework/_tools.py
Original file line number Diff line number Diff line change
Expand Up @@ -304,11 +304,18 @@ def __repr__(self) -> str:
def _get_tool_name(tool: Any) -> str | None:
"""Extract a tool name from a tool object or dict tool definition."""
if isinstance(tool, Mapping):
if tool.get("type") == "function": # type: ignore
name = tool.get("name") # type: ignore
if isinstance(name, str):
return name

func = tool.get("function", None) # type: ignore
if func and isinstance(func, Mapping):
name = func.get("name") # type: ignore
return name if isinstance(name, str) else None

return None

name = getattr(tool, "name", None)
return name if isinstance(name, str) else None

Expand Down
80 changes: 79 additions & 1 deletion python/packages/core/tests/core/test_middleware.py
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@
MiddlewareTermination,
categorize_middleware,
)
from agent_framework._tools import FunctionTool
from agent_framework._tools import FunctionTool, ToolTypes


class TestAgentContext:
Expand Down Expand Up @@ -111,6 +111,84 @@ def test_init_with_custom_metadata(self, mock_function: FunctionTool) -> None:
assert context.metadata == metadata


def test_remove_flat_function_mapping_by_name() -> None:
"""Flat Responses-style function mappings can be removed by name."""
lookup = {
"type": "function",
"name": "lookup",
"parameters": {"type": "object"},
}
tools: list[ToolTypes] = [lookup]

context = FunctionInvocationContext(
function=FunctionTool(
name="loader",
description="Load tools",
func=lambda: None,
),
arguments={},
tools=tools,
)

context.remove_tools("lookup")

assert tools == []


def test_add_rejects_duplicate_flat_function_mapping() -> None:
"""Flat function mappings participate in duplicate-name checks."""
lookup = {
"type": "function",
"name": "lookup",
"parameters": {"type": "object"},
}
duplicate = {
"type": "function",
"name": "lookup",
"parameters": {
"type": "object",
"properties": {"query": {"type": "string"}},
},
}
tools: list[ToolTypes] = [lookup]

context = FunctionInvocationContext(
function=FunctionTool(
name="loader",
description="Load tools",
func=lambda: None,
),
arguments={},
tools=tools,
)

with pytest.raises(ValueError, match="lookup"):
context.add_tools(duplicate)


def test_remove_name_preserves_non_function_builtin_mapping() -> None:
"""A named non-function built-in mapping is not treated as a function."""
builtin_tool = {
"type": "web_search_preview",
"name": "lookup",
}
tools: list[ToolTypes] = [builtin_tool]

context = FunctionInvocationContext(
function=FunctionTool(
name="loader",
description="Load tools",
func=lambda: None,
),
arguments={},
tools=tools,
)

context.remove_tools("lookup")

assert tools == [builtin_tool]


class TestChatContext:
"""Test cases for ChatContext."""

Expand Down
Loading