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:
2026-05-12 16:42:34 -04:00
parent 0dbbb98cf5
commit 460959f0d4
2 changed files with 25 additions and 11 deletions
+3 -11
View File
@@ -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]:
+22
View File
@@ -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