Chatbox 中有两条相近但不同的检索路径:
- Knowledge Base:用户长期维护的知识库,文件被解析、切块、embedding 后写入向量库,多个会话可复用。
- Session Attachment RAG:当前会话上传的大文件索引,生命周期与 session attachment 关联,避免把整个文件直接塞进上下文。
它们共享“解析 → chunk → embedding → search”的思想,但在存储、状态、权限和 UI 生命周期上分开。
flowchart LR
File[用户添加文件]
DB[Knowledge Base DB\nfile + task 状态]
Worker[后台 worker]
Loader[格式 loader\ntext/markdown/html/json/office]
Embed[Embedding Model]
Vector[libsql vector store]
Query[AI knowledge-base tool]
File --> DB --> Worker --> Loader --> Embed --> Vector
Query --> Vector --> Results[score + chunk + filename]
原始设计文档中明确了 worker 任务模型:文件先落数据库,任务进入待处理,worker 被触发时处理一批并停止,下一次触发继续。这种设计避免常驻 worker 复杂化 Electron 生命周期。
libsql 等向量数据库可能包含原生模块或需要直接访问 db 文件,因此知识库服务放在 Main。Renderer 通过 Platform.getKnowledgeBaseController() 调用它,并提供当前 provider 的 embedding/rerank 配置。
Renderer Settings / Provider
→ 初始化 KB controller
→ 通过 IPC 把 provider host/key/model 传给 Main
→ Main parser + worker + libsql
→ Renderer 得到 task status / search results
这不是把 API key 复制给数据库,而是把“如何创建 embedding 模型”的依赖在调用边界上注入进去。
文件解析根据后缀或 MIME 选择 loader:
- 文本、Markdown、HTML、JSON:MDocument 等本地 loader。
- Office:officeparser。
- 其他类型:可选的 Chatbox AI/Unstructured/MinerU 服务。
- 解析失败:写入结构化错误状态,不让任务永久卡在 processing。
解析结果不是最终答案,而是后续 chunking 和 embedding 的输入。解析层需要保留 filename、mimeType、fileId 和 chunk index,检索结果才能回到 UI 做引用和调试。
Provider 模型信息把 model 分为 chat、embedding、rerank、image 等类型。知识库页面不能把任意 chat model 当 embedding model;它应从模型注册信息中过滤可用类型,默认选择 provider 设置里的第一个合适模型。
这说明 Model Registry 不只是 UI 下拉框的来源,它实际决定 RAG pipeline 是否可以启动。
Chatbox 支持两种形态:模型可直接调用 knowledge_base_search 之类的工具,或者在不可靠工具调用的场景中使用 prompt engineering,让模型输出一个结构化 search action:
生成搜索动作
→ { action: search, query }
→ controller.search(knowledgeBaseId, query)
→ 格式化 document blocks
→ 与用户最新问题放入新的 user message
→ 再调用模型回答
constructMessagesWithKnowledgeBaseResults() 会把结果包装为 [document N begin] ... end,并保留用户最新问题。这是“检索结果和问题在同一轮交给模型”的上下文协议。
上传的大文件不能总是作为 inline content 发送。消息文件 schema 记录:
ragMode: inline | session-retrieval。sessionAttachmentId。sessionAttachmentAvailability。indexStatus:pending、indexing、ready、failed。- chunk count、total chunks、embedded chunks 和 indexing stage。
refreshSessionAttachmentStatuses() 在生成前刷新状态;getSessionAttachmentRagIds() 只把可用、非 blocked 的 attachment ID 交给工具构建器。模型看到的是检索工具,不是完整文件内容。
附件 RAG 不是“有 ID 就能搜”。状态需要经过:
pending → indexing → ready
└────→ failed
blocked:策略/格式/权限导致不可用
如果 attachment 被 blocked,工具构建器不应把它注入为可检索资源;UI 需要展示 warning reason,避免用户误以为模型已经读过文件。
检索结果也会吃 token,因此需要:
- 限制 top-k 和每个 chunk 的字符数。
- 保留 filename、score、chunk index 等来源信息。
- 大文件正文存储在索引或 blob,不把完整文档放进 Message。
- 结合
maxContextMessageCount和上下文压缩,避免“RAG 结果把历史挤出去”。
RAG 不是上下文管理的替代品,而是一个需要被上下文管理器约束的数据源。
- Main 执行向量库:隔离原生依赖和 db 文件;代价是 IPC 初始化与错误传播更复杂。
- 长期 KB 与 Session Attachment 分开:生命周期清晰;代价是需要两套状态/维护入口。
- 工具 + prompt engineering 双路径:兼容弱工具模型;代价是必须维护两种结果协议。
- 结果保留元数据:便于引用和调试;代价是模型上下文更长,需要预算控制。
src/main/knowledge-base/、src/main/session-attachment-rag/、src/renderer/platform/knowledge-base/、src/renderer/platform/session-attachment-rag/、src/renderer/packages/model-calls/toolsets/knowledge-base.ts、session-attachment-rag.ts、src/renderer/stores/session/attachment-resolver.ts。
RAG 的主线不是“调用一次向量搜索”,而是把文件生命周期、模型类型、后台任务、IPC、检索结果和上下文预算连接起来。下一章回到所有这些状态的共同基础:存储、迁移和备份。