mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
fix(mcp): return only non-MCP tools when semantic filter finds zero matches
Closes #24984 - When filter_tools() finds zero semantic matches, it previously returned all available tools, which could exceed the 128 tool limit - Now returns only non-MCP tools (built-in tools without server prefix) - Added regression test for zero-match fallback behavior
This commit is contained in:
parent
d1df4e838b
commit
3ff9c98066
2 changed files with 67 additions and 1 deletions
|
|
@ -7,6 +7,8 @@ from typing import TYPE_CHECKING, Any, Dict, List, Optional
|
|||
|
||||
from litellm._logging import verbose_logger
|
||||
|
||||
from litellm.proxy._experimental.mcp_server.utils import is_tool_name_prefixed
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from semantic_router.routers import SemanticRouter
|
||||
|
||||
|
|
@ -190,7 +192,13 @@ class SemanticMCPToolFilter:
|
|||
matched_tool_names = self._extract_tool_names_from_matches(matches)
|
||||
|
||||
if not matched_tool_names:
|
||||
return available_tools
|
||||
# Return only non-MCP tools (built-in tools without a server prefix)
|
||||
# to avoid exceeding provider tool limits when no semantic matches exist
|
||||
return [
|
||||
tool
|
||||
for tool in available_tools
|
||||
if not is_tool_name_prefixed(self._extract_tool_info(tool)[0])
|
||||
]
|
||||
|
||||
return self._get_tools_by_names(matched_tool_names, available_tools)
|
||||
|
||||
|
|
|
|||
|
|
@ -191,6 +191,64 @@ async def test_semantic_filter_disabled():
|
|||
assert len(filtered) == len(tools), f"Expected all {len(tools)} tools, got {len(filtered)}"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_semantic_filter_zero_matches_returns_only_non_mcp_tools():
|
||||
"""
|
||||
Test that when zero semantic matches are found, only non-MCP tools are returned.
|
||||
|
||||
Given: A mix of MCP tools (prefixed) and built-in tools (not prefixed)
|
||||
When: Semantic filter finds zero matches (e.g., irrelevant query like "hello")
|
||||
Then: Only built-in (non-prefixed) tools should be returned, not all tools
|
||||
|
||||
Regression test for: https://github.com/BerriAI/litellm/issues/24984
|
||||
"""
|
||||
from litellm.proxy._experimental.mcp_server.semantic_tool_filter import (
|
||||
SemanticMCPToolFilter,
|
||||
)
|
||||
|
||||
# Create mock tools: 3 MCP tools (prefixed with server name) and 2 built-in tools
|
||||
mcp_tools = [
|
||||
MCPTool(name="server1-tool_a", description="MCP tool A", inputSchema={"type": "object"}),
|
||||
MCPTool(name="server1-tool_b", description="MCP tool B", inputSchema={"type": "object"}),
|
||||
MCPTool(name="server2-tool_c", description="MCP tool C", inputSchema={"type": "object"}),
|
||||
]
|
||||
builtin_tools = [
|
||||
MCPTool(name="web_search", description="Search the web", inputSchema={"type": "object"}),
|
||||
MCPTool(name="code_interpreter", description="Run code", inputSchema={"type": "object"}),
|
||||
]
|
||||
all_tools = mcp_tools + builtin_tools
|
||||
|
||||
# Mock router that returns zero matches for any query
|
||||
mock_router = Mock()
|
||||
mock_router.return_value = [] # No semantic matches
|
||||
|
||||
filter_instance = SemanticMCPToolFilter(
|
||||
embedding_model="text-embedding-3-small",
|
||||
litellm_router_instance=Mock(),
|
||||
top_k=5,
|
||||
similarity_threshold=0.1,
|
||||
enabled=True,
|
||||
)
|
||||
|
||||
# Inject a mock router that returns empty matches
|
||||
filter_instance.tool_router = mock_router
|
||||
|
||||
# Filter with a query that matches nothing
|
||||
filtered = await filter_instance.filter_tools(
|
||||
query="hello",
|
||||
available_tools=all_tools,
|
||||
)
|
||||
|
||||
# Should return only non-MCP tools, not all tools
|
||||
assert len(filtered) == 2, f"Expected 2 non-MCP tools, got {len(filtered)}"
|
||||
filtered_names = [t.name for t in filtered]
|
||||
assert "web_search" in filtered_names
|
||||
assert "code_interpreter" in filtered_names
|
||||
# No MCP tools should be returned
|
||||
for name in filtered_names:
|
||||
assert "-" not in name, f"MCP tool {name} should not be returned on zero matches"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_semantic_filter_empty_tools():
|
||||
"""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue