113 Desktop 受治理模型入口设计
状态:源码已落地,生产部署待验收
核对基线:main@5db62db246(2026-09-07)
发布边界:该实现晚于chainlesschain@0.166.30的发布 SHA87ddf8b126,不得解释为 npm CLI 字节或已公开 Desktop 安装包已经包含本能力。
1. 背景与目标
Desktop 历史上存在多条直接调用模型或 opaque AI backend 的路径。它们分散在普通对话、流式响应、函数工具循环、图像/音频/视频、embedding、reranker、项目创建、文档生成和旧 RAG 客户端中。只在主聊天入口记录治理事件,会让相同用户内容经辅助模型、fallback 或旧 HTTP client 绕过证据投影和账本。
本模块建立一个宿主拥有的模型入口,目标是:
- 每次允许的模型请求在网络发送前进入唯一
EvolutionRun。 - 用户输入、工具请求/结果、模型终态与缓存回放保持同一 Run lineage。
- OpenAI、Anthropic、Gemini、Ollama 和多模态请求使用各自 wire protocol,同时共享治理不变量。
- 未迁移的 opaque backend 在接触网络 client 前失败闭合,不静默降级到另一条直连路径。
- renderer、IPC payload、provider 配置和运行时 fallback 都不能替换宿主 authority。
非目标:
- 不把健康检查、模型列表、模型构建等无用户内容的控制面误记为推理。
- 不在本模块中提供生产 KMS/HSM、PKI、身份、策略、witness 或 grader。
- 不承诺所有失败的远程功能已有受治理替代;部分旧入口当前按设计直接拒绝。
2. 架构
Renderer / IPC / Desktop service
│
▼
LLMManager / provider client
│
▼
branded DesktopModelIngressHost
│ creates one Run per workflow
▼
AgentEvolutionRuntimeComposition
├─ evidence projection / redaction
├─ EvolutionRun + ArtifactStore
├─ signed Ledger + witness
├─ tool request/result lineage
└─ response/cache receipt binding
│ admission succeeds
▼
OpenAI / Anthropic / Gemini / Ollama
Legacy media / embedding / reranker / project / document / RAG
│ no trusted bridge
└──────────────► fail before network I/Odesktop-model-ingress.js 以 WeakMap 保存 host/client 绑定,以 AsyncLocalStorage 保存活动 workflow。宿主工厂必须返回 branded composition,且 runId、taskId、tenant 与 ingress 必须精确匹配。已绑定 client 的 authority 不允许被另一 host 替换。
3. 请求生命周期
3.1 普通与流式请求
- 捕获 provider 最终 wire payload,而不是较早的 UI 输入草稿。
- 创建
desktop-model-*Run,持久化run-started与 UserPrompt。 - 调用
prepareModelRequest()生成带来源说明的投影消息与工具定义。 - 只有投影成功后才能向 provider 发出 HTTP 请求。
- 流式响应必须以合法终止帧结束;终止后出现新内容、残缺 tool call 或异常 JSON 均失败。
response-complete与 Desktop result 写入同一 ingress,随后完成 Run。
任何证据写入、readback 或终态结算失败统一转换为 CC_AGENT_EVOLUTION_INGRESS_FAILED,调用方不得把它吞掉并切换到未治理 fallback。
3.2 函数工具循环
一个用户请求中的多轮模型调用与工具执行共用同一 Run:
- 工具定义名称必须唯一;每批调用在产生副作用前完整校验。
- tool call ID 不得重复,参数必须是 JSON object,调用只能命中声明过的函数。
tool-executing、tool-result或tool-error在继续下一轮模型请求前进入 ingress。- 默认最多 8 轮,可配置范围为 1–16;耗尽上限时失败闭合。
- nested summary 等中间模型步骤复用当前 Run,不能另开一个脱离父 lineage 的 Run。
3.3 多模态请求
图片块不被强制转成文本。desktop-multimodal-model-* Run 先把 opaque image transport 写成摘要绑定 commitment,经认证 readback 后才恢复 provider 所需的原始块。返回的 messages/tools 必须与当前 tenant 和 Run 绑定。
外部 DALL·E、Stable Diffusion、LLaVA、Whisper、浏览器视觉、视频生成和 bundled media handler 在没有受治理多模态桥时于 fetch/client 调用前拒绝,避免通过“辅助功能”泄露 prompt、图片或音频。
4. Provider 协议适配
| Provider | 投影与终态要求 |
|---|---|
| OpenAI-compatible | 投影 messages/tools,SSE 必须收到终态,完整记录 assistant/tool calls |
| Anthropic | 来源说明合并到 system,保留投影后的 message 顺序与完整终态 |
| Gemini | systemInstruction 与 contents.parts 仅接受 text part;流结束必须带 finishReason |
| Ollama | 拒绝 opaque context token,要求显式 conversation messages;NDJSON 必须出现 done: true |
切换 provider 时先 staging 新 client,再替换活动引用;budget listener 与并发 Run 隔离。IPC reconfiguration 不得丢失已建立的 model authority。
5. 响应缓存
响应缓存只接受认证 receipt 回放:
- cache key 绑定 tenant、provider、model、connection、messages 与完整 request options。
AbortSignal等不可序列化或不透明 options 使请求不可缓存,但不绕过模型 admission。- cache hit 必须先验证 receipt 与 request key,再恢复绑定的 Desktop result。
- cache miss 的 provider response 必须与 Run 中记录的结果完全一致,随后才能创建 cache receipt。
- nested model step 不单独读写 response-cache receipt,避免把中间结果当成用户终态。
普通 TTL 命中、旧裸缓存值或调用方提供的 wasCached 标记都不能替代 receipt。
6. 旧入口处置矩阵
| 类别 | 当前处置 | 用户可见结果 |
|---|---|---|
| LLMManager 对话、query、stream、工具循环 | 接入受治理 Run | 正常执行;治理/证据失败则明确终止 |
| OpenAI / Anthropic / Gemini / Ollama | provider 原生协议桥接 | 请求投影与终态统一入账 |
| embedding / reranker | 已识别直连路径发送前拒绝 | RAG 不会把治理拒绝伪装成普通降级 |
| 图像 / 语音 / 视频 | 有多模态桥的请求接入;其余直连拒绝 | 不会静默把媒体发往旧 provider |
| 项目 AI / create stream | opaque backend 发送前拒绝 | 本地受治理路径或确定性 fallback 可继续 |
| Task Planner / Word / PPT / PDF / Excel / Document | 旧 /api/chat/stream 发送前拒绝 | 保留规则/默认结构等本地 fallback |
| legacy RAG index/query/update | HTTP client 前拒绝 | 本地 ProjectRAGManager 不受影响 |
| Volcengine health check | 不再发模型调用 | 仅检查本地配置完整性;不产生 token/费用 |
7. Ledger witness 的 trust epoch
长账本追加会反复验证历史 witness。源码新增可选 verifier.getTrustEpoch():
- 只有受信 verifier 在一次读取前后返回相同、非空且有界的 epoch,才复用该 epoch 内的逐记录验签结果。
- epoch 改变时重新验证完整 history,以使撤销或 trust-root 变化生效。
- 未实现该端口、epoch 过长或读取期间变化时继续失败闭合。
- epoch 不从 witness 文件或调用方 payload 读取,避免攻击者选择缓存域。
1,000-event 仓库演练把 witness signature verification 从约 2,017,022 次降至 1,002 次,同时保留旧 segment 和 witness 篡改拒绝。该优化不解决完整 history JSON 每次 append 的 parse/serialize/fsync 线性成本;生产仍需认证的 epoch/revocation snapshot,并评估不可变分段或认证压缩。
8. 安全不变量
- 网络发送必须发生在 admission 与投影之后。
- 客户端不能传入或替换 ingress、composition factory、tenant、receipt verifier 或 active writer。
- 治理错误不得被 cache、关键词 rerank、规则 fallback 或 provider fallback 吞掉。
- tool side effect 必须位于对应
tool-executing与 terminal result/error 事件之间。 - 响应缓存只能重放与当前请求精确绑定的认证结果。
- 健康检查不得以“探测”为名触发真实模型推理。
- 测试 factory、HMAC key 与 isolated local profile 不等于生产 authority。
9. 运维与排障
CC_AGENT_EVOLUTION_INGRESS_FAILED 表示治理链、证据持久化、投影或终态结算失败。运维应保留原始 cause,检查 deployment descriptor、tenant identity、ArtifactStore、Ledger/witness、policy 与 provider wire response;不得改回 direct HTTP。
旧媒体、项目、文档或 RAG 功能提示“requires a governed model ingress”时,表示该远程路径已主动关闭。可使用已有本地/确定性 fallback,或等待目标部署提供受治理 bridge。
Volcengine 健康检查只验证 apiKey、baseUrl 与 model 是否配置,不代表远端服务、余额或模型权限已验证。需要产生真实请求的连通性测试必须显式执行,并可能产生费用。
10. 验证范围与未完成项
当前回归覆盖 host branding、并发 Run/summary 隔离、四类 provider、流式终态、工具调用、cache receipt、媒体与 embedding 阻断、reranker、项目、文档、planner、legacy RAG 和 trust-epoch witness。关键测试位于:
desktop-app-vue/src/main/evolution/__tests__/desktop-evolution-deployment.test.jsdesktop-app-vue/src/main/llm/__tests__/openai-client.test.jsdesktop-app-vue/src/main/llm/__tests__/ollama-client.test.jsdesktop-app-vue/tests/unit/rag/embeddings-service.test.jsdesktop-app-vue/tests/unit/rag/reranker.test.jsdesktop-app-vue/tests/unit/project/project-ai-ingress-governance.test.jsdesktop-app-vue/tests/unit/document/document-backend-ingress-governance.test.jspackages/cli/__tests__/unit/evolution-file-witness.test.js
仍需目标环境完成:
- 生产签名 deployment descriptor、KMS/HSM、PKI、identity/policy 与跨主机 witness。
- Desktop 安装、升级、回滚和真实 provider/media/RAG journey 的 fresh-main 资格验收。
- 为当前失败闭合的旧功能提供统一受治理远程 bridge。
- Ledger 分段/压缩、掉电注入、生产量级和真实撤销延迟基线。
