M3a:息壤三级记忆域——归真触发提取管线/白名单明忆/重忆确认/正影版本链/删除墓碑/双通道召回注入,退役旧记忆-抽取-反思-大周天-演化管线(迁移 004,验收 7 用例)
This commit is contained in:
1 parent
2a640a901b
commit
9c98c2f85e
69 files changed
+1193
-6033
No files matched your search
@@ -1,120 +0,0 @@
|
||||
"""演化候选派生 applier 测试。"""
|
||||
|
||||
import pytest
|
||||
|
||||
from app.db.session import AsyncSessionLocal
|
||||
from app.models.big_cycle import BigCycle
|
||||
from app.models.evolution import EvolutionCandidate
|
||||
from app.models.memory import Memory
|
||||
from app.repositories import evolution as evolution_repo
|
||||
from app.repositories import personality as personality_repo
|
||||
from app.repositories.json_utils import dumps
|
||||
from app.services import evolution as evolution_service
|
||||
from app.services.characters import get_active_character_model
|
||||
from app.services.personality import create_default_personality
|
||||
|
||||
|
||||
async def _setup(session) -> tuple:
|
||||
character = await get_active_character_model(session)
|
||||
if await personality_repo.get_latest_personality(session, character.id) is None:
|
||||
await create_default_personality(session, character.id)
|
||||
await session.flush()
|
||||
cycle = BigCycle(
|
||||
character_id=character.id,
|
||||
trigger="test",
|
||||
status="completed",
|
||||
memory_count_at_trigger=20,
|
||||
)
|
||||
session.add(cycle)
|
||||
await session.flush()
|
||||
return character, cycle
|
||||
|
||||
|
||||
def _candidate(character_id: str, cycle_id: str, **overrides) -> EvolutionCandidate:
|
||||
base = {
|
||||
"character_id": character_id,
|
||||
"cycle_id": cycle_id,
|
||||
"type": "personality",
|
||||
"source": "big_cycle",
|
||||
"level": "L4",
|
||||
"scope": "character_scope",
|
||||
"title": "t",
|
||||
"proposal": "p",
|
||||
"delta_json": "{}",
|
||||
"evidence_ids_json": "[]",
|
||||
"confidence": 0.5,
|
||||
"benefit_score": 0.5,
|
||||
"risk_score": 0.5,
|
||||
"drift_score": 0.0,
|
||||
"auto_apply_allowed": False,
|
||||
"status": "pending",
|
||||
}
|
||||
base.update(overrides)
|
||||
return EvolutionCandidate(**base)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_apply_personality_candidate_derives_version() -> None:
|
||||
async with AsyncSessionLocal() as session:
|
||||
character, cycle = await _setup(session)
|
||||
base_version = await personality_repo.get_latest_personality(session, character.id)
|
||||
candidate = _candidate(
|
||||
character.id,
|
||||
cycle.id,
|
||||
type="personality",
|
||||
delta_json=dumps({"expression_style": "更简洁、更主动"}),
|
||||
title="表达风格演化",
|
||||
)
|
||||
await evolution_repo.add_candidates(session, [candidate])
|
||||
await session.flush()
|
||||
|
||||
target_id = await evolution_service.apply_evolution_candidate(session, candidate)
|
||||
await session.commit()
|
||||
|
||||
assert target_id is not None
|
||||
async with AsyncSessionLocal() as session:
|
||||
new_version = await personality_repo.get_personality(session, target_id)
|
||||
character2 = await get_active_character_model(session)
|
||||
assert new_version is not None
|
||||
assert new_version.version == base_version.version + 1
|
||||
assert new_version.created_by == "big_cycle"
|
||||
assert "更简洁" in new_version.expression_style
|
||||
assert character2.current_personality_version_id == target_id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_apply_procedural_candidate_creates_memory() -> None:
|
||||
async with AsyncSessionLocal() as session:
|
||||
character, cycle = await _setup(session)
|
||||
candidate = _candidate(
|
||||
character.id,
|
||||
cycle.id,
|
||||
type="procedural",
|
||||
level="L2",
|
||||
delta_json=dumps({"title": "先确认关键假设", "content": "技术问题先确认前提再作答"}),
|
||||
)
|
||||
await evolution_repo.add_candidates(session, [candidate])
|
||||
await session.flush()
|
||||
|
||||
target_id = await evolution_service.apply_evolution_candidate(session, candidate)
|
||||
await session.commit()
|
||||
|
||||
assert target_id is not None
|
||||
async with AsyncSessionLocal() as session:
|
||||
mem = await session.get(Memory, target_id)
|
||||
assert mem is not None
|
||||
assert mem.type == "procedural"
|
||||
assert mem.title == "先确认关键假设"
|
||||
assert mem.source == "big_cycle"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_apply_unimplemented_type_returns_none() -> None:
|
||||
async with AsyncSessionLocal() as session:
|
||||
character, cycle = await _setup(session)
|
||||
candidate = _candidate(character.id, cycle.id, type="tool_strategy", level="L2")
|
||||
await evolution_repo.add_candidates(session, [candidate])
|
||||
await session.flush()
|
||||
|
||||
result = await evolution_service.apply_evolution_candidate(session, candidate)
|
||||
assert result is None
|
||||
@@ -1,156 +0,0 @@
|
||||
"""大周天抽取候选审核服务测试(记忆候选 + 世界实体候选)。"""
|
||||
|
||||
import pytest
|
||||
from sqlalchemy import select
|
||||
|
||||
from app.core.errors import AppError
|
||||
from app.db.session import AsyncSessionLocal
|
||||
from app.models.big_cycle import BigCycle
|
||||
from app.models.candidates import MemoryCandidate
|
||||
from app.models.memory import Memory
|
||||
from app.models.world import CharacterWorldEntity
|
||||
from app.services import extraction as extraction_service
|
||||
from app.services.characters import get_active_character_model
|
||||
|
||||
|
||||
async def _seed_cycle(session, character_id: str) -> str:
|
||||
cycle = BigCycle(
|
||||
character_id=character_id,
|
||||
trigger="test",
|
||||
status="completed",
|
||||
memory_count_at_trigger=20,
|
||||
)
|
||||
session.add(cycle)
|
||||
await session.flush()
|
||||
return cycle.id
|
||||
|
||||
|
||||
async def _seed_memory_candidate(
|
||||
session, character_id: str, cycle_id: str, status: str = "pending"
|
||||
) -> str:
|
||||
cand = MemoryCandidate(
|
||||
character_id=character_id,
|
||||
cycle_id=cycle_id,
|
||||
type="preference",
|
||||
title="喜欢简洁",
|
||||
content="用户偏好简洁直接的回答",
|
||||
confidence=0.7,
|
||||
importance=0.8,
|
||||
sensitivity="low",
|
||||
status=status,
|
||||
)
|
||||
session.add(cand)
|
||||
await session.flush()
|
||||
return cand.id
|
||||
|
||||
|
||||
async def _seed_world_entity(session, character_id: str, status: str = "pending") -> str:
|
||||
e = CharacterWorldEntity(
|
||||
character_id=character_id,
|
||||
type="project",
|
||||
name="项目X",
|
||||
state_type="inference",
|
||||
confidence=0.6,
|
||||
status=status,
|
||||
)
|
||||
session.add(e)
|
||||
await session.flush()
|
||||
return e.id
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_approve_memory_candidate_derives_memory() -> None:
|
||||
async with AsyncSessionLocal() as session:
|
||||
character = await get_active_character_model(session)
|
||||
cycle_id = await _seed_cycle(session, character.id)
|
||||
cid = await _seed_memory_candidate(session, character.id, cycle_id)
|
||||
await session.commit()
|
||||
|
||||
resp = await extraction_service.approve_memory_proposal(cid)
|
||||
assert resp.status == "applied"
|
||||
|
||||
async with AsyncSessionLocal() as session:
|
||||
mem = (
|
||||
await session.execute(
|
||||
select(Memory).where(
|
||||
Memory.character_id == character.id,
|
||||
Memory.title == "喜欢简洁",
|
||||
)
|
||||
)
|
||||
).scalar_one_or_none()
|
||||
assert mem is not None
|
||||
assert mem.source == "big_cycle"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reject_memory_candidate() -> None:
|
||||
async with AsyncSessionLocal() as session:
|
||||
character = await get_active_character_model(session)
|
||||
cycle_id = await _seed_cycle(session, character.id)
|
||||
cid = await _seed_memory_candidate(session, character.id, cycle_id)
|
||||
await session.commit()
|
||||
|
||||
resp = await extraction_service.reject_memory_proposal(cid)
|
||||
assert resp.status == "rejected"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_approve_non_pending_memory_conflicts() -> None:
|
||||
async with AsyncSessionLocal() as session:
|
||||
character = await get_active_character_model(session)
|
||||
cycle_id = await _seed_cycle(session, character.id)
|
||||
cid = await _seed_memory_candidate(session, character.id, cycle_id, status="applied")
|
||||
await session.commit()
|
||||
|
||||
with pytest.raises(AppError):
|
||||
await extraction_service.approve_memory_proposal(cid)
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_approve_world_entity_confirms() -> None:
|
||||
async with AsyncSessionLocal() as session:
|
||||
character = await get_active_character_model(session)
|
||||
eid = await _seed_world_entity(session, character.id)
|
||||
await session.commit()
|
||||
|
||||
resp = await extraction_service.approve_world_entity(eid)
|
||||
assert resp.status == "confirmed"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_reject_world_entity() -> None:
|
||||
async with AsyncSessionLocal() as session:
|
||||
character = await get_active_character_model(session)
|
||||
eid = await _seed_world_entity(session, character.id)
|
||||
await session.commit()
|
||||
|
||||
resp = await extraction_service.reject_world_entity(eid)
|
||||
assert resp.status == "rejected"
|
||||
|
||||
|
||||
@pytest.mark.asyncio
|
||||
async def test_list_memory_proposals_source_cycle() -> None:
|
||||
async with AsyncSessionLocal() as session:
|
||||
character = await get_active_character_model(session)
|
||||
cycle_id = await _seed_cycle(session, character.id)
|
||||
await _seed_memory_candidate(session, character.id, cycle_id)
|
||||
# 导入来源候选(import_job_id 非空)
|
||||
session.add(
|
||||
MemoryCandidate(
|
||||
character_id=character.id,
|
||||
import_job_id="import_test",
|
||||
type="user",
|
||||
title="导入记忆",
|
||||
content="来自导入",
|
||||
confidence=0.5,
|
||||
importance=0.5,
|
||||
sensitivity="low",
|
||||
status="pending",
|
||||
)
|
||||
)
|
||||
await session.commit()
|
||||
|
||||
page = await extraction_service.list_memory_proposals(character_id=character.id, source="cycle")
|
||||
assert page.total == 1
|
||||
assert page.items[0].title == "喜欢简洁"
|
||||
assert page.items[0].cycle_id == cycle_id
|
||||
Reference in new issue
Block a user