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>
126 lines
3.8 KiB
Python
126 lines
3.8 KiB
Python
"""Tests for the semantic nearest-neighbour fallback (issue #116, epic #34)."""
|
|
|
|
from __future__ import annotations
|
|
|
|
from compiler.common.config import Config
|
|
from compiler.common.embedding_index import EmbeddingIndex, build_index_rows
|
|
from compiler.common.knowledge_base import KnowledgeBase, KnowledgeBaseEntry
|
|
from compiler.resolver.illustrious_resolver import resolve_scene
|
|
from schemas.models import Scene
|
|
|
|
|
|
def entry(id_, tags, category="clothing", aliases=(), rating="general"):
|
|
return KnowledgeBaseEntry(
|
|
id=id_, tags=tuple(tags), category=category, aliases=tuple(aliases), rating=rating
|
|
)
|
|
|
|
|
|
def kb() -> KnowledgeBase:
|
|
return KnowledgeBase(
|
|
[
|
|
entry("thighhighs", ["thighhighs"], aliases=["stockings"]),
|
|
entry("dress", ["dress"]),
|
|
entry("pussy", ["pussy"], category="body", rating="explicit"),
|
|
]
|
|
)
|
|
|
|
|
|
def index(knowledge: KnowledgeBase) -> EmbeddingIndex:
|
|
return EmbeddingIndex(build_index_rows(knowledge))
|
|
|
|
|
|
def scene_with(concept: str) -> Scene:
|
|
return Scene.from_json(
|
|
{
|
|
"characters": [
|
|
{
|
|
"id": 0,
|
|
"identity": [concept],
|
|
"appearance": [],
|
|
"clothing": [],
|
|
"accessories": [],
|
|
"pose": [],
|
|
"expression": [],
|
|
"actions": [],
|
|
}
|
|
],
|
|
"interactions": [],
|
|
"objects": [],
|
|
"environment": [],
|
|
"camera": [],
|
|
"lighting": [],
|
|
"metadata": {"schema_version": "1.0"},
|
|
}
|
|
)
|
|
|
|
|
|
def semantic_config(min_similarity: float = 0.5, include_nsfw: bool = False) -> Config:
|
|
return Config.from_json(
|
|
{
|
|
"semantic": {"enabled": True, "min_similarity": min_similarity},
|
|
"resolver": {"include_nsfw": include_nsfw},
|
|
}
|
|
)
|
|
|
|
|
|
def tags(result):
|
|
return [t.tag for t in result.data]
|
|
|
|
|
|
def codes(messages):
|
|
return [m.code for m in messages]
|
|
|
|
|
|
def test_near_concept_resolves_via_fallback_and_emits_sc0020() -> None:
|
|
knowledge = kb()
|
|
result = resolve_scene(
|
|
scene_with("thighighs"), knowledge, semantic_config(), embedding_index=index(knowledge)
|
|
)
|
|
assert tags(result) == ["thighhighs"]
|
|
assert "SC0020" in codes(result.warnings)
|
|
|
|
|
|
def test_far_concept_still_drops() -> None:
|
|
knowledge = kb()
|
|
result = resolve_scene(
|
|
scene_with("quantum flux inverter"),
|
|
knowledge,
|
|
semantic_config(min_similarity=0.6),
|
|
embedding_index=index(knowledge),
|
|
)
|
|
assert tags(result) == []
|
|
assert "SC0001" in codes(result.warnings)
|
|
assert "SC0020" not in codes(result.warnings)
|
|
|
|
|
|
def test_disabled_feature_is_byte_identical() -> None:
|
|
knowledge = kb()
|
|
disabled = resolve_scene(scene_with("thighighs"), knowledge, Config())
|
|
with_index_but_disabled = resolve_scene(
|
|
scene_with("thighighs"), knowledge, Config(), embedding_index=index(knowledge)
|
|
)
|
|
assert tags(disabled) == tags(with_index_but_disabled) == []
|
|
assert "SC0020" not in codes(with_index_but_disabled.warnings)
|
|
|
|
|
|
def test_deterministic_lookup_always_wins() -> None:
|
|
knowledge = kb()
|
|
result = resolve_scene(
|
|
scene_with("dress"), knowledge, semantic_config(), embedding_index=index(knowledge)
|
|
)
|
|
assert tags(result) == ["dress"]
|
|
assert "SC0020" not in codes(result.warnings) # exact hit, fallback not used
|
|
|
|
|
|
def test_gated_nsfw_neighbour_is_not_used() -> None:
|
|
knowledge = kb()
|
|
# "pusy" is closest to the explicit "pussy" entry, but NSFW is gated off.
|
|
result = resolve_scene(
|
|
scene_with("pusy"),
|
|
knowledge,
|
|
semantic_config(include_nsfw=False),
|
|
embedding_index=index(knowledge),
|
|
)
|
|
assert "SC0020" not in codes(result.warnings)
|
|
assert "SC0001" in codes(result.warnings)
|