Files
zclaw_openfang/wiki/routing.md
iven c10e50d58e docs(wiki): Phase D完成 — 6模块页重构(routing/chat/butler/hands-skills/pipeline/data-model)
- 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>
2026-04-22 21:53:17 +08:00

132 lines
5.1 KiB
Markdown
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

---
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) |