M0:灵台 Kernel 骨架(事件溯源/命台席位/瀑布总线/插件注册)与神识客户端边界,新增 POST /runs 占位闭环与测试,Alembic 基线合并为单一起点
This commit is contained in:
1 parent
99e55fa0a8
commit
7ef3a15061
23 files changed
+2014
-1399
No files matched your search
@@ -0,0 +1,16 @@
|
||||
"""灵台插件化运行时(事件总线 / 插件 / Profile)。Kernel 见 ``app.kernel``。"""
|
||||
|
||||
from app.runtime.bus import RunContext, Veto, WaterfallBus
|
||||
from app.runtime.plugin import Plugin, PluginRegistry
|
||||
from app.runtime.profile import Profile, ProfileLayer, resolve_profile
|
||||
|
||||
__all__ = [
|
||||
"Plugin",
|
||||
"PluginRegistry",
|
||||
"Profile",
|
||||
"ProfileLayer",
|
||||
"RunContext",
|
||||
"Veto",
|
||||
"WaterfallBus",
|
||||
"resolve_profile",
|
||||
]
|
||||
@@ -0,0 +1,79 @@
|
||||
"""运行时事件总线:瀑布节点 + 可否决监听器。
|
||||
|
||||
设计依据 docs/architecture/lingtai-runtime.md §1/§2:注入、审计、安全都在事件层做,
|
||||
不改循环骨架。监听器按注册序执行;任一监听器抛 ``Veto`` 即中断该节点(否决),
|
||||
否决事实由调用方记录(safety.ruled 等事件),总线本身不落库。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
import fnmatch
|
||||
from collections import defaultdict
|
||||
from collections.abc import Awaitable, Callable
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
# 瀑布节点名(契约词典 §8.3 的可否决子集)
|
||||
NODE_RUN_REQUESTED = "run.requested"
|
||||
NODE_TURN_STARTED = "turn.started"
|
||||
NODE_LLM_PRE_REQUEST = "llm.pre_request"
|
||||
NODE_TOOL_PRE_EXECUTE = "tool.pre_execute"
|
||||
NODE_RESULT_PRE_ACCEPT = "result.pre_accept"
|
||||
|
||||
WATERFALL_NODES = frozenset(
|
||||
{
|
||||
NODE_RUN_REQUESTED,
|
||||
NODE_TURN_STARTED,
|
||||
NODE_LLM_PRE_REQUEST,
|
||||
NODE_TOOL_PRE_EXECUTE,
|
||||
NODE_RESULT_PRE_ACCEPT,
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
class Veto(Exception):
|
||||
"""监听器否决:携带裁决摘要(reason / 规则标识)。"""
|
||||
|
||||
def __init__(self, reason: str, *, rule: str = "", details: dict[str, Any] | None = None):
|
||||
super().__init__(reason)
|
||||
self.reason = reason
|
||||
self.rule = rule
|
||||
self.details = details or {}
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class RunContext:
|
||||
"""节点监听器可见的运行上下文(只读视图,变更经事件日志)。"""
|
||||
|
||||
run_id: str
|
||||
character_id: str
|
||||
trace_id: str | None = None
|
||||
payload: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
Listener = Callable[[str, RunContext], Awaitable[None]]
|
||||
|
||||
|
||||
class WaterfallBus:
|
||||
"""瀑布节点订阅与触发。"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._listeners: dict[str, list[Listener]] = defaultdict(list)
|
||||
|
||||
def on(self, node: str, listener: Listener) -> None:
|
||||
"""注册节点监听器;支持 glob 通配(如 ``llm.*``)。"""
|
||||
if not any(fnmatch.fnmatch(node, pattern) for pattern in WATERFALL_NODES):
|
||||
raise ValueError(f"未知瀑布节点:{node}")
|
||||
self._listeners[node].append(listener)
|
||||
|
||||
async def fire(self, node: str, context: RunContext) -> None:
|
||||
"""按注册序触发监听器;任一否决即停止后续。
|
||||
|
||||
精确键与通配键(如 ``llm.*``)各自成列表,列表内保持注册序;
|
||||
两种键之间的相对顺序不保证,需要严格顺序的场景用精确注册。
|
||||
"""
|
||||
for key, listeners in self._listeners.items():
|
||||
if key != node and not fnmatch.fnmatch(node, key):
|
||||
continue
|
||||
for listener in list(listeners):
|
||||
await listener(node, context)
|
||||
@@ -0,0 +1,42 @@
|
||||
"""插件契约与注册表。
|
||||
|
||||
一切能力插件化(docs/architecture/lingtai-runtime.md §4):插件只通过订阅瀑布节点
|
||||
与提供能力参与运行,彼此不直接调用。M0 只需注册表与装配骨架,首个真实插件(司命
|
||||
占位裁决器)随 M5 落地;禁域挂载点在 Kernel,不在插件。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from typing import Protocol, runtime_checkable
|
||||
|
||||
from app.runtime.bus import WaterfallBus
|
||||
|
||||
|
||||
@runtime_checkable
|
||||
class Plugin(Protocol):
|
||||
"""插件契约:稳定标识 + 版本 + 注册入口。"""
|
||||
|
||||
@property
|
||||
def name(self) -> str: ...
|
||||
|
||||
@property
|
||||
def version(self) -> str: ...
|
||||
|
||||
def register(self, bus: WaterfallBus) -> None: ...
|
||||
|
||||
|
||||
class PluginRegistry:
|
||||
"""按注册序持有插件;同名插件后注册覆盖前注册(Profile 层叠语义)。"""
|
||||
|
||||
def __init__(self) -> None:
|
||||
self._plugins: dict[str, Plugin] = {}
|
||||
|
||||
def register(self, plugin: Plugin) -> None:
|
||||
self._plugins[plugin.name] = plugin
|
||||
|
||||
def bootstrap(self, bus: WaterfallBus) -> None:
|
||||
for plugin in list(self._plugins.values()):
|
||||
plugin.register(bus)
|
||||
|
||||
def names(self) -> list[str]:
|
||||
return list(self._plugins)
|
||||
@@ -0,0 +1,42 @@
|
||||
"""Profile:一次 Run 的插件装配清单与参数包。
|
||||
|
||||
层叠顺序(产品 PRD §9.2 / lingtai-runtime.md §4.2):
|
||||
平台硬边界 → 平台目录 → 用户偏好/BYOK → 人物覆写 → 镜树临时选择。
|
||||
M0 只有平台目录层;后续里程碑逐层接入。Profile 快照写入事件日志,保证可回放。
|
||||
"""
|
||||
|
||||
from __future__ import annotations
|
||||
|
||||
from dataclasses import dataclass, field
|
||||
from typing import Any
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class ProfileLayer:
|
||||
"""层叠中的一层:来源标识 + 参数(后者覆盖前者同键)。"""
|
||||
|
||||
source: str
|
||||
params: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
|
||||
_PLATFORM_LAYER = ProfileLayer(source="platform", params={"slot": "chat"})
|
||||
|
||||
|
||||
@dataclass(frozen=True)
|
||||
class Profile:
|
||||
"""解析后的最终装配:插件名列表 + 合并参数。"""
|
||||
|
||||
plugins: tuple[str, ...] = ()
|
||||
params: dict[str, Any] = field(default_factory=dict)
|
||||
|
||||
def snapshot(self) -> dict[str, Any]:
|
||||
"""写入 context.projected 事件的快照视图。"""
|
||||
return {"plugins": list(self.plugins), "params": dict(self.params)}
|
||||
|
||||
|
||||
def resolve_profile(layers: list[ProfileLayer] | None = None) -> Profile:
|
||||
"""层叠解析:平台层恒在最底,逐层覆盖参数;M0 无插件装配。"""
|
||||
merged: dict[str, Any] = {}
|
||||
for layer in [_PLATFORM_LAYER, *(layers or [])]:
|
||||
merged.update(layer.params)
|
||||
return Profile(plugins=(), params=merged)
|
||||
Reference in new issue
Block a user