mirror of
https://github.com/PR0M3TH3AN/SeedPass.git
synced 2026-11-02 02:42:04 +00:00
feat: add semantic search modes across cli api and tuis
This commit is contained in:
1 parent
ec407c611a
commit
f0c120f84d
16 files changed
+460
-49
No files matched your search
@@ -1725,11 +1725,18 @@ async def test_semantic_endpoints(client, monkeypatch):
|
||||
|
||||
class DummySemanticService:
|
||||
def __init__(self, _pm):
|
||||
self.enabled = True
|
||||
self.last_enabled = None
|
||||
self.last_search = None
|
||||
self.mode = "keyword"
|
||||
|
||||
def status(self):
|
||||
return {"enabled": True, "built": True, "records": 4}
|
||||
return {
|
||||
"enabled": bool(self.enabled),
|
||||
"built": True,
|
||||
"records": 4,
|
||||
"mode": self.mode,
|
||||
}
|
||||
|
||||
def build(self):
|
||||
return {"enabled": True, "built": True, "records": 4}
|
||||
@@ -1739,10 +1746,22 @@ async def test_semantic_endpoints(client, monkeypatch):
|
||||
|
||||
def set_enabled(self, enabled: bool):
|
||||
self.last_enabled = bool(enabled)
|
||||
self.enabled = bool(enabled)
|
||||
return {"enabled": bool(enabled)}
|
||||
|
||||
def search(self, query: str, *, k: int = 10, kind: str | None = None):
|
||||
self.last_search = (query, k, kind)
|
||||
def set_mode(self, mode: str):
|
||||
self.mode = str(mode)
|
||||
return self.status()
|
||||
|
||||
def search(
|
||||
self,
|
||||
query: str,
|
||||
*,
|
||||
k: int = 10,
|
||||
kind: str | None = None,
|
||||
mode: str | None = None,
|
||||
):
|
||||
self.last_search = (query, k, kind, mode)
|
||||
return [{"entry_id": 10, "kind": kind or "document", "score": 0.5}]
|
||||
|
||||
service = DummySemanticService(None)
|
||||
@@ -1763,21 +1782,22 @@ async def test_semantic_endpoints(client, monkeypatch):
|
||||
|
||||
configured = await cl.post(
|
||||
"/api/v1/semantic/config",
|
||||
json={"enabled": False},
|
||||
json={"enabled": False, "mode": "hybrid"},
|
||||
headers=headers,
|
||||
)
|
||||
assert configured.status_code == 200
|
||||
assert configured.json() == {"enabled": False}
|
||||
assert configured.json() == {"enabled": False, "mode": "hybrid"}
|
||||
assert service.last_enabled is False
|
||||
assert service.mode == "hybrid"
|
||||
|
||||
searched = await cl.post(
|
||||
"/api/v1/semantic/search",
|
||||
json={"query": "relay notes", "k": 6, "kind": "document"},
|
||||
json={"query": "relay notes", "k": 6, "kind": "document", "mode": "semantic"},
|
||||
headers=headers,
|
||||
)
|
||||
assert searched.status_code == 200
|
||||
assert searched.json()["results"][0]["entry_id"] == 10
|
||||
assert service.last_search == ("relay notes", 6, "document")
|
||||
assert service.last_search == ("relay notes", 6, "document", "semantic")
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
@@ -1790,9 +1810,9 @@ async def test_semantic_search_requires_query(client):
|
||||
|
||||
|
||||
@pytest.mark.anyio
|
||||
async def test_semantic_config_requires_enabled_flag(client):
|
||||
async def test_semantic_config_requires_enabled_or_mode(client):
|
||||
cl, token = client
|
||||
headers = {"Authorization": f"Bearer {token}"}
|
||||
res = await cl.post("/api/v1/semantic/config", json={}, headers=headers)
|
||||
assert res.status_code == 400
|
||||
assert res.json() == {"detail": "Missing enabled"}
|
||||
assert res.json() == {"detail": "Missing enabled or mode"}
|
||||
@@ -9,19 +9,33 @@ runner = CliRunner()
|
||||
|
||||
|
||||
class _DummySemanticService:
|
||||
def __init__(self) -> None:
|
||||
self.mode = "keyword"
|
||||
|
||||
def status(self):
|
||||
return {"enabled": True, "built": True, "records": 2}
|
||||
return {"enabled": True, "built": True, "records": 2, "mode": self.mode}
|
||||
|
||||
def set_enabled(self, enabled: bool):
|
||||
return {"enabled": bool(enabled), "built": True, "records": 2}
|
||||
|
||||
def set_mode(self, mode: str):
|
||||
self.mode = str(mode)
|
||||
return self.status()
|
||||
|
||||
def build(self):
|
||||
return {"enabled": True, "built": True, "records": 3}
|
||||
|
||||
def rebuild(self):
|
||||
return {"enabled": True, "built": True, "records": 3}
|
||||
|
||||
def search(self, query: str, *, k: int = 10, kind: str | None = None):
|
||||
def search(
|
||||
self,
|
||||
query: str,
|
||||
*,
|
||||
k: int = 10,
|
||||
kind: str | None = None,
|
||||
mode: str | None = None,
|
||||
):
|
||||
if not query:
|
||||
return []
|
||||
return [
|
||||
@@ -70,7 +84,25 @@ def test_semantic_cli_commands(monkeypatch):
|
||||
|
||||
result = runner.invoke(
|
||||
app,
|
||||
["semantic", "search", "relay", "--k", "3", "--kind", "document", "--json"],
|
||||
[
|
||||
"semantic",
|
||||
"search",
|
||||
"relay",
|
||||
"--k",
|
||||
"3",
|
||||
"--kind",
|
||||
"document",
|
||||
"--mode",
|
||||
"hybrid",
|
||||
"--json",
|
||||
],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert '"entry_id": 7' in result.stdout
|
||||
|
||||
result = runner.invoke(
|
||||
app,
|
||||
["semantic", "config", "--enabled", "true", "--mode", "semantic"],
|
||||
)
|
||||
assert result.exit_code == 0
|
||||
assert '"mode": "semantic"' in result.stdout
|
||||
@@ -49,6 +49,7 @@ def test_semantic_index_build_and_search(tmp_path: Path) -> None:
|
||||
class _DummyConfig:
|
||||
def __init__(self) -> None:
|
||||
self.enabled = False
|
||||
self.mode = "keyword"
|
||||
|
||||
def set_semantic_index_enabled(self, enabled: bool) -> None:
|
||||
self.enabled = bool(enabled)
|
||||
@@ -56,6 +57,12 @@ class _DummyConfig:
|
||||
def get_semantic_index_enabled(self) -> bool:
|
||||
return bool(self.enabled)
|
||||
|
||||
def set_semantic_search_mode(self, mode: str) -> None:
|
||||
self.mode = str(mode)
|
||||
|
||||
def get_semantic_search_mode(self) -> str:
|
||||
return str(self.mode)
|
||||
|
||||
|
||||
class _DummyEntryManager:
|
||||
def __init__(self) -> None:
|
||||
@@ -106,3 +113,14 @@ def test_semantic_index_service_build_and_status(tmp_path: Path) -> None:
|
||||
results = service.search("agent", k=3)
|
||||
assert len(results) >= 1
|
||||
assert results[0]["entry_id"] == 1
|
||||
|
||||
service.set_mode("semantic")
|
||||
semantic_only = service.search("agent", k=3, mode="semantic")
|
||||
assert len(semantic_only) >= 1
|
||||
|
||||
service.set_mode("keyword")
|
||||
keyword_only = service.search("agent", k=3, mode="keyword")
|
||||
assert len(keyword_only) >= 1
|
||||
|
||||
hybrid = service.search("agent", k=3, mode="hybrid")
|
||||
assert len(hybrid) >= 1
|
||||
@@ -375,9 +375,10 @@ def test_semantic_submenu_search_flow(monkeypatch, capsys):
|
||||
class DummySemanticService:
|
||||
def __init__(self, _pm):
|
||||
self.last_query = None
|
||||
self.mode = "keyword"
|
||||
|
||||
def status(self):
|
||||
return {"enabled": False, "built": False, "records": 0}
|
||||
return {"enabled": False, "built": False, "records": 0, "mode": self.mode}
|
||||
|
||||
def set_enabled(self, enabled: bool):
|
||||
return {"enabled": bool(enabled), "records": 0}
|
||||
@@ -388,6 +389,10 @@ def test_semantic_submenu_search_flow(monkeypatch, capsys):
|
||||
def rebuild(self):
|
||||
return {"enabled": True, "built": True, "records": 2}
|
||||
|
||||
def set_mode(self, mode: str):
|
||||
self.mode = str(mode)
|
||||
return {"mode": self.mode}
|
||||
|
||||
def search(self, query: str, *, k: int = 10, kind: str | None = None):
|
||||
self.last_query = query
|
||||
return [{"entry_id": 1, "kind": "document", "label": "Doc", "score": 0.5}]
|
||||
@@ -397,11 +402,12 @@ def test_semantic_submenu_search_flow(monkeypatch, capsys):
|
||||
pm, _, _ = setup_pm(tmp_path, monkeypatch)
|
||||
monkeypatch.setattr(main, "SemanticIndexService", DummySemanticService)
|
||||
monkeypatch.setattr(main, "pause", lambda: None)
|
||||
inputs = iter(["1", "2", "4", "6", "relay docs", ""])
|
||||
inputs = iter(["1", "7", "hybrid", "2", "4", "6", "relay docs", ""])
|
||||
with patch("builtins.input", side_effect=lambda *_: next(inputs)):
|
||||
main.handle_semantic_index_menu(pm)
|
||||
out = capsys.readouterr().out
|
||||
assert "Status: enabled=False, built=False, records=0" in out
|
||||
assert "Status: enabled=False, built=False, records=0, mode=keyword" in out
|
||||
assert "Semantic search mode set to hybrid." in out
|
||||
assert "Semantic index enabled" in out
|
||||
assert "Semantic index built with 2 records" in out
|
||||
assert "Semantic Matches:" in out
|
||||
|
||||
@@ -561,6 +561,8 @@ async def test_tui2_matrix_actions_palette_events_and_guards() -> None:
|
||||
app._run_palette_command("setting-kdf-iterations 100000")
|
||||
app._run_palette_command("setting-kdf-mode pbkdf2")
|
||||
app._run_palette_command("semantic-status")
|
||||
app._run_palette_command("search-mode")
|
||||
app._run_palette_command("search-mode nope")
|
||||
app._run_palette_command("semantic-enable now")
|
||||
app._run_palette_command("semantic-disable now")
|
||||
app._run_palette_command("semantic-build now")
|
||||
@@ -607,6 +609,9 @@ async def test_tui2_matrix_actions_palette_events_and_guards() -> None:
|
||||
app._run_palette_command("lock")
|
||||
app._run_palette_command("unlock hunter2")
|
||||
app._run_palette_command("search Doc")
|
||||
app._run_palette_command("search-mode hybrid")
|
||||
app._run_palette_command("search-mode semantic")
|
||||
app._run_palette_command("search-mode keyword")
|
||||
app._run_palette_command("semantic-status")
|
||||
app._run_palette_command("semantic-search notes")
|
||||
app.action_focus_left()
|
||||
|
||||
@@ -567,6 +567,7 @@ class FakeSemanticService:
|
||||
self.enabled = False
|
||||
self.built = False
|
||||
self.records = 0
|
||||
self.mode = "keyword"
|
||||
self.last_query: str | None = None
|
||||
|
||||
def status(self):
|
||||
@@ -574,6 +575,7 @@ class FakeSemanticService:
|
||||
"enabled": bool(self.enabled),
|
||||
"built": bool(self.built),
|
||||
"records": int(self.records),
|
||||
"mode": str(self.mode),
|
||||
}
|
||||
|
||||
def set_enabled(self, enabled: bool):
|
||||
@@ -590,8 +592,21 @@ class FakeSemanticService:
|
||||
self.records = 2
|
||||
return self.status()
|
||||
|
||||
def search(self, query: str, *, k: int = 10, kind: str | None = None):
|
||||
def set_mode(self, mode: str):
|
||||
self.mode = str(mode)
|
||||
return self.status()
|
||||
|
||||
def search(
|
||||
self,
|
||||
query: str,
|
||||
*,
|
||||
k: int = 10,
|
||||
kind: str | None = None,
|
||||
mode: str | None = None,
|
||||
):
|
||||
self.last_query = str(query)
|
||||
if mode:
|
||||
self.mode = str(mode)
|
||||
if not query.strip():
|
||||
return []
|
||||
return [
|
||||
@@ -1669,6 +1684,11 @@ async def test_tui2_textual_profiles_and_settings_palette_commands() -> None:
|
||||
assert semantic.enabled is True
|
||||
assert "Semantic index enabled" in _status_text(app)
|
||||
|
||||
app._run_palette_command("search-mode hybrid")
|
||||
await pilot.pause()
|
||||
assert semantic.mode == "hybrid"
|
||||
assert "Search mode set to hybrid" in _status_text(app)
|
||||
|
||||
app._run_palette_command("semantic-build")
|
||||
await pilot.pause()
|
||||
assert semantic.built is True
|
||||
@@ -1916,6 +1936,10 @@ async def test_tui2_textual_profiles_and_settings_palette_validation() -> None:
|
||||
await pilot.pause()
|
||||
assert "Semantic service unavailable" in _status_text(app)
|
||||
|
||||
app._run_palette_command("search-mode semantic")
|
||||
await pilot.pause()
|
||||
assert "Semantic service unavailable" in _status_text(app)
|
||||
|
||||
app._run_palette_command("semantic-search")
|
||||
await pilot.pause()
|
||||
assert "Semantic service unavailable" in _status_text(app)
|
||||
@@ -1974,6 +1998,16 @@ async def test_tui2_textual_profiles_and_settings_palette_validation() -> None:
|
||||
await pilot.pause()
|
||||
assert "Usage: semantic-enable" in _status_text(app)
|
||||
|
||||
app._run_palette_command("search-mode")
|
||||
await pilot.pause()
|
||||
assert "Usage: search-mode <keyword|hybrid|semantic>" in _status_text(app)
|
||||
|
||||
app._run_palette_command("search-mode nope")
|
||||
await pilot.pause()
|
||||
assert "search-mode must be one of: keyword, hybrid, semantic" in _status_text(
|
||||
app
|
||||
)
|
||||
|
||||
app._run_palette_command("semantic-search")
|
||||
await pilot.pause()
|
||||
assert "Usage: semantic-search <query>" in _status_text(app)
|
||||
|
||||
Reference in new issue
Block a user