模型清单与上游网关白名单不同步:claude-fable-5 与 voyage/voyage-4 均被 400 INVALID_MODEL 拒绝
- Dominant language
- TypeScript
- Stars
- 2.7k
- Forks
- 401
- Avg merge
- 21h 48m
- Merged PRs (30d)
- 776
Description
**提交人**: YIKUAN HE
**客户端版本**: 0.1.18
---
## 现象概述
Cindy 客户端展示为可用(status: active)的两个模型,实际请求到上游网关后都被 400 拒绝,错误模板一致。影响两项功能:高阶模型切换、聊天记录语义索引。
---
## 问题一:切换到 Claude Fable 5 后每次发送都失败
**现象**:在会话中将模型切为 Fable 5,切换本身显示成功(engineReady: true),但发出的每一轮都以 api_error 终止。
**会话日志原文**:
```
startSession
model : claude-fable-5[1m]
providerId : anthropic
credentialMode : oauth-bearer
effort : max
endpoint : http://127.0.0.1:56159
SDK ◀ turn ended with error
stopReason : stop_sequence
terminalReason : api_error
durationMs : 9597
output : API Error: 400 400: {'error': 'anthropic_messages: Invalid model name passed in model=claude-fable-5. Call `/v1/models` to view available models for your key.'}
```
**发生时间(跨 2 个会话复现 3 次,非偶发)**:
- 2026-07-27 15:31:10 — session `cf5cce0e-fc82-4303-9b79-8d3da284d861`
- 2026-07-27 19:51:12 — session `33409694-5a06-46cb-8713-ebd41e931e14`
- 2026-07-27 19:53:57 — session `33409694-5a06-46cb-8713-ebd41e931e14`
同时段内其他 Claude 模型(claude-opus-5、claude-sonnet-5)均正常,**仅 claude-fable-5 被拒**。
**客户端侧清单确实声明它可用** — `model-discovery/anthropic-models.json`(fetchedAt 2026-07-26T13:05:02Z):
```json
{ "id": "claude-fable-5", "name": "Claude Fable 5", "contextWindow": 1000000,
"efforts": ["low","medium","high","xhigh","max"], "status": "active" }
```
### 关键对照实验(已排除账号权限因素)
Cindy 会将 `ANTHROPIC_BASE_URL` 设为 `http://127.0.0.1:56159`。清掉该变量、用本机原生 Claude Code CLI(同一 OAuth 账号)直连 Anthropic 测同一个模型:
```
> claude --model claude-fable-5 -p "Reply with exactly: PONG"
PONG
exit=0
```
**结论:账号对 claude-fable-5 有正常访问权限。拒绝发生在 Cindy 转发的上游网关,而非 Anthropic 侧。**
---
## 问题二:聊天记录语义索引全天未能建立(同一根因)
**现象**:embedding worker 持续入队、持续失败,`doneCount` 始终为 0。
```
{"event":"embeddingHost.enqueueJobs","source":"chat","total":1,"inserted":1,"skipped":0}
{"event":"embeddingWorker.batch.failed","source":"chat","modelId":"voyage/voyage-4",
"code":"INVALID_MODEL",
"error":"XD Gateway /v1/embeddings 400: /embeddings: Invalid model name passed in model=voyage/voyage-4. Call `/v1/models` to view available models for your key."}
{"event":"embeddingWorker.tick.end","processed":1,"doneCount":0,"failCount":1}
```
**持续区间**:2026-07-27 06:36:04 → 19:54:47,全天持续发生,无一次成功。
**功能层后果**:`search_chat_history` 返回的回执直接体现了后果:
```json
{ "vector_used": false, "pool_size": 0,
"vector_skip_reason": "尚无已嵌入的聊天向量(未开启"聊天记录语义索引"或嵌入未完成), 本次仅用 FTS。" }
```
向量池为 0,语义检索完全退回关键词匹配。注意 `enqueueJobs` 持续写入,说明功能是开启状态,失败原因是嵌入调用被拒而非未开启。
---
## 根因推断
两个问题的错误模板完全一致(`Invalid model name passed in model=X. Call /v1/models to view available models for your key.`),指向同一个网关在做模型名白名单校验。
**Cindy 客户端声明的可用模型清单,与上游网关实际放行的白名单不同步。** 用户在 UI 上能选中、切换也报成功,但每一次实际请求都被拒。
## 复现步骤
1. 在任意会话将模型切为 Claude Fable 5(effort 任意)
2. 发送任意一条消息
3. 观察:轮次以 api_error 终止,日志出现 400 INVALID_MODEL
4. 语义索引部分:调用 `search_chat_history`,观察返回 `vector_used: false` 与 `pool_size: 0`
## 期望行为
1. 上游网关补上 `claude-fable-5` 与 `voyage/voyage-4` 的映射;或
2. 客户端模型清单改为以网关实际白名单为准,不展示未放行的模型;且
3. 模型不可用时在 UI 层给出明确提示,而不是切换报成功、发送才静默失败;以及
4. embedding 持续失败时向用户可见地告警,避免用户在不知情的情况下以为长期记忆语义检索已生效。
---
**OS**: win32 x64 (10.0.26200)
**界面语言**: zh-CN
Contributor guide
Assessment
This issue has not been assessed yet.