[调研] CN 聊天语义搜索 embedding 模型可用性与职责边界分析
- Dominant language
- TypeScript
- Stars
- 2.7k
- Forks
- 401
- Avg merge
- 21h 48m
- Merged PRs (30d)
- 776
Description
## 问题描述 / What happened
CN AI Gateway 当前不提供 `voyage/voyage-4`,但 Desktop 的聊天记录语义索引将它硬编码为默认 embedding 模型,并且该功能在没有用户配置文件时默认开启。结果是 CN 用户正常聊天时,后台持续向 `/v1/embeddings` 发送必然失败的请求。
产品讨论中提出了一个需要进一步确认的前提:如果 embedding 仅用于 Cindy 自身的聊天搜索,它可能更适合被视为 Cindy 产品能力,而不是用户自行选择的通用模型能力;若免费用户也应获得语义搜索,则远程推理的成本、授权和限流需要由 Cindy 的产品基础设施统一承担。本 issue 只记录现状、根因和可选架构,不预设最终产品决策。
### 实际行为与根因链路
1. 聊天 embedding 设置在无配置文件时默认 `enabled: true`:
`apps/desktop/src/main/maker-host/chat-embedding-settings-store.ts:42-44`。
2. 聊天索引模型硬编码为 `voyage/voyage-4`:
`apps/desktop/src/main/embedders/chat-history-embedder.ts:38-40`。
3. 每条符合 role/大小条件的新消息都会持久化为 embedding job:
`apps/desktop/src/main/embedders/chat-history-embedder.ts:184-220`,入口在
`apps/desktop/src/main/localDb/ipc/messages.ts:1014-1020`。
4. `@cindy/embedding-client` 的静态 catalog 无条件把该模型视为已知模型,没有区域或租户可用性:
`packages/embedding-client/src/catalog.ts:1-10,41-47`。
5. model-access 的动态模型目录只同步 Gateway `mode=chat` 投影,不覆盖 embedding 能力:
`apps/desktop/src/main/model-access/index.ts:124-127`。
6. Worker 定期批量读取 job 并调用 Gateway:
`apps/desktop/src/main/embedding-host/EmbeddingWorker.ts:32-33,156-164,242-247`。
7. HTTP client 会把 400/404/422 映射为 `INVALID_MODEL`,但 Worker 明确忽略错误分类,对所有失败统一重试:
`packages/embedding-client/src/client.ts:252-255`,
`apps/desktop/src/main/embedding-host/EmbeddingWorker.ts:269-285`。
8. 失败 job 会按统一策略多次重新调度,直至进入终态:
`apps/desktop/src/main/localDb/worker/opHandlers/tx.ts:10-11,917-947`。
因此,区域内不存在的模型不仅无法完成聊天索引,还会被后台队列重复请求,造成请求放大。
## 调研范围
- 源码基线:`ae263d0f7ed0`(当前 `main` 可见完整问题链路)
- 范围:Desktop 聊天历史 embedding、model-access 能力同步、AI Gateway 调用及失败重试
- 未覆盖:具体发布版本分布、服务端实现细节和最终产品权益定义
## 源码验证路径
1. 使用 CN 构建登录 Cindy,确保 `/chat-embedding-settings.json` 不存在或 `enabled: true`。
2. 等待用户数据库 ready;`attemptStartEmbeddingHost()` 会启动 embedding host。
3. 创建一条符合条件的 user / assistant 消息。
4. 观察 `embedding_jobs` 出现 `model_id='voyage/voyage-4'` 的 pending job。
5. 观察 Desktop 向 CN Gateway `/v1/embeddings` 发送 `model='voyage/voyage-4'`。
6. Gateway 返回模型不存在后,观察同一 job 被重新调度,直到达到失败终态。
## 可能方案分析(待讨论)
一种可能的职责拆分是把能力理解为三个层次。以下仅为分析框架,不代表已确定方案。
### 方案组成 A:AI Gateway 作为 embedding 数据面
- 可以继续负责实际 embedding 推理、供应商路由、限流和成本记账。
- 可以提供 Cindy 产品级稳定别名,例如 `cindy/chat-search-v1`,隐藏实际供应商模型。
- CN / Global 可以映射到不同的实际模型;若需要复用本地旧向量,则需同时定义 embedding space 兼容语义。
- 若聊天语义搜索被定义为免费产品能力,需要另行确定成本归属和防滥用边界。
### 方案组成 B:Cindy Server / model-access 作为控制面
- 可以从当前区域 Gateway 投影真实 embedding 能力。
- 可以按产品权益下发 feature capability、短期限用途凭证、dimension 和 `embeddingSpaceId`。
- 可以承载灰度、区域策略、速率策略和紧急 kill switch。
- 若采用客户端直连 Gateway,需要确认凭证能否限制到聊天搜索 embedding 别名,避免形成免费通用 Gateway key。
可能的契约形态:
```json
{
"chatSearch": {
"enabled": true,
"model": "cindy/chat-search-v1",
"embeddingSpaceId": "chat-search-cn-v1",
"dimensions": 1024,
"credential": ""
}
}
```
### 方案组成 C:Desktop 保留本地索引与检索
- 当前消息筛选、异步队列、本地 vec 表、FTS + vector 混合检索可以继续留在 Desktop。
- 若引入服务端 capability,可考虑用 `userEnabled && serverCapability.chatSearch.enabled` 作为有效状态。
- capability 不可用时,可以选择暂停 Worker 并只使用 FTS。
- `embeddingSpaceId` 变化时,需要在新索引代次、清理重建或多代并存之间做取舍;不同模型产生的向量不能直接混用。
### 调用路径选项
| 选项 | 优点 | 代价 / 风险 |
|---|---|---|
| Desktop 使用限用途凭证直连 Gateway | 少一次网络跳转,延迟和服务端带宽较低 | Gateway 必须支持严格的用途、模型、用户、设备和速率约束 |
| Cindy Server 代理产品级 embedding API | 产品授权和防滥用边界集中,客户端不接触 Gateway 能力 | 多一次数据跳转;Server 承担文本流量、容量和隐私责任 |
| Desktop 使用本地 embedding 模型 | 离线可用、无云端边际推理成本、数据不离开设备 | 安装体积、设备性能、模型升级和跨平台一致性成本较高 |
| 暂时只使用 FTS | 实现最简单,不依赖远程模型 | 只能覆盖关键词检索,缺少语义召回 |
## 需要进一步确认的问题
- 聊天语义搜索是否是所有 Cindy 用户(包括免费用户)的基础权益,还是可选增强能力?
- 云端 embedding 成本由 Cindy 产品统一承担,还是优先采用本地模型?
- AI Gateway 是否已经支持按用途签发受限凭证和稳定产品别名?
- embedding 能力事实源应直接来自 Gateway,还是由 model-access 统一投影?
- CN / Global 使用不同模型时,索引是否需要跨区域或跨设备兼容?
- 模型切换时选择清理重建、后台迁移还是保留多代索引?
- 对 `INVALID_MODEL` 等终态错误,应立即停单个 job、暂停整个 embedding space,还是触发 capability 刷新后再决定?
## 相关 issue(非重复)
- #35:仅包含 embedding-host 启动日志等 model-access 遗留项。
- #101:自定义供应商把非 chat 模型导入聊天选择器的问题。
- #158:聊天模型 Provider/Auth/Catalog/Capabilities 的运行时 Generation;可复用其生命周期思想,但当前明确不处理 embedding 模型能力。
Contributor guide
Research direction
Trace the embedding lifecycle through chat-embedding-settings-store.ts, chat-history-embedder.ts, messages.ts, EmbeddingWorker.ts, the embedding client, and worker transaction handlers. Reproduce the CN failure path described in the issue, then document the confirmed capability, ownership, authorization, model-versioning, and retry decisions with clear follow-up scope.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- typescript
- Domain
- api, backend, databases, desktop
- Issue type
- Feature
- Difficulty
- 5/5
- Estimated time
- Over a week
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 35/100