☰
Agent应用开发实战:Plugin/Skills/CLI三层架构解析
2026/10/14 23:33:47 网站建设 项目流程

做 Agent 应用开发,绕来绕去总会撞上同一个问题:你的智能体怎么跟真实世界发生连接?模型再聪明,调不了外部系统的接口,也干不了活。最近几年,各家云厂商都在推 Agent 开发工具链,火山引擎 Agent Toolkit 是其中比较有意思的一套,它把开发过程拆成 Plugin、Skills、CLI 三层。我花了一段时间实际跑过云上开发流程,这篇文章就围绕这三层,讲清楚它们各自负责什么、怎么配合,以及真正用起来有哪些坑。

如果你正在做智能体应用,或者打算把大模型接到公司内部系统上,这篇文章应该能帮你少走不少弯路。我会先从分层逻辑讲起,再一层一层拆开看,最后给一个完整的实操案例和常见问题排查清单。

1. 先理解三层架构为什么这么设计

1.1 三层各管一段,边界非常清晰

Plugin、Skills、CLI 并不是三个并列的功能模块,它们是围着"一个 Agent 应用从开发到上线"这件事,切出来的三个关注面。

  • Plugin 解决的是"能力从哪来"。Agent 要调用外部系统,比如云 API、数据库、内部配置中心、消息通知,Plugin 就把每个外部系统包装成标准接口,按统一协议接进来。Agent 不关心外部系统的真实协议是什么,它只认识 Plugin 暴露出来的工具。
  • Skills 解决的是"能力怎么用"。同一个工具,在不同任务下用法完全不一样。Skill 把针对某一类任务的一组提示词、工具调用顺序、参数约束、结果校验封装起来。Agent 发现任务匹配某个 Skill 后,就按里面定义的步骤执行。
  • CLI 解决的是"整个开发过程怎么管"。项目的初始化、依赖管理、本地调试、打包部署、日志检索,全都在 CLI 里完成。它是开发者和这套三层架构之间的交互入口。

把这三层合在一起看,其实是典型的控制面与数据面分离思路:CLI 是控制面,负责工程生命周期和运行时管理;Plugin 和 Skills 是数据面,负责具体业务逻辑。这样的分层不是凭空拍脑袋,而是真实踩过"所有逻辑堆在一起改不动"的坑之后,才会认同的做法。

1.2 分层到底带来了什么实际收益

第一是关注点分离。负责接外部系统的同学只需要维护 Plugin,负责设计业务流程的同学只需要编辑 Skill,写发布流水线的同学只跟 CLI 打交道。每个人改动自己那部分,不会互相干扰。

第二是可复用性。一个写好的配置中心 Plugin,可以在多个 Agent 项目里复用;一个沉淀下来的部署 Skill,可以套用到不同服务。时间越长,资产积累的效果越明显。

第三是本地可调试性。CLI 提供了本地运行时,Plugin 和 Skill 的加载、调用、返回都在本地就能完整走一遍。没有这套机制的话,每次调试都要发布到云上等日志,一次循环少说几分钟,有了本地环境,一次迭代只需要十几秒。

1.3 三层之间靠什么串起来

三层之间不是靠代码互相引用,而是靠一套声明式的配置文件来组合。项目里一般会有一个主配置描述部署目标,一个插件清单描述要加载哪些 Plugin,一个或多个 Skill 文件描述业务技能。CLI 在初始化时会读取这些文件,把三层在本地"装配"成一个可运行的 Agent 应用。

这种声明式组合有一个很实际的好处:同一个配置既能在本地跑,也能在云端跑。本地调试时,CLI 会模拟云上环境;部署时,CLI 又按同一份配置生成上线的运行环境。两边对得上,很多"本地能跑、线上跑不起来"的玄学问题就能提前消灭在调试阶段。

2. Plugin:把外部系统变成模型能用的工具

2.1 Plugin 的本质不是功能实现,而是协议适配

很多人第一次接触 Plugin 时,会把它想成一个"功能模块",这是理解上最大的偏差。Plugin 真正要做的事,是把外部系统的私有协议翻译成模型能理解的公共协议。

打个比方:模型像一个只会讲通用语言的外派员工,外部系统像一家家语言不通的本地机构。Plugin 就是公司配备的翻译官。模型说出意图,翻译官负责跟本地机构沟通,再把结果用标准格式汇报回来。

