如何让 Agent 获取更多的能力?插件 / 技能系统

这篇我想讲怎么让第三方往你的 Agent 里热插拔能力,同时又不把安全、命名、生命周期搞成一团乱。总结就是:可扩展的关键,不是「加功能容易」,而是「加功能时不用改核心」。

前言

很多人对拓展能力会有一点错误的认知,认为「可扩展」就是多留几个口子、多写几个工具塞进去。于是 Agent 越写越像一锅乱粥------几十个工具平铺在一个文件里,每加一个能力都要改主循环,改完还得担心怕把旧的弄坏。

问题不在「功能多」,而在「边界糊」。真正好扩展的 Agent,会提前回答三个问题:新能力从哪进来?它跟别的能力撞名字怎么办?它不用了怎么干净地退场? 这三个问题,就是插件 / 技能系统要解决的全部。而在动手之前,得先分清楚一件事------「扩展」其实有两条完全不同的线。

一、先分清两件事:Skill 改「怎么想」,Plugin 改「能做什么」

Agent 的「能力」由两部分决定:它会什么工具 (能做什么),以及它知道怎么用这些工具办事(怎么想)。这两部分对应两种完全不同的扩展方式:

  • Skill(技能) :往 System Prompt 里注入一段「知识」,改变 Agent 怎么想。它是一份 Markdown 文档,告诉模型「遇到这类事,按这个流程做」。
  • Plugin(插件) :往工具注册表里注入一批「工具」,改变 Agent 能做什么。它是一段代码,给 Agent 新增它原本没有的执行能力。
markdown 复制代码
<!-- Skill:知识,注入 System Prompt,改「怎么想」 -->
---
name: code-review
description: 以高级工程师的视角审查代码变更
whenToUse: 用户提交了改动、PR,或要求 review 时
---
1. 先看 git diff 收集变更范围
2. 按「正确性 → 安全 → 可维护性」逐条审查
3. 输出:位置、原因、建议
ts 复制代码
// Plugin:代码,注册工具,改「能做什么」
interface Plugin {
  name: string
  version: string
  description: string
  register(api: PluginApi): void   // 注册工具 / 技能
  destroy(): Promise<void>         // 清理资源
}

取舍点 :为什么不把两者做成一个东西?因为它们的信任边界完全不同 。Skill 只是文字,模型读它、照它思考,但它碰不到执行层 ------天然的零副作用,第三方随便写都不用太担心。Plugin 是真代码,它跑在你的进程里、能开网络、能读文件,等于让第三方把代码塞进了你的 Agent。所以 Skill 可以宽松,Plugin 必须设防。把这俩分开,安全策略才能分开定。

二、Skill:把「怎么做事」写成一份文档

Skill 的精髓是渐进式披露(progressive disclosure) :不是把一堆技能全文都塞进上下文,而是只常驻一个一句话描述,等模型判断「这个技能用得上」了,才把正文加载进来。

markdown 复制代码
---
name: pdf-reader
description: 读取 PDF、提取文字和表格
whenToUse: 用户给了 .pdf 文件或要读论文 / 合同时
---
# 读取 PDF
1. 先检查文件是否存在
2. 用 pdf 工具提取文字
3. 表格数据转成 Markdown 再回复

这里 description 永远在上下文里(占十几个 token),而正文只在需要时才进。取舍点 :这是「少即是多」的典型------一个只有 3 个技能、每个都全文常驻的 Agent,比一个有 30 个技能、但只有描述常驻的 Agent,上下文还更挤。Skill 的边界也很清楚:它只约束模型怎么想,不能直接产生副作用------这正好跟上一篇的安全话题接上,Skill 天然是「安全的扩展」。

三、Plugin:把「能做什么」打包成可插拔模块

Plugin 是真正的代码,所以它要回答三个更硬的问题:你是谁、你注册什么、你什么时候退场。这就是插件接口契约的全部。

