跳到主要内容

数据截至 (上游 commit dad6f5196773)

LobeHub (LobeChat) — 架构与原理

30 秒导读: LobeHub(前身 LobeChat)是一个开源的多模型 AI 对话与 agent 平台。它真正的技术核心不是聊天界面,而是一台指令驱动的运行时:负责思考的 Agent 只输出一条条纯 JSON 指令("调模型""调工具""要人批准""结束"),负责干活的 AgentRuntime 只负责把指令翻译成真实动作。因为指令是数据不是回调,同一段 agent 逻辑可以原封不动地跑在浏览器里、服务器进程里、云沙箱里,或者派给你桌面上的一个 CLI 进程。

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

一句话定义: LobeHub 是一个你可以自己部署的 AI 工作台——一边像 ChatGPT 那样聊天,一边把「一个个 agent」当成能被雇佣、被排班、被派活、会汇报的员工来管理。

解决什么问题 / 给谁用:

  • 你不想被单一厂商绑死:今天用 Claude、明天换 GPT、后天连本地 Ollama,历史对话还在。
  • 你要的不止是聊天,而是让 AI 真的动手:搜网、读你上传的文件、写代码、跑命令、操作你的电脑。
  • 你不想一直守在屏幕前:任务应该能定时跑、能后台跑、跑完给你一份简报。

它能做什么(功能):

能力说明
多供应商模型内置 82 家供应商的运行时实现(packages/model-runtime/src/runtimeMap.ts:85
内置工具31 个 builtin-tool-* 包:计算器、网页浏览、知识库、本地文件系统、云沙箱、记事本、记忆……
外部工具MCP 协议插件,与内置工具走同一套调用路径
多端Web / 移动端 / Electron 桌面端 / 服务端 / 命令行 lhapps/cli
多 agent子 agent 派发、群聊式多 agent 编排(Supervisor 轮流点名)
运营定时任务、后台执行、简报(brief)、人类审批闸门

用起来什么样: 除了网页,它还有一个 CLI。最能说明"这不只是聊天应用"的是下面这段——你把自己的机器注册成一台执行设备,云端的 agent 就能把活派到这台机器上跑:

# 示意,非源码;命令名取自 apps/cli/src/commands/
lh login # 绑定账号
lh connect --daemon # 把本机接入设备网关,后台待命接活
lh agent # 管理你的 agent
lh task # 建一个会定时自己跑的任务

一句话直觉: 把它当成 "AI 的操作系统 + 人事系统"。操作系统那部分负责"一次调用怎么执行";人事系统那部分负责"哪个 agent、在哪台机器上、什么时候执行"。

本节到此为止不谈代码。记住一句:看点在运行时,不在界面。

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

2.1 最核心的一张图:大脑与引擎

整个项目的地基是一个两角色循环。代码注释里就是这么叫的——Agent 是 Brain,AgentRuntime 是 Engine(packages/agent-runtime/src/core/runtime.ts:23-27)。

先说清两个承重词,后文一词一义:

  • 指令(instruction):一个纯 JSON 对象,形如 { type: 'call_llm', payload: {...} }。全部 14 种指令列在一个联合类型里(packages/agent-runtime/src/types/instruction.ts:396)。
  • 执行器(executor):一个 type 对应一个执行器函数,负责把指令变成真实副作用。

怎么读这张图: 从上往下是一次 step(),最后一根箭头绕回去就是下一次 step()

┌────────────────┐ ① 传入「现在处于什么阶段」+ 全量状态
│ Agent 大脑 │◄───────────────────────────────────┐
│ 无状态·只决策 │ │
└───────┬────────┘ │
│ ② 返回 1 条或多条「指令」(纯 JSON,可序列化) │
▼ │
┌────────────────┐ │
│ AgentRuntime │ ③ 按 type 查执行器表,逐条跑 │
│ 引擎·只执行 │ │
└───────┬────────┘ │
│ ④ 吐出 新状态 + 事件流 + nextContext │
└─────────────────────────────────────────────┘
⑤ 状态变成 done / error / 被挡住 → 外层 while 循环退出

大脑那一侧的实现是 GeneralChatAgent.runner():它只是一个大 switch,按 context.phaseuser_input / llm_result / tool_result / human_abort / …)决定下一条指令(packages/agent-runtime/src/agents/GeneralChatAgent.ts:569-579)。

