Files
zclaw_openfang/wiki/routing.md
iven 27b98cae6f
Some checks failed
CI / Lint & TypeCheck (push) Has been cancelled
CI / Unit Tests (push) Has been cancelled
CI / Build Frontend (push) Has been cancelled
CI / Rust Check (push) Has been cancelled
CI / Security Scan (push) Has been cancelled
CI / E2E Tests (push) Has been cancelled
docs: wiki 全量更新 — 2026-04-14 代码验证驱动
关键数字修正:
- Rust 77K行(274 .rs)、Tauri 189命令、SaaS 137 routes
- Admin V2 17页、SaaS 16模块(含industry)、@reserved 22
- SQL 20迁移/42表、TODO/FIXME 4个、dead_code 16

内容更新:
- known-issues: V13-GAP 全部标记已修复 + 三端联调测试结果
- middleware: 14层 runtime + 10层 SaaS HTTP 完整清单
- saas: industry模块、路由模块13个、数据表42个
- routing: Store含industryStore、21个Store文件
- butler: 行业配置接入ButlerPanel、4内置行业
- log: 三端联调+V13修复记录追加
2026-04-14 22:15:53 +08:00

242 lines
8.1 KiB
Markdown
Raw Permalink 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-14
status: active
tags: [module, routing, connection]
---
# 客户端路由
> 从 [[index]] 导航。关联模块: [[chat]] [[saas]]
## 设计思想
**核心决策: Tauri 桌面端通过 SaaS Token Pool 中转访问 LLM不直连。**
为什么?
1. **集中密钥管理** — 用户不需要自己的 API KeySaaS 维护共享 Key 池
2. **用量追踪 + 计费** — 每次调用经过 SaaS`record_usage` worker 记录 token 消耗
3. **模型白名单** — Admin 配置哪些模型可用,`listModels()` 返回白名单
4. **降级保障** — SaaS 挂了自动切本地 Kernel桌面端不变砖
## 代码逻辑
### 4 分支决策树
入口: `connectionStore.ts:349``connect(url?, token?)`
```
connect()
├── [1] Admin 强制路由: localStorage llm_routing
│ ├── "relay" → 强制 SaaS Relay 模式
│ └── "local" → 强制本地 Kernel (adminForceLocal=true)
├── [2] SaaS Relay 模式: localStorage('zclaw-connection-mode') === 'saas'
│ ├── Tauri: KernelClient + baseUrl = saasUrl/api/v1/relay
│ │ apiKey = SaaS JWT (不是 LLM Key!)
│ ├── Browser: SaaSRelayGatewayClient (SSE)
│ └── SaaS 不可达 → 降级到本地 Kernel
├── [3] 本地 Kernel: isTauriRuntime() === true
│ KernelClient + 用户自定义模型配置
│ 用户需要自己的 API Key
└── [4] External Gateway (fallback)
GatewayClient via WebSocket/REST
```
### SaaS Relay 主路径 (Tauri 桌面端)
关键代码: `connectionStore.ts:482-535`
```ts
kernelClient.setConfig({
provider: 'custom',
model: modelToUse, // 从 SaaS listModels() 获取
apiKey: session.token, // SaaS JWT不是 LLM Key
baseUrl: `${session.saasUrl}/api/v1/relay`, // 指向 SaaS relay
apiProtocol: 'openai',
});
```
**注意**: Kernel 仍然执行 LLM 调用逻辑,但请求发往 SaaS relay 而非直连 LLM。
SaaS relay 接到请求后,从 Token Pool 中取一个可用 Key转发给真实 LLM。
### SaaS 降级流程
关键代码: `connectionStore.ts:446-468`
```
listModels() 失败
→ 401 → session 过期 → logout → 要求重新登录
→ 其他错误 → saasDegraded = true
→ saasStore.saasReachable = false
→ 降级到本地 Kernel 模式
```
### 客户端类型
| 客户端 | 传输 | 文件 | 用途 |
|--------|------|------|------|
| GatewayClient | WebSocket + REST | `lib/gateway-client.ts` | 外部 Gateway 进程 |
| KernelClient | Tauri invoke() | `lib/kernel-chat.ts` | 内置 Kernel (桌面端) |
| SaaSRelayGatewayClient | HTTP SSE | `lib/saas-relay-client.ts` | 浏览器端 SaaS 中继 |
`getClient()` 定义: `connectionStore.ts:844`
所有 Store 通过 `initializeStores()` (store/index.ts:94) 获取共享 client。
### Store 层 (17 文件 + chat/4 = 21)
```
desktop/src/store/
├── index.ts Store 协调器 + client 注入
├── agentStore.ts Agent 分身管理
├── browserHandStore.ts 浏览器 Hand 状态
├── chatStore.ts 聊天通用状态
├── classroomStore.ts 课堂模式
├── configStore.ts 配置读写
├── connectionStore.ts 路由决策核心
├── handStore.ts Hand 状态管理
├── industryStore.ts 行业配置 (V13 新增,已接通 ButlerPanel)
├── memoryGraphStore.ts 记忆图谱
├── offlineStore.ts 离线队列
├── saasStore.ts SaaS 认证
├── securityStore.ts 安全状态
├── sessionStore.ts 会话管理
├── uiModeStore.ts 双模式 UI
├── workflowBuilderStore.ts 工作流构建器
├── workflowStore.ts 工作流状态
└── chat/
├── artifactStore.ts 聊天产物
├── conversationStore.ts 会话管理
├── messageStore.ts 消息持久化
└── streamStore.ts 流式编排
```
### lib/ 工具层 (85 个文件)
关键分类:
| 类别 | 文件 | 数量 |
|------|------|------|
| Kernel 通信 | kernel-chat/kernel-client/kernel-agent/... | 7 |
| SaaS 通信 | saas-client/saas-auth/saas-billing/... | 9 |
| Gateway | gateway-client/gateway-api/gateway-auth/... | 6 |
| Intelligence | intelligence-client/ + 5 fallback | 7 |
| 工具 | config-parser/crypto-utils/logger/utils | 10+ |
| Tauri 集成 | safe-tauri/tauri-gateway/secure-storage | 3 |
## 模型路由
### 完整链路 (Tauri SaaS Relay 主路径)
```
前端模型选择
├─ conversationStore.currentModel (用户上次选择的模型)
│ 持久化到 IndexedDB跨会话保留
├─ connectionStore 连接时获取 SaaS 可用模型
│ saasClient.listModels() → [{id: "deepseek-chat"}, {id: "GLM-4.7"}, ...]
│ relayModels[0]?.id 作为 fallback
└─ 最终: preferredModel || fallbackId
kernelClient.setConfig({ model: modelToUse })
kernel_init (Tauri Command)
│ KernelConfigRequest { model, api_key, base_url }
│ base_url = "https://saas-host/api/v1/relay"
│ api_key = SaaS JWT (不是 LLM Key!)
Kernel::boot(config)
│ config.llm.model = modelToUse
│ config.llm.base_url = SaaS relay URL
loop_runner → LLM Driver (OpenAI compatible)
│ POST {base_url}/chat/completions
│ body: { model: modelToUse, messages: [...] }
│ header: Authorization: Bearer {SaaS JWT}
SaaS Relay Handler (handlers.rs)
│ cache.get_model(model_name) → 精确匹配 model_id
│ ⚠️ 无别名解析! "glm-4-flash" ≠ "deepseek-chat"
│ 找不到 → 400 "模型 xxx 不存在或未启用"
Key Pool 轮换
│ priority ASC → last_used_at ASC → cooldown 检查 → RPM/TPM 滑动窗口
真实 LLM API
│ 429 → mark cooldown → 切换 key
│ 5xx → exponential backoff
│ model_group → 跨 Provider 故障转移
响应 → SSE 流式返回 → 前端
```
### 辅助 LLM 调用 (非聊天主路径)
这些 Rust 端组件也通过同一个 relay 发起 LLM 请求:
| 组件 | 文件 | 模型来源 | 触发时机 |
|------|------|----------|----------|
| 记忆摘要 | `summarizer_adapter.rs` | kernel_init 传入的 model | 定期 L0/L1 摘要生成 |
| 记忆提取 | `extraction_adapter.rs` | kernel_init 传入的 model | 中间件触发提取 |
| 管家路由 | ButlerRouter via loop_runner | 同聊天模型 | 聊天中间件链 |
**关键**: `summarizer_adapter.rs``extraction_adapter.rs``kernel_init` 时配置,
使用与聊天相同的 `model``base_url`。未配置时会明确报错,不会静默 fallback 到错误模型。
### SaaS Relay 模型匹配规则
```
前端发送 model: "deepseek-chat"
→ SaaS cache 按 model_id 精确匹配
→ 匹配: cache.models["deepseek-chat"] → 命中
→ 不匹配: cache.models["glm-4-flash"] → null → 400 错误
⚠️ config.toml 中的 [llm.aliases] 仅用于本地 KernelSaaS relay 不解析别名!
```
### Browser 模式模型路由
```
createSaaSRelayGatewayClient(saasUrl, getModel)
│ getModel() 回调 → conversationStore.currentModel || relayModels[0]?.id
chatStream() → saasClient.chatCompletion({ model: getModel() })
│ 未获取到模型时 → onError 报错,不发请求
POST /api/v1/relay/chat/completions → SSE 流
```
## 关联模块
- [[chat]] — 路由决定使用哪种 ChatStream
- [[saas]] — Token Pool、认证、模型管理
- [[middleware]] — 请求经过中间件链处理
- [[butler]] — 管家模式通过 ButlerRouter 中间件介入
## 关键文件
| 文件 | 职责 |
|------|------|
| `desktop/src/store/connectionStore.ts` | 路由决策核心 |
| `desktop/src/lib/gateway-client.ts` | WebSocket 客户端 |
| `desktop/src/lib/kernel-chat.ts` | Tauri 内核聊天 |
| `desktop/src/lib/kernel-client.ts` | Kernel 客户端配置 |
| `desktop/src/lib/saas-relay-client.ts` | SaaS SSE 中继 |
| `desktop/src/lib/saas-client.ts` | SaaS API 客户端 |
| `desktop/src/store/index.ts` | Store 协调器 + client 注入 |