具体到技术层面,Plugin 做的事情无非三件:注册工具、执行调用、规整返回。注册工具时,要声明这个工具叫什么、能干什么、接受什么参数,全部写成 JSON Schema 形式给模型看。执行调用时,Plugin 负责真实的 HTTP 请求、鉴权、超时、重试这些脏活。规整返回时,要把外部系统返回的各种格式整理成 JSON,必要时做截断或摘要,避免把模型上下文撑爆。

2.2 一个最小可用 Plugin 包含什么

我实际用下来,一个结构清晰的 Plugin 至少包含四部分:

  • 清单文件:声明插件名、版本号、包含哪些工具、每个工具的入参和出参 Schema。
  • 实现代码:每个工具对应的实际执行逻辑,比如发请求、查数据库、调内部 API。
  • 测试用例:至少覆盖正常返回、鉴权失败、服务端异常这三种情况。
  • 说明文档:写清楚这个插件需要哪些环境变量、有没有特殊权限要求。

清单文件是整个 Plugin 的"门面",也是模型唯一能看到的部分。工具描述应该尽量具体,例如"根据服务名拉取当前生效的配置,返回一个 JSON 对象"就比"获取配置"要好得多。参数名也要用开发者能一眼看懂的名字,不要出现含义不明的缩写。

2.3 Plugin 开发里最常见的四个坑

第一个坑是不设超时。大模型编排的调用链路很长,某个外部接口偶尔慢一次,如果没有超时控制,整个 Agent 就卡在那一环上。我在项目里统一给所有出网调用加了连接超时和读超时,分别设置成 3 秒和 10 秒,宁可失败也不无限等。

第二个坑是返回内容失控。有些内部系统一次返回上万字的日志,模型上下文直接被打爆。最好的做法是插件端先做摘要或只返回前 N 条,把后处理逻辑留在插件内部,而不是把压力丢给模型。

第三个坑是鉴权信息乱打日志。插件在调外部系统时要带 token 或密钥,排查问题时随手打日志,很容易把密钥打出来。我的习惯是日志里永远不打印完整凭据,只输出认证是否成功的布尔结果。

第四个坑是重试没有区分幂等性。读接口可以放心重试,写接口重试前一定要确认接口是否幂等,否则一次网络抖动就可能产生两条重复数据。吃过大亏之后,我对写操作一律不自动重试,改为把错误返回给 Agent 让它决定下一步。

2.4 Plugin 与模型的协作契约

模型调用 Plugin,靠的是函数调用能力。它在推理时会看工具列表,选出匹配意图的工具,然后按工具声明的参数格式生成调用请求。这个过程中,模型只是"生成参数"和"声明要调哪个工具",真正执行和校验的都是运行时。

所以,工具 Schema 写得好不好,直接影响调用成功率。我一般遵循三条原则:必填参数尽量少,值域写清楚枚举或范围,描述里写明白调用前置条件。例如一个查询接口,前置条件是服务名必须精确匹配,否则返回空。这种信息写进描述里,模型就不会拿一个模糊的拼写去撞运气。

3. Skills:把零散工具编排成业务技能

3.1 Skill 不是工具列表,而是任务流程

如果说 Plugin 解决的是"能用什么",Skill 解决的就是"这件事该怎么做"。一个 Skill 通常包含四块内容:任务的触发条件、按顺序执行的步骤、每步调用的 Plugin 工具、以及失败时的回退策略。

举个例子。某团队要做一个"配置巡检 Agent",输入一个服务名,Agent 自动去配置中心拉当前配置,跟基线配置比对,最后生成一份差异报告。这个流程如果让模型自由发挥,它可能每次调用顺序都不一样,有的情况下还会漏掉基线比对这一步。把流程固化成 Skill 之后,模型一旦命中这个技能,就必须按步骤执行,拉配置、拉基线、做比对、出报告,一步都不会少。

这就像把散装的操作方法写成了标准作业流程,对一致性要求高的场景非常有用。

3.2 Skill 怎么决定用哪个 Plugin

Skill 文件里会声明自己依赖哪些工具 ID,这些 ID 对应 Plugin 注册出来的工具。运行时加载 Skill 时,会去 Plugin 列表里查找对应的工具,查不到会直接报错,避免运行时才发现工具缺失。

执行顺序上,我习惯把 Skill 分成"串行步骤"和"可选步骤"两种。串行步骤是主干,比如先拉当前配置、再拉基线配置;可选步骤是增强项,比如拉不到基线时,可以尝试从备份节点拉一次。模型在按照 Skill 编排执行时,遇到可选步骤会根据实际情况决定要不要走。