引擎那一侧是 AgentRuntime.step():拿到指令数组,逐条查执行器表执行,累积事件,返回新状态(packages/agent-runtime/src/core/runtime.ts:82)。

2.2 部件一句话职责

部件干什么在哪
AgentRuntime引擎:查执行器表、跑指令、管步数与中断packages/agent-runtime/src/core/runtime.ts:27
GeneralChatAgent大脑:按 phase 决定下一条指令packages/agent-runtime/src/agents/GeneralChatAgent.ts:47
MessagesEngine上下文流水线:7 个阶段把消息拼成模型能吃的样子packages/context-engine/src/engine/messages/MessagesEngine.ts:99
ToolsEngine把 manifest 转成模型能看懂的工具 schemapackages/context-engine/src/engine/tools/ToolsEngine.ts:20
ModelRuntime把 82 家供应商收敛成同一个 chat()packages/model-runtime/src/core/ModelRuntime.ts:141
executeClientAgent / createRuntimeExecutors浏览器侧执行入口 / 服务端侧执行器表src/store/chat/slices/agentRun/actions/transports/client/streamingExecutor.ts:493apps/server/src/modules/AgentRuntime/RuntimeExecutors.ts:9
selectRuntimeType唯一的执行面路由决策点src/store/chat/slices/agentRun/actions/dispatch/agentDispatcher.ts:113

2.3 四个执行面

这是 LobeHub 与普通聊天客户端拉开差距的地方:同一套指令,四个落地位置。前端只做一次三选一的决策,其中 gateway 一支到了服务端还会再分叉。

怎么读这张图: 从上往下是一次派发;缩进的三条支线是 gateway 到了服务端之后的再分流。

用户点发送 / 定时任务到点


selectRuntimeType() ← 唯一路由决策点

┌─────────────────────┼─────────────────────┐
▼ ▼ ▼
client gateway hetero
浏览器内直接跑 WebSocket 交给服务端 桌面端本地拉起 CLI 子进程
AgentRuntime │ (claude-code / codex)
├─► 服务器进程内跑 AgentRuntime
├─► 拉起云沙箱执行
└─► 派给 `lh connect` 接入的那台设备

优先级写死在一处:parentRuntime > 异构 CLI(按执行目标再分)> gateway > clientagentDispatcher.ts:111-176)。子 agent 继承父 operation 的执行面——父在 gateway 跑,子就不许自己跳回浏览器。

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

  1. 用户回车,selectRuntimeType() 选定执行面。
  2. 造出大脑 GeneralChatAgent 与引擎 AgentRuntime,并装配这一面的执行器表(streamingExecutor.ts:692)。
  3. while 循环,每步先算 stepContext(当前 todo、已激活工具、已激活技能、有无排队消息),再调 runtime.step()
  4. 大脑看阶段:刚收到用户输入 → 先判断要不要压缩上下文,不用就出 call_llm
  5. call_llm 执行器先跑 MessagesEngine 的 7 段流水线拼消息与工具,再交给 ModelRuntime 流式取结果。
  6. 模型回了工具调用 → 大脑先过一遍干预检查:命中安全黑名单或需要人批准的,出 request_human_approve;其余的一次 call_tools_batch 并发执行。
  7. 工具结果回流成新的 phase,循环回到第 4 步,直到大脑出 finish

看懂这条主线,整个仓库就拎起来了。

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

想搞懂…读哪章
指令是什么、step 循环怎么转、步数与中断怎么管运行时内核:大脑出指令、引擎跑指令
一次调模型前,system prompt、历史、记忆、工具清单是怎么被 7 段流水线拼出来的上下文工程:每次调模型前,消息和工具是怎么被拼出来的
82 家供应商怎么共用一套代码、各家流式协议怎么归一模型运行时:一套代码接住几十家供应商与它们各异的流
一个内置工具从 manifest 声明、UI 渲染到真正执行的全链路,以及 MCP 怎么接工具体系:一个 builtin tool 从声明、渲染到落地执行
浏览器 / 服务端 / 云沙箱 / 本地 CLI 各自怎么跑、事件怎么回流四个执行面:同一条指令流跑在浏览器、服务端、云沙箱和本地 CLI 上
子 agent、群聊编排、定时任务、以及那棵要渲染的对话树从聊天到 7×24 运营:多 agent 编排、任务调度与对话树渲染

