hermes-agent/tests/plugins/memory/test_holographic_retrieval.py

98 lines
3.4 KiB
Python

"""Tests for FactRetriever FTS5 query sanitization.
These tests cover the fix where raw natural-language queries passed to
FTS5 MATCH were AND-joined by default, dropping recall to zero on any
multi-word prose query. The sanitizer drops stopwords and OR-joins the
remaining content tokens as phrase literals.
"""
from __future__ import annotations
import pytest
pytest.importorskip("numpy") # retrieval module imports numpy indirectly
from plugins.memory.holographic.retrieval import FactRetriever
from plugins.memory.holographic.store import MemoryStore
# ---------------------------------------------------------------------------
# _sanitize_fts_query — unit tests (no DB required)
# ---------------------------------------------------------------------------
@pytest.mark.parametrize(
"query,expected_tokens",
[
# stopwords dropped
("what happened with the deployment rollback", {"happened", "deployment", "rollback"}),
# single content word passes through
("compaction", {"compaction"}),
# all stopwords → falls back to raw
("the and of", None), # None = sentinel for fallback-to-raw
# empty string → empty output
("", ""),
# FTS5 operator characters stripped
("context: length-probe", {"context", "lengthprobe"}),
# trailing punctuation stripped by tokenizer
("hello, world!", {"hello", "world"}),
],
)
def test_sanitize_fts_query_extracts_content_tokens(query, expected_tokens):
result = FactRetriever._sanitize_fts_query(query)
if expected_tokens == "":
assert result == ""
return
if expected_tokens is None:
# Pathological case: all stopwords — should fall back to raw query
assert result == query
return
# OR-joined phrase literals: `"tok1" OR "tok2" OR ...`
# Extract the tokens between quotes, order-independent.
import re
matches = re.findall(r'"([^"]+)"', result)
assert set(matches) == expected_tokens, f"got {result!r}"
# ---------------------------------------------------------------------------
# Integration test — actually run _fts_candidates against an in-memory DB
# ---------------------------------------------------------------------------
@pytest.fixture
def retriever_with_facts(tmp_path):
"""MemoryStore seeded with a few facts for retrieval tests."""
db_path = tmp_path / "test_facts.db"
store = MemoryStore(str(db_path))
store.add_fact(
content="The Thursday deployment rollback failed because of stale migration state.",
category="project",
)
store.add_fact(
content="Compaction settings tuned to 0.85 threshold.",
category="tool",
)
store.add_fact(
content="Venice.ai advertises availableContextTokens inside model_spec.",
category="tool",
)
retriever = FactRetriever(store=store)
yield retriever
store.close()
def test_prefetch_recovers_prose_query(retriever_with_facts):
"""A natural-language query should now match the relevant fact.
Before the sanitizer fix, 'what happened with the deployment rollback'
returned zero hits because FTS5 required every token to co-occur.
"""
results = retriever_with_facts.search(
"what happened with the deployment rollback"
)
assert len(results) >= 1
# The top hit should be the deployment rollback fact
assert "deployment rollback" in results[0]["content"].lower()