99 lines
3.0 KiB
Python
99 lines
3.0 KiB
Python
"""Tests for the synthesized-memories service (T54).
|
|
|
|
When the user jump-skips ("a week later") they are prompted "anything
|
|
notable happen?" If they answer with prose, this service parses it into
|
|
1-N synthesized memories per present bot. Each memory carries
|
|
``source="synthesized"`` and ``reliability=0.7`` (the caller — T62 skip
|
|
flow — applies those tags when persisting; this service just produces
|
|
the structured digest).
|
|
|
|
These tests cover:
|
|
|
|
* The happy path: a canned classifier response parses cleanly into a
|
|
populated :class:`SynthesizedDigest` with one memory.
|
|
* Empty prose short-circuits before any classifier call — the mock has
|
|
no canned responses, so an accidental call would raise
|
|
``IndexError``.
|
|
* Classifier failure (3 bad responses, exhausting :func:`classify`'s
|
|
retry budget) falls back to an empty default digest.
|
|
"""
|
|
|
|
from __future__ import annotations
|
|
|
|
import json
|
|
|
|
import pytest
|
|
|
|
from chat.llm.mock import MockLLMClient
|
|
from chat.services.synthesized_memories import (
|
|
SynthesizedDigest,
|
|
SynthesizedMemory,
|
|
synthesize_memories,
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_synthesize_parses_canned_prose():
|
|
canned = json.dumps(
|
|
{
|
|
"memories": [
|
|
{
|
|
"text": "Maya started a new pottery class.",
|
|
"significance": 1,
|
|
"affinity_delta": 0,
|
|
"trust_delta": 0,
|
|
}
|
|
]
|
|
}
|
|
)
|
|
mock = MockLLMClient(canned=[canned])
|
|
result = await synthesize_memories(
|
|
mock,
|
|
classifier_model="x",
|
|
prose="we saw each other at her pottery class once",
|
|
bot_name="Maya",
|
|
bot_persona="warm potter, mid-30s",
|
|
you_name="Sam",
|
|
)
|
|
assert isinstance(result, SynthesizedDigest)
|
|
assert len(result.memories) == 1
|
|
mem = result.memories[0]
|
|
assert isinstance(mem, SynthesizedMemory)
|
|
assert mem.text == "Maya started a new pottery class."
|
|
assert mem.significance == 1
|
|
assert mem.affinity_delta == 0
|
|
assert mem.trust_delta == 0
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_empty_prose_returns_empty_digest():
|
|
"""Empty prose short-circuits — the classifier must not be called."""
|
|
mock = MockLLMClient(canned=[])
|
|
result = await synthesize_memories(
|
|
mock,
|
|
classifier_model="x",
|
|
prose="",
|
|
bot_name="Maya",
|
|
bot_persona="warm potter, mid-30s",
|
|
you_name="Sam",
|
|
)
|
|
assert result == SynthesizedDigest()
|
|
assert result.memories == []
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_classifier_failure_returns_empty_default():
|
|
"""Three bad responses exhaust the classifier's retry budget; the
|
|
service then returns the empty default digest."""
|
|
mock = MockLLMClient(canned=["bad", "bad", "bad"])
|
|
result = await synthesize_memories(
|
|
mock,
|
|
classifier_model="x",
|
|
prose="we saw each other at her pottery class once",
|
|
bot_name="Maya",
|
|
bot_persona="warm potter, mid-30s",
|
|
you_name="Sam",
|
|
)
|
|
assert result == SynthesizedDigest()
|
|
assert result.memories == []
|