建议顺序: 01 → 02 → 03 是主干(一次调用怎么走完);04 是横向能力;05、06 是"平台化"那一层,可按兴趣挑。

4. 巧妙之处(先剧透六个,细节在各章)

① 指令是数据,不是回调 —— 这是全项目的地基。 大脑返回的是 { type, payload } 纯对象,引擎按 type 查表执行。因为指令可序列化,浏览器和服务端可以各有一套执行面却共用同一个大脑:浏览器侧 executeClientAgentsrc/store/chat/slices/agentRun/actions/transports/client/streamingExecutor.ts:493)与服务端 createRuntimeExecutorsapps/server/src/modules/AgentRuntime/RuntimeExecutors.ts:9,上游已把原 createAgentExecutors 表移除、执行器实现重构进 packages/agent-runtime)实现完全不同,GeneralChatAgent 一行不改。

② 执行器表是三层覆盖的。 内置执行器 < config.executors < agent.executors,后者优先级最高(packages/agent-runtime/src/core/runtime.ts:40-52)。所以"换一个执行面"只是换一张表,不是分叉一份运行时。

③ 步数超限不是硬停,是"缴械后让它自己收尾"。 超过 maxSteps 时不抛错、不截断,而是把 forceFinish 置位(runtime.ts:93-102);下一次调模型会剥掉全部工具并注入一段总结提示(ForceFinishSummaryInjector,在流水线的清理阶段)。模型于是只能输出一段纯文本收尾,用户拿到的是结论而不是半截报错。

配套还有个小心思:finish 指令不是一次真正的执行,所以引擎把刚才 +1 的 stepCount 又减回去(runtime.ts:212-215)。

④ 并发工具"同源分叉、事后归并"。 一批工具用 pMap 并发跑,但每个工具都从同一份 structuredClone(baseState) 出发,互不看见对方的中间状态;跑完再由 mergeToolResultstool_call_id 去重合并消息、累加用量与成本(runtime.ts:732-746:752)。这样避免了并发写同一状态对象的竞态。

⑤ 工具的渐进式披露:先给名字,用之前必须先"激活"。 工具多了,全量塞 schema 会把上下文撑爆。LobeHub 的做法是:先由 ToolDiscoveryProvider 往上下文里塞一份只有名字和描述的可用工具清单(packages/context-engine/src/providers/ToolDiscoveryProvider.ts:29),模型想用哪个,得先调 activateTools 这个元工具把完整 schema 请出来(packages/builtin-tool-activator/src/manifest.ts:6)。激活结果按步累积成 StepToolDelta 注入下一轮(packages/context-engine/src/engine/tools/buildStepToolDelta.ts:38)。

⑥ 安全黑名单跑在所有干预策略之前。 InterventionChecker.shouldIntervene() 第一件事是过 checkSecurityBlacklistpackages/agent-runtime/src/core/InterventionChecker.ts:61,39)。黑名单是一组正则,专挡 rm -rf ~、读 /etc/shadow 这类动作(packages/agent-runtime/src/audit/defaultSecurityBlacklist.ts:12)。注释写得很直白:这些规则即使在自动运行模式下也照挡不误——auto-run 只能豁免标记为 required 的普通规则。

⑦(附赠)上下文流水线的第一段不能挪。 HistoryTruncateProcessor 必须第一个跑,源码里专门加了注释说明"后续所有 processor 只在被截断后的消息上工作"(packages/context-engine/src/engine/messages/MessagesEngine.ts:262-268)。这条约束决定了后面 6 个阶段的全部成本上限。

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

按"先读哪个"排序。行号 as-of 805b15a;行号漂移时用符号名 grep。

