跳到主要内容

AnythingLLM — 架构与原理

30 秒导读: AnythingLLM 是一个可私有部署的「全能 AI 应用」——既能拿你的文档做 RAG 聊天,也能在同一个对话里切换成会用工具的 agent(上网搜索、查数据库、读写文件、调 MCP 服务……)。本文档只讲它最有工程含量的那一半:agent 子系统。它的内核叫 aibitat,核心是一个「把对话建模成节点之间互发消息的图」+「递归的工具调用循环」。

本卷是多文件文档的入口。读完本页你能判断:agent 这块大概怎么转、要不要继续往下读、该跳哪一章


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

一句话定义: AnythingLLM 是一个自托管的「私有 ChatGPT + 文档库 + agent 平台」,把多用户聊天文档向量检索(RAG)工具型 agent 打包进一个 workspace。

它解决谁的什么问题。 假设你是一家公司的工程师,想要:把内部文档喂给一个聊天机器人;还想让它能上网、能查你们的数据库、能生成报表——而且全部跑在自己的机器上、按用户分权限。AnythingLLM 就是把这套东西「开箱即用」地给你。

本文档的范围。 整个项目很大(server + frontend + collector 三个子工程)。本卷只聚焦 agent,也就是 server/utils/agents/ 这棵树。普通 RAG 聊天不在范围内。

agent 能做什么(开箱内置的技能):

技能干什么
web-browsing调 Google/Bing/Tavily/DuckDuckGo 等搜索引擎查实时信息
web-scraping抓取并阅读一个网页
rag-memory / doc-summarizer在 workspace 文档里检索、对长文档做摘要
memory跨对话记住关于用户/workspace 的事实
sql-agent连接并查询 Postgres/MySQL/SQL Server
filesystem / create-files读写文件、生成 docx/pdf/pptx/xlsx
rechart生成图表
gmail / outlook / google-calendar收发邮件、读日历

用起来什么样(最小真实示例): 用户在聊天框里 @agent 一句话就触发 agent 模式——

用户: @agent 上网查一下 AnythingLLM 最近有什么更新,然后总结成三点
agent: (statusResponse) Swapping over to agent chat...
agent: (introspect) agent is executing `web-browsing` tool {"query":"AnythingLLM recent updates"}
agent: 1. ... 2. ... 3. ...

两条触发路径(server/utils/agents/index.js:51 AgentHandler.isAgentInvocation):

  • 显式 @agent:消息里带 @agent 句柄。
  • automatic 模式:workspace 设成 automatic 且模型支持原生工具调用时,每条消息都走 agent。

一句话直觉/类比:aibitat 想成一个群聊里的「主持人」:群里有「用户(USER)」和「agent(@agent)」两个固定成员;主持人让他们轮流发言,而 agent 发言前可以「停一下,先去用个工具,把结果带回来再说」。这个「停一下用工具、带结果回来、可能再停一下」的循环,就是整个 agent 的心脏。

本节不出现底层代码。下一节给你大盘。


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

2.1 一条 agent 消息的生命周期

agent 不是普通的 HTTP 请求-响应——它中途从 HTTP 切换到 WebSocket,因为 agent 可能要跑很久、要实时汇报、还可能要回头问用户。流向是这样的:

用户发消息 (HTTP /chat 流)


grepAgents() ── 检测到 @agent 或 automatic+原生工具
│ chats/agents.js:38
│ 创建 WorkspaceAgentInvocation(拿到一个 uuid)
│ 缓存这次的附件(图片等)

告诉前端:"agentInitWebsocketConnection" + uuid,然后关掉 HTTP 流

▼ 前端用 uuid 拨过来
WebSocket /agent-invocation/:uuid
│ endpoints/agentWebsocket.js

new AgentHandler({uuid}).init()
│ ① 校验 invocation ② 选 provider+model ③ 取回附件

handler.createAIbitat({ socket })
│ 装 websocket 插件、chat-history 插件
│ 装 USER / @agent 两个 agent + 它们的工具

handler.startAgentCluster() ──► aibitat.start({from:USER, to:@agent, ...})


┌─────────────── aibitat 内核(本文档主角)───────────────┐
│ start → chat → reply → handleExecution(工具循环) → ... │
└────────────────────────────────────────────────────────┘
│ 整个过程通过 socket 实时推 token / 状态 / 工具调用 给前端

socket close → WorkspaceAgentInvocation.close(uuid)

怎么读这张图: 从上到下是时间顺序。关键转折是中间那次「HTTP → WebSocket」切换——grepAgents 不直接跑 agent,它只登记一个 invocation 并把球踢给 WebSocket 端点(chats/agents.js:38-110),真正的 agent 在 WebSocket 连上后才启动(endpoints/agentWebsocket.js)。

2.2 部件与职责