3.3 编写 Skill 的几个关键细节

第一,触发描述要写在最前面,而且要具体。"当用户提及对某个服务的配置进行巡检、对比或基线检查时",就比"配置相关任务"容易命中。我会把最常见的用户说法直接写进描述里,比如"查一下某服务配置有没有问题"。

第二,参数 Schema 尽量收紧。Skill 接收的入参越明确,执行就越可控。宁可让 Agent 反问用户一次要参数,也不要让它拿着残缺参数硬跑流程。

第三,风险操作要有确认点。涉及发布、删除、覆盖操作的 Skill,我通常在关键步骤前后加确认机制,让 Agent 输出即将执行的操作预览,等人类确认后再继续。机器可以快,但破坏性操作必须慢。

第四,失败回退要写得像剧本。不要只说"失败了就返回错误",而是写清楚"拉取配置失败时,先尝试备用节点;备用节点也失败时,把错误原样返回给用户,并提示检查服务名拼写"。模型有据可依,就不会临场乱发挥。

3.4 多个 Skill 并存时的编排问题

当一个 Agent 挂了多个 Skill,模型会做一次"技能选择",相当于从目录里挑最匹配的一项。这里最怕的是两个 Skill 的描述语义重叠,比如一个叫"配置巡检",一个叫"配置查看",用户说"看看配置"时,模型可能随机二选一。

我的处理办法是给每个 Skill 划分明确边界,描述里带上"仅在什么情况下使用"。如果还是容易混淆,就合并成一个技能,在内部用条件分支区分不同路径。别舍不得拆,也别舍不得合,一切以模型命中率为准。

4. CLI:把云上开发串起来的那根线

4.1 CLI 的职责地图

CLI 是开发者每天实际接触最多的部分。我整理了自己常用的子命令,大致可以画成一张职责表:

命令职责典型使用时机
init初始化项目骨架,生成配置模板新建 Agent 项目
plugin add添加依赖的 Plugin,并写入清单项目需要接入新系统
skill new生成 Skill 文件模板定义新业务流程
debug启动本地运行时,进入交互式调试开发过程中反复验证
validate校验配置语法和依赖完整性提交代码前、接入 CI
deploy打包并发布到云端运行环境准备上线
logs拉取线上运行日志排查生产问题

这个清单不一定覆盖所有能力,但核心工作流的骨架就是这几件事:初始化、调试、部署。

4.2 本地调试:最值回票价的功能

CLI 的 debug 命令是我个人最依赖的功能。它会启动一个本地运行时,加载当前项目的 Plugin 和 Skill,然后给你一个交互式终端。你在终端里输入一段话,本地 Agent 就会完整走一遍"理解意图、选择技能、调用工具、生成回复"的流程,而且每一步的日志都会实时打出来。

这意味着什么?过去调试 Agent,通常是部署到云端,再翻半天日志才能定位是哪一步出了问题。现在本地直接把链路全部摊开:模型选了哪个 Skill,调了哪个 Plugin,传了什么参数,外部系统返回了什么,一眼就能看到。我实测下来,一个场景的调试循环从原来的十几分钟压缩到一分钟以内,效率提升非常明显。

4.3 部署动作拆开看

CLI 的 deploy 命令看起来是一行命令,背后其实做了好几件事。先校验本地配置语法,再把 Plugin、Skill 和运行依赖一起打进发布包,上传到云端存储。接着创建或更新运行实例,注入环境变量,最后做健康检查,通过后才返回访问地址。

这里有一个值得强调的设计:本地调试和云端发布共用同一份项目配置。所以部署不是另起炉灶,而是把本地已经验证过的组合方式原样搬到云端。环境变量可能会有差异,但 Plugin 和 Skill 的组装方式是一致的。这从根本上减少了"本地环境与线上环境不一致"这类问题。

4.4 把 CLI 接进 CI/CD 流水线

CLI 不仅能人肉敲,也很适合接进自动化流水线。我的习惯是在提交代码的流水线上加一步 validate,只校验配置和依赖,不产生部署动作。这样每个 MR 合并之前就知道配置有没有问题,一些手滑导致的格式错误或者缺依赖的情况,在代码评审阶段就会被拦住。

