Files
wehub-resource-sync 6c9c7fe7f3
CI / integration tests (3.13) (push) Failing after 1s
Commit lint / pull request title (push) Has been skipped
Docs / links (push) Failing after 1s
CI / unit tests (3.13) (push) Failing after 1s
CI / lint (push) Failing after 1s
CI / integration tests (push) Failing after 1s
CI / package build (push) Failing after 1s
Commit lint / commit messages (push) Failing after 1s
CI / unit tests (push) Failing after 1s
chore: import upstream snapshot with attribution
2026-07-13 12:24:24 +08:00

195 lines
6.8 KiB
Python

"""Unit tests for ``memory.search.hierarchy``."""
from __future__ import annotations
import datetime as _dt
import pytest
from everalgo.rank.fusion import cosine_to_lr_score
from everalgo.types import Candidate, FactCandidate
from everos.memory.search.hierarchy import (
build_ep_to_fact_parents,
heap_expand,
)
# ── Helpers ──────────────────────────────────────────────────────────────
def _ts() -> _dt.datetime:
return _dt.datetime(2026, 1, 1, tzinfo=_dt.UTC)
def _ep(
*,
ep_id: str = "ep-1",
score: float = 0.7,
memcell_id: str = "mc-1",
entry_id: str | None = None,
source: str = "vector",
) -> Candidate:
metadata: dict = {
"parent_id": memcell_id,
"owner_id": "u1",
"owner_type": "user",
"session_id": "sess-1",
"timestamp": _ts(),
"episode": "Some episode text.",
"sender_ids": ["u1"],
"subject": "Test subject",
"summary": "Test summary",
}
if entry_id is not None:
metadata["entry_id"] = entry_id
return Candidate(id=ep_id, score=score, source=source, metadata=metadata)
def _fact(
*,
fact_id: str = "fact-1",
parent_episode_id: str = "ep-1",
score: float = 0.9,
) -> FactCandidate:
return FactCandidate(
id=fact_id,
parent_episode_id=parent_episode_id,
score=score,
metadata={"fact": "Some fact text."},
)
# ── build_ep_to_fact_parents ─────────────────────────────────────────────
class TestBuildEpToFactParents:
def test_entry_id_only(self) -> None:
"""entry_id equal to parent_id → deduped to a single parent."""
ep = _ep(ep_id="ep-1", memcell_id="ent-1", entry_id="ent-1")
result = build_ep_to_fact_parents([ep])
assert result == {"ep-1": ["ent-1"]}
def test_parent_id_only(self) -> None:
ep = _ep(ep_id="ep-1", memcell_id="mc-1")
result = build_ep_to_fact_parents([ep])
assert result == {"ep-1": ["mc-1"]}
def test_both_entry_and_parent(self) -> None:
ep = _ep(ep_id="ep-1", memcell_id="mc-1", entry_id="ent-1")
result = build_ep_to_fact_parents([ep])
assert result == {"ep-1": ["ent-1", "mc-1"]}
def test_empty_list(self) -> None:
assert build_ep_to_fact_parents([]) == {}
def test_no_ids_in_metadata(self) -> None:
ep = Candidate(id="ep-1", score=0.5, metadata={})
assert build_ep_to_fact_parents([ep]) == {}
# ── heap_expand ──────────────────────────────────────────────────────────
class TestHeapExpand:
def test_empty_inputs_returns_empty(self) -> None:
assert heap_expand(sparse=[], dense=[], episode_to_facts={}) == []
def test_episodes_only_no_facts_sorted_by_lr(self) -> None:
"""No facts → all episodes survive, sorted by LR score descending."""
sparse = [_ep(ep_id="ep-a", score=5.0), _ep(ep_id="ep-b", score=3.0)]
dense = [_ep(ep_id="ep-a", score=0.8), _ep(ep_id="ep-b", score=0.6)]
result = heap_expand(sparse=sparse, dense=dense, episode_to_facts={}, top_k=2)
assert len(result) == 2
assert result[0].id == "ep-a"
assert result[1].id == "ep-b"
assert all(r.item_type == "episode" for r in result)
assert result[0].score == pytest.approx(cosine_to_lr_score(0.8, 5.0))
assert result[1].score == pytest.approx(cosine_to_lr_score(0.6, 3.0))
def test_fact_evicts_parent_episode(self) -> None:
"""A high-scoring fact enters top-N and evicts its parent episode."""
sparse = [_ep(ep_id="ep-1", score=2.0)]
dense = [_ep(ep_id="ep-1", score=0.5)]
facts = {"ep-1": [_fact(fact_id="f1", parent_episode_id="ep-1", score=0.95)]}
result = heap_expand(
sparse=sparse, dense=dense, episode_to_facts=facts, top_k=2
)
fact_items = [r for r in result if r.item_type == "atomic_fact"]
ep_items = [r for r in result if r.item_type == "episode"]
assert len(fact_items) == 1
assert fact_items[0].id == "f1"
assert fact_items[0].parent_episode_id == "ep-1"
assert len(ep_items) == 0
def test_global_competition_fact_evicts_weaker_episode(self) -> None:
"""Fact from ep-a can push ep-b out of top-N if ep-b scores lower."""
sparse = [
_ep(ep_id="ep-a", score=4.0),
_ep(ep_id="ep-b", score=1.0),
]
dense = [
_ep(ep_id="ep-a", score=0.7),
_ep(ep_id="ep-b", score=0.3),
]
facts = {
"ep-a": [
_fact(fact_id="f1", parent_episode_id="ep-a", score=0.95),
_fact(fact_id="f2", parent_episode_id="ep-a", score=0.90),
],
}
result = heap_expand(
sparse=sparse, dense=dense, episode_to_facts=facts, top_k=1
)
assert len(result) == 1
assert result[0].item_type == "atomic_fact"
assert result[0].id == "f1"
def test_top_k_caps_output(self) -> None:
sparse = [_ep(ep_id=f"ep-{i}", score=float(5 - i)) for i in range(5)]
dense = [_ep(ep_id=f"ep-{i}", score=0.9 - i * 0.1) for i in range(5)]
result = heap_expand(sparse=sparse, dense=dense, episode_to_facts={}, top_k=3)
assert len(result) == 3
def test_convergence_stops_loop(self) -> None:
"""With max_convergence_rounds=1, loop stops after 1 round of no change."""
sparse = [_ep(ep_id="ep-1", score=2.0), _ep(ep_id="ep-2", score=1.0)]
dense = [_ep(ep_id="ep-1", score=0.5), _ep(ep_id="ep-2", score=0.4)]
result = heap_expand(
sparse=sparse,
dense=dense,
episode_to_facts={},
top_k=2,
max_convergence_rounds=1,
)
assert len(result) == 2
def test_alpha_blending(self) -> None:
"""alpha=0.5 blends child and parent LR scores equally."""
sparse = [_ep(ep_id="ep-1", score=2.0)]
dense = [_ep(ep_id="ep-1", score=0.5)]
facts = {"ep-1": [_fact(fact_id="f1", parent_episode_id="ep-1", score=0.95)]}
result = heap_expand(
sparse=sparse,
dense=dense,
episode_to_facts=facts,
top_k=2,
alpha=0.5,
)
child_lr = cosine_to_lr_score(0.95, 2.0)
parent_lr = cosine_to_lr_score(0.5, 2.0)
expected = 0.5 * child_lr + 0.5 * parent_lr
fact_items = [r for r in result if r.item_type == "atomic_fact"]
assert fact_items
assert fact_items[0].score == pytest.approx(expected)