"""Kernel Run 循环骨架(M0 占位形态)。 事件序列对应 docs/architecture/lingtai-runtime.md §3.1;瀑布节点可被插件否决。 M0 的投影为固定占位(系统契约占位 + 用户输入),M2 起接入镜树祖先路径、获准记忆 与显式引文三类来源。 """ from __future__ import annotations import asyncio from dataclasses import replace from datetime import datetime, UTC from typing import Any from sqlalchemy.ext.asyncio import AsyncSession, async_sessionmaker from app.core.ids import new_run_id, new_trace_id from app.kernel.eventlog import KernelEventLog from app.kernel.invariants import ( InvariantViolation, assert_model_visible_logged, assert_stream_reconstruction, ) from app.kernel.seat import SeatManager from app.models.runtime import Run from app.runtime.bus import ( NODE_LLM_PRE_REQUEST, NODE_RESULT_PRE_ACCEPT, NODE_RUN_REQUESTED, NODE_TURN_STARTED, RunContext, Veto, WaterfallBus, ) from app.runtime.profile import Profile, resolve_profile from app.shenshi_client.client import ShenshiClient from app.shenshi_client.types import ShenshiMessage, ShenshiChatRequest _SYSTEM_CONTRACT_PLACEHOLDER = "你是魂镜人物运行时(M0 骨架占位契约)。" class KernelRunLoop: """单次 Run 的执行骨架:命台席位 → 事件序列 → 神识流 → 接纳断言。""" def __init__( self, *, event_log: KernelEventLog, seats: SeatManager, bus: WaterfallBus, shenshi: ShenshiClient, session_factory: async_sessionmaker[AsyncSession], timeout_seconds: float, ) -> None: self._log = event_log self._seats = seats self._bus = bus self._shenshi = shenshi self._sessions = session_factory self._timeout = timeout_seconds async def execute( self, *, run_id: str, character_id: str, prompt: str, trace_id: str | None = None ) -> None: trace = trace_id or new_trace_id() try: async with asyncio.timeout(self._timeout): await self._execute( run_id=run_id, character_id=character_id, prompt=prompt, trace_id=trace ) except TimeoutError: await self._finish_interrupted( run_id, character_id, trace, code="run_timeout", message="运行超时被中断" ) except asyncio.CancelledError: await self._emit(run_id, character_id, trace, "run.cancelled", visibility="public") await self._update_run(run_id, status="cancelled") raise async def _execute( self, *, run_id: str, character_id: str, prompt: str, trace_id: str ) -> None: ctx = RunContext(run_id=run_id, character_id=character_id, trace_id=trace_id) try: await self._bus.fire(NODE_RUN_REQUESTED, ctx) except Veto as veto: await self._record_veto(run_id, character_id, trace_id, veto) await self._finish_interrupted( run_id, character_id, trace_id, code="HJ-1301", message=veto.reason ) return await self._emit(run_id, character_id, trace_id, "run.queued") async with self._seats.foreground(character_id): await self._update_run(run_id, status="running") await self._emit(run_id, character_id, trace_id, "run.started") await self._turn(run_id, character_id, trace_id, prompt) async def _turn( self, run_id: str, character_id: str, trace_id: str, prompt: str ) -> None: ctx = RunContext( run_id=run_id, character_id=character_id, trace_id=trace_id, payload={"prompt": prompt} ) profile: Profile = resolve_profile() await self._emit(run_id, character_id, trace_id, "turn.started") await self._bus.fire(NODE_TURN_STARTED, ctx) request = ShenshiChatRequest( model=profile.params.get("model"), slot=str(profile.params.get("slot", "chat")), messages=[ ShenshiMessage(role="system", content=_SYSTEM_CONTRACT_PLACEHOLDER), ShenshiMessage(role="user", content=prompt), ], run_id=run_id, trace_id=trace_id, character_id=character_id, ) await self._emit( run_id, character_id, trace_id, "context.projected", payload={ "profile": profile.snapshot(), "sources": {"system_contract": "placeholder", "user_input": True}, }, visibility="internal", ) try: await self._bus.fire(NODE_LLM_PRE_REQUEST, ctx) except Veto as veto: await self._record_veto(run_id, character_id, trace_id, veto) await self._finish_interrupted( run_id, character_id, trace_id, code="HJ-1301", message=veto.reason ) return # "模型可见即可重建"锚点:完整 IR 请求进事件日志 await self._emit( run_id, character_id, trace_id, "llm.requested", payload={"request": request.model_dump(mode="json")}, visibility="internal", ) text_parts: list[str] = [] usage: dict[str, Any] = {} try: async for delta in self._shenshi.stream_chat(request): if delta.delta: text_parts.append(delta.delta) await self._emit( run_id, character_id, trace_id, "llm.delta", payload={"delta": delta.delta} ) if delta.finish_reason: usage = { "prompt_tokens": delta.usage_prompt_tokens, "completion_tokens": delta.usage_completion_tokens, "finish_reason": delta.finish_reason, } except Exception as exc: # 神识侧失败:如实上报,不伪装完成 await self._emit( run_id, character_id, trace_id, "llm.failed", payload={"error": str(exc)}, visibility="internal", ) await self._finish_interrupted( run_id, character_id, trace_id, code="HJ-2001", message=f"神识模型调用失败:{exc}" ) return await self._emit(run_id, character_id, trace_id, "llm.completed", payload={"usage": usage}) accepted_text = "".join(text_parts) await self._bus.fire( NODE_RESULT_PRE_ACCEPT, replace(ctx, payload={"result": accepted_text}) ) events = await self._log.list_by_run(run_id) try: assert_model_visible_logged(request, events) assert_stream_reconstruction(events, accepted_text) except InvariantViolation as violation: await self._emit( run_id, character_id, trace_id, "run.invariant_violation", payload={"invariant": violation.invariant, "detail": violation.detail}, visibility="audit", ) await self._finish_interrupted( run_id, character_id, trace_id, code="HJ-9000", message=f"Kernel 不变量违反:{violation.invariant}", ) return await self._emit( run_id, character_id, trace_id, "result.accepted", payload={"content": accepted_text} ) await self._emit(run_id, character_id, trace_id, "turn.finished") await self._emit(run_id, character_id, trace_id, "run.finished") await self._update_run(run_id, status="completed") async def _record_veto( self, run_id: str, character_id: str, trace_id: str, veto: Veto ) -> None: await self._emit( run_id, character_id, trace_id, "safety.ruled", payload={"reason": veto.reason, "rule": veto.rule, "details": veto.details}, visibility="audit", ) async def _finish_interrupted( self, run_id: str, character_id: str, trace_id: str, *, code: str, message: str, ) -> None: await self._emit(run_id, character_id, trace_id, "run.interrupted") await self._update_run(run_id, status="interrupted", error_code=code, error_message=message) async def _emit( self, run_id: str, character_id: str, trace_id: str, type: str, # noqa: A002 payload: dict[str, Any] | None = None, visibility: str = "public", ) -> None: await self._log.emit( type=type, run_id=run_id, character_id=character_id, trace_id=trace_id, payload=payload, visibility=visibility, ) async def _update_run( self, run_id: str, *, status: str, error_code: str | None = None, error_message: str | None = None, ) -> None: async with self._sessions() as session: run = await session.get(Run, run_id) if run is None: return run.status = status run.error_code = error_code run.error_message = error_message if status == "running": run.started_at = datetime.now(UTC) if status in ("completed", "failed", "cancelled", "interrupted"): run.completed_at = datetime.now(UTC) await session.commit() def new_kernel_run_id() -> str: return new_run_id()