ts 复制代码
// 插件通过「受限的 api」注册能力,而不是直接摸宿主内部
interface PluginApi {
  registerTool(tool: Tool): void
  registerSkill(skill: Skill): void
}

async function loadPlugin(def: Plugin) {
  if (loaded.has(def.name)) return        // 幂等:别重复加载
  def.register(api)                       // 激活
  loaded.set(def.name, def)
}

async function unloadPlugin(name: string) {
  const def = loaded.get(name)
  if (!def) return
  await def.destroy()                     // 释放连接、清定时器
  loaded.delete(name)
  unregisterTools(name)                   // 把它注册的工具一起摘掉
}

三个取舍点,一个比一个容易被忽略:

  1. 命名隔离 。两个插件都可能想叫自己的工具 search。不隔离,后加载的就把先加载的覆盖了,Agent 调的是谁都说不清。约定 插件名__工具名github__search_issuesnotion__search_pages),冲突从根上消失。
  2. API 隔离 。插件只拿得到一个受限的 PluginApi,而不是宿主对象本体。它能「注册工具」,但不能「改别的插件的工具」「关掉审计 Hook」。这跟上一篇的原则是同一件事------给插件的能力,也应该「刚刚够用」
  3. 生命周期 。加载要幂等、卸载要干净。一个插件开了数据库连接、挂了定时器,卸载时没清,就是内存泄漏和幽灵任务。所以 register 之外必须配一个 destroy

取舍点 :Plugin 最大的坑是信任 。加载一个第三方插件,等于 import 一段未知代码进你的进程------它理论上能读你的全部文件、发网络请求。所以要么插件都来自可信源、要么上沙箱 / 权限隔离。这是「可扩展」和「安全」不可回避的交换,没有免费午餐。

四、原点:可扩展 = 把「变化」隔离到边界

把 Skill 和 Plugin 放一起看,它们解决的是同一件事的两种形态:

Skill (改怎么想)+ Plugin(改能做什么)= 不碰核心代码,就能加能力。

共同点是:变化被隔离到了「边界」,核心循环保持稳定。 你加一个技能、加一个插件,主循环、工具执行、上下文组装这些核心代码一行都不用改。判断一个 Agent 框架扩不扩展,就看这一条:加一个新能力,要不要动核心?要动,就是边界没切好。

这也解释了为什么很多 Agent 框架把「Skill」「Plugin」「MCP」三样都摆出来------它们其实是扩展的三个信任等级:Skill 是零副作用的文字(最宽松),Plugin 是受限的本进程代码(要设防),MCP 是把工具挪到另一个进程 / 服务去跑(最隔离,但也最重)。选哪种,取决于你愿意为「隔离」付多少代价。

结语

回到开头那句:怎么让第三方往你的 Agent 里热插拔能力,又不搞成一团乱?答案是先把「扩展」拆成两条线------Skill 管它怎么想、Plugin 管它能做什么,再各自守住边界:Skill 靠「描述常驻、正文按需」省上下文;Plugin 靠「命名隔离 + API 隔离 + 生命周期」防冲突、防越权、防泄漏。

可扩展性从来不是「多留几个口子」,而是提前把边界切好,让变化永远停在核心之外。


参考:

相关推荐
甲维斯1 小时前
我的个人网站流量暴涨了,Codex+SOL实战分析!
人工智能
mit6.8241 小时前
Cursor 的记忆导出
人工智能
阡陌数智1 小时前
基于大语言模型的通用可配置 PII 隐私数据检测方案
人工智能·语言模型·自然语言处理
weixin_435208161 小时前
【大模型面试宝典】开源啦!!!
人工智能·职场和发展·agent·deepseek
Ticnix1 小时前
答案一个字一个字往外蹦,聊天记录却一条都没存下来
python·agent
user_admin_god1 小时前
第 11 篇:实践三 —— 表单 / 合同字段抽取
java·人工智能·spring boot·语言模型
北极有牛1 小时前
cuda算子--矩阵转置
人工智能·算法
诺鸭船长1 小时前
上帝之手Blender丨从入门到榨干
人工智能