- routing.md: 移除Store/lib列表+5节模板 (330→131行) - chat.md: 添加集成契约+不变量 (180→134行) - butler.md: 移除重复→引用memory/hands-skills (215→150行) - hands-skills.md: 5节模板+契约+不变量 (281→170行) - pipeline.md: 添加契约+重组 (157→154行) - data-model.md: 添加契约+双库架构图 (181→153行) Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
132 lines
5.1 KiB
Markdown
132 lines
5.1 KiB
Markdown
---
|
||
title: 客户端路由
|
||
updated: 2026-04-22
|
||
status: active
|
||
tags: [module, routing, connection]
|
||
---
|
||
|
||
# 客户端路由
|
||
|
||
> 从 [[index]] 导航。关联模块: [[chat]] [[saas]]
|
||
|
||
## 1. 设计决策
|
||
|
||
**核心: Tauri 桌面端通过 SaaS Token Pool 中转访问 LLM,不直连。**
|
||
|
||
| 决策 | 原因 |
|
||
|------|------|
|
||
| 5 分支路由 | 覆盖全部部署形态: Admin本地 / Tauri+SaaS / Browser+SaaS / Tauri本地 / 外部Gateway |
|
||
| SaaS Relay 中转 | 集中密钥管理 — 用户无需自备 API Key;用量追踪计费 — 每次调用经 SaaS;模型白名单 — Admin 控制可用模型 |
|
||
| 自动降级到本地 Kernel | SaaS 不可达时桌面端不变砖,无感切换,不需要用户干预 |
|
||
| Kernel 不直连 LLM | 直连是降级后备。主路径经 SaaS Token Pool 做 RPM/TPM 轮换 + 故障转移 |
|
||
| getClient() 全局单例 | 所有 Store 通过 `initializeStores()` 获取共享 client,避免重复连接 |
|
||
|
||
## 2. 关键文件 + 数据流
|
||
|
||
### 核心文件
|
||
|
||
| 文件 | 职责 |
|
||
|------|------|
|
||
| `desktop/src/store/connectionStore.ts` | 路由决策核心: 5 分支 + 降级 + 模型路由 |
|
||
| `desktop/src/lib/kernel-chat.ts` | KernelClient ChatStream (Tauri Event) |
|
||
| `desktop/src/lib/kernel-client.ts` | Kernel 客户端配置 (setConfig/boot) |
|
||
| `desktop/src/lib/saas-relay-client.ts` | SaaS Relay ChatStream (SSE) |
|
||
| `desktop/src/lib/gateway-client.ts` | External Gateway ChatStream (WebSocket) |
|
||
| `desktop/src/store/index.ts` | Store 协调器 + client 注入 |
|
||
|
||
### 5 分支决策树
|
||
|
||
```
|
||
connect()
|
||
├─ [1] Admin强制本地: adminRouting=local && isTauri → Kernel 直连
|
||
├─ [2] SaaS+Tauri: savedMode=saas && isTauri → KernelClient + baseUrl=SaaS relay
|
||
│ └─ SaaS不可达 → 降级 [4]
|
||
├─ [3] SaaS+Browser: savedMode=saas && !isTauri → SaaSRelayClient (SSE)
|
||
│ └─ SaaS不可达 → 降级 [4]
|
||
├─ [4] 本地Kernel: isTauriRuntime && 非SaaS → KernelClient + 用户自配 Key
|
||
└─ [5] 外部Gateway: !isTauri → GatewayClient (WebSocket)
|
||
```
|
||
|
||
### 集成契约
|
||
|
||
| 方向 | 模块 | 接口 | 说明 |
|
||
|------|------|------|------|
|
||
| Calls -> | saas | relay URL + JWT | Chat relay, model list, 用量上报 |
|
||
| Calls -> | kernel | Tauri invoke | Kernel boot, chat, config |
|
||
| Called by <- | all stores | `getClient()` | 每个 API 调用都经过路由决策 |
|
||
| Provides -> | UI | Connection status, model list | 所有聊天依赖组件消费 |
|
||
|
||
## 3. 代码逻辑
|
||
|
||
### 模型路由链 (SaaS Relay 主路径)
|
||
|
||
```
|
||
前端选择模型 → preferredModel || fallbackId
|
||
→ kernelClient.setConfig({ model, apiKey: JWT, baseUrl: saasUrl/api/v1/relay })
|
||
→ Tauri invoke kernel_init → Kernel::boot(config)
|
||
→ loop_runner → POST {base_url}/chat/completions
|
||
→ SaaS Relay → cache 精确匹配 model_id → Key Pool 轮换 → 真实 LLM
|
||
→ SSE 流式返回
|
||
```
|
||
|
||
### SaaS 降级流程
|
||
|
||
```
|
||
listModels() 失败
|
||
→ 401 → session 过期 → logout
|
||
→ 其他 → saasDegraded=true → 降级本地 Kernel
|
||
```
|
||
|
||
### 客户端类型
|
||
|
||
| 客户端 | 传输 | 用途 |
|
||
|--------|------|------|
|
||
| GatewayClient | WebSocket + REST | 外部 Gateway 进程 |
|
||
| KernelClient | Tauri invoke() | 内置 Kernel (桌面端) |
|
||
| SaaSRelayGatewayClient | HTTP SSE | 浏览器端 SaaS 中继 |
|
||
|
||
### 不变量
|
||
|
||
- `getClient()` 是全局单例,所有 Store 通过 `initializeStores()` 获取共享 client
|
||
- SaaS 不可达时自动降级到本地 Kernel,不需要用户干预
|
||
- SaaS Relay 按 `model_id` 精确匹配,不解析别名 (`config.toml [llm.aliases]` 仅本地 Kernel)
|
||
- Provider Key 解密失败时 warn+skip,不 500 (`key_pool.rs`)
|
||
|
||
### Tauri 命令
|
||
|
||
| 命令 | 说明 |
|
||
|------|------|
|
||
| `kernel_init` | 初始化 Kernel 配置 (model, apiKey, baseUrl) |
|
||
| `zclaw_start` / `zclaw_stop` / `zclaw_restart` | Kernel 生命周期管理 |
|
||
| `zclaw_health_check` / `zclaw_ping` | 健康检查 + 端口检测 |
|
||
| `zclaw_doctor` | 完整诊断报告 |
|
||
|
||
### SaaS Relay 路由
|
||
|
||
| 路径 | 说明 |
|
||
|------|------|
|
||
| `POST /api/v1/relay/chat/completions` | 主聊天中转 (认证+配额) |
|
||
| `GET /api/v1/relay/models` | 可用模型列表 |
|
||
|
||
## 4. 活跃问题 + 注意事项
|
||
|
||
| 问题 | 状态 | 说明 |
|
||
|------|------|------|
|
||
| Tauri invoke 参数名 snake_case | ✅ 已修复 (f6c5dd2) | Tauri 2.x 默认 `rename_all="camelCase"`,invoke 必须用 camelCase |
|
||
| Provider Key 解密致 relay 500 | ✅ 已修复 (b69dc61) | decrypt 失败 warn+skip,启动时 `heal_provider_keys()` 自动重新加密 |
|
||
| Tauri 命令孤儿 | ~0 (差异来自内部调用) | 190 定义 / 104 invoke / 97 @reserved |
|
||
|
||
**注意事项:**
|
||
- `summarizer_adapter.rs` 和 `extraction_adapter.rs` 在 `kernel_init` 时配置,使用与聊天相同的 model+base_url。未配置时明确报错,不静默 fallback
|
||
- Browser 模式 `getModel()` 未获取到模型时 onError 报错,不发请求
|
||
|
||
## 5. 变更日志
|
||
|
||
| 日期 | 变更 |
|
||
|------|------|
|
||
| 04-22 | Wiki 重写: 5 节模板,移除 Store/lib 全量列表 |
|
||
| 04-21 | 上一轮更新 |
|
||
| 04-19 | TRUTH.md 数字校准: 190 命令 / 104 invoke / 97 @reserved |
|
||
| 04-16 | Provider Key 解密修复 (b69dc61) |
|
||
| 04-16 | Tauri invoke 参数名修复 (f6c5dd2) |
|