"""小周天每轮记忆候选 + 反思抽取测试。 高置信度(≥0.7)低敏感度记忆候选直接落地到 memories 表而非 MemoryCandidate。 """ import pytest from sqlalchemy import select from app.application.reflection import ( drain_pending_reflections, start_reflection_after_turn, ) from app.db.session import AsyncSessionLocal from app.models.memory import Memory from app.repositories import candidates as candidates_repo from app.services.characters import get_active_character_model _REFLECTION_RESPONSE = ( '{"memories":[{"type":"preference","title":"喜欢简洁","content":"用户偏好简洁",' '"importance":0.8,"sensitivity":"low","confidence":0.7}],' '"reflection":"本轮应更主动确认用户意图。"}' ) @pytest.fixture def mock_reflection_chat(monkeypatch: pytest.MonkeyPatch) -> None: async def fake_chat(*_args, **_kwargs): return _REFLECTION_RESPONSE monkeypatch.setattr("app.application.reflection._call_model", fake_chat) @pytest.mark.asyncio async def test_reflection_extracts_memory_and_writes_reflection( mock_reflection_chat, # noqa: ARG001 ) -> None: async with AsyncSessionLocal() as session: character = await get_active_character_model(session) start_reflection_after_turn(character.id, "session_test", "你好", "你好,我是魂镜。") await drain_pending_reflections() async with AsyncSessionLocal() as session: # 高置信度记忆直接落地到 memories 表(非 MemoryCandidate) auto_mem = ( await session.execute( select(Memory).where( Memory.character_id == character.id, Memory.type == "preference", Memory.source == "reflection", ) ) ).scalar_one_or_none() # 反思文本也落地为 memories 表 ref_mem = ( await session.execute( select(Memory).where( Memory.character_id == character.id, Memory.type == "reflection", ) ) ).scalar_one_or_none() assert auto_mem is not None assert auto_mem.title == "喜欢简洁" assert ref_mem is not None assert ref_mem.source == "reflection" assert ref_mem.status == "active" # Memory 无 status 字段,用 default assert "确认用户意图" in ref_mem.content @pytest.mark.asyncio async def test_reflection_without_memories_only_writes_reflection( monkeypatch: pytest.MonkeyPatch, ) -> None: async def fake_chat(*_args, **_kwargs): return '{"memories":[],"reflection":"只反思不抽记忆。"}' monkeypatch.setattr("app.application.reflection._call_model", fake_chat) async with AsyncSessionLocal() as session: character = await get_active_character_model(session) start_reflection_after_turn(character.id, "session_test", "hi", "你好。") await drain_pending_reflections() async with AsyncSessionLocal() as session: cands = await candidates_repo.list_memory_candidates(session, character.id, source="cycle") ref_mem = ( await session.execute( select(Memory).where( Memory.character_id == character.id, Memory.type == "reflection", ) ) ).scalar_one_or_none() # 无记忆候选(空 memories 数组),只有反思 assert len(cands) == 0 assert ref_mem is not None assert "只反思" in ref_mem.content