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

文章来源声明: 原文作者:sarasuki; 来源站点:掘金; 原文链接:https://juejin.cn/post/7686485244199944202; 本文基于上述来源整理/加工,觅优补充点评,仅供技术学习交流。版权归原作者所有。
觅优短评

把「扩展」拆成 Skill 与 Plugin 两条线,并对应到不同信任等级,是很有操作性的设计视角。适合正在搭建 Agent 框架、纠结插件安全与边界的开发者阅读参考。

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

前言

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

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

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

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

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

<span>// Plugin:代码,注册工具,改「能做什么」</span>
<span>interface</span> <span>Plugin</span> {
  <span>name</span>: <span>string</span>
  <span>version</span>: <span>string</span>
  <span>description</span>: <span>string</span>
  <span>register</span>(<span>api</span>: <span>PluginApi</span>): <span>void</span>   <span>// 注册工具 / 技能</span>
  <span>destroy</span>(): <span>Promise</span><<span>void</span>>         <span>// 清理资源</span>
}

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

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

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

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

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

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

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

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

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

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

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

  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 隔离 + 生命周期」防冲突、防越权、防泄漏。

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


参考: