Overhaul docs retrieval and web search quality
Replace the abandoned llms-txt-mcp/Chroma docs backend with an in-repo MCP service: SQLite WAL + FTS5 + sentence-transformer embeddings, transactional source replacement, persisted state across restarts, singleflight refresh with conditional requests, hybrid lexical/semantic ranking with exact-duplicate collapse, source/host filters, and explicit-by-default content retrieval. Add docs_rebuild and a docs-rebuild CLI command. Add deterministic llms-full.txt snapshot generation for machine-local menus with hash-validated provenance manifests; lifecycle commands promote a local menu to its snapshot only when the manifest validates. Switch public source profiles to content-bearing llms-full.txt feeds. Improve web search: bounded provider fallback with per-attempt diagnostics and cancellation, an optional Brave Search API provider, strict SearXNG engine selection, capped link/media extraction, and a real engine=browser renderer that routes every request through the existing SSRF vetting while blocking WebSockets, non-GET traffic, and private destinations. Extend release checks with offline unit suites and isolated candidate container tests for both images.
This commit is contained in:
105
docker/docs/tests/test_search.py
Normal file
105
docker/docs/tests/test_search.py
Normal file
@@ -0,0 +1,105 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
from context_docs.models import PreparedDocument, SourceUpdate
|
||||
from context_docs.search import HybridSearch
|
||||
from context_docs.store import IndexStore
|
||||
|
||||
from .fakes import FakeEmbedder
|
||||
|
||||
|
||||
def prepared(identifier: str, source: str, canonical: str, title: str, content: str, vector) -> PreparedDocument:
|
||||
return PreparedDocument(
|
||||
id=identifier,
|
||||
configured_source=source,
|
||||
resolved_source=source,
|
||||
source_host="source.test",
|
||||
canonical_url=canonical,
|
||||
canonical_host=canonical.split("/")[2],
|
||||
title=title,
|
||||
description="",
|
||||
heading_path=title,
|
||||
content=content,
|
||||
content_hash=__import__("hashlib").sha256(content.encode()).hexdigest(),
|
||||
embedding=np.asarray(vector, dtype=np.float32),
|
||||
)
|
||||
|
||||
|
||||
def source_update(source: str, documents: list[PreparedDocument]) -> SourceUpdate:
|
||||
return SourceUpdate(
|
||||
configured_source=source,
|
||||
resolved_source=source,
|
||||
etag=None,
|
||||
last_modified=None,
|
||||
body_hash="body",
|
||||
raw_body="# source",
|
||||
parser_fingerprint="parser-v1",
|
||||
embedding_fingerprint="fake-embedder-v1",
|
||||
checked_at=1.0,
|
||||
indexed_at=1.0,
|
||||
stale_at=9999.0,
|
||||
documents=documents,
|
||||
)
|
||||
|
||||
|
||||
class HybridSearchTest(unittest.IsolatedAsyncioTestCase):
|
||||
async def asyncSetUp(self) -> None:
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
self.store = IndexStore(Path(self.tmp.name) / "docs.sqlite3")
|
||||
self.a = "https://a.test/llms.txt"
|
||||
self.b = "https://b.test/llms.txt"
|
||||
self.store.configure_sources([self.a, self.b])
|
||||
duplicate = "Shared exact content."
|
||||
self.store.replace_source(
|
||||
source_update(
|
||||
self.a,
|
||||
[
|
||||
prepared("exact", self.a, "https://rails.test/exact", "Environment", "IMMICH_IGNORE_MOUNT_CHECK_ERRORS identifier", [1, 0, 0, 0]),
|
||||
prepared("duplicate-a", self.a, "https://docs.test/shared", "Shared", duplicate, [0, 1, 0, 0]),
|
||||
],
|
||||
)
|
||||
)
|
||||
self.store.replace_source(
|
||||
source_update(
|
||||
self.b,
|
||||
[
|
||||
prepared("persistence", self.b, "https://langgraph.test/persistence", "Persistence", "Durable checkpoint state", [0, 1, 0, 0]),
|
||||
prepared("duplicate-b", self.b, "https://docs.test/shared-copy", "Shared copy", duplicate, [0, 1, 0, 0]),
|
||||
],
|
||||
)
|
||||
)
|
||||
self.search = HybridSearch(self.store, FakeEmbedder())
|
||||
|
||||
async def asyncTearDown(self) -> None:
|
||||
self.store.close()
|
||||
self.tmp.cleanup()
|
||||
|
||||
async def test_exact_identifier_is_ranked_first(self) -> None:
|
||||
result = await self.search.search("IMMICH_IGNORE_MOUNT_CHECK_ERRORS", limit=5)
|
||||
self.assertEqual("exact", result[0].id)
|
||||
self.assertEqual(1, result[0].lexical_rank)
|
||||
|
||||
async def test_source_and_host_filters_apply_before_ranking(self) -> None:
|
||||
by_source = await self.search.search("persistence", limit=5, sources=[self.b])
|
||||
by_host = await self.search.search("identifier", limit=5, hosts=["rails.test"])
|
||||
|
||||
self.assertTrue(by_source)
|
||||
self.assertTrue(all(item.configured_source == self.b for item in by_source))
|
||||
self.assertEqual(["exact"], [item.id for item in by_host])
|
||||
|
||||
async def test_exact_duplicate_content_is_collapsed_with_alternates(self) -> None:
|
||||
result = await self.search.search("Shared exact content", limit=10)
|
||||
shared = [item for item in result if item.content_hash == __import__("hashlib").sha256("Shared exact content.".encode()).hexdigest()]
|
||||
|
||||
self.assertEqual(1, len(shared))
|
||||
self.assertEqual(2, shared[0].duplicate_count)
|
||||
self.assertEqual(1, len(shared[0].alternate_sources))
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user