139 lines
5.6 KiB
Python
139 lines
5.6 KiB
Python
import importlib.util
|
|
import json
|
|
import subprocess
|
|
import sys
|
|
import types
|
|
import unittest
|
|
from pathlib import Path
|
|
from typing import cast
|
|
from unittest import mock
|
|
|
|
MODULE_PATH = Path(__file__).resolve().parents[1] / "scripts" / "obsidian-reindex-server.py"
|
|
|
|
|
|
def load_module():
|
|
spec = importlib.util.spec_from_file_location("obsidian_reindex_server", MODULE_PATH)
|
|
assert spec is not None and spec.loader is not None
|
|
module = importlib.util.module_from_spec(spec)
|
|
sys.modules[spec.name] = module
|
|
spec.loader.exec_module(module)
|
|
return cast(types.ModuleType, module)
|
|
|
|
|
|
class SemanticSearchRerankTests(unittest.TestCase):
|
|
def setUp(self):
|
|
self.server = load_module()
|
|
self.results = [
|
|
{"id": "a", "text": "alpha doc", "path": "a.md", "score": 0.1},
|
|
{"id": "b", "text": "beta doc", "path": "b.md", "score": 0.2},
|
|
{"id": "c", "text": "gamma doc", "path": "c.md", "score": 0.3},
|
|
]
|
|
|
|
def _mock_search_run(self, expected_top_k=None):
|
|
def fake_run(cmd, capture_output, text, timeout, env):
|
|
if expected_top_k is not None:
|
|
self.assertEqual(cmd[cmd.index("--top-k") + 1], str(expected_top_k))
|
|
return subprocess.CompletedProcess(
|
|
cmd,
|
|
0,
|
|
stdout=json.dumps({"index": "obsidian_bge_npu", "results": self.results}),
|
|
stderr="",
|
|
)
|
|
|
|
return fake_run
|
|
|
|
def test_disabled_rerank_preserves_vector_order(self):
|
|
setattr(self.server, "RAG_RERANK_ENABLED", False)
|
|
with mock.patch.object(self.server.subprocess, "run", self._mock_search_run(expected_top_k=2)):
|
|
payload = self.server.run_semantic_search("npu smoke", top_k=2)
|
|
self.assertTrue(payload["ok"])
|
|
self.assertEqual(payload["search_k"], 2)
|
|
self.assertEqual([item["id"] for item in payload["results"]], ["a", "b"])
|
|
self.assertEqual(payload["rerank"]["reason"], "disabled")
|
|
self.assertFalse(payload["rerank"]["attempted"])
|
|
|
|
def test_enabled_rerank_reorders_matching_results(self):
|
|
setattr(self.server, "RAG_RERANK_ENABLED", True)
|
|
setattr(self.server, "RAG_RERANK_INITIAL_K", 3)
|
|
setattr(self.server, "RAG_RERANK_TOP_K", 2)
|
|
|
|
class FakeResponse:
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *args):
|
|
return False
|
|
|
|
def read(self):
|
|
return json.dumps(
|
|
{
|
|
"ok": True,
|
|
"model": "synthetic-reranker",
|
|
"device": "NPU",
|
|
"npu_busy_delta_us": 123,
|
|
"results": [
|
|
{"id": "c", "score": 9.0},
|
|
{"id": "a", "score": 7.0},
|
|
],
|
|
}
|
|
).encode()
|
|
|
|
with mock.patch.object(self.server.subprocess, "run", self._mock_search_run(expected_top_k=3)), mock.patch.object(
|
|
self.server.request, "urlopen", return_value=FakeResponse()
|
|
):
|
|
payload = self.server.run_semantic_search("npu smoke", top_k=2)
|
|
self.assertEqual([item["id"] for item in payload["results"]], ["c", "a"])
|
|
self.assertTrue(payload["rerank"]["attempted"])
|
|
self.assertTrue(payload["rerank"]["ok"])
|
|
self.assertEqual(payload["rerank"]["npu_busy_delta_us"], 123)
|
|
self.assertEqual(payload["results"][0]["rerank_rank"], 1)
|
|
|
|
def test_enabled_rerank_error_falls_back_to_vector_order(self):
|
|
setattr(self.server, "RAG_RERANK_ENABLED", True)
|
|
setattr(self.server, "RAG_RERANK_INITIAL_K", 3)
|
|
setattr(self.server, "RAG_RERANK_TOP_K", 2)
|
|
with mock.patch.object(self.server.subprocess, "run", self._mock_search_run(expected_top_k=3)), mock.patch.object(
|
|
self.server.request, "urlopen", side_effect=OSError("reranker unavailable")
|
|
):
|
|
payload = self.server.run_semantic_search("npu smoke", top_k=2)
|
|
self.assertEqual([item["id"] for item in payload["results"]], ["a", "b"])
|
|
self.assertTrue(payload["rerank"]["attempted"])
|
|
self.assertFalse(payload["rerank"]["ok"])
|
|
self.assertIn("reranker unavailable", payload["rerank"]["error"])
|
|
|
|
def test_enabled_rerank_requires_positive_npu_proof(self):
|
|
setattr(self.server, "RAG_RERANK_ENABLED", True)
|
|
setattr(self.server, "RAG_RERANK_INITIAL_K", 3)
|
|
setattr(self.server, "RAG_RERANK_TOP_K", 2)
|
|
setattr(self.server, "RAG_RERANK_REQUIRE_NPU_PROOF", True)
|
|
|
|
class FakeResponse:
|
|
def __enter__(self):
|
|
return self
|
|
|
|
def __exit__(self, *args):
|
|
return False
|
|
|
|
def read(self):
|
|
return json.dumps(
|
|
{
|
|
"ok": True,
|
|
"device": "NPU",
|
|
"npu_busy_delta_us": 0,
|
|
"results": [{"id": "c", "score": 9.0}],
|
|
}
|
|
).encode()
|
|
|
|
with mock.patch.object(self.server.subprocess, "run", self._mock_search_run(expected_top_k=3)), mock.patch.object(
|
|
self.server.request, "urlopen", return_value=FakeResponse()
|
|
):
|
|
payload = self.server.run_semantic_search("npu smoke", top_k=2)
|
|
self.assertEqual([item["id"] for item in payload["results"]], ["a", "b"])
|
|
self.assertTrue(payload["rerank"]["attempted"])
|
|
self.assertFalse(payload["rerank"]["ok"])
|
|
self.assertIn("positive npu_busy_delta_us", payload["rerank"]["error"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|