跳到主要内容

四套能力扩展:技能 / Flow / MCP / 导入插件

本章讲什么: agent 的「手脚」从哪来?AnythingLLM 有四种给 agent 加能力的途径,来源各异(代码里写死的、用户拖拽画出来的、外部进程提供的、从 Hub 下载的),但它们最后都被收敛成同一个东西:一个通过 aibitat.function() 注册的工具。本章讲这四种来源,以及那套用「前缀字符串」编码来源、再在 #attachPlugins 里统一派发的装配机制。


1. 统一的终点:aibitat.function()

不管能力从哪来,最终都长成一个工具定义对象,塞进 aibitat.functions Map(index.js:1453 function())。一个工具至少有:

// 这是一个工具的最小形状(示意,取自 web-browsing 真实结构)
aibitat.function({
name: "web-browsing",
description: "Search the internet for real-time information...",
examples: [{ prompt: "...", call: JSON.stringify({ query: "..." }) }], // few-shot,给 UnTooled 用
parameters: { /* JSON Schema */ },
handler: async function ({ query }) { return await this.search(query); }, // 真正执行体
});

(真实例子 server/utils/agents/aibitat/plugins/web-browsing.js:14)。

注意 examples 字段——它在 native 轨没用,但在 UnTooled 轨会被 showcaseFunctions 渲染成 few-shot(见第 02 章)。这是「同一份工具定义服务两轨」的体现。


2. 四种来源

来源是什么谁产出编码前缀
内置技能(plugin)代码里写死的工具,如 web-browsing、sql-agent项目自带无 / parent#child
无代码 Flow用户在 UI 里拖拽出的工作流,存成 JSONAgentFlows@@flow_<uuid>
MCP server外部进程按 Model Context Protocol 暴露的工具MCPCompatibilityLayer@@mcp_<name>
导入插件从 Community Hub 下载的第三方插件ImportedPlugin@@<hubId>

这四种来源在 WORKSPACE_AGENT.getDefinition 里被汇成 @agentfunctions 清单(server/utils/agents/defaults.js:84):

// @agent 的工具清单 = 四种来源拼起来(defaults.js:84-93)
functions: [
...(await agentSkillsFromSystemSettings()), // 内置技能(含 parent#child 子技能)
...clarifyingQuestionsSkills, // 「问用户」子技能(若启用)
...ImportedPlugin.activeImportedPlugins(), // @@<hubId>
...AgentFlows.activeFlowPlugins(), // @@flow_<uuid>
...(await new MCPCompatibilityLayer().activeMCPServers()), // @@mcp_<name>
]

此时清单里装的只是名字字符串(可能带前缀),真正的工具实现还没加载。加载发生在装配阶段。


3. 装配:#attachPlugins 的前缀派发

AgentHandler.#attachPlugins(server/utils/agents/index.js:546)遍历工具名清单,按前缀分流到不同的加载逻辑——这是把「四种异构来源」收敛成统一工具的关键开关:

遍历 #funcsToLoad 里每个 name:
name 含 "#" → 内置技能的子工具:从 AgentPlugins[parent] 里找 child,use(child.plugin(opts))
name 以 "@@flow_" → Flow:AgentFlows.loadFlowPlugin(uuid) → 把占位名换成真工具名 → use
name 以 "@@mcp_" → MCP:convertServerToolsToPlugins(name) 把一台 MCP 的所有工具转成插件 → 逐个 use
name 以 "@@" → 导入插件:ImportedPlugin.loadPluginByHubId(hubId) → use
否则 → 普通内置单体技能:use(AgentPlugins[name].plugin(opts))

每个分支最后都调 this.aibitat.use(plugin),而 use 就是调插件的 setup(aibitat)(index.js:150),setup 里再调 aibitat.function(...) 完成注册。所有路最终汇到 aibitat.function()

3.1 内置技能与子技能命名

有些技能是「一个父技能下挂多个子工具」(如 sql-agent 下有 list-database-connectionsquery 等)。agentSkillsFromSystemSettings(defaults.js:123)把这种父技能展开成 parent#child 名字(defaults.js:179)。#attachPlugins 看到含 # 的名字就走子工具加载分支(index.js:549)。默认启用的技能写在 DEFAULT_SKILLS(defaults.js:10):memory、doc-summarizer、web-scraping。

3.2 Flow:把一张工作流图变成一个工具

