Files
87d9525176 feat(resolver): opt-in semantic nearest-neighbour fallback (#34) (#140)
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>
2026-07-13 18:23:38 +02:00

47 lines
1.5 KiB
Python

"""Build the committed Knowledge Base embedding index (issue #115, epic #34).
Embeds each Knowledge Base entry's surface forms (canonical id, tags, aliases)
with the deterministic offline backend and writes the index snapshot to
``data/kb_embedding_index.json``. The snapshot maps vectors back to existing entry
ids only, so the semantic fallback can never invent a tag. It is committed so CI
never recomputes embeddings.
Paths are hard-coded constants (no CLI/untrusted input) matching the style of
``scripts/build_vocab.py`` (SonarCloud S8707).
Usage:
python scripts/build_embedding_index.py
"""
from __future__ import annotations
import json
import sys
from pathlib import Path
sys.path.insert(0, str(Path(__file__).resolve().parent.parent))
from compiler.common.embedding_index import build_index_rows # noqa: E402
from compiler.common.knowledge_base import load_knowledge_base # noqa: E402
_REPO_ROOT = Path(__file__).resolve().parent.parent
_KB_DIR = _REPO_ROOT / "knowledge_base"
_OUTPUT = _REPO_ROOT / "data" / "kb_embedding_index.json"
def build() -> int:
kb = load_knowledge_base(_KB_DIR)
rows = build_index_rows(kb)
_OUTPUT.parent.mkdir(parents=True, exist_ok=True)
_OUTPUT.write_text(
json.dumps(rows, ensure_ascii=False, separators=(",", ":")) + "\n", encoding="utf-8"
)
print(
f"wrote {len(rows)} index rows for {len(kb)} entries to {_OUTPUT.relative_to(_REPO_ROOT)}"
)
return len(rows)
if __name__ == "__main__":
build()