diff --git a/desktop/src-tauri/src/summarizer_adapter.rs b/desktop/src-tauri/src/summarizer_adapter.rs index d7ab086..ad12750 100644 --- a/desktop/src-tauri/src/summarizer_adapter.rs +++ b/desktop/src-tauri/src/summarizer_adapter.rs @@ -31,7 +31,9 @@ impl TauriSummaryDriver { async fn call_llm(&self, prompt: String) -> Result { let client = reqwest::Client::new(); - let model = self.model.clone().unwrap_or_else(|| "glm-4-flash".to_string()); + let model = self.model.clone().ok_or_else(|| { + "Summary driver model not configured — kernel_init must be called first".to_string() + })?; let request = serde_json::json!({ "model": model, diff --git a/desktop/src/lib/saas-relay-client.ts b/desktop/src/lib/saas-relay-client.ts index e56b551..d338c32 100644 --- a/desktop/src/lib/saas-relay-client.ts +++ b/desktop/src/lib/saas-relay-client.ts @@ -180,8 +180,15 @@ export function createSaaSRelayGatewayClient( ? [...history, { role: 'user' as const, content: maskedMessage }] : [{ role: 'user' as const, content: maskedMessage }]; + const model = getModel(); + if (!model) { + callbacks.onError('No model available — please check SaaS relay configuration'); + callbacks.onComplete(); + return { runId }; + } + const body: Record = { - model: getModel() || 'glm-4-flash-250414', + model, messages, stream: true, }; diff --git a/wiki/log.md b/wiki/log.md index d384ea7..9b9846f 100644 --- a/wiki/log.md +++ b/wiki/log.md @@ -9,6 +9,12 @@ tags: [log, history] > Append-only 操作记录。格式: `## [日期] 类型 | 描述` +## [2026-04-11] fix | 模型路由链路修复 — 消除硬编码不匹配模型 + +1. summarizer_adapter.rs — "glm-4-flash" 硬编码 fallback → 未配置时明确报错 (fail fast) +2. saas-relay-client.ts — 'glm-4-flash-250414' 硬编码 fallback → 未获取模型时报错 +3. Wiki routing.md — 新增完整模型路由文档 (Tauri SaaS Relay 主路径 + 辅助 LLM + Browser 模式) + ## [2026-04-11] fix | Skill/MCP 调用链路修复 3 个断点 1. Anthropic Driver ToolResult 格式 — ContentBlock 添加 ToolResult 变体, tool_call_id 不再丢弃 diff --git a/wiki/routing.md b/wiki/routing.md index 175d8f8..e9d3722 100644 --- a/wiki/routing.md +++ b/wiki/routing.md @@ -126,6 +126,100 @@ desktop/src/store/ | 工具 | 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] 仅用于本地 Kernel,SaaS 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