跳到主要内容

DeepChat — 架构与原理

30 秒导读: DeepChat 是一个开源、本地优先的 AI Agent 桌面客户端(Electron + Vue 3)。它能接 OpenAI/Gemini/Anthropic/Ollama 等模型,也能挂 MCP 工具、Skills、ACP 编码 agent,还能从 Telegram/飞书等远程控制。它最独特的设计是 Tape:把一次会话的全过程(消息、工具调用、结果、压缩、分叉)记成一条 append-only(只追加)的事件日志,需要展示给模型的「当前对话」是从这条日志 折叠(fold) 出来的——所以历史永远可恢复、可追溯、可重放。


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

一句话定义: DeepChat 是一个装在你电脑上的「带手脚的聊天客户端」——它不只是把你的话发给大模型,还能让模型读写文件、跑命令、搜代码、调用 MCP 工具,并且把整个过程完整记录下来。

解决什么问题 / 给谁用:

假设你是工程师,想在本地用一个统一界面:既能跟云端 GPT/Claude 聊天,又能让它在你的项目目录里帮你改代码、跑测试;还希望换一台机器、关掉再打开,之前那段 agent 工作能原样恢复,而不是「聊天记录还在,但工具调用、被压缩掉的上下文全丢了」。DeepChat 就是为这个场景做的。

它能做什么(功能):

  • 统一管理多家云模型 + 本地 Ollama(一个 app 不用来回切)
  • 本地 agent 工具:读写文件、执行命令、glob/grep 搜代码、子 agent 编排、计划任务
  • MCP(Model Context Protocol)工具:一键安装外部工具服务器
  • Skills:按会话装载可复用的任务说明(代码审查、文档、前端等)
  • ACP(Agent Client Protocol):把外部编码 agent 当成一个「模型」选项跑
  • 远程控制:从 Telegram、飞书/Lark、QQ、Discord、微信 iLink 驱动会话
  • 本地数据加密(SQLCipher)、会话搜索、上下文压缩、请求 Trace 预览

用起来什么样: 桌面里像普通聊天窗口,但模型回复中会穿插「工具调用块」(读了哪个文件、跑了什么命令、要不要授权),你可以批准/拒绝;每条消息还能点开 Trace 看这次真正发给 provider 的请求长什么样。

一句话直觉/类比: 把会话想成一盘磁带(Tape)——只能往后录(append-only),不能擦改;你看到的「当前对话」是放磁带时实时算出来的剪辑版。要发给模型的上下文,就是这盘磁带按预算裁出来的一段。这正是 DeepChat 标榜的 Tape.systems 哲学:keep the process(保留过程)

本节不出现底层代码。目标:你现在应该能说清「DeepChat 是个本地 agent 客户端,卖点是把会话过程完整存成可恢复的磁带」。


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

DeepChat 是 Electron 应用,分三个世界:Renderer(Vue 界面)、preload bridge(安全 IPC 桥)、Main(Node 主进程,所有重活在这)。Main 里用 Presenter 模式——每个子系统一个 presenter 类持有 runtime 状态。

怎么读这张图:从上往下是一条用户消息的去向。Renderer 不直接碰 presenter,
中间隔着 typed route(类型化路由),真正的聊天 runtime 在 AgentRuntimePresenter。

