跳到主要内容

Chatbox — 架构与原理

30 秒导读: Chatbox 是一个开源的桌面/手机/Web AI 聊天客户端(像 ChatGPT 桌面版,但你自带 API key、数据存本地、能接几十家模型)。它的技术看点不在“界面”,而在渲染进程里那套完整的 agent 引擎:统一的供应商抽象、流式工具调用循环、知识库 RAG、上下文自动压缩、以及一个把模型生成的 shell 命令关进沙箱的执行环境。

1. 这是什么(零基础也能懂)

一句话定义: Chatbox 是一个 Electron + React 写的本地优先 AI 客户端——你填入 OpenAI / Claude / Gemini / Ollama 等任意一家的 key,就能在一个干净的界面里和模型对话,数据全部存在你自己机器上。

解决什么问题 / 给谁用:

  • 你不想用某一家的官方网页(被绑定、隐私、要翻墙、不能切模型)。
  • 你想一个界面接所有模型:今天用 GPT,明天换 Claude,后天连本地 Ollama,对话历史还在。
  • 你想要的不只是聊天,还要让模型读你上传的文件、搜网、查知识库、甚至跑代码

它能做什么(功能):

  • 接 30+ 模型供应商(云端 + 本地),一套设置切换。
  • 流式回复、推理过程(reasoning)展示、Markdown/LaTeX/代码高亮。
  • 工具调用:联网搜索、读取大文件、知识库检索、MCP 外部工具、沙箱执行 shell。
  • 文档知识库(向量 RAG)、会话附件 RAG、超长对话自动压缩。
  • 跨平台:Windows / macOS / Linux 桌面 + iOS / Android + 纯 Web 版(同一份代码)。

用起来什么样: 桌面端启动后,左边是会话列表,中间是对话区,底部输入框旁有“联网 / 知识库 / 附件”开关。你打字、回车,模型流式回字;如果开了联网且模型支持工具,它会先冒出一个 web_search 调用卡片,再基于结果回答。

一句话直觉/类比: 把它想成**“浏览器之于网站”——它是 LLM 之于各家模型的统一外壳**。浏览器把 HTTP/HTML 的差异藏起来给你统一的“网页”;Chatbox 把各家 API 的差异藏在一个 AbstractAISDKModel 后面,给你统一的“对话 + 工具”。

本节不谈代码。记住一句:真正有料的部分在渲染进程的 agent 引擎,而不是 Electron 外壳。

2. 顶层全景(它大概怎么转)

Chatbox 是标准的 Electron 三进程结构,但它有个反常识的设计决定:几乎所有 AI 逻辑都跑在渲染进程(浏览器侧),而不是 main 进程。

怎么读下面这张图: 从上到下是三层进程。AI 的“大脑”(模型调用、工具循环、RAG 检索)在中间的渲染进程;main 进程只做渲染进程碰不到的脏活(子进程、文件系统、沙箱、原生 MCP)。

┌──────────────────────────────────────────────────────────────┐
│ renderer (React + 浏览器环境, 这里是 AI 大脑) │
│ │
│ 用户输入 ──► sessionActions.generate │
│ │ │
│ ▼ │
│ orchestrateGeneration ← 生成主线 (第3章) │
│ ├─ buildContext ← 拼历史/附件/压缩 (第5章) │
│ ├─ buildToolsForSession ← 按模型能力拼工具 (第4章) │
│ ├─ createModel ← 供应商注册表 (第2章) │
│ └─ model.chatStream ← 统一流式抽象 (第1章) │
│ │ AsyncGenerator<chunk> │
│ ▼ │
│ processStreamChunk → 边流边写进消息 → 落盘 │
└───────────────┬───────────────────────────┬──────────────────┘
│ IPC (electronAPI.invoke) │
▼ ▼
┌──────────────────────────┐ ┌──────────────────────────────┐
│ preload (桥) │ │ main (Node, 干脏活) │
│ 暴露白名单 IPC 给 window │ │ ├─ sandbox 管理 (第6章) │
└──────────────────────────┘ │ ├─ knowledge-base 向量库 │
│ ├─ skills 文件/脚本 │
│ └─ MCP stdio 子进程 │
└──────────────────────────────┘

部件一句话职责:

部件干什么在哪
AbstractAISDKModel把任意供应商收敛成一个 chatStream() 流式接口src/shared/models/abstract-ai-sdk.ts
供应商注册表用 id 查到“怎么造这个模型”的工厂src/shared/providers/registry.ts
orchestrateGeneration生成主线:从一条消息到一段流式回复的全过程src/renderer/stores/session/orchestration.ts
buildToolsForSession按模型能力 + 会话开关动态拼出工具集src/renderer/stores/session/tools-builder.ts
buildContext把历史消息、附件、压缩摘要拼成 promptsrc/shared/context/builder.ts
知识库 / 沙箱 / 技能 / MCPmain 进程里的重型能力,经 IPC 暴露src/main/{knowledge-base,sandbox,skills,mcp}/

主线走一遍(高层): 用户回车 → generate() 选“聊天还是画图” → orchestrateGeneration() 拼上下文、建工具、用注册表造出模型 → 调 model.chatStream() 拿到一个异步生成器,逐块吐出 文本/推理/工具调用/工具结果/图片 → 每块都被 processStreamChunk 累积进消息的 contentParts,每 2 秒落一次盘 → 流结束,标记 generating: false,最终落盘。

看懂这条主线,整个项目就拎起来了。下面分章往下钻。

3. 阅读地图(建议顺序)

想搞懂…读哪章
“30 家供应商怎么变成同一个接口”、流式重试为何要小心计费01 — 模型抽象
“Claude / OpenAI / Ollama 的差异写在哪、怎么按 id 造模型”02 — 供应商注册表
“一条消息从输入到回复的完整代码路径”03 — 生成主线
“联网/读文件/沙箱/MCP/技能这些 agent 能力怎么接进来”04 — 工具与 agent
“上下文怎么拼、知识库 RAG 怎么检索、对话太长怎么压”05 — 上下文/RAG/压缩
“模型生成的 shell 命令凭什么敢执行”06 — 沙箱与安全

4. 一眼看懂的几个“精华”

先剧透三个最值得带走的设计,细节在各章:

  • 计费安全的重试策略(第1章):普通 SDK 默认网络出错就重发,但 POST 给模型的请求一旦服务端已处理、连接才断,重发就重复扣费。Chatbox 把 AI SDK 自带重试关掉(maxRetries: 0),只对 429 / 5xx(服务端没动手)这种计费安全的错误用自己的 wrapper 重试。
  • 能力门控的工具拼装(第4章):工具不是“全开”,而是逐个问模型 isSupportToolUse('read-file')isSupportToolUse('web-browsing')——模型不支持就不给那个工具,并对不支持工具的老模型走 prompt 工程降级(把搜索结果直接塞进上下文)。
  • 把 shell 关进笼子(第6章):沙箱执行直接复用了 Anthropic 的 @anthropic-ai/sandbox-runtime,默认拒读 ~/.ssh/~/.aws、拒写 .env,用 wrapWithSandbox 包裹命令后再 spawn。