Co-Authored-By: Claude <noreply@anthropic.com> Co-Authored-By: XNLLLLH <XNLLLLH@users.noreply.github.com>
204 lines
7.4 KiB
Python
204 lines
7.4 KiB
Python
"""Phase 8 redesign — load-bearing infrastructure tests.
|
|
|
|
Verifies the new shared-cosine helpers introduced by against
|
|
the locked decisions in `internal architecture spec
|
|
08-CONTEXT.md`:
|
|
|
|
- single shared cosine pass — `_collect_graph_pool` is the (ids, embs)
|
|
pool collector that feeds the one-shot matmul at the top of `_recall_core`.
|
|
- mode-dependent community-gate soft bias — `COMMUNITY_BIAS_VERBATIM`
|
|
(0.0, HIPPEA pure / EPF literal / hippocampal episodic) and
|
|
`COMMUNITY_BIAS_CONCEPT` (0.1, CLS neocortical semantic / categorical
|
|
hint), dispatched by `_gate_bias_for_mode(mode)` from the cue-classifier
|
|
in `core.dispatch()` (Plan 06-04 R5).
|
|
- candidate-pool size — `K_CANDIDATES = 200`, justified by the
|
|
empirical 99th-percentile gold rank from the LongMemEval-S v1 trace
|
|
plus 30% margin.
|
|
- `_RecallCoreResult` — dataclass shape returned by `_recall_core`.
|
|
|
|
These probes are the first wave of the redesign tests; the
|
|
heavier behavioural fixture (matmul-counter, gate-as-diagnostic, verbatim
|
|
filter placement, etc.) lives in `tests/test_recall_core_unit.py`.
|
|
"""
|
|
from __future__ import annotations
|
|
|
|
from datetime import datetime, timezone
|
|
from uuid import uuid4
|
|
|
|
import numpy as np
|
|
|
|
from iai_mcp.community import CommunityAssignment
|
|
from iai_mcp.graph import MemoryGraph
|
|
from iai_mcp.store import MemoryStore
|
|
from iai_mcp.types import EMBED_DIM, MemoryRecord
|
|
|
|
|
|
def _make(vec: list[float], text: str = "rec") -> MemoryRecord:
|
|
"""Construct a MemoryRecord for shared-cosine pool tests."""
|
|
now = datetime.now(timezone.utc)
|
|
return MemoryRecord(
|
|
id=uuid4(),
|
|
tier="episodic",
|
|
literal_surface=text,
|
|
aaak_index="",
|
|
embedding=vec,
|
|
community_id=None,
|
|
centrality=0.0,
|
|
detail_level=2,
|
|
pinned=False,
|
|
stability=0.0,
|
|
difficulty=0.0,
|
|
last_reviewed=None,
|
|
never_decay=False,
|
|
never_merge=False,
|
|
provenance=[],
|
|
created_at=now,
|
|
updated_at=now,
|
|
tags=[],
|
|
language="en",
|
|
)
|
|
|
|
|
|
# --------------------------------------------------- _collect_graph_pool tests
|
|
|
|
|
|
def test_collect_graph_pool_returns_aligned_ids_and_embeddings(tmp_path) -> None:
|
|
"""D-01 fast path: graph._nx node attr "embedding" is the cheap source.
|
|
|
|
Build 5 nodes whose primary-axis embeddings are pre-installed onto
|
|
the NetworkX node dict (the `build_runtime_graph` shape). Assert the
|
|
returned (ids, embs) pair is row-aligned: pool_embs[i] == records[i].embedding
|
|
when pool_ids[i] == records[i].id.
|
|
"""
|
|
from iai_mcp.pipeline import _collect_graph_pool
|
|
|
|
store = MemoryStore(path=tmp_path / "lancedb")
|
|
records: list[MemoryRecord] = []
|
|
for i in range(5):
|
|
vec = [0.0] * EMBED_DIM
|
|
vec[i] = 1.0
|
|
rec = _make(vec, text=f"rec{i}")
|
|
store.insert(rec)
|
|
records.append(rec)
|
|
graph = MemoryGraph()
|
|
for rec in records:
|
|
graph.add_node(rec.id, community_id=None, embedding=list(rec.embedding))
|
|
# Mirror what build_runtime_graph does: pour the payload onto the
|
|
# NetworkX node attr dict so _collect_graph_pool's fast path hits.
|
|
graph._nx.nodes[str(rec.id)].update({"embedding": list(rec.embedding)})
|
|
|
|
pool_ids, pool_embs = _collect_graph_pool(graph, None, store)
|
|
|
|
assert len(pool_ids) == 5
|
|
assert pool_embs.shape == (5, EMBED_DIM)
|
|
assert pool_embs.dtype == np.float32
|
|
# Row alignment: pool_embs[i] reflects pool_ids[i]'s record.
|
|
id_to_rec = {r.id: r for r in records}
|
|
for i, rid in enumerate(pool_ids):
|
|
rec = id_to_rec[rid]
|
|
np.testing.assert_allclose(
|
|
pool_embs[i], np.asarray(rec.embedding, dtype=np.float32)
|
|
)
|
|
|
|
|
|
def test_collect_graph_pool_empty_graph(tmp_path) -> None:
|
|
"""Empty graph returns ([], np.zeros((0, embed_dim), dtype=float32)).
|
|
|
|
The shape and dtype contract is load-bearing: downstream callers
|
|
(_recall_core) need a 2D float32 array even when the pool is empty,
|
|
so `pool_embs @ cue_vec` short-circuits cleanly to an empty result.
|
|
"""
|
|
from iai_mcp.pipeline import _collect_graph_pool
|
|
|
|
store = MemoryStore(path=tmp_path / "lancedb")
|
|
graph = MemoryGraph()
|
|
pool_ids, pool_embs = _collect_graph_pool(graph, None, store)
|
|
assert pool_ids == []
|
|
assert pool_embs.shape == (0, store.embed_dim)
|
|
assert pool_embs.dtype == np.float32
|
|
|
|
|
|
def test_collect_graph_pool_falls_back_to_store_get(tmp_path) -> None:
|
|
"""When _nx.nodes has no embedding, _collect_graph_pool falls back to store.get."""
|
|
from iai_mcp.pipeline import _collect_graph_pool
|
|
|
|
store = MemoryStore(path=tmp_path / "lancedb")
|
|
vec = [1.0] + [0.0] * (EMBED_DIM - 1)
|
|
rec = _make(vec, text="store-only")
|
|
store.insert(rec)
|
|
graph = MemoryGraph()
|
|
graph.add_node(rec.id, community_id=None, embedding=list(vec))
|
|
# Ensure the _nx node attr does NOT carry the embedding (force fallback).
|
|
if "embedding" in graph._nx.nodes[str(rec.id)]:
|
|
del graph._nx.nodes[str(rec.id)]["embedding"]
|
|
|
|
pool_ids, pool_embs = _collect_graph_pool(graph, None, store)
|
|
|
|
assert pool_ids == [rec.id]
|
|
assert pool_embs.shape == (1, EMBED_DIM)
|
|
np.testing.assert_allclose(
|
|
pool_embs[0], np.asarray(vec, dtype=np.float32)
|
|
)
|
|
|
|
|
|
# --------------------------------------------------------- module-level constants
|
|
|
|
|
|
def test_K_CANDIDATES_is_200() -> None:
|
|
"""K_CANDIDATES = 200, single module constant (no tier branch)."""
|
|
from iai_mcp.pipeline import K_CANDIDATES
|
|
|
|
assert K_CANDIDATES == 200
|
|
assert isinstance(K_CANDIDATES, int)
|
|
|
|
|
|
def test_COMMUNITY_BIAS_constants_are_mode_dependent() -> None:
|
|
"""verbatim=0.0 (HIPPEA pure) and concept=0.1 (CLS neocortical).
|
|
|
|
Constants live at module level for downstream (`_recall_core` Stage 5)
|
|
+ test introspection. They are floats, never strings or ints.
|
|
"""
|
|
from iai_mcp.pipeline import COMMUNITY_BIAS_CONCEPT, COMMUNITY_BIAS_VERBATIM
|
|
|
|
assert COMMUNITY_BIAS_VERBATIM == 0.0
|
|
assert COMMUNITY_BIAS_CONCEPT == 0.1
|
|
assert isinstance(COMMUNITY_BIAS_VERBATIM, float)
|
|
assert isinstance(COMMUNITY_BIAS_CONCEPT, float)
|
|
|
|
|
|
def test_gate_bias_for_mode_returns_correct_value() -> None:
|
|
"""D-02 helper: dispatch off mode parameter; defensive default is 0.0.
|
|
|
|
Anything other than the literal string "concept" returns
|
|
COMMUNITY_BIAS_VERBATIM (0.0) so a malformed / missing / case-mismatched
|
|
mode never accidentally biases recall toward categorical filtering.
|
|
"""
|
|
from iai_mcp.pipeline import _gate_bias_for_mode
|
|
|
|
assert _gate_bias_for_mode("verbatim") == 0.0
|
|
assert _gate_bias_for_mode("concept") == 0.1
|
|
# Defensive defaults — "never accidentally bias" rule.
|
|
assert _gate_bias_for_mode("unknown") == 0.0
|
|
assert _gate_bias_for_mode("") == 0.0
|
|
# Case-sensitive: "CONCEPT" is NOT "concept".
|
|
assert _gate_bias_for_mode("CONCEPT") == 0.0
|
|
|
|
|
|
def test_RecallCoreResult_dataclass_has_required_fields() -> None:
|
|
"""`_RecallCoreResult` is the shape returned by `_recall_core`.
|
|
|
|
Default-constructed instance has all 7 fields present with
|
|
correct empty/default values so downstream entry points
|
|
(recall_for_response / recall_for_benchmark in 08-02) can apply
|
|
pack/cap on a fully-populated structure.
|
|
"""
|
|
from iai_mcp.pipeline import _RecallCoreResult
|
|
|
|
r = _RecallCoreResult()
|
|
assert r.scored_hits == []
|
|
assert r.activation_trace == []
|
|
assert r.anti_hits == []
|
|
assert r.hints == []
|
|
assert r.patterns_observed == []
|
|
assert r.cue_mode == "concept"
|
|
assert r.budget_used == 0
|