Skip to content

Commit 53dc30a

Browse files
Address co-pilot comment
1 parent ab79b5c commit 53dc30a

File tree

2 files changed

+80
-2
lines changed

2 files changed

+80
-2
lines changed

stackone_ai/toolset.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -933,8 +933,8 @@ def _search_one(c: str) -> list[SemanticSearchResult]:
933933
matched_tools = [t for t in all_tools if t.name in seen_names]
934934
matched_tools.sort(key=lambda t: action_order.get(t.name, float("inf")))
935935

936-
# If semantic returned results but none matched MCP tools, fall back to local search
937-
if len(all_results) > 0 and len(matched_tools) == 0:
936+
# Auto mode: if semantic returned results but none matched MCP tools, fall back to local
937+
if effective_search == "auto" and len(matched_tools) == 0:
938938
logger.warning(
939939
"Semantic search returned %d results but none matched MCP tools, "
940940
"falling back to local search",

tests/test_semantic_search.py

Lines changed: 78 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -959,3 +959,81 @@ def test_search_action_names_with_duplicates(self, mock_search: MagicMock) -> No
959959
assert results[0].id == "breathehr_1.0.0_breathehr_list_employees_global"
960960
# Sorted by score descending
961961
assert results[0].similarity_score == 0.95
962+
963+
964+
class TestZeroMatchFallback:
965+
"""Tests for fallback when semantic results don't match MCP tools."""
966+
967+
@patch.object(SemanticSearchClient, "search")
968+
@patch("stackone_ai.toolset._fetch_mcp_tools")
969+
def test_auto_mode_falls_back_when_no_tools_match(
970+
self,
971+
mock_fetch: MagicMock,
972+
mock_search: MagicMock,
973+
) -> None:
974+
"""Auto mode falls back to local search when semantic results don't match any MCP tools."""
975+
from stackone_ai import StackOneToolSet
976+
from stackone_ai.toolset import _McpToolDefinition
977+
978+
# Semantic returns results with IDs that won't match MCP tool names
979+
mock_search.return_value = SemanticSearchResponse(
980+
results=[
981+
SemanticSearchResult(
982+
id="unknown_1.0.0_nonexistent_action_global",
983+
similarity_score=0.95,
984+
),
985+
],
986+
total_count=1,
987+
query="manage employees",
988+
)
989+
990+
mock_fetch.return_value = [
991+
_McpToolDefinition(
992+
name="bamboohr_create_employee",
993+
description="Creates a new employee in BambooHR",
994+
input_schema={"type": "object", "properties": {}},
995+
),
996+
]
997+
998+
toolset = StackOneToolSet(api_key="test-key", search={"method": "auto"})
999+
tools = toolset.search_tools("manage employees", top_k=5)
1000+
1001+
# Should fall back to local search and return results (not empty)
1002+
assert len(tools) > 0
1003+
1004+
@patch.object(SemanticSearchClient, "search")
1005+
@patch("stackone_ai.toolset._fetch_mcp_tools")
1006+
def test_semantic_mode_does_not_fall_back(
1007+
self,
1008+
mock_fetch: MagicMock,
1009+
mock_search: MagicMock,
1010+
) -> None:
1011+
"""Semantic mode returns empty results when no tools match, does not fall back."""
1012+
from stackone_ai import StackOneToolSet
1013+
from stackone_ai.toolset import _McpToolDefinition
1014+
1015+
# Semantic returns results with IDs that won't match MCP tool names
1016+
mock_search.return_value = SemanticSearchResponse(
1017+
results=[
1018+
SemanticSearchResult(
1019+
id="unknown_1.0.0_nonexistent_action_global",
1020+
similarity_score=0.95,
1021+
),
1022+
],
1023+
total_count=1,
1024+
query="manage employees",
1025+
)
1026+
1027+
mock_fetch.return_value = [
1028+
_McpToolDefinition(
1029+
name="bamboohr_create_employee",
1030+
description="Creates a new employee in BambooHR",
1031+
input_schema={"type": "object", "properties": {}},
1032+
),
1033+
]
1034+
1035+
toolset = StackOneToolSet(api_key="test-key", search={"method": "auto"})
1036+
tools = toolset.search_tools("manage employees", search="semantic", top_k=5)
1037+
1038+
# Semantic mode should return empty, not fall back
1039+
assert len(tools) == 0

0 commit comments

Comments
 (0)