到了发布阶段,可以在流水线里执行 deploy,或者把导出的发布包交给后端发布系统走审批流程。对需要严格变更管控的团队来说,这种"先打包后审批再发布"的方式比手工执行命令容易审计得多。

5. 实操记录:三层协作完成一次云上开发

5.1 一个具体的开发场景

前面讲了不少理论,这里我把一个真实做过的小项目完整拆开,带你走一遍全过程。

背景是这样:某团队维护了一批内部服务,配置散落在一个配置中心里。组里希望有一个 Agent,输入服务名就能自动对比当前配置与基线配置,并给出差异清单。这个需求非常适合用 Agent 来做,因为它涉及两个外部数据源、一步对比逻辑和一份报告输出,流程固定但需要一点智能判断。

5.2 初始化项目并规划目录

先用 CLI 初始化项目,命令大致是这样:

agent-toolkit init config-inspector cd config-inspector

初始化完成之后,项目目录结构大概是:

config-inspector/ ├── project.yaml ├── plugin/ │ └── config-center/ │ ├── manifest.yaml │ └── main.py ├── skills/ │ └── config-audit.yaml └── runtime/ └── requirements.txt

project.yaml 描述部署目标与运行环境,plugin 目录放配置中心 Plugin,skills 目录放巡检技能,runtime 目录放 Python 依赖。

5.3 实现 Plugin 的两个工具

这个场景只需要配置中心一个插件,包含两个工具。一个拉取当前配置,一个拉取基线配置。清单文件的核心部分大概是:

name: config-center version: 1.0.0 tools: - name: fetch_config description: 根据服务名从配置中心拉取当前生效配置,返回 JSON 对象 parameters: - service_name: type: string required: true description: 目标服务名,必须精确匹配 - name: fetch_baseline description: 根据服务名从配置中心拉取基线配置,返回 JSON 对象 parameters: - service_name: type: string required: true description: 目标服务名,必须精确匹配

实现代码里,两个工具就是对配置中心接口做封装。拉取成功后返回标准化 JSON,失败时抛出带错误码的异常,让上层 Skill 能识别失败原因。

5.4 定义巡检 Skill

接着在 skills/config-audit.yaml 里定义技能。触发条件写成"用户请求对某服务进行配置巡检、对比、或基线检查时使用",步骤定义成四条:

  1. 调用 fetch_config 拉取当前配置。
  2. 调用 fetch_baseline 拉取基线配置。
  3. 对比两份配置的差异,生成差异清单。
  4. 把差异清单整理成报告,标注配置项位置、期望值与实际值。

失败回退也写清楚:如果 fetch_config 返回空,提示用户检查服务名拼写;如果 fetch_baseline 返回空,说明缺少基线数据,建议先补充基线再巡检。

这个 Skill 的好处是,整个对比流程被固定下来。模型不需要临场发明步骤,只要按顺序执行就行,输出的一致性大幅提升。

5.5 本地调试验证效果

接下来执行 debug 进入交互式终端,输入一段自然语言:"帮我巡检下订单服务,看看它的配置和基线差了多少。"

本地运行时会完整记录这一次请求的链路。我观察到的调用顺序基本符合预期:模型先命中 config-audit 技能,然后依次调用 fetch_config 和 fetch_baseline,最后输出差异清单。

测试中发现一个问题,当输入"看看订单服务配置"时,模型没有命中巡检技能,而是直接把配置原始 JSON 返回给用户。原因是这段描述的语义更接近"查看配置",而不是"对比配置"。我把触发条件里补了"查看配置变化"这种说法,并加了示例问题,再测试命中率就上来了。这种问题在本地调试阶段发现并修正,成本非常低。

5.6 部署到云端并接到入口

本地验证通过后,我在 project.yaml 里配置了运行环境、资源规格和环境变量,然后执行 deploy。CLI 先做配置校验,再打包上传,创建云端运行实例,健康检查通过后返回了访问地址。

部署完成后的 Agent 只提供了执行能力,真正对外服务还要接一个入口。我这边接的是团队内部的消息平台,用户直接在群里发消息就能触发巡检。这一步主要是把 Agent 暴露成 HTTP 服务,再在消息平台后端配置回调,跟这套三层架构本身没有太多耦合。

5.7 这次实操最大的体会

整个流程走下来,最有价值的设计是三层分工让每一步的验收边界都很清晰:Plugin 的验收标准是"工具能不能正确返回数据",Skill 的验收标准是"业务流程能不能稳定复现",CLI 的验收标准是"从本地到云端能不能一键复现"。每层各自被验证过后,组合起来的系统可靠性就有底气了。

