M0:灵台 Kernel 骨架(事件溯源/命台席位/瀑布总线/插件注册)与神识客户端边界,新增 POST /runs 占位闭环与测试,Alembic 基线合并为单一起点

This commit is contained in:
chenxin committed 2026-08-21 21:58:09 +08:00
1 parent 99e55fa0a8
commit 7ef3a15061
23 files changed
+2014 -1399

No files matched your search

+25
View File
@@ -0,0 +1,25 @@
"""神识客户端边界(灵台唯一模型出口)。"""
from app.shenshi_client.client import (
EchoShenshi,
HttpShenshiClient,
ShenshiClient,
build_shenshi_client,
)
from app.shenshi_client.types import (
ShenshiChatRequest,
ShenshiDelta,
ShenshiError,
ShenshiMessage,
)
__all__ = [
"EchoShenshi",
"HttpShenshiClient",
"ShenshiChatRequest",
"ShenshiClient",
"ShenshiDelta",
"ShenshiError",
"ShenshiMessage",
"build_shenshi_client",
]
+93
View File
@@ -0,0 +1,93 @@
"""神识客户端:灵台唯一的模型出口边界。
两种实现可互换(Protocol):
- ``HttpShenshiClient``:经 HTTP+SSE 调用 Go 神识服务(SHENSHI_BASE_URL 配置时启用);
- ``EchoShenshi``:进程内回声桩,逐字回流输入,供本地开发与测试在无神识时跑通端到端。
领域模块禁止绕过本边界 import Provider SDK(docs/architecture/service-boundaries.md §4)。
"""
from __future__ import annotations
import json
from collections.abc import AsyncIterator
from typing import Protocol
import httpx
from app.shenshi_client.types import ShenshiChatRequest, ShenshiDelta, ShenshiError
class ShenshiClient(Protocol):
"""神识数据面契约:流式对话补全。"""
def stream_chat(self, request: ShenshiChatRequest) -> AsyncIterator[ShenshiDelta]: ...
class EchoShenshi:
"""进程内回声桩:把最后一条用户消息按固定粒度切分回流。
只用于 M0 骨架验证(登录 → 占位 Run → SSE 端到端),不模拟任何 Provider 行为。
"""
chunk_size = 8
async def stream_chat(self, request: ShenshiChatRequest) -> AsyncIterator[ShenshiDelta]:
prompt = next((m.content for m in reversed(request.messages) if m.role == "user"), "")
text = f"【回声桩】{prompt}"
for i in range(0, len(text), self.chunk_size):
yield ShenshiDelta(delta=text[i : i + self.chunk_size])
yield ShenshiDelta(
finish_reason="stop",
usage_prompt_tokens=max(len(prompt) // 4, 1),
usage_completion_tokens=max(len(text) // 4, 1),
)
class HttpShenshiClient:
"""HTTP+SSE 神识客户端。
认证:服务令牌经 ``Authorization: Bearer`` 透传(管理面 RBAC 不在此层)。
流解析:``data:`` 帧为 ShenshiDelta JSON,``[DONE]`` 结束;非 2xx 映射 ShenshiError。
"""
def __init__(self, base_url: str, service_token: str, timeout: float = 120.0) -> None:
self._base_url = base_url.rstrip("/")
self._headers = {"Authorization": f"Bearer {service_token}"} if service_token else {}
self._timeout = timeout
async def stream_chat(self, request: ShenshiChatRequest) -> AsyncIterator[ShenshiDelta]:
payload = request.model_dump(mode="json")
assert payload.get("stream") is True # 数据面当前只有流式形态
async with httpx.AsyncClient(trust_env=False, timeout=self._timeout) as client:
async with client.stream(
"POST",
f"{self._base_url}/v1/chat/completions",
json=payload,
headers=self._headers,
) as response:
if response.status_code >= 400:
body = (await response.aread()).decode("utf-8", errors="replace")
code = f"HJ-2{max(response.status_code - 400, 0):03d}"
try:
detail = json.loads(body)
code = detail.get("error", {}).get("code", code)
body = detail.get("error", {}).get("message", body)
except json.JSONDecodeError:
pass
raise ShenshiError(code, body)
async for line in response.aiter_lines():
if not line.startswith("data:"):
continue
data = line[len("data:") :].strip()
if data == "[DONE]":
return
if data:
yield ShenshiDelta.model_validate_json(data)
def build_shenshi_client(base_url: str, service_token: str) -> ShenshiClient:
"""按配置装配客户端:未配置神识地址时降级为回声桩(M0 骨架形态)。"""
if base_url:
return HttpShenshiClient(base_url, service_token)
return EchoShenshi()
+63
View File
@@ -0,0 +1,63 @@
"""神识客户端边界类型:灵台 → 神识的版本化 IR。
对应 docs/architecture/shenshi-gateway.md §5:灵台只发送内部 IR,不感知 Provider 方言。
"""
from __future__ import annotations
from typing import Literal
from pydantic import BaseModel, ConfigDict, Field
Role = Literal["system", "user", "assistant", "tool"]
class ShenshiMessage(BaseModel):
"""IR 消息。tool 消息须携带 tool_call_id,与 tool_calls 配对不拆散。"""
model_config = ConfigDict(extra="forbid")
role: Role
content: str
tool_call_id: str | None = None
name: str | None = None
class ShenshiChatRequest(BaseModel):
"""版本化模型请求(IR 入站)。
逻辑模型与能力槽位解耦:默认按 ``slot`` 解析,``model`` 为人物覆写或镜树临时选择。
"""
model_config = ConfigDict(extra="forbid")
ir_version: int = 1
slot: str = "chat"
model: str | None = None
messages: list[ShenshiMessage]
run_id: str
trace_id: str | None = None
character_id: str | None = None
temperature: float | None = None
max_tokens: int | None = None
stream: bool = True
class ShenshiDelta(BaseModel):
"""神识标准化流的增量事件(与 Provider 方言无关)。"""
model_config = ConfigDict(extra="forbid")
delta: str = ""
finish_reason: str | None = None
usage_prompt_tokens: int | None = None
usage_completion_tokens: int | None = None
class ShenshiError(Exception):
"""神识侧错误:携带契约错误码(HJ-2xxx,见契约词典 §15)。"""
def __init__(self, code: str, message: str) -> None:
super().__init__(message)
self.code = code
self.message = message