# aichat API Key 鉴权失败排查 > 2026-06-15 排查 | 看板索引:`docs/todo.md`「🔴 aichat API Key 401 排查」区块 ## 现象 - aichat 对话失败,提示「调用失败: API Key 无效或无权限」 - 重新设置 apikey 后仍无效 ## 错误来源 - `stream_recv.rs:107-113`:`provider.stream(request)` 直接返回 `Err` 分支 - 错误文本:`format!("AI 调用失败: {}", e)`,`e` 为服务端返回的 401/403 - 即:请求发出去了,被服务端以鉴权/权限为由拒绝。**非流式中断、非网络断连。** ## 排查结论:代码链路全对(排除代码 bug) ### 保存链路(正确) - `commands.rs ai_save_provider:329-333`:`api_key` 非空 → `set_provider_secret` 写 OS keyring - 写失败会返回「密钥保存到系统钥匙串失败」(用户未报此错 → **写入成功**) - `:334`:DB `api_key` 列恒空(FR-S1 设计,真实密钥唯一源 = OS keyring) ### 读取链路(正确) 所有 `build_provider` 调用点均经 `resolve_provider_secret`(keyring 优先,fallback DB): | 调用点 | 行号 | 状态 | |--------|------|------| | agentic.rs(对话主循环) | :50 | ✓ | | project.rs(技术栈扫描) | :378 | ✓ | | knowledge_inject.rs(知识提炼/嵌入) | :33, :323 | ✓ | | title.rs(标题生成) | :63 | ✓ | - 例外:`df-nodes/ai_node.rs:118` 用 `p.api_key`(工作流 AI 节点独立配置,不经 keyring,与本问题无关) ### 鉴权头(正确) - `openai_compat`:`Authorization: Bearer {api_key}` - `anthropic_compat`:`x-api-key: {api_key}` + `anthropic-version` ### URL 智能拼接(正确,三规则容错) - `openai_compat.chat_url`(openai_compat.rs:253-262): - 已含 `/chat/completions` → 直接用 - 以 `/v<数字>` 结尾(如 `/v1` `/v4`,GLM 的 `/api/paas/v4`)→ 补 `/chat/completions` - 仅域名(`api.openai.com` / `api.deepseek.com`)→ 补 `/v1/chat/completions` - `anthropic_compat.messages_url`(anthropic_compat.rs:255-264): - 已含 `/v1/messages` → 直接用 - 以 `/v1` 结尾 → 补 `/messages` - 否则(`.../api/anthropic`、`api.anthropic.com`)→ 补 `/v1/messages` **链路已全部验证无误。401 来自服务端,根因不在 devflow 代码。** ## 根因方向(服务端 401,按概率排序) ### 1. API Key 本身无效(最常见) - 过期 / 欠费 / 额度用尽 - 粘贴时带空格、引号、回车或多余字符 - 中转 key 与官方 key 混淆(不同渠道 key 不通用) ### 2. provider_type 与端点不匹配 - `openai_compat` 用 `Bearer`,`anthropic_compat` 用 `x-api-key`,两者鉴权头互斥 - 若实际是 OpenAI 兼容端点却选 `anthropic_compat`(或反之)→ 鉴权头不被识别 → 401 - 典型: - GLM 官方 chat 端点(`open.bigmodel.cn/api/paas`)→ `openai_compat` - GLM 的 Anthropic 兼容端点(`open.bigmodel.cn/api/anthropic`)→ `anthropic_compat` - Claude 官方(`api.anthropic.com`)→ `anthropic_compat` - 各类中转/聚合站(one-api/new-api)→ 一般 `openai_compat` ### 3. base_url 填错 - 多/少 `/v1` 段(智能拼接已容错多数情况,但中转站路径各异仍可能错) - 协议错(`http` vs `https`) - 域名拼写错误 ### 4. model 名错/无权限 - 部分 API 网关对无效 model 返回 401 而非 404 - model 名大小写、版本号、前缀错(如 `glm-4` vs `glm-4-flash`,`claude-3-5-sonnet` vs `claude-3-5-sonnet-20241022`) ## 验证步骤(直连测试,区分 key / 配置) 在终端用 curl 直连,绕开 devflow,精准定位是 key 还是配置问题: ### A. provider_type = openai_compat(Bearer 鉴权) ```bash curl -s "{base_url}/v1/chat/completions" \ -H "Authorization: Bearer {KEY}" \ -H "Content-Type: application/json" \ -d '{"model":"{MODEL}","messages":[{"role":"user","content":"hi"}],"max_tokens":5}' ``` ### B. provider_type = anthropic_compat(x-api-key 鉴权) ```bash curl -s "{base_url}/v1/messages" \ -H "x-api-key: {KEY}" \ -H "anthropic-version: 2023-06-01" \ -H "Content-Type: application/json" \ -d '{"model":"{MODEL}","max_tokens":5,"messages":[{"role":"user","content":"hi"}]}' ``` > `{base_url}` `{KEY}` `{MODEL}` 替换为设置页实际填的值(base_url 按拼接规则:不带末尾的 `/v1/...`,但带了也兼容)。 ### 结果判断 | 返回 | 含义 | |------|------| | 正常 completion JSON | key+url+type+model 全对 → 问题在 devflow 内部(概率极低,链路已验证;此时查 Rust 日志看实际请求) | | `401` / `authentication_error` | key 无效,**或** provider_type 选错(鉴权头不匹配) | | `404` | base_url 拼接错,端点不存在 | | `400` model 相关 | model 名错/无权限 | | 连接失败/超时 | base_url 域名或网络问题 | ## TODO - [ ] S-260615-01 用户:执行直连测试(A 或 B),确认是 key、provider_type、base_url、model 哪一项的问题 - [ ] 用户:核对 provider_type 与实际端点匹配(见「根因方向 2」的典型对照) - [ ] 用户:核对 model 名拼写(对照 API 提供商文档) - [ ] 若直连测试通过、devflow 内仍 401:开 Rust tracing 日志,查实际发出的 url + header 是否与直连一致(排除 body 字段触发的鉴权问题) - [ ] **B-260615-01(可选增强)** `stream_recv.rs:107` Err 分支增加诊断信息:把 provider_type + 实际请求 url + HTTP 状态码记入日志/错误返回,便于 401 快速定位(当前错误只透传服务端文本,看不出打了哪个 url)