refactor(tools): search_projects uses shared score_project_match helper
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -135,20 +135,12 @@ async def update_project_tool(*, user_id, arguments, **_ctx):
|
||||
briefing=True,
|
||||
)
|
||||
async def search_projects_tool(*, user_id, arguments, **_ctx):
|
||||
from difflib import SequenceMatcher
|
||||
|
||||
from fabledassistant.services.projects import list_projects
|
||||
from fabledassistant.services.tools._helpers import score_project_match
|
||||
|
||||
query = str(arguments.get("query", "")).lower()
|
||||
query = str(arguments.get("query", ""))
|
||||
projects = await list_projects(user_id)
|
||||
scored: list[tuple[float, object]] = []
|
||||
for p in projects:
|
||||
combined = f"{p.title} {p.description or ''} {p.auto_summary or ''}".lower()
|
||||
base_score = SequenceMatcher(None, query, combined).ratio()
|
||||
query_words = set(query.split())
|
||||
overlap = sum(1 for w in query_words if w in combined)
|
||||
score = base_score + overlap * 0.05
|
||||
scored.append((score, p))
|
||||
scored: list[tuple[float, object]] = [(score_project_match(query, p), p) for p in projects]
|
||||
scored.sort(key=lambda x: x[0], reverse=True)
|
||||
results = []
|
||||
for score, p in scored[:5]:
|
||||
|
||||
@@ -87,3 +87,25 @@ def test_score_project_match_empty_query_returns_zero():
|
||||
from fabledassistant.services.tools._helpers import score_project_match
|
||||
p = _project(5, "Famous-Supply Work topics")
|
||||
assert score_project_match("", p) == 0.0
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_search_projects_tool_ranks_substring_match_above_others():
|
||||
"""Substring hits (score 0.85) must rank above SequenceMatcher misses
|
||||
for unrelated projects."""
|
||||
from fabledassistant.services.tools import projects as projects_tool
|
||||
|
||||
fake_projects = [
|
||||
_project(12, "Minstrel", auto_summary="self-hosted music server"),
|
||||
_project(5, "Famous-Supply Work topics", auto_summary="AT&T fiber circuit"),
|
||||
_project(13, "ImageRepo", auto_summary="self-hosted Flask app"),
|
||||
]
|
||||
|
||||
with patch.object(projects_tool, "list_projects", new=AsyncMock(return_value=fake_projects)):
|
||||
result = await projects_tool.search_projects_tool(
|
||||
user_id=1, arguments={"query": "famous supply project"},
|
||||
)
|
||||
|
||||
top = result["data"]["projects"][0]
|
||||
assert top["id"] == 5, f"expected Famous-Supply to rank first, got {top}"
|
||||
assert top["score"] >= 0.85
|
||||
|
||||
Reference in New Issue
Block a user