AgentFlows.loadFlowPlugin(uuid)(server/utils/agentFlows/index.js:218)读出存盘的 Flow JSON,包装成一个插件:它的 handlerAgentFlows.executeFlow(index.js:177)按步骤执行整张图,把结果返回。装配时还有个小动作:把清单里的 @@flow_<uuid> 占位名替换成 Flow 实际的工具名(server/utils/agents/index.js:593),这样第 01 章 reply 里按名字查工具才查得到。

3.3 MCP:一台 server → 多个工具

MCP 是「Model Context Protocol」——外部进程按标准协议暴露工具。MCPCompatibilityLayer(server/utils/MCP/index.js,单例)负责:

  • activeMCPServers():启动所有配置的 MCP server,返回 @@mcp_<name> 名字(MCP/index.js:18)。
  • convertServerToolsToPlugins(name):对一台 server 调 listTools(),把每个工具转成一个 aibitat 插件(MCP/index.js:25)。工具名规整成 <server>-<tool>,inputSchema 直接当 parameters,handler 转发给 MCP server 执行。

所以一行 @@mcp_github 在装配后可能展开成 github-create_issuegithub-list_repos… 一堆工具(server/utils/agents/index.js:611)。转出的工具带 isMCPTool: true 标记(MCP/index.js),供去重器对 MCP 工具加冷却防循环(见第 04 章)。

3.4 导入插件:Hub 下载的第三方

ImportedPlugin(server/utils/agents/imported.js)处理从 Community Hub 下载、存在本地插件目录的第三方插件。activeImportedPlugins() 列出激活的(imported.js:64),loadPluginByHubId(hubId) 按 hubId 加载(imported.js:33),validateImportedPluginHandler 在装配前校验(imported.js:148)。装配走 name.startsWith("@@") 分支(server/utils/agents/index.js:644)。


4. 一张图:从「名字清单」到「可调工具」

WORKSPACE_AGENT.getDefinition() #attachPlugins(按前缀派发) aibitat.functions Map
┌───────────────────────────┐ ┌──────────────────────────┐ ┌────────────────────┐
│ "web-scraping" │──普通──► │ AgentPlugins[name].plugin │──use─►│ web-scraping │
│ "sql-agent#query" │──#────► │ 父.plugin.find(child) │──use─►│ sql-agent:query │
│ "@@flow_abc123" │──flow─► │ AgentFlows.loadFlowPlugin │──use─►│ <flow 真名> │
│ "@@mcp_github" │──mcp──► │ convertServerToolsToPlugins──use─►│ github-create_issue│
│ │ │ (一台 server → 多工具) │ ... │ github-list_repos │
│ "@@some-hub-id" │──@@──► │ ImportedPlugin.loadByHubId│──use─►│ <imported 工具> │
└───────────────────────────┘ └──────────────────────────┘ └────────────────────┘
(只是字符串名) (解析来源、实例化) (reply 时按名可查)

怎么读: 左边是「计划要装的工具名」(带来源前缀),中间 #attachPlugins 按前缀认领并实例化,右边是注册完、reply 能按名查到的真工具。前缀就是来源的编码。


5. 巧妙之处

  • 「前缀编码来源 + 统一终点」:用 @@flow_/@@mcp_/@@/# 这套字符串约定,让四种完全异构的能力来源共用一条装配流水线,最后都落到 aibitat.function()——内核与对话循环对「工具从哪来」零感知(server/utils/agents/index.js:546)。
  • MCP 的「一服务多工具」自动展开:配置层只写一台 server,装配层 listTools 动态展开成 N 个工具并即时把名字回填到 agent 清单(MCP/index.js:25server/utils/agents/index.js:627)。
  • examples 字段双轨复用:同一份工具定义里的 few-shot,native 轨忽略、UnTooled 轨拿去拼 prompt——一份定义喂两类模型。

6. 代码地图

主题文件符号
工具清单汇总server/utils/agents/defaults.jsWORKSPACE_AGENT.getDefinition, agentSkillsFromSystemSettings, DEFAULT_SKILLS
前缀派发装配server/utils/agents/index.js#attachPlugins
插件注册server/utils/agents/aibitat/index.jsuse, function
内置技能注册表server/utils/agents/aibitat/plugins/index.jsmodule.exports
示例技能server/utils/agents/aibitat/plugins/web-browsing.jswebBrowsing
Flowserver/utils/agentFlows/index.jsAgentFlows.loadFlowPlugin, activeFlowPlugins, executeFlow
MCPserver/utils/MCP/index.jsMCPCompatibilityLayer, activeMCPServers, convertServerToolsToPlugins
导入插件server/utils/agents/imported.jsImportedPlugin.loadPluginByHubId, activeImportedPlugins