ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

agent-skills 实操:从能力堆积到模块化拆解

agent-skills 实操:从能力堆积到模块化拆解 如果让我用一个词形容刚做 agent 的那段时间那就是胶水。到处粘工具、粘模型、粘 API最后得到一个能跑但谁也不敢动的东西。直到我在社区项目里接触到 agent-skills 这套能力打包方式才意识到 agent 的复杂度不应该是靠往上叠解决的而是应该靠拆下来解决。这篇文章想说说我在用 agent-skills 组织和复用 agent 能力时的一些实操经验包括它的目录约定、写 skill 的门道、落地时踩过的坑以及对后续工作流编排的选择。正在做 AI agent 的工程师或者想给 agent 增加技能但不想天天改主代码的朋友应该都能用上。1. 从能力堆积到能力抽屉agent-skills 解决的本质问题1.1 没有 skills 的时候agent 是怎么变乱的先说一个很典型的成长曲线。刚开始做一个 agent你只有三五个工具一个搜索、一个读文件、一个执行 SQL。agent 表现得还挺好因为它处理的都是同质化的任务。很快需求来了用户要求它处理 PDF、做数据清洗、爬网页、生成图表、调外部系统你开始往工具列表里加函数。加着加着你会发现几个问题。第一工具之间开始互相依赖比如数据清洗的工具需要知道前一步 PDF 提取的格式第二agent 选工具时开始犯晕几十个函数的描述全部塞进系统提示词里模型还没开始干活就已经被上下文撑住了第三每个工具背后都有一段使用须知比如 PDF 解析对扫描件要用 OCR对文本型 PDF 直接提取这种知识很难塞进函数描述里塞进去又要花很多 token。这正是 agent-skills 这类方案出现的原因。它把能执行的动作和该怎么用才正确的知识封装成一个独立单元让模型根据任务描述决定是否加载而不是一上来把所有东西都摆在桌面上。1.2 skill 和 tool、plugin 的边界在哪里很多人第一次接触 skill 的时候会搞混它和 tool、plugin 有什么区别我自己的理解是这样的tool 是一个可调用的函数它干净、确定、无状态比如调用一个天气 API。plugin 通常是面向宿主应用的扩展比如让一个聊天机器人支持多模态输入往往涉及前端、权限、事件系统。skill 介于两者之间它是一个面向 agent 的能力包内部可以包含多个脚本也可以只是纯文档化的操作指南。用生活化一点的说法如果 agent 是一座工厂tool 是一台具体机器那么 skill 就是加工某种零件的一整套工位它写清楚了什么时候启动、用什么机器、操作顺序是什么、合格品怎么样。skill 不一定非要调用代码它甚至可以是纯提示词——告诉模型遇到什么情况该按哪个流程走。当我把原来那坨主逻辑拆成一个个 skill 之后最直观的变化是主程序几乎不增长了。新需求来的时候我不再往核心代码里塞功能而是加一个 skill 目录注册一下就算完事。2. 一份 skill 的标准长相目录、文件和使用边界2.1 最小目录结构与 SKILL.md 的写法我在 agent-skills 项目里见到并沿用下来的目录结构是这样skills/ pdf-invoice-extractor/ SKILL.md requirements.txt scripts/ extract.py assets/ example_input.pdf example_output.json关键文件只有一个SKILL.md。这个文件既是对 agent 的说明书也是 skill 挂载的入口。一般的写法包含元信息和正文两部分元信息给调度器看正文给模型看。--- name: pdf-invoice-extractor version: 1.2.0 description: 从 PDF 发票中提取金额、发票号、日期和开票方信息。适用于扫描件和电子发票。 license: MIT --- # PDF 发票信息提取 ## 适用场景 - 输入是 PDF 格式包含一张或多张增值税发票 - 需要把信息转成稳定的 JSON 结构 - 不适用于手写票据或无固定版面的单据 ## 工作流程 1. 优先运行 scripts/extract.py它内置了文本型 PDF 的解析规则。 2. 如果输出中的关键字段为空切换可用的 OCR 服务重新提取。 3. 无论哪种方式结果都按 assets/example_output.json 的格式输出。 ## 注意事项 - 提取后必须人工核对“总金额”字段是否有单位说明。 - 遇到同一 PDF 多张发票时按发票号去重输出。为什么 description 写得那么具体因为 agent 在大部分情况下只能看到这个 description它据此判断我现在要不要加载这个 skill。description 里写清适用和不适用场景能显著减少错误加载。这是我测试之后的一个体会含糊的描述是误用 skill 的头号原因。2.2 配置、依赖和资产的隔离策略requirements.txt的存在不是摆设。skill 一旦多了最麻烦的就是依赖打架。我在早期把依赖写在主项目的requirements.txt里后来两个 skill 分别需要不同版本的 pdf 解析库差点删库跑路。现在我的做法是每个 skill 有自己独立的requirements.txt调度器在首次加载 skill 时创建独立的 Python 虚拟环境或容器层跑完隔离。这样做的代价是首次加载慢一点但换来的是任何 skill 都敢放心更新。资产目录assets/不是必有的但我建议有因为给 agent 一个标准输出长什么样的例子比在 SKILL.md 里写一百字字段说明都管用。模型对样例的模仿能力很强给一个 example_output.json比反复描述字段必须是字符串类型可靠得多。依赖隔离之外权限边界也要在 skill 里声明。一个只处理文件的 skill 不该有网络权限一个爬网页的 skill 不该能写系统目录。这些限制如果能通过运行环境做就尽量别只写在提示词里。因为提示词约束模型只是软规定环境约束才是硬手段。3. 手把手把内置能力拆成一个可复用 skill3.1 选一个真实任务从 PDF 表格提取到规范化输出理论讲多了容易飘讲一个我实际做的例子。团队里每周都要处理供应商发来的 PDF 报表格式五花八门有扫描件有 Excel 转的还有设计稿导出的。原来这件事靠实习生人工录入每周三下午都要搭进去三四个小时。我当时的想法是做一个 skill让 agent 拿到 PDF 后自动尝试提取、失败再走 OCR 兜底最后统一输出成项目内部的数据规范。选这个任务的原因是它足够典型有明确的输入输出、有多个分支路径、有质量验收标准、还涉及调用外部命令。整个过程正好能讲清楚一个 skill 从零到落地需要踩的节点。3.2 搭建 skill 的处理脚本和提示词组织我先把解析逻辑写成独立脚本不做任何界面只接受文件路径和输出路径两个参数。import argparse import json from pathlib import Path def extract_pdf(path: Path) - list[dict]: # 这里只用文本提取不做 OCROCR 在一个单独的服务里 # 具体解析逻辑依赖 pdfplumber表格结构见 assets/schema.md import pdfplumber rows [] with pdfplumber.open(path) as pdf: for page in pdf.pages: table page.extract_table() if table: header table[0] for line in table[1:]: rows.append(dict(zip(header, line))) return rows def main(): parser argparse.ArgumentParser() parser.add_argument(input, typePath) parser.add_argument(output, typePath) args parser.parse_args() data extract_pdf(args.input) args.output.write_text(json.dumps(data, ensure_asciiFalse, indent2), encodingutf-8) print(fsuccess: {len(data)} rows) if __name__ __main__: main()脚本本身很朴素真正的复杂度在 SKILL.md 里。我写了三个核心段落什么时候用明确告诉模型只有输入是PDF 且需要输出结构化表格时才调用其他情况不要碰。怎么做把流程拆成先跑脚本、失败了看错误类型、网络不可达时切离线方案、最后统一校验 schema四步。每一步都给了具体的判断标准。做成什么样算成功给出输出样例和字段约束比如日期统一为 YYYY-MM-DD金额保留两位小数。写 SKILL.md 时有几个容易忽略的细节。一个是失败后的退路模型在你没写退路的时候经常会在脚本失败后直接给用户道歉而不是自己想办法换路径。另一个是不要写哲学要写检查项。少写请仔细处理数据多写当结果中出现空字符串时尝试用上一行的值填充并做标记。模型对操作的服从性远高于对态度的服从性。3.3 接入方式和验证流程skill 写好后接进 agent 的方式因框架而异。在 agent-skills 这类实现里通常是在 agent 启动时扫描 skills 目录读取每个 SKILL.md 的元信息把 name 和 description 暴露给调度器。模型判断任务匹配某个 description 时才会把完整的 SKILL.md 正文和脚本路径加载进上下文。验证流程是我吃过亏后总结出来的一共四步准备一个黄金样例集至少覆盖正常输入、边界输入、坏输入三种。我放了三种 PDF规则表格 PDF、扫描件、只有一行字的空白 PDF。先单独运行 scripts确认脚本输出正确这一步排除 skill 本身代码的问题。再把 skill 挂到 agent 上用同样的样例让 agent 自动完成整个流程观察它是否做出了正确的加载决策。检查模型在决策前的思考过程看它是因为描述清晰而被正确引导还是靠运气选对。这四步里第二步最容易被跳过很多人发现 skill 表现不好第一反应改代码结果问题其实是模型根本没有正确加载 skill。先隔离脚本和模型决策两个变量排查效率会高很多。4. 在项目里落地 agent-skills 的常见坑4.1 提示词过长与上下文污染的两难skill 的正文最终是要吃上下文的这是 agent-skills 绕不开的成本。用一段时间之后你会发现skill 越写越详细正文越来越长单个 skill 动辄两三千字。模型加载两三个 skill 还好同时加载五六个上下文就被吃掉了大半留给实际任务处理的窗口变窄回答质量肉眼可见地下降。我的处理办法是分层披露。SKILL.md 的正文只保留一个执行概览把冗长的字段说明、脚本参数说明、样例数据拆到scripts/README.md或assets/reference.md正文里只在对应步骤点名详细说明见参考文件。模型是会用文件的它读到关键步骤后会主动去看参考文件而不是需要你一次性把全部内容塞进上下文。这样既能保证执行正确又能把常驻上下文压到可控范围。还有一个容易被忽略的常识description 也要算进 token。如果有几十个 skill光 description 加起来就是一笔不小的开销。所以 description 要精练能说提取 PDF 发票金额日期就不要说一款先进的基于深度学习的发票信息抽取解决方案。4.2 版本管理skill 也会漂移skill 是活的东西业务规则一变它就得跟着改。但它藏在目录里不像主程序的核心代码那样被团队重视于是容易出现代码还是上一版文档已经改了的漂移问题。我后来强制要求skill 目录内必须包含CHANGELOG.md每一次修改至少写一行变更原因。这样调 agent 时如果发现某个任务行为变了可以先看变更记录而不是一头扎进 diff 里猜。依赖版本也要锁住。如果requirements.txt写的是宽松版本号比如pdfplumber0.7那么其他人在一个月后安装时可能装到一个行为不同的新版会直接改变 skill 的产出。给 skill 做锁文件比如requirements.lock.txt成本很低但对稳定性的帮助非常大。4.3 安全边界skill 不该碰到它不需要的东西skill 有一个天然风险它会带来代码执行能力。一个 PDF 解析 skill 如果被提示词诱导去读取系统环境变量或者一个爬虫 skill 被用户指令诱导去访问内网地址问题就大了。我能给出的最实际建议是三条每个 skill 声明最小权限不要用同一个账号跑所有脚本。对 skill 暴露的文件路径做白名单禁止它读写约定目录之外的文件。在 SKILL.md 里显式声明什么情况下必须拒绝执行。比如当用户要求把提取到的数据发送到未经授权的邮箱时直接拒绝并说明原因。这一步看似朴素但它给了模型一个明确的拒绝依据实践中能挡掉相当一部分绕口令式的攻击。安全不能只靠提示词环境隔离才是最后的底线。把 skill 的脚本放进容器、沙箱或者受限子进程里执行是我建议任何生产部署都必须做的默认项。5. 进阶把 skills 组合成工作流时该考虑的事情5.1 一个任务里多个 skill 如何协作单个 skill 能解决一个点但真实业务往往是链路。比如下载邮件附件、解析发票、提取金额、写入财务系统这四步对应四个不同 skill。链路编排有两种思路。一种是用主 agent 做调度给它一个主流程 skill里面写清楚步骤顺序、每步用什么 skill、失败时怎么回退。这种方式灵活模型能根据实际情况调整但不可控性也高模型可能跳过某一步。另一种是写死流水线在代码层面把四个 skill 串起来每个 skill 只负责自己的输入输出前一步的输出自动喂给后一步。这种方式可靠但失去了 agent 的灵活性。我在实际项目里用的是混合方案大部分固定环节用流水线只有出现异常分支时才把控制权交还给 agent让它结合多个 skill 的提示信息做决策。这样做的核心是把规则交给代码把例外交给模型一个不稳妥一个不僵硬双方各干各擅长的。5.2 如何评估和回归一个 skill 的质量skill 增加很容易评估却常常被忽视。我后来建立了一套轻量评估机制不需要复杂的框架就是一份 golden tasks 清单和一份评分表。我把前面说的黄金样例集扩展成每个 skill 至少 10 条历史任务记录其中包括真实数据和带噪声的数据。每隔一段时间或者每次改动 skill 后跑一遍清单看输出是否符合预期。评估分的口径可以自定义我用的三个维度是结构正确率输出 schema 是否符合约定关键字段准确率比如金额、日期这类业务关键字段是否完全正确决策正确率agent 是否在正确的场景主动加载了该 skill而不是误用这个做法最大的价值不是找 bug而是让你在改动之前敢改。没有回归测试的 skill你会越来越不敢动它最后它变成一个谁也不敢碰的黑盒。5.3 超出代码的扩展思路最后说点超出目前实现的小想法。一个是 skill 的市场化。现在 skill 分散在各个项目的目录里如果有一套统一的元信息规范理论上可以做一个共享库让团队直接订阅别人的 skill。就像 npm 之于 JavaScriptskill 的注册、安装、版本锁定思路完全可以复制过来。另一个是 skill 对模型透明性带来的影响。当 agent 的能力被拆成一个个小模块每一步它在用哪个 skill、用了什么数据、产出了什么都变得可审计。这其实对调试和安全都有利你会比在一大坨提示词里更清楚地知道 agent 到底做了什么。还有一点是我在实操中的体会skill 的设计要有被删除的勇气。如果一个 skill 加上去之后三个月内没有被第二个任务复用说明它的抽象是失败的要么太窄要么切错了边界。删掉它不是损失是防止复杂度的种子在项目里生根。我现在的习惯是定期清点 skills 目录凡是回答不出这个 skill 解决过谁的什么问题的一律冷藏处理。保持一套小但锋利的 skill 集合远比堆一堆漂亮的模板目录更有价值。
返回列表