Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,15 @@ class _DedupDecision(BaseModel):
text: str = "" # the synthesized merged observation (when action == "merge")
reason: str = ""

@field_validator("action", mode="before")
@classmethod
def _normalize_action(cls, value: object) -> str:
if isinstance(value, str) and value in {"merge", "keep"}:
return value

logger.warning("Invalid consolidation dedup action %r; defaulting to keep", value)
return "keep"


_DEDUP_PROMPT = """You reconcile long-term memory observations. A NEW observation is about to be \
stored, and it is highly similar to an EXISTING one:
Expand Down
33 changes: 33 additions & 0 deletions hindsight-api-slim/tests/test_consolidation_dedup.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
the path stochastically.
"""

import logging
import types
import uuid
from dataclasses import dataclass
Expand Down Expand Up @@ -142,6 +143,38 @@ async def test_dedup_llm_missing_action_defaults_to_keep() -> None:
conn.execute.assert_not_called() # missing action is a conservative no-merge


def test_dedup_decision_accepts_exact_valid_actions() -> None:
assert _DedupDecision(action="merge").action == "merge"
assert _DedupDecision(action="keep").action == "keep"


def test_dedup_decision_invalid_action_defaults_to_keep(caplog) -> None:
with caplog.at_level(logging.WARNING):
decision = _DedupDecision(action="need_input", reason="model asked for more context")

assert decision.action == "keep"
assert "need_input" in caplog.text
assert "defaulting to keep" in caplog.text


def test_dedup_decision_near_miss_merge_defaults_to_keep(caplog) -> None:
with caplog.at_level(logging.WARNING):
decision = _DedupDecision(action="Merge")

assert decision.action == "keep"
assert "Merge" in caplog.text


def test_dedup_decision_non_scalar_action_defaults_to_keep(caplog) -> None:
with caplog.at_level(logging.WARNING):
list_decision = _DedupDecision(action=[])
dict_decision = _DedupDecision(action={"value": "merge"})

assert list_decision.action == "keep"
assert dict_decision.action == "keep"
assert "defaulting to keep" in caplog.text


async def test_dedup_llm_merge_folds_into_twin() -> None:
kwargs, conn, llm = _ctx()
kwargs["create_source_ids"] = [uuid.uuid4(), uuid.uuid4()]
Expand Down