"""大周天执行器:回顾→对照→提议→提交的最小实现(ADR-018/023)。 评估调用经模型网关(真实流);提议产物为 persona_candidate(pending), 经既有审核机制确认后才影响道体——候选先于写入不变。 """ from datetime import UTC, datetime from typing import Any from sqlalchemy import select from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from app.gateway.adapters import AdapterRequest, GatewayError, stream_chat from app.models import ( AuditEvent, BigCycle, Character, CharacterVersion, Message, OutboxEvent, ProviderEndpoint, ) EVALUATION_SYSTEM_PROMPT = ( "你是角色演化评估器。根据近期对话、当前道体基线与内驱力," "提出一至三条渐进式人格或关系演化建议。只输出建议正文,简洁分条。" ) async def _select_endpoint(db: AsyncSession) -> ProviderEndpoint | None: return ( await db.execute( select(ProviderEndpoint) .where( ProviderEndpoint.enabled.is_(True), ProviderEndpoint.logical_model_id == "default-chat", ) .order_by(ProviderEndpoint.priority.asc()) .limit(1) ) ).scalar_one_or_none() async def execute_big_cycle( factory: async_sessionmaker[AsyncSession], cycle_id: str, transport: Any | None = None, ) -> None: """四阶段:回顾(拉取消息)→ 对照(道体/内驱力)→ 提议(模型)→ 提交(候选落库)。""" async with factory() as db: cycle = await db.get(BigCycle, cycle_id) if cycle is None: return character = await db.get(Character, cycle.character_id) assert character is not None try: cycle.status = "running" await db.commit() # 阶段一:回顾——同域近期消息(对话暂未绑定角色维度,按个人域取窗口) from app.models import Conversation messages = list( ( await db.execute( select(Message) .where( Message.conversation_id.in_( select(Conversation.id).where( Conversation.personal_domain_id == character.personal_domain_id ) ), Message.role.in_(["user", "assistant"]), ) .order_by(Message.created_at.desc()) .limit(20) ) ).scalars().all() ) def _text_of(m: Any) -> str: joined = "".join( str(b.get("text", "")) for b in m.content if isinstance(b, dict) ) return joined[:120] recent = "\n".join(f"{m.role}: {_text_of(m)}" for m in reversed(messages)) cycle.phase_memory = {**cycle.phase_memory, "recall": {"messages": len(messages)}} await db.commit() # 阶段二:对照——道体与内驱力快照 cycle.phase_memory = { **cycle.phase_memory, "evaluate": { "identity_baseline": character.identity_baseline, "drives": character.drives, }, } await db.commit() # 阶段三:提议——模型评估(真实流) endpoint = await _select_endpoint(db) if endpoint is None: cycle.status = "failed" cycle.completed_at = datetime.now(UTC) cycle.phase_memory = {**cycle.phase_memory, "error": "no_endpoint_available"} await db.commit() return prompt = [ {"role": "system", "content": EVALUATION_SYSTEM_PROMPT}, { "role": "user", "content": ( f"角色:{character.name}\n道体基线:{character.identity_baseline}\n" f"内驱力:{character.drives}\n\n近期对话:\n{recent}\n\n" "请提出演化建议。" ), }, ] parts: list[str] = [] async for chunk in stream_chat( endpoint.base_url, endpoint.model_id, endpoint.api_key_env, AdapterRequest(messages=prompt, model=endpoint.model_id), transport=transport, ): if chunk.kind == "delta" and chunk.text: parts.append(chunk.text) proposal = "".join(parts).strip() # 阶段四:提交——persona_candidate(pending,等待人工审核) version = CharacterVersion( character_id=character.id, kind="persona_candidate", status="pending", payload={"proposal": proposal, "cycle_id": cycle.id, "source": "big_cycle"}, ) db.add(version) await db.flush() cycle.produced_version_ids = [version.id] cycle.status = "completed" cycle.completed_at = datetime.now(UTC) db.add( AuditEvent( actor_type="system", actor_id="big_cycle", action="big_cycle.completed", aggregate_type="character", aggregate_id=character.id, payload={"cycle_id": cycle.id, "version_id": version.id}, ) ) db.add( OutboxEvent( aggregate_type="character", aggregate_id=character.id, event_type="character.persona_candidate_submitted", payload={"cycle_id": cycle.id, "version_id": version.id}, ) ) await db.commit() except GatewayError as exc: cycle.status = "failed" cycle.completed_at = datetime.now(UTC) cycle.phase_memory = {**cycle.phase_memory, "error": str(exc)[:300]} await db.commit() except Exception: await db.rollback() raise