部件干什么在哪个文件
grepAgents在普通聊天流里识别 agent 触发,创建 invocation,切到 WebSocketserver/utils/chats/agents.js:38
agentWebsocket 端点WebSocket 路由,把前端消息中继给 agent,处理退出命令server/endpoints/agentWebsocket.js
AgentHandleragent 的「装配工」:选 provider、加载 agent 与工具、建 aibitatserver/utils/agents/index.js:19
AIbitat内核引擎:节点对话图 + 工具调用循环server/utils/agents/aibitat/index.js:15
defaults.js定义 USER / @agent 两个内置 agent 的角色与技能清单server/utils/agents/defaults.js
Provider(ai-provider.js 等 40+)把 aibitat 的「补全请求」翻译成各家 LLM API,处理工具调用server/utils/agents/aibitat/providers/
Plugins(技能)一个个工具的实现,通过 aibitat.function() 注册server/utils/agents/aibitat/plugins/
websocket 插件把内核事件实时推给前端;做工具人工审批、问用户server/utils/agents/aibitat/plugins/websocket.js
MCP 兼容层把外部 MCP server 的工具转成 aibitat 插件server/utils/MCP/
AgentFlows无代码可视化「工作流」转成一个可调用工具server/utils/agentFlows/

2.3 主线走一遍(高层、不进代码)

  1. 触发:用户消息命中 @agent 或 automatic 模式 → grepAgents 建 invocation、切 WebSocket。
  2. 装配:AgentHandler 选好 provider/model,把 USER@agent 两个 agent 放进 aibitat,并把 @agent 要用的工具(技能 + Flow + MCP + 导入插件)逐个装进去。
  3. 对话:aibitat.start 把第一条用户消息塞进历史,然后进入 chat → reply 循环,让 @agent 回话。
  4. 工具循环:@agent 回话时,如果模型决定调工具,内核进入 handleExecution 递归:执行工具 → 把结果拼回消息 → 再问模型 → ……直到模型给出纯文本回答或撞到工具次数上限。
  5. 流式输出:整个过程 token、状态、工具调用、引用都通过 socket 实时推给前端。
  6. 收尾:产出落库(chat-history 插件),socket 关闭,invocation 标记 closed。

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

这套 agent 子系统有四个值得单独讲的硬核点,各成一章,由浅入深排好了:

  1. 01 — aibitat 内核 先理解最底的抽象:为什么把对话建成「节点互发消息的图」?start/chat/reply 三个方法怎么递归?消息历史的 state: success|interrupt|error 状态机是什么?这是后面所有内容的地基,先读它。

  2. 02 — 工具循环与 provider 双轨 agent 的心脏:handleExecution(同步)和 handleAsyncExecution(流式)两套递归工具循环。以及最巧妙的设计——同一套循环既能跑「原生函数调用」的模型,也能跑「靠 prompt 模拟工具调用」的弱模型(UnTooled)

  3. 03 — 四套能力扩展 能力是怎么插进来的?内置技能(plugin)、无代码 Flow、MCP server、Hub 导入插件——四种东西最后都被规整成同一个 aibitat.function() 注册。看 #attachPlugins@@flow_ / @@mcp_ / @@ 三种前缀的派发。

  4. 04 — 流式、审批、引用 面向「人在回路」的工程细节:WebSocket 实时事件协议、工具执行前的人工审批、引用/图片附件如何回灌进对话、Deduplicator 如何阻止弱模型把同一个工具调爆。

如果你只想抓「精华」:读 02 的工具循环 + 03 的四前缀派发 + 04 的去重器,这三处是这个项目最值得借鉴的设计。


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

主题文件关键符号
agent 触发判定server/utils/agents/index.jsAgentHandler.isAgentInvocation
HTTP→WS 切换server/utils/chats/agents.jsgrepAgents
WebSocket 端点server/endpoints/agentWebsocket.jsagentWebsocket, relayToSocket
agent 装配server/utils/agents/index.jsAgentHandler.createAIbitat, #attachPlugins, #loadAgents
内核引擎server/utils/agents/aibitat/index.jsAIbitat, start, chat, reply
工具循环server/utils/agents/aibitat/index.jshandleExecution, handleAsyncExecution
内置 agent 定义server/utils/agents/defaults.jsUSER_AGENT, WORKSPACE_AGENT, agentSkillsFromSystemSettings
provider 基类server/utils/agents/aibitat/providers/ai-provider.jsProvider, LangChainChatModel, systemPrompt
弱模型工具模拟server/utils/agents/aibitat/providers/helpers/untooled.jsUnTooled, functionCall, buildToolCallMessages
去重防循环server/utils/agents/aibitat/utils/dedupe.jsDeduplicator, isDuplicate, markUnique
MCP 兼容层server/utils/MCP/index.jsMCPCompatibilityLayer, convertServerToolsToPlugins
无代码 Flowserver/utils/agentFlows/index.jsAgentFlows.loadFlowPlugin, executeFlow
实时事件/审批server/utils/agents/aibitat/plugins/websocket.jswebsocket, requestToolApproval, introspect