┌─────────────────────────────────────────────┐
│ Renderer (Vue 3 / Pinia stores / views) │
│ renderer/api/*Client.ts ← 组件唯一入口 │
└───────────────┬─────────────────────────────┘
│ window.deepchat.invoke(route)
┌───────────────▼─────────────────────────────┐
│ preload bridge (createBridge.ts) │ contextIsolation on
└───────────────┬─────────────────────────────┘
│ shared/contracts 路由 + 事件契约
┌───────────────▼─────────────────────────────┐
│ Main: src/main/routes (typed dispatcher) │
│ SessionService / ChatService / ... │
└───────────────┬─────────────────────────────┘
│ 窄 port
┌───────────────▼─────────────────────────────┐
│ AgentSessionPresenter 会话生命周期/绑定窗口 │
└───────────────┬─────────────────────────────┘
│ 委托执行
┌───────────────▼─────────────────────────────┐
│ AgentRuntimePresenter ← 聊天 runtime 核心 │
│ ┌─ Tape (append-only 事件日志 → effective) │
│ ├─ contextBuilder (token 预算裁剪) │
│ ├─ processStream (统一 stream/tool 循环) │
│ └─ compaction / messageStore / trace │
└──────┬──────────────────────┬────────────────┘
│ │
┌──────▼────────┐ ┌──────▼─────────────────┐
│ ToolPresenter │ │ LLMProviderPresenter │
│ MCP + 本地 │ │ BaseLLMProvider. │
│ agent tools │ │ coreStream() + ACP │
└───────────────┘ └────────────────────────┘

部件一句话职责:

部件干什么在哪个文件
renderer/api/*Clienttyped renderer 客户端,吸收 bridge/channel 细节src/renderer/api/
shared contractsroute 注册表 + schema + typed 事件目录src/shared/contracts/
preload bridge暴露 window.deepchat.invoke/onsrc/preload/createBridge.ts
main routestyped route 分发与 servicesrc/main/routes/index.ts
AgentSessionPresenter创建/恢复/激活/分叉会话,绑定窗口src/main/presenter/agentSessionPresenter/index.ts
AgentRuntimePresenter聊天 loop、stream、工具交互、持久化src/main/presenter/agentRuntimePresenter/index.ts
DeepChatTapeServiceTape 服务边界:回填 facts、search/anchor/handoff/forksrc/main/presenter/agentRuntimePresenter/tapeService.ts
ToolPresenter聚合 MCP + 本地 agent tools,路由调用src/main/presenter/toolPresenter/index.ts
LLMProviderPresenterprovider 实例、model 管理、ACP helper、AI SDK runtimesrc/main/presenter/llmProviderPresenter/index.ts

主线走一遍(高层,不进代码):

  1. 用户在界面发消息 → ChatClientwindow.deepchat.invoke('chat...') → main route。
  2. route 调 AgentSessionPresenter,后者把执行委托给 AgentRuntimePresenter.processMessage()
  3. runtime 先 ensureSessionTapeReady(确保磁带就绪并折叠出当前历史),用 contextBuilder 按 token 预算裁出要发的上下文。
  4. 进入 processStream 统一循环:调 provider 的 coreStream 拿流式事件;如果模型要调工具,就交给 ToolPresenter 执行,把结果接回去继续循环;需要授权时暂停,等用户响应。
  5. 流式过程实时 echo 给 renderer(chat.stream.* 事件);结束时把消息和工具结果作为 新的 facts 追加进 Tape,落库。

看懂这五步,就抓住了 DeepChat 的「大盘」。下面四章逐个拆。


3. 这套文档怎么读(阅读地图)

DeepChat 体量大,按下面顺序由浅入深:

章节讲什么何时读
01-tape-system.mdTape:append-only 事件日志 + 折叠出 effective view。理解 DeepChat 必读的第一章。想懂「为什么会话可恢复/可重放」
02-chat-loop.md一条消息端到端:上下文预算 → 统一 stream/tool 循环 → 压缩与持久化。想追一次真实聊天链路
03-tool-system.md工具怎么聚合(MCP + 本地 agent tools)、怎么路由、权限预检查与暂停交互。想加工具 / 懂权限模型
04-provider-acp.md两层 Provider 抽象 + coreStream 标准化事件;ACP 把外部 agent 当「模型」。想接新 provider 或懂 ACP

4. 巧妙之处(一眼提炼)

  • 「过程即真相」:不把 UI 上的聊天记录当唯一来源,而是把每一步记成只追加的 facts,展示用的对话是算出来的视图——这让压缩、分叉、子 agent 合并都成了「在日志上加一条 anchor」而非「破坏性改写」。详见 01
  • 统一 stream 循环:简单补全和多轮工具调用走同一段代码路径(processStream),靠 stopReason === 'tool_use' 决定是否继续,而不是两套分支。详见 02
  • provider 无关的工具视图:AgentRuntimePresenter 只拿一个统一的 MCPToolDefinition[],完全不知道某个工具是 MCP 还是本地——来源映射藏在 ToolMapper 里。详见 03
  • provenance-key 幂等去重:Tape 追加用 provenanceKey 做唯一约束,重复回填同一条 fact 不会重复入库——这让「懒回填旧会话」安全可重入。详见 01

5. 边界与局限(诚实)

  • 重 Electron、重原生依赖:better-sqlite3、duckdb VSS、各平台 runtime 注入(node/uv/rtk),不是「npm install 就能跑」的轻应用;package.json 里大量 installRuntime:* / plugin:bundle 脚本印证了这点。
  • Tape 的语义边界:当前 baseline 仍以 legacy_context_v1 选择器折出上下文(docs/architecture/deepchat-tape-baseline/spec.md);ViewManifest 目前主要作为「解释与回归工件」,memory graph 检索、跨会话召回、CI 内 live replay 都是 deferred scope
  • 历史兼容残留:SessionPresenter、legacy import、旧 conversations/messages 表仍保留,但只承担兼容职责,不在活跃聊天主链路上(见 docs/ARCHITECTURE.md 第 5 节)。

6. 横向对比(同 shelf 兄弟)

  • 与「会话即数据库行」的传统聊天客户端不同,DeepChat 把过程而非结果当一等公民:别人存「最终消息」,它存「产生消息的事件流」,再折叠出消息。这与 agent 记忆/可观测方向的项目(把 trace 当训练/回放素材)同源。
  • 与纯 MCP host 相比,DeepChat 把 MCP 工具、本地 agent 工具、ACP 外部 agent 三类异构能力统一到同一个工具/模型选择面之下,屏蔽来源差异。

7. 代码地图(导航索引)

主题文件路径符号名
会话编排入口src/main/presenter/agentSessionPresenter/index.tsAgentSessionPresenter
聊天 runtime 核心src/main/presenter/agentRuntimePresenter/index.tsAgentRuntimePresenterprocessMessage
统一 stream/tool 循环src/main/presenter/agentRuntimePresenter/process.tsprocessStream
Tape 服务边界src/main/presenter/agentRuntimePresenter/tapeService.tsDeepChatTapeServiceensureSessionTapeReady
Tape 折叠(effective view)src/main/presenter/agentRuntimePresenter/tapeEffectiveView.tsbuildEffectiveTapeView
Tape facts 写入src/main/presenter/agentRuntimePresenter/tapeFacts.tsappendMessageRecordToTapeappendToolFactsToTape
Tape 存储表src/main/presenter/sqlitePresenter/tables/deepchatTapeEntries.tsDeepChatTapeEntriesTableappend
上下文预算src/main/presenter/agentRuntimePresenter/contextBuilder.tsbuildContextWithMetadataselectTurnHistoryTurns
工具聚合/路由src/main/presenter/toolPresenter/index.tsToolPresentergetAllToolDefinitions
本地 agent 工具src/main/presenter/toolPresenter/agentTools/agentToolManager.tsAgentToolManager
Provider 抽象基类src/main/presenter/llmProviderPresenter/baseProvider.tsBaseLLMProvidercoreStream
ACP provider runtimesrc/main/presenter/llmProviderPresenter/acp/acpSessionManageracpProcessManager