101 lines
3.5 KiB
Python
101 lines
3.5 KiB
Python
"""小周天每轮记忆候选 + 反思抽取测试。
|
||
|
||
高置信度(≥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
|