Closes #114, #115, #116, #117. - Deterministic, offline, dependency-free char-ngram embedding backend + config section (semantic.enabled default false, min_similarity, backend). - Committed embedding index (data/kb_embedding_index.json) mapping vectors to real KB entry ids only; built by scripts/build_embedding_index.py, loaded once/cached. - Resolver falls back to the nearest entry on a deterministic miss when enabled, accepting >= min_similarity and emitting SC0020; NSFW-gated neighbours are skipped. Disabled => byte-identical to before. - Guarantee tests: deterministic lookup always wins (exact hit never queries the index), fallback emits only KB tags (no invention), identical input => identical output. SC0020 registered. Co-authored-by: kunail0804 <kunail0804@gmail.com> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
66 lines
2.2 KiB
Python
66 lines
2.2 KiB
Python
"""Tests for the Knowledge Base embedding index (issue #115, epic #34)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from pathlib import Path
|
|
|
|
from compiler.common.embedding_index import EmbeddingIndex, build_index_rows, load_index
|
|
from compiler.common.knowledge_base import KnowledgeBase, KnowledgeBaseEntry, load_knowledge_base
|
|
|
|
REPO_ROOT = Path(__file__).resolve().parent.parent
|
|
KB_DIR = REPO_ROOT / "knowledge_base"
|
|
INDEX_FILE = REPO_ROOT / "data" / "kb_embedding_index.json"
|
|
|
|
|
|
def _entry(id_, tags, aliases=()):
|
|
return KnowledgeBaseEntry(id=id_, tags=tuple(tags), category="clothing", aliases=tuple(aliases))
|
|
|
|
|
|
# --- committed reference index ---------------------------------------------
|
|
|
|
|
|
def test_committed_index_maps_known_concept_to_expected_entry() -> None:
|
|
index = load_index(INDEX_FILE)
|
|
match = index.nearest("stockings")
|
|
assert match.entry_id == "thighhighs" # via the ingested alias surface
|
|
assert match.score > 0.99 # near-exact (weights are rounded in the snapshot)
|
|
|
|
|
|
def test_index_only_references_real_kb_entries() -> None:
|
|
kb = load_knowledge_base(KB_DIR)
|
|
index = load_index(INDEX_FILE)
|
|
# Every row id is a real entry: a fallback can never invent a tag.
|
|
match = index.nearest("blue eyes")
|
|
assert kb.get(match.entry_id) is not None
|
|
|
|
|
|
# --- build determinism ------------------------------------------------------
|
|
|
|
|
|
def test_build_is_deterministic() -> None:
|
|
kb = KnowledgeBase(
|
|
[
|
|
_entry("thighhighs", ["thighhighs"], aliases=["thigh highs", "stockings"]),
|
|
_entry("pantyhose", ["pantyhose"], aliases=["tights"]),
|
|
]
|
|
)
|
|
assert build_index_rows(kb) == build_index_rows(kb)
|
|
|
|
|
|
def test_nearest_returns_expected_surface() -> None:
|
|
kb = KnowledgeBase(
|
|
[
|
|
_entry("thighhighs", ["thighhighs"], aliases=["stockings"]),
|
|
_entry("dress", ["dress"]),
|
|
]
|
|
)
|
|
index = EmbeddingIndex(build_index_rows(kb))
|
|
match = index.nearest("stockings")
|
|
assert match.entry_id == "thighhighs"
|
|
assert match.surface == "stockings"
|
|
|
|
|
|
def test_empty_query_has_no_neighbour() -> None:
|
|
index = EmbeddingIndex(build_index_rows(KnowledgeBase([_entry("dress", ["dress"])])))
|
|
assert index.nearest("") is None
|