主题文件路径符号名
指令与 Agent 契约(先读这个)packages/agent-runtime/src/types/instruction.ts:69,106,379AgentAgent.runnerAgentInstruction
引擎主循环packages/agent-runtime/src/core/runtime.ts:27,82AgentRuntimeAgentRuntime.step
内置执行器工厂packages/agent-runtime/src/core/runtime.ts:421,506,695createCallLLMExecutorcreateCallToolExecutorcreateFinishExecutor
并发工具与归并packages/agent-runtime/src/core/runtime.ts:720,752executeToolsBatchmergeToolResults
状态机与阻塞判定packages/agent-runtime/src/types/state.ts:129packages/agent-runtime/src/utils/status.ts:11,18AgentState.statusisParkedStatusisBlockedStatus
事件流类型packages/agent-runtime/src/types/event.tsAgentEventllm_stream / tool_result / human_approve_required / done …)
默认大脑packages/agent-runtime/src/agents/GeneralChatAgent.ts:42,436GeneralChatAgentGeneralChatAgent.runner
人类干预与安全黑名单packages/agent-runtime/src/core/InterventionChecker.ts:30,39,61packages/agent-runtime/src/audit/defaultSecurityBlacklist.ts:12InterventionCheckercheckSecurityBlacklistshouldInterveneDEFAULT_SECURITY_BLACKLIST
上下文流水线内核packages/context-engine/src/pipeline.ts:19,68ContextEngineContextEngine.process
7 段流水线装配(含阶段注释)packages/context-engine/src/engine/messages/MessagesEngine.ts:92,138,224MessagesEnginebuildProcessors
工具 schema 生成 / 技能packages/context-engine/src/engine/tools/ToolsEngine.ts:20packages/context-engine/src/engine/skills/SkillEngine.ts:15ToolsEngineSkillEngine
工具渐进式披露packages/context-engine/src/providers/ToolDiscoveryProvider.ts:29packages/context-engine/src/engine/tools/buildStepToolDelta.ts:38ToolDiscoveryProviderbuildStepToolDelta
模型运行时门面packages/model-runtime/src/core/ModelRuntime.ts:141,179,503ModelRuntimeModelRuntime.chatinitializeWithProvider
供应商表(82 家,未知 provider 兜底 OpenAI)packages/model-runtime/src/runtimeMap.ts:85ModelRuntime.ts:519providerRuntimeMap
OpenAI 兼容工厂 / 流协议packages/model-runtime/src/core/openaiCompatibleFactory/index.ts:335packages/model-runtime/src/core/streams/protocol.ts:402createOpenAICompatibleRuntimecreateSSEProtocolTransformer
内置工具清单与注册packages/builtin-tools/src/identifiers.ts:34packages/builtin-tools/src/register.tsbuiltinToolIdentifiers
一个最小工具的完整解剖packages/builtin-tool-calculator/src/manifest.ts:6 + 同包 systemRole.ts / executor/ / client/CalculatorManifest
元工具:激活其它工具packages/builtin-tool-activator/src/manifest.ts:6LobeActivatorManifest
执行面路由src/store/chat/slices/agentRun/actions/dispatch/agentDispatcher.ts:113src/helpers/executionTarget.ts:172selectRuntimeTyperesolveExecutionTarget
浏览器执行面src/store/chat/slices/agentRun/actions/transports/client/streamingExecutor.ts:493executeClientAgent
服务端执行面apps/server/src/services/agentRuntime/AgentRuntimeService.ts:264,2451apps/server/src/modules/AgentRuntime/RuntimeExecutors.ts:9AgentRuntimeServicecreateRuntimeExecutors
事件回流(WebSocket,带断线重放)packages/agent-gateway-client/src/client.ts:64AgentStreamClient
异构 CLI agent 适配packages/heterogeneous-agents/src/registry.ts:15,26packages/heterogeneous-agents/src/adapters/registrycreateAdapterClaudeCodeAdapterCodexAdapter
设备执行面(lh connectapps/cli/src/device/agentRun.ts:55apps/cli/src/commands/connect.ts:86spawnHeteroAgentRun
云沙箱packages/builtin-tool-cloud-sandbox/src/manifest.tsapps/server/src/services/sandbox/providers/CloudSandboxManifest
多 agent 编排packages/agent-runtime/src/groupOrchestration/GroupOrchestrationRuntime.ts:26,47GroupOrchestrationSupervisor.ts:38GroupOrchestrationRuntimeGroupOrchestrationSupervisor
定时任务apps/server/src/services/taskRunner/scheduleTick.ts:38apps/server/src/services/taskScheduler/impls/runScheduleTickQStashTaskSchedulerLocalTaskScheduler
对话树解析(渲染前的三段式)packages/conversation-flow/src/parse.ts:23parse(indexing → structuring → transformation)