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:
98
docker/docs/tests/test_service.py
Normal file
98
docker/docs/tests/test_service.py
Normal file
@@ -0,0 +1,98 @@
|
||||
from __future__ import annotations
|
||||
|
||||
import tempfile
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
|
||||
import numpy as np
|
||||
|
||||
from context_docs.models import PreparedDocument, RefreshOutcome, SourceUpdate
|
||||
from context_docs.search import HybridSearch
|
||||
from context_docs.service import DocsService
|
||||
from context_docs.store import IndexStore
|
||||
|
||||
from .fakes import FakeEmbedder
|
||||
|
||||
|
||||
class NoopRefresh:
|
||||
async def refresh(self, source: str, force: bool = False) -> RefreshOutcome:
|
||||
return RefreshOutcome(source, "fresh", 1)
|
||||
|
||||
|
||||
class ServiceTest(unittest.IsolatedAsyncioTestCase):
|
||||
async def asyncSetUp(self) -> None:
|
||||
self.tmp = tempfile.TemporaryDirectory()
|
||||
self.store = IndexStore(Path(self.tmp.name) / "docs.sqlite3")
|
||||
self.source = "https://example.test/llms.txt"
|
||||
self.store.configure_sources([self.source])
|
||||
content = "IMMICH_IGNORE_MOUNT_CHECK_ERRORS " + "x" * 200
|
||||
item = PreparedDocument(
|
||||
id="exact",
|
||||
configured_source=self.source,
|
||||
resolved_source=self.source,
|
||||
source_host="example.test",
|
||||
canonical_url="https://docs.example.test/environment",
|
||||
canonical_host="docs.example.test",
|
||||
title="Environment",
|
||||
description="",
|
||||
heading_path="Environment",
|
||||
content=content,
|
||||
content_hash="content-hash",
|
||||
embedding=np.asarray([1.0, 0.0, 0.0, 0.0], dtype=np.float32),
|
||||
)
|
||||
self.store.replace_source(
|
||||
SourceUpdate(
|
||||
configured_source=self.source,
|
||||
resolved_source=self.source,
|
||||
etag=None,
|
||||
last_modified=None,
|
||||
body_hash="body",
|
||||
raw_body="# body",
|
||||
parser_fingerprint="context-docs-parser-v1",
|
||||
embedding_fingerprint="fake-embedder-v1",
|
||||
checked_at=1.0,
|
||||
indexed_at=1.0,
|
||||
stale_at=9_999_999_999.0,
|
||||
documents=[item],
|
||||
)
|
||||
)
|
||||
embedder = FakeEmbedder()
|
||||
self.service = DocsService(
|
||||
self.store,
|
||||
HybridSearch(self.store, embedder),
|
||||
NoopRefresh(),
|
||||
max_get_bytes=100,
|
||||
)
|
||||
|
||||
async def asyncTearDown(self) -> None:
|
||||
self.store.close()
|
||||
self.tmp.cleanup()
|
||||
|
||||
async def test_query_does_not_retrieve_content_by_default(self) -> None:
|
||||
response = await self.service.query("IMMICH_IGNORE_MOUNT_CHECK_ERRORS")
|
||||
|
||||
self.assertEqual({}, response["retrieved_content"])
|
||||
self.assertEqual("exact", response["search_results"][0]["id"])
|
||||
|
||||
async def test_explicit_retrieval_respects_global_byte_cap(self) -> None:
|
||||
response = await self.service.query(
|
||||
"IMMICH_IGNORE_MOUNT_CHECK_ERRORS",
|
||||
retrieve_ids=["exact"],
|
||||
max_bytes=10_000,
|
||||
)
|
||||
retrieved = response["retrieved_content"]["exact"]
|
||||
|
||||
self.assertLessEqual(len(retrieved["content"].encode()), 100)
|
||||
self.assertTrue(retrieved["truncated"])
|
||||
|
||||
async def test_high_default_threshold_only_retrieves_strong_hybrid_match(self) -> None:
|
||||
response = await self.service.query(
|
||||
"IMMICH_IGNORE_MOUNT_CHECK_ERRORS",
|
||||
auto_retrieve=True,
|
||||
)
|
||||
|
||||
self.assertIn("exact", response["retrieved_content"])
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user