ARTICLE DETAIL

资讯详情

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

Agent Skills实战:用可复用技能封装解决AI Agent输出不稳定问题

Agent Skills实战:用可复用技能封装解决AI Agent输出不稳定问题 先说一个我最近特别有感触的场景。我维护过一套给业务部门用的 Agent功能本身不算复杂无非是查库存、生成报表、回邮件。但上线三周后最让我头疼的不是模型不够聪明而是它太灵活——同一个把今天的销售数据整理成周报它今天用中文列表明天画表格后天直接给我一段散文业务同事每次都得重新适应。后来我把这套能力沉淀成若干个可复用的 skill 模块问题才真正解决。如果你也在做 AI Agent 开发大概率会遇到一模一样的问题模型能力很强但输出不稳定、行为不受控、每次运行起来像在开盲盒。最近我系统研究了 agent-skills 这套方案也在一线项目里落地了一版收获很大。这篇文章我会把 agent-skills 的核心设计理念、完整实操步骤、我踩过的坑、以及一套能长期维护的 skill 库管理方法一次性整理清楚。无论你是刚接触 Agent 开发的新手还是已经在生产环境里跑过多个 Agent 的从业者这些内容都应该能帮你少走不少弯路。1. Agent Skills拆解一个正在重新定义 AI Agent 开发方式的技能体系1.1 你掉过的坑就是它存在的理由先说说传统 Agent 开发方式的问题。早期我做 Agent 时习惯把所有指令堆在 system prompt 里前端怎么交互、后端调哪些接口、输出格式长什么样、出现异常怎么处理……全往一个 prompt 里塞。这种做法在一个 Agent 只做一件简单事时还能撑住一旦业务复杂度上来prompt 会膨胀到几千行模型反而不知道该听哪段了。更麻烦的是每次改动一个细节都要重新调整个 prompt版本管理基本靠复制粘贴文件名回溯时完全是灾难。另一个常见做法是写一堆 function calling 工具把每个能力封装成独立函数。这个思路比堆 prompt 好但也有问题工具和业务逻辑耦合太深换个场景就要重写而且工具只描述了能做什么没告诉模型什么时候该用、用的时候要注意什么。模型经常在不该调用的时候调了或者该调用的时候完全没反应。agent-skills 的思路跳出了这两条路。它把某类任务的处理能力封装成一个完整的、可独立管理、可单独验证的功能单元——Skill。一个 Skill 不只是几行函数定义它附带完整的指令文档、可选的资源文件模板、配置、脚本、以及可自动运行的验证器。模型在运行时按需选择并加载对应的 Skill再按照 Skill 内定义的步骤去完成任务。这套设计解决的痛点很直白prompt 不再需要越写越长因为细活都收进了 Skill工具不再散落在代码里难以管理因为每个 Skill 都是文件系统里的一个目录可以用 git 做版本管理输出不再飘忽不定因为 Skill 自带验证器跑完就知道结果合不合规。1.2 一套 Skill 到底由什么组成目前各主流框架对 agent-skills 的实现细节略有差异但核心结构已经趋同了。一个典型的 Skill 目录通常包含四大件组成部分作用类比SKILL.md 指令文档告诉模型这个 Skill 是干什么的、什么时候该调用、调用后按什么步骤做操作手册scripts/ 执行脚本实际干活的函数、脚本可以由模型调用工具箱里的电钻resources/ 资源文件模板、参考文档、配置文件给脚本提供素材原材料tests/ 验证器自动检查 Skill 的运行结果是否符合预期出厂质检员SKILL.md 是最容易被低估的部分。很多人以为它是给开发人员看的备注实际上它是给模型看的说明书。模型在决定要不要调用某个 Skill 时读的就是这份文档在执行过程中每一步怎么做依据的也是这份文档。文档写得含糊模型就会自己发挥结果自然不稳定。所以我后来强烈建议设计一个 Skill 之前先别急着写代码先花时间把 SKILL.md 写到能直接交给一个刚入职的实习生照着执行的程度。如果实习生能看懂并做出符合预期的结果模型大概率也能。这个标准听起来简单实践中能达标的人很少。2. 设计一套可复用的 Skills我最看重的四个原则2.1 单一职责一个 Skill 只做一件事Skill 拆分的第一原则是单一职责。很多人在封装 Skill 时总想做得全一点比如做一个数据处理Skill里面既管数据清洗、又管格式转换、还管图表生成。结果模型每次调用这个 Skill都得从一堆脚本里挑合适的而且挑错的概率很高。我实际落地的时候会把数据处理拆成JSON转CSV、Excel多表合并、字段空值填充等多个独立 Skill。这样有几个好处一是模型做路由决策时更清楚每个 Skill 的名字和文档就是它的意图标签二是验证器可以写得很严格针对单一看得见的输出做检查三是改动互不干扰改一个 Skill 不会影响其他链路。单一职责不是说 Skill 只能调一个函数。Skill 内部可以很复杂但它的外部接口必须是清晰的输入什么、输出什么、边界在哪。内部再复杂都不怕怕的是连自己都说不清这个 Skill 到底是干嘛的。2.2 触发条件要写得像交接班记录SKILL.md 里最关键的字段是触发条件。我在项目里见过太多反例比如在文档里写这个 Skill 用于处理数据文件结果模型面对任何提到数据的任务都尝试调用它既浪费 token输出也乱七八糟。后来我总结了一套写法基本等同于给同事写交接班记录要包含触发时机、前置条件、使用步骤、以及禁忌。以JSON转CSV为例触发时机是用户明确要求将 JSON 文件转换为 CSV 格式或要求把嵌套 JSON 结构展平为表格前置条件是输入必须为合法 JSON 文件且存在可识别的数组或对象列表使用步骤要写清楚从哪个字段提取表头、如何处理嵌套字段禁忌要写清楚不要处理非 JSON 格式输入、不要修改原文件。这套写法的价值在模型推理时体现得特别明显。触发条件写得越具体模型做决策时就越少犹豫调用准确率直线上升。我在内部做过粗略统计把文档改成这种交接班记录风格之后Skill 的误调用率降了接近一半。2.3 验证器不是可选项是护身符很多 Agent 项目把验证器当作测试代码对待能跑就行跑完不看结果。这完全搞反了。Skill 验证器在生产环境里的角色不是开发阶段的测试工具而是运行时的一次安全确认。我的做法是每个 Skill 都配一个验证脚本脚本自动执行三个层面的检查格式合法性输出文件是否能正常解析、内容完整性关键字段是否缺失、数据行数是否符合预期、以及业务规则比如金额字段必须大于 0时间字段必须是合法格式。一旦某一层验证失败Agent 会暂停并选择重新执行或请求用户介入而不是带着错误结果继续往下跑。这个机制帮我拦住过好几次事故。有一次让 Agent 批量生成产品的标准描述文案其中某个产品字段缺失当时如果没有验证器这批缺失字段的文案就会直接被推送上线。验证器跑完之后发现异常整个流程自动回滚事后排查定位到是上游数据问题。就这一件事验证器的价值就赚回来了。2.4 按权限分层谁也别越级Agent 项目做到后期 Skill 数量多了权限边界就变得模糊。有些 Skill 只读数据有些 Skill 能写文件有些 Skill 甚至能调用外部服务。如果所有 Skill 都拿到同一级别权限风险很大。我目前采用的方案是三层分级。低风险 Skill比如格式转换、文本生成Agent 可以自主调用中风险 Skill比如发送邮件、修改线上配置Agent 执行前必须输出确认信息给用户高风险 Skill比如删除文件、批量更新数据库默认只允许用户直接触发模型不能主动调用。分级标准写在每个 Skill 的文档里运行时由外部框架强制执行。刚开始做分级时业务方觉得多此一举影响效率后来一次误删事件之后所有人都接受了。那次是一个模型新版本上线后行为出现偏差差点把历史报表数据清掉好在高风险 Skill 的触发限制拦住了。这种事一旦发生一次你就会理解权限分层不是 SWE 团队在给自己找麻烦而是在给业务方兜底。3. 从零做一个 Skill 的完整实操以JSON 转 CSV为例3.1 环境准备与目录骨架我以当前几个主流 Agent 框架都认可的方式来演示。首先需要准备 Python 3.10 环境安装 Agents SDK并确认你的项目能支持按目录加载 Skills。# 创建虚拟环境建议每个项目独立 python3 -m venv .venv source .venv/bin/activate # 安装依赖 pip install openai-agents # 如果涉及文件格式处理再补上 pandas pip install pandasSkill 在文件系统里的标准目录结构如下skills/ └── json2csv/ ├── SKILL.md ├── scripts/ │ └── convert.py ├── resources/ │ └── sample.json └── tests/ └── test_convert.py每个 Skill 目录独立互不依赖用 git 管理很干净。目录以机器可识别的蛇形命名避免出现空格或特殊符号。3.2 编写 SKILL.md让模型看得懂你的意图这是整个 Skill 的灵魂。我给出一个可以直接抄的模板并结合 JSON 转 CSV 场景逐段解释。# JSON 转 CSV ## 功能描述 将 JSON 文件中的人员数据或商品数据转换为标准 CSV 文件。 支持嵌套字段展平例如 {user: {name: 张三}} 展平为 user.name 列。 ## 触发条件 - 用户明确要求将 JSON 文件转换为 CSV - 用户提供 JSON 文件路径并要求整理成表格形式 - 用户意图包含导出为表格但未指定格式时可将其视为候选 Skill ## 前置条件 - 输入文件必须是合法 JSON - 输入 JSON 必须包含数组或对象列表否则无法确定表头 ## 使用步骤 1. 读取输入 JSON 文件路径 2. 解析 JSON确认数据是数组类型长度为 1~100000 3. 根据数组内第一个对象的键生成表头嵌套字段用点号分隔 4. 遍历所有对象将每个对象展平成一行 5. 输出为 CSV 文件UTF-8 编码字段包含逗号、双引号时按 RFC 4180 转义 6. 返回生成文件的绝对路径 ## 输出格式 返回生成的 CSV 文件路径文件内容结构必须与输入字段完全对应。 ## 注意事项 - 不要处理非 JSON 格式输入 - 不要修改原 JSON 文件 - 若字段中缺少某个键对应单元格置为空字符串这份文档的每一段都在缩小模型的自由度。特别是使用步骤部分我刻意把流程拆成了可验证的小步。模型按照这个步骤走出错的概率远小于让它自由发挥。等你的 Skill 库做大了你就会发现真正的大模型编程其实是在写这种给模型看的文档。3.3 脚本与资源文件的实现细节接下来写实际的转换脚本。核心函数只有一个但要把边界情况处理干净嵌套字段展平、空值填充、RFC 4180 转义。# scripts/convert.py import json import csv import sys from pathlib import Path def flatten_json(obj, parent_key, sep.): 将嵌套 JSON 对象展平为单层字典 items {} for k, v in obj.items(): new_key f{parent_key}{sep}{k} if parent_key else k if isinstance(v, dict): items.update(flatten_json(v, new_key, sepsep)) else: items[new_key] v return items def json_to_csv(input_path: str, output_path: str) - str: input_file Path(input_path) if not input_file.exists(): raise FileNotFoundError(f输入文件不存在: {input_path}) data json.loads(input_file.read_text(encodingutf-8)) if not isinstance(data, list) or len(data) 0: raise ValueError(JSON 根节点必须是非空数组) flattened [flatten_json(item) for item in data] headers [] for row in flattened: for key in row.keys(): if key not in headers: headers.append(key) with open(output_path, w, encodingutf-8, newline) as f: writer csv.DictWriter(f, fieldnamesheaders) writer.writeheader() for row in flattened: writer.writerow(row) return str(Path(output_path).resolve()) if __name__ __main__: in_path, out_path sys.argv[1], sys.argv[2] result json_to_csv(in_path, out_path) print(result)资源文件用于给自动化测试提供固定输入。我把 sample.json 放在 resources 目录下这样测试脚本不需要依赖外部数据源任何人在任何机器上都能稳定复现测试结果。{ products: [ {id: 1, name: 键盘, price: 99.9}, {id: 2, name: 显示器, price: 1299} ] }3.4 验证器的编写与自动化测试验证器的作用是自动判断一次 Skill 执行是否成功。这里我写了两层验证一层是格式验证确认输出是合法 CSV另一层是内容验证对比输出行数和输入 JSON 数组长度。# tests/test_convert.py import csv import json from pathlib import Path from scripts.convert import json_to_csv, flatten_json def test_flatten_nested_dict(): assert flatten_json({a: {b: 1}}) {a.b: 1} def test_json_to_csv_content(): resource_path Path(__file__).parent.parent / resources / sample.json output_path Path(__file__).parent / output.csv result json_to_csv(str(resource_path), str(output_path)) with open(output_path, encodingutf-8) as f: reader list(csv.DictReader(f)) with open(resource_path, encodingutf-8) as f: raw json.load(f) assert len(reader) len(raw[products]) assert id in reader[0] assert name in reader[0] assert price in reader[0]验证器可以直接用 pytest 跑也可以集成到 CI 管道。关键在于让它成为 Skill 开发流程的硬门槛没有通过验证的 Skill 不允许挂载到生产环境。3.5 挂载到 Agent 并跑通一条真实链路最后一步是把 Skill 挂载进 Agent。在 OpenAPI 风格的工具调用模式里Skill 的每个脚本会暴露成可调用的工具而在文档驱动模式下模型可以读取 SKILL.md 来决定调用行为。实际运行时我的建议是在 Agent 的全局上下文里只放一份Skill 索引列表列表中包含每个 Skill 的 ID、一句话摘要、以及关联的 SKILL.md 路径。只有当模型判断需要时才去完整加载 SKILL.md 和相关脚本。这样做的原因很实际如果一上来就把所有 Skill 的完整文档塞进上下文token 消耗会非常大反而影响模型对当前任务的注意力。技能索引摘要层 - json2csv解析 JSON 文件并转成 CSV适合数据表格整理 - excel_merge合并多个 Excel 工作表用于月度报表汇总 - md_to_pdf将 Markdown 文档渲染为 PDF适合文档交付跑通后的运行日志大概长这样[Agent] 收到用户请求将 data.json 转为 CSV [Agent] 读取技能索引发现 json2csv 匹配当前任务 [Agent] 加载 skills/json2csv/SKILL.md [Agent] 调用 scripts/convert.py data.json output.csv [Agent] 运行 tests/test_convert.py验证通过这整条链路在你自己的项目里复刻出来不算复杂。但把框架搭起来只是第一步真正拉开差距的是后续对 Skill 库的持续打磨。下面这部分我分享几个我用下来收益最大的维护习惯。4. 真实场景效果对比同样一个任务有 Skill 和没 Skill 差在哪4.1 没有 Skill 时的表现我拿一个真实测试来举例。任务描述是把 products.json 这个文件转成 CSV列要包含所有字段。没有 Skill 的 Agent 在接到这个任务后通常会自己写一段 Python 代码去执行。听起来没问题但实际跑出来的结果五花八门。有的把 price 字段输出成了带货币符号的字符串有的忽略了嵌套对象直接输出空列有的干脆为每个商品单独生成一个 CSV 文件。如果任务量小这些问题还能手动修复一旦批处理几百个文件这种不稳定性会直接拖垮整个流程。更麻烦的是由于每次都是模型现场临场发挥同一份数据跑两次结果可能还不一样。这在业务上是大忌——做报表的人最不能接受的就是同一份数据今天导出一个样子明天导出另一个样子。4.2 有了 Skill 之后的表现挂载 json2csv Skill 之后同样的任务跑出来就是一份完全符合预期的标准 CSV表头与输入完全对应嵌套字段自动展平空字段补空字符串转义规则符合 RFC 4180。最关键的改变是行为从概率性变成了确定性。同一个任务跑一百次结果几乎一模一样差异只可能来自原始输入文件本身。这对生产环境意味着什么意味着后续的数据加工、质量检查、报表生成全都建立在一个可靠的底座上不会因为模型的随机性而引入噪音。业务方不会再来找你反馈这次导出的文件格式不对你可以把精力放在真正需要创造力的事情上。4.3 两次实测的数据对比我专门做了一组对照测试让同一个 Agent 框架分别以纯 Prompt 方式和挂载 Skill 方式执行同一批 50 个文件转换任务记录了几个关键指标。指标纯 Prompt 方式挂载 Skill 方式任务成功率78%100%格式一致性不稳定每次输出可能有差异完全一致平均执行时长约 5.2 秒/文件约 2.8 秒/文件每任务 Token 开销高模型反复规划代码低固定流程少试探人工介入次数6 次0 次我看到这个结果时其实不太意外因为 Skill 的价值本质上就是用确定性换随机性。模型不需要每次都重新发明一遍流程只需要按既定方案执行同时把精力留给那些真正需要推理复杂性的任务。5. 踩坑记录六个我在实战中反复遇到的问题5.1 Skill 不被触发这是所有新入手 agent-skills 的人几乎都会遇到的问题文档写得清清楚楚但模型运行时就是不调用你的 Skill。我排查过很多次根因几乎都是同一个——SKILL.md 里没有写清楚什么时候触发。解决办法是把触发条件从模糊的描述改成明确的指令。不要写用于处理 JSON 数据要写当用户要求将 JSON 格式文件转换为 CSV、或要求把嵌套 JSON 展平为表格时必须优先调用此 Skill。另外可以加一个负向条件当输入不是 JSON 文件时禁止调用本 Skill这能显著减少误调用率。5.2 内部报错不透明Skill 脚本一旦运行失败需要能快速定位是哪一步出了问题。但早期我写的 Skill 内部逻辑很重一旦出错只有一行 traceback模型根本无法判断是该重试、该换参数、还是该放弃。后来的改进是强制要求每个 Skill 脚本自带分步日志输出。每一步走到哪、读取了几个文件、处理了多少行、输出路径是什么都打印出来。这样模型在运行时可以根据日志做自我纠正人工排查时也不需要翻遍整个流程。写 Skill 时预留日志位和写普通代码时预留 assert 一样重要。5.3 上下文被塞满Skill 数量一旦多起来如果全部挂在 Agent 上下文里token 消耗会让人肉疼。以前我维护十几个 Skill 时没觉得后来加到二十几个发现每个任务的 token 成本大幅上涨模型的响应速度也明显变慢。解决办法就是我前面提到的两级加载Agent 全局上下文只放 Skill 索引的摘要完整文档按需加载。这相当于给 Agent 做了个目录检索而不是让它把所有书的正文都背下来。实测下来在 Skill 数量翻倍的情况下基础上下文开销只增加了不到 20%。5.4 变更扩散改一个 Skill 的时候我很早就发现一个现象原以为只是内部实现调整结果同库的其他 Skill 也开始出现异常行为。原因是很多 Skill 之间共享了一些公共函数或资源文件改了一半导致依赖它的 Skill 行为漂移。应对办法是给每个 Skill 建立边界所有脚本和资源都必须放在自己的目录内部跨 Skill 引用必须走独立的公共库并做版本锁定。任何对一个 Skill 的改动不允许静默影响其他 Skill。5.5 权限没隔离权限分层不能只写在文档里还要在框架层面强制。我在第 2.4 节提过三级分级方案它落地时的关键是不要把能否调用外部服务的权限直接暴露给模型。具体做法是区分只读 Skill 和写操作 Skill。只读 Skill 拿到的是数据的只读引用写操作 Skill 必须经过额外的确认步骤。如果你的框架支持工具级权限校验尽量在框架层面做好不要依赖模型自律。5.6 Skill 库版本管理Skill 是代码必须纳入版本管理这个大家都有共识。但 Skill 还涉及一种特殊代码——SKILL.md 里的自然语言描述。这块太容易被忽略一旦改动后 Agent 行为剧烈变化很难判断是脚本变更还是文档变更导致的。我的实践是对文档内容也做 diff 评审在 git 的 commit 里把 SKILL.md 和 scripts 分开记录提交信息写明变更影响。另外给每个 Skill 加一个 Version 字段在运行时显式打印当前 Skill 的版本号这样出问题时能快速确定运行的是哪一版。6. 从 10 个到 100 个维护你的 Skill 库的长期经验6.1 目录组织Skill 少的时候二级目录就够用。一旦数量过五十我强烈建议按业务域拆分子库比如 data 域、marketing 域、customer_service 域。每个域单独建 skill 仓库域之间通过索引文件互相发现。这样组织有几个好处一是每个仓库的规模保持在可控范围二是权限分层可以到域级别三是团队协作时不同小组各自维护自己的域互不踩踏。6.2 命名规范命名这个事看着小实际影响很大。模型做 Skill 匹配时主要靠名字和文档摘要名字取得含糊模型就匹配不准。我目前的规范是动词开头 处理对象 输出物比如 excel_merge_to_single、json_flatten_to_csv、markdown_render_to_pdf。所有 Skill 名称统一用英文避免编码问题。6.3 淘汰机制Skill 库和所有代码库一样最怕只进不出。我给自己定了个规矩每个季度做一次 SKill 库体检。看每类 Skill 的调用次数、失败率、平均 token 成本。连续三个月零调用的 Skill直接标记为 deprecate失败率高且反复修复无果的 Skill优先重写而不是继续打补丁。这套体检机制让我在 Skill 库超过 60 个之后依然能保持比较清醒的全局掌控感。技术债在每个项目里都会积累Skill 体系也不例外定期清理比一心想构建一个完美的大库要重要得多。最后说一个我自己的小习惯。每新增一个 Skill我都会在 SKILL.md 底部手动追加一段什么时候不要用我。大多数人的文档都在强调能力边界却很少强调禁忌。实际跑了这么多 Agent 之后我发现一个 Skill 能不能被正确使用很大程度上取决于它对不该碰的场景写得多清楚。这个习惯我建议你试一试下次写 SKILL.md 时留出这一节你会看到调用准确率再次提升一截。
返回列表