M2a:镜树域后端核心——树/节点/引文/归真/移栽全量 API 与 Kernel 完成回调(芽体固化/残答封存),8 用例钉死 PRD 场景 1/2/3/5/6/7/8

This commit is contained in:
chenxin committed 2026-08-21 23:00:07 +08:00
1 parent 1cd0069d65
commit e6a3b7121f
16 files changed
+2002 -74

No files matched your search

+89 -25
View File
@@ -1,20 +1,22 @@
"""Kernel Run 循环骨架(M0 占位形态)。
"""Kernel Run 循环骨架。
事件序列对应 docs/architecture/lingtai-runtime.md §3.1;瀑布节点可被插件否决。
M0 的投影为固定占位(系统契约占位 + 用户输入),M2 起接入镜树祖先路径、获准记忆
与显式引文三类来源。
投影当前为「调用方组装」形态:prompt 由领域层按镜树祖先链组装后传入,
三类投影来源(祖先路径/获准记忆/显式引文)的完整接入随 M3 息壤落地。
``RunOutcome`` 携带终态与生成内容,供领域层(镜树节点固化等)在 Run 结束时回调。
"""
from __future__ import annotations
import asyncio
from dataclasses import replace
from datetime import datetime, UTC
from collections.abc import Awaitable, Callable
from dataclasses import dataclass, field, replace
from datetime import UTC, datetime
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.core.ids import new_trace_id
from app.kernel.eventlog import KernelEventLog
from app.kernel.invariants import (
InvariantViolation,
@@ -34,13 +36,29 @@ from app.runtime.bus import (
)
from app.runtime.profile import Profile, resolve_profile
from app.shenshi_client.client import ShenshiClient
from app.shenshi_client.types import ShenshiMessage, ShenshiChatRequest
from app.shenshi_client.types import ShenshiChatRequest, ShenshiMessage
_SYSTEM_CONTRACT_PLACEHOLDER = "你是魂镜人物运行时(M0 骨架占位契约)。"
_SYSTEM_CONTRACT_PLACEHOLDER = "你是魂镜人物运行时(骨架占位契约)。"
# Run 结束回调:(生成内容, 终态)。终态仅 completed / interrupted。
OnRunFinal = Callable[[str, str], Awaitable[None]]
@dataclass
class RunOutcome:
"""Run 终态载体:循环内各终止路径写入,execute 返回前触发回调。"""
status: str = "running" # running / completed / interrupted / cancelled
content: str = ""
error_code: str = ""
extras: dict[str, Any] = field(default_factory=dict)
class KernelRunLoop:
"""单次 Run 的执行骨架:命台席位 → 事件序列 → 神识流 → 接纳断言。"""
"""单次 Run 的执行骨架:命台席位 → 事件序列 → 神识流 → 接纳断言。
``character_id`` 允许为空(照心试演无正式人物),此时必须提供 ``seat_key``。
"""
def __init__(
self,
@@ -60,30 +78,62 @@ class KernelRunLoop:
self._timeout = timeout_seconds
async def execute(
self, *, run_id: str, character_id: str, prompt: str, trace_id: str | None = None
) -> None:
self,
*,
run_id: str,
character_id: str | None,
prompt: str,
trace_id: str | None = None,
seat_key: str | None = None,
system_contract: str | None = None,
on_final: OnRunFinal | None = None,
) -> RunOutcome:
assert character_id or seat_key, "Run 必须绑定人物或提供席位键(试演)"
trace = trace_id or new_trace_id()
outcome = RunOutcome()
try:
async with asyncio.timeout(self._timeout):
await self._execute(
run_id=run_id, character_id=character_id, prompt=prompt, trace_id=trace
run_id=run_id,
character_id=character_id,
prompt=prompt,
trace_id=trace,
seat_key=seat_key,
system_contract=system_contract,
outcome=outcome,
)
except TimeoutError:
outcome.status = "interrupted"
outcome.error_code = "run_timeout"
await self._finish_interrupted(
run_id, character_id, trace, code="run_timeout", message="运行超时被中断"
)
except asyncio.CancelledError:
outcome.status = "cancelled"
await self._emit(run_id, character_id, trace, "run.cancelled", visibility="public")
await self._update_run(run_id, status="cancelled")
raise
if on_final is not None and outcome.status in ("completed", "interrupted"):
await on_final(outcome.content, outcome.status)
return outcome
async def _execute(
self, *, run_id: str, character_id: str, prompt: str, trace_id: str
self,
*,
run_id: str,
character_id: str | None,
prompt: str,
trace_id: str,
seat_key: str | None,
system_contract: str | None,
outcome: RunOutcome,
) -> 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:
outcome.status, outcome.error_code = "interrupted", "HJ-1301"
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
@@ -91,13 +141,26 @@ class KernelRunLoop:
return
await self._emit(run_id, character_id, trace_id, "run.queued")
async with self._seats.foreground(character_id):
async with self._seats.foreground(seat_key or character_id or run_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)
await self._turn(
run_id,
character_id,
trace_id,
prompt,
system_contract or _SYSTEM_CONTRACT_PLACEHOLDER,
outcome,
)
async def _turn(
self, run_id: str, character_id: str, trace_id: str, prompt: str
self,
run_id: str,
character_id: str | None,
trace_id: str,
prompt: str,
system_contract: str,
outcome: RunOutcome,
) -> None:
ctx = RunContext(
run_id=run_id, character_id=character_id, trace_id=trace_id, payload={"prompt": prompt}
@@ -110,7 +173,7 @@ class KernelRunLoop:
model=profile.params.get("model"),
slot=str(profile.params.get("slot", "chat")),
messages=[
ShenshiMessage(role="system", content=_SYSTEM_CONTRACT_PLACEHOLDER),
ShenshiMessage(role="system", content=system_contract),
ShenshiMessage(role="user", content=prompt),
],
run_id=run_id,
@@ -124,13 +187,14 @@ class KernelRunLoop:
"context.projected",
payload={
"profile": profile.snapshot(),
"sources": {"system_contract": "placeholder", "user_input": True},
"sources": {"system_contract": "assembled", "user_input": True},
},
visibility="internal",
)
try:
await self._bus.fire(NODE_LLM_PRE_REQUEST, ctx)
except Veto as veto:
outcome.status, outcome.error_code = "interrupted", "HJ-1301"
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
@@ -163,6 +227,7 @@ class KernelRunLoop:
"finish_reason": delta.finish_reason,
}
except Exception as exc: # 神识侧失败:如实上报,不伪装完成
outcome.status, outcome.error_code = "interrupted", "HJ-2001"
await self._emit(
run_id,
character_id,
@@ -187,6 +252,7 @@ class KernelRunLoop:
assert_model_visible_logged(request, events)
assert_stream_reconstruction(events, accepted_text)
except InvariantViolation as violation:
outcome.status, outcome.error_code = "interrupted", "HJ-9000"
await self._emit(
run_id,
character_id,
@@ -210,9 +276,11 @@ class KernelRunLoop:
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")
outcome.status = "completed"
outcome.content = accepted_text
async def _record_veto(
self, run_id: str, character_id: str, trace_id: str, veto: Veto
self, run_id: str, character_id: str | None, trace_id: str, veto: Veto
) -> None:
await self._emit(
run_id,
@@ -226,7 +294,7 @@ class KernelRunLoop:
async def _finish_interrupted(
self,
run_id: str,
character_id: str,
character_id: str | None,
trace_id: str,
*,
code: str,
@@ -238,7 +306,7 @@ class KernelRunLoop:
async def _emit(
self,
run_id: str,
character_id: str,
character_id: str | None,
trace_id: str,
type: str, # noqa: A002
payload: dict[str, Any] | None = None,
@@ -273,7 +341,3 @@ class KernelRunLoop:
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()