6. 常见问题与排查技巧实录

6.1 新增的 Plugin 工具在列表里看不到

这是我最常被问到的问题。现象是清单文件里加了新工具,但模型在调试时根本看不到它。

原因通常是调试进程在启动时已经加载了旧的插件列表,新增工具没有触发重新加载。解决办法很简单:重启 debug 会话,或者使用 CLI 提供的热加载命令重新读取配置。我自己的习惯是每次改完 manifest 就顺手重启一次会话,免得排查问题时被假象干扰。

6.2 模型老是选错 Skill

当两个技能描述边界不清时,模型很容易选错。比如我前面提到的"配置巡检"和"配置查看",用户话说得模糊,模型就随机二选一。

处理方式是先给每个 Skill 加严格的适用条件,再复查一遍描述。如果两个技能确实经常同时匹配,不如合并成一个技能,内部用分支处理。别为了追求技能数量而强行拆分,质量比数量重要。

6.3 本地跑得好好的,部署到云端就 401

这个问题的根源几乎都是身份和密钥没切换。本地开发时,开发者通常用自己的个人身份调内部系统;云端运行时,必须换成服务身份。很多人改项目代码时没注意环境变量,导致线上跑起来发现鉴权失败。

排查时先把环境变量列出来,比对本地的密钥和服务身份配置,再看网络白名单有没有放行云端出口 IP,最后确认角色权限是否包含目标接口的调用权限。按这个顺序查,基本能定位九成以上的线上鉴权问题。

6.4 外部接口偶尔变慢,整个 Agent 跟着卡死

这个问题我在前面提过,值得再强调一遍。Agent 工具链上的每个出网调用都必须设置超时,否则一个慢接口会拖住整条链路。

我的做法是统一在 Plugin 层给所有 HTTP 请求设置连接超时 3 秒、读超时 10 秒。另外给重试加上次数限制和退避策略,读接口最多重试两次,写接口不自动重试。这样的配置虽然简单,却能把"偶发网络抖动"变成"单次调用失败",而不是"全链路无限等待"。

6.5 复杂度高了之后,怎么快速定位是哪一层出了问题

当一次 Agent 请求链路很长时,靠肉眼刷日志效率太低。我常用的办法是利用请求追踪 ID,一次用户请求从入口开始会拿到一个唯一 ID,贯穿所有 Plugin 调用和 Skill 步骤。排查时先用这个 ID 过滤出整条链路的日志,然后按阶段切分:意图识别对不对、技能命中准不准、插件调用成不成功。定位到具体环节之后,再进那个环节看日志细节。

我自己会按这个表格快速对症状:

症状优先排查对象处理建议
模型没按预期调用工具Skill 描述与触发条件补充示例问题,收紧适用边界
工具调用了但返回异常Plugin 外部调用查看外部系统状态码与鉴权配置
同一请求在云端报错多环境变量与服务身份对比本地与云端配置差异
响应慢但没报错Plugin 超时与重试给调用加超时,检查是否串行执行
Skill 执行顺序不对Skill 文件流程定义检查步骤是否写清串行/可选

6.6 一个小技巧:先让模型说要怎么做,再让它动手

排查类任务里,我习惯在 Skill 开头加一个"规划输出"步骤。让模型先简短说明自己准备怎么查、会用到哪些工具,然后再真正执行。这样做有两个好处,一是用户能看到 Agent 的思路,减少"黑盒感";二是模型在输出规划的过程中会重新理解任务,减少后续误操作。

我实测下来,这个技巧在面对多源数据对比、多步骤操作场景时,能把无效调用降低不少,最终结果也更稳定。尤其是当你把 Agent 开放给不懂技术的同事使用时,他们会因为能看到"它在想什么"而对结果更信任。

写到现在,我最想说的是:Plugin、Skills、CLI 这三层,真正解决的不是"能不能开发 Agent"的问题,而是"能不能高效、可靠地开发 Agent"的问题。分层让每个人只关注自己那一层,声明式配置让本地与云端保持一致,CLI 把整个生命周期串成一条顺畅的流。这套思路如果你还没有用过,建议找个内部小需求,照着上面的步骤跑一遍。第一次接触可能会觉得配置有点多,但跑通两三个项目之后,你会感受到这套结构带来的踏实感。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询