ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Agent开发核心:用Skills技能包定义任务边界,告别低效提示词

Agent开发核心:用Skills技能包定义任务边界,告别低效提示词 做Agent开发这一年多我最大的感受是模型能力再强架不住任务边界不清。你以为自己在调教AI实际上大部分时间都在跟“它不知道什么时候该做什么”较劲。直到我把工作重心从“写更好的提示词”转到“设计更好的Skills”整个Agent的行为质量才真正开始稳定下来。这篇文章就想聊聊围绕skills这个词这几年我摸索出来的设计思路、目录结构、落地步骤和踩坑经验。如果你也在做Agent类应用或者正被“提示词越写越长但效果越来越差”折磨这篇应该能帮上忙。我先把话放前面Skills不是提示词的换皮它是一个带触发条件、执行流程、输入输出协议、验证机制和回退策略的完整行为单元。把它当成“给Agent写岗位说明书”来理解很多设计决策都会变得清晰。1. 为什么通用Agent搞不定专业任务1.1 症状对话能力强执行能力弱先看一个典型场景。你让Agent“帮我检查一下这个项目的代码质量”它大概率会回你一段泛泛的代码风格建议。你让它“把这份周报转成管理层摘要”它给你的可能是格式漂亮的模板但核心数据全没对齐。这不是模型笨是任务目标本身模糊——通用对话模型擅长的是“生成合理文本”而不是“按照某个专业流程完成一个确定的结果”。我做个类比你请一个刚入职的实习生干活只告诉他“认真一点”他什么也干不好。你得告诉他岗位叫什么、服务对象是谁、遇到什么情况该启动、第一步做什么、第二步做什么、做到什么程度算合格、做不了怎么办。Skills干的就是这件事——把Agent从一个“什么都能聊的实习生”变成一个“知道自己在什么岗位上、按什么规程办事”的专职员工。1.2 真正意义上的Skills到底是什么我说的Skills是指以技能包Skill Pack形式存在的一整套定义。它通常包含几个关键部分技能元数据名称、版本、描述、触发场景让调度层知道“什么时候该用这个技能”。执行指令分步骤的操作说明告诉Agent每一步具体做什么。输入协议接受哪些参数、字段格式是什么避免模型自由发挥。输出协议要求返回什么结构可以是JSON、标记语言或固定模板。辅助资源必要的参考文档、脚本、数据字典供技能执行时调用。验证与回退规则怎样算成功、失败后怎么办、能不能降级处理。你可能注意到了这很像一个微服务的接口定义加上一份SOP标准作业程序。Skills的本质就是把“模型的能力”和“业务的要求”之间那层模糊地带抹平。它不试图改变模型的智商而是改变模型的工作方式。1.3 从“提示词”到“技能包”的转变很多人一上来就问Skill和写一个很长的system prompt有什么区别区别大了。提示词是一次性的、静态的、面向“对话”的。你写了“请按照以下步骤检查代码”模型会照做——但前提是它每一步都记得住、不跑偏。而技能包是面向“任务执行”的。它把检查代码这个任务拆解成先读哪些文件、按什么优先级检查、发现问题时怎么归类、最后输出什么格式。我自己的经验是提示词适合解决“模型不知道怎么答才好的问题”比如风格、语气、知识范围技能包适合解决“模型不知道怎么拆步骤才好的问题”比如多环节操作、有标准交付物的任务。这两者不能互相替代但一旦任务涉及三步以上操作技能包的优势就会非常明显。一个400字的技能包往往比2000字的提示词更有效因为它把话都说在了节点上。2. 把Skills放对位置体系设计比写内容更重要2.1 三种能力形态的分工在Agent体系里至少要区分三种能力形态形态适合解决什么问题典型例子生命周期系统提示词设定总体人设与回答基调“你是资深技术顾问回答要简洁”全局常驻工具调用执行确定性操作获取结构化结果查数据库、调API、发请求按需触发技能包编排多步骤、有稳定交付物的专业任务代码审查、周报提炼、发布检查按场景加载你会发现前两种大家都很熟了但真正让Agent具备“岗位能力”的恰恰是第三种。技能包是连接“模型通用能力”和“业务确定流程”的胶水层。2.2 技能包与插件、工作流的边界还有一个容易混淆的概念。插件Plugin往往强调“接入外部能力”比如读文件、访问网络、调用某个云服务。工作流Workflow强调的是“多个节点按预定逻辑串联”。而技能包介于两者之间它可能要调用外部工具也可能只是内部推理但它的核心特征是“为一个完整任务定义行为边界”。举个例子我做过一个“发布前检查”技能包。它不直接操作发布系统也不需要用户输入什么复杂参数它的任务就是读取一段发布描述然后按十项检查规则逐条核验最后输出一个检查报告。这中间没调用任何外部API纯粹靠指令编排。但它依然是个标准的Skill——因为它有触发条件用户要给出版本描述、执行流程十条规则、产出格式检查报告。技能包的调度可以靠模型自主判断也可以靠上层路由规则显式触发。我的经验是宁可上层多写几条显式路由规则也不要完全依赖模型自由判断。否则模型会时而想起用Skill时而直接凭感觉回答。2.3 标准目录结构我在项目里常用的结构是这样的skills/ ├── review-pr/ │ ├── SKILL.md │ ├── scripts/ │ │ └── collect_diff.py │ ├── references/ │ │ └── review_rules.md │ └── examples/ │ └── output_sample.md ├── weekly-report/ │ ├── SKILL.md │ └── templates/ │ └── exec_summary.md └── ...每个技能包独立目录互不干扰。SKILL.md是主入口脚本文件夹放可执行工具references放参考规则examples放输出样例。这个结构的好处是迁移方便整个目录拷走就能用、版本清晰每个技能包自己管理版本、还可以单独评测。2.4 技能包与外部工具如何配合技能包里的工具尽量做成“单一职责”的小脚本。比如上面collect_diff.py只做一件事把当前分支相对主干的所有变更文件列表和关键diff提取成结构化JSON。Skill指令里不要求模型自己去理解Git底层逻辑只要求它调用这个脚本获取结果然后基于结果做判断。这样做有三个好处减少模型误操作的概率、提高执行速度、结果可复现。凡是涉及确定性计算的东西取时间、算差异、查状态全部交给脚本凡是涉及判断、权衡、归纳的东西交给模型。这个分工一旦确立技能包的稳定性会大幅提升。提示如果有人让你写一个技能包先问一句这个任务里哪些步骤是“计算”哪些步骤是“判断”把“计算”尽量脚本化把“判断”留给模型。这是整个设计里性价比最高的一步。3. 实战手写一个能落地的Review Skill3.1 需求定义先写清楚这个技能解决什么问题我一直强调动手写SKILL.md之前先把需求压缩成两句话。拿我常用的“PR代码审查技能”来说需求是这样定义的服务对象需要快速得到结构化代码审查意见的开发者。核心任务输入一个PR描述和变更文件列表输出按严重级别分级的审查意见包括问题定位、原因分析、修改建议。不要小看这两句话后面所有的元数据和指令设计都从这两句话推导出来。你要是连服务对象和核心任务都说不清技能包必然会写成一本毫无重点的百科全书。3.2 搭建目录与初始化文件先建目录然后把核心脚本准备好。我这里的collect_diff.py大概长这样#!/usr/bin/env python3 import json import subprocess import sys def main(basemain): diff_result subprocess.run( [git, diff, --name-only, base], capture_outputTrue, textTrue ) changed_files diff_result.stdout.strip().splitlines() payload {changed_files: changed_files, count: len(changed_files)} print(json.dumps(payload, ensure_asciiFalse, indent2)) if __name__ __main__: main(sys.argv[1] if len(sys.argv) 1 else main)再说一次这个脚本解决的是“确定性计算”有哪些文件变了、变化数量是多少。至于“这些变更是否合理”脚本不负责。3.3 SKILL.md的核心字段怎么设计SKILL.md是整个技能包的大脑。我建议用YAML前置元数据加正文指令的结构。看一个实际例子--- name: review-pr version: 1.2.0 description: 对给定的PR信息执行结构化代码审查输出分级意见。 triggers: - 用户要求审查PR - 用户提供diff或变更文件列表并要求检查 - 代码合并前的质量把关 inputs: pr_description: 用户提供的PR描述文本 changed_files: 变更文件列表可通过脚本获取 outputs: type: structured_report format: markdown_table required_fields: [severity, file, issue, suggestion] validation: - 必须包含至少一条高优先级问题如存在 - 每条建议必须可执行不得模棱两可 fallback: - 如果无法获取变更列表询问用户是否提供 - 如果审查目标不明确先向用户确认范围 --- # Review PR 执行说明 你是一个代码审查专家请严格按以下步骤执行 1. 通过脚本获取变更文件列表若用户已提供则可跳过。 2. 按 references/review_rules.md 中的规则逐项检查。 3. 每个问题必须给出文件路径、严重级别、问题描述、具体修改建议。 4. 输出时按以下格式组织 | 严重级别 | 文件 | 问题描述 | 修改建议 | | --- | --- | --- | --- | 严重级别只允许三个值高、中、低。 高可能引起功能异常或安全风险。 中影响代码可维护性或潜在边界情况。 低风格、命名、注释等非功能性建议。 如果发现的问题少于三条请如实输出不要为了凑数而编造。我特别看重validation和fallback两个字段。validation约束了输出的底线——比如“必须包含问题描述和修改建议”这能防止模型输出一些正确的废话。fallback则定义了异常兜底路径——比如“拿不到变更列表怎么处理”它让技能包在真实环境里不至于愣住。这两个字段是你和模型的“契约”务必写清楚。3.4 设计可验证的输出格式输出协议往往被忽略但它是评测技能包效果的关键。我的规则是让输出尽量结构化结构化到可以被脚本直接判分。举个例子上面表格里的严重级别字段我可以用一段小脚本验证import re VALID_LEVELS {高, 中, 低} def check_report(text): rows re.findall(r^\| (高|中|低) \|, text, re.MULTILINE) bad [r for r in rows if r not in VALID_LEVELS] return len(bad) 0有了这种验证脚本你就能对技能包做回归测试了。改一版Skill后跑一遍测试集是变好还是变差一目了然。这就是“可验证产出”的价值。可验证才有资格谈迭代否则全凭感觉。3.5 跑通第一个测试用例技能包写完后先别急着接入正式流程。用三个测试用例过一遍理想输入完整的PR描述加清晰的变更列表看输出是否符合格式。模糊输入用户只说“帮我检查一下代码”看触发和追问是否合理。异常输入变更文件特别多、超出模型上下文看是否有截断或降级方案。我第一次调这个Review Skill时“模糊输入”测试立刻暴露了问题模型在没有变更列表的情况下自行脑补了一堆“可能存在的问题”输出了一版看似专业、实则全错的审查报告。后来我在fallback里加了“如果无法获取变更列表先明确告知用户需要哪些信息不要凭空猜测”这个问题才被按住。你写技能包时一定要把这个“宁缺毋滥”原则写进去否则模型会为了完成任务而编造内容。4. 调试Skills时踩过的坑与完整排查链路4.1 现象模型每次跑到第三步就停下来有个版本我的Review Skill在测试时总出现一个诡异问题前两步执行正常到第三步“按规则逐项检查”时模型开始输出“无法完成所有检查项”然后停下来。一开始我以为是模型能力问题换了更强的模型也没用。4.2 排查记录从现象到根因我把完整排查过程列出来你可以照着这个思路走第一步锁定范围。单独测试Skill去掉外部干扰发现依旧中断。确认不是环境问题。第二步隔离变量。临时把references/review_rules.md里的检查规则从15条缩减到5条结果能跑完了。怀疑是规则数量超出模型有效注意力范围。第三步核查上下文。我打印了Skill加载后的完整指令体量发现SKILL.md正文加辅助文档加起来超过三千字。模型长上下文确实能装下这些文字但“装得下”不代表“每一步都记得住”。第四步读取失败输出。发现模型的最后输出是“已完成检查未发现高优先级问题”——它跳过了中间推理直接给了结论。这说明模型为了赶进度选择了偷懒路径规则根本没被逐条执行。第五步重构指令表达。我把15条规则从“长段落描述”改成“分组的检查清单”每组明确写清检查标准和产出要求并加入“必须逐条输出每条检查的结论不得跳过”。问题立刻消失。根因总结不是指令不够多而是指令的组织方式不符合模型的处理模式。长段落容易被“压缩”而结构化清单更容易被“逐项执行”。这是一个容易被忽视的认知差异。4.3 常见问题对照表我整理了技能包开发中最常踩的几类坑供你对照症状可能原因处理手段完全不触发Skill触发条件描述太窄模型没识别出场景扩充triggers加入同义说法触发了但不按指令走SKILL.md正文过长、重点分散精简指令步骤化、清单化输出格式不稳定输出协议不够硬模型自由发挥了给出模板并用脚本校验必填字段总是编造事实缺少“不确定时如实说明”的兜底规则加入fallback和“宁缺毋滥”原则效果时好时坏技能包内依赖了模型自由推理的关键步骤把确定性步骤脚本化、自动化4.4 一个高效技巧给Skill设计“干跑模式”所谓干跑模式就是让模型在不做真实修改、不调外部API的前提下走一遍完整流程只输出中间结果和最终报告。我几乎每个技能包都会在SKILL.md里加一个开关字段比如dry_run: true时脚本只打印将要执行的动作不实际执行。这个模式的价值在于它把“技能包的逻辑正确性”和“外部系统的真实影响”解耦了。你可以在几分钟内测完一个技能包的所有分支而不用准备真实数据、不用处理副作用。尤其是那些涉及写文件、发消息、改配置的Skill干跑模式能救你无数次。5. 评测与迭代让一个Skill从“能用”到“好用”5.1 建立技能包的回归测试集技能包写出来不难难的是持续改进。我的做法是给每个技能包建一个test_cases目录里面放十到二十个历史真实案例。每个案例包含输入样例尽量贴近真实用户表达期望输出特征比如必须包含三级优先级、必须引用具体文件禁止事项比如不得编造文件路径每次修改技能包后跑一遍测试集记录通过率。通过率不降说明改动至少没变差通过率提高说明优化有效。这个机制听起来简单但大多数团队根本没给技能包建过测试集导致技能包永远停留在“好像还行”的状态。5.2 三档评测维度我给技能包的每次输出打三个维度的分评测维度核心问题评分方式格式合规是否按输出协议返回脚本自动判分逻辑完整步骤是否执行完有没有跳过、编造人工抽查业务有效输出能否直接投入使用业务方验收第一个维度必须机器判分第二个维度可以做抽检第三个维度就要靠真实用户反馈了。这三档全部合格我才认为一个技能包是“可用”的而不是“能跑通”的。5.3 失败样本驱动的迭代循环迭代技能包不需要拍脑袋想新功能直接从失败样本开始。每次测试集里有不合格的输出问三个问题是模型没理解指令还是指令本身有歧义是任务超出边界还是技能包漏了场景是输出格式问题还是业务逻辑缺失针对问题定向修改SKILL.md或辅助脚本然后重新跑测试集。我的经验是迭代十轮之后技能包的稳定性会有质变。最初的几次修改往往是补规则、加边界后面几次开始变成精简表达、合并重复项。你会发现技能包从“每轮加内容”变成“每轮删内容”这个时候它才真正成熟。5.4 十二条实操经验清单最后分享十二个我写技能包几年攒下来的经验不按优先级排都是平级有用的技能包的第一版不用追求大而全能把一条主线走通就算成功。给技能包起名时动词开头会比名词开头更容易被调度层识别。SKILL.md正文超过1500字就必须考虑拆分子文档。输出协议里固定枚举值远比自由文本稳定尽量让模型从几个候选项里选。永远给技能包一个“无法完成时该说什么”的默认回复模板。脚本出错时不要让模型“猜”报错信息里就要给出可执行的下一步。不要把外部系统的账号密码放进技能包脚本里用环境变量注入。版本号要跟着技能包的规则变化走而不是跟脚本代码走。定期让业务方直接试用技能包你会听到很多开发者视角想不到的问题。每个技能包都要有一个负责人否则它会在无人注意时悄悄腐烂。技能包的日志要记录输入和输出摘要出问题时才能回溯是哪个环节坏了。如果同一个任务要写两个技能包才能覆盖优先考虑是不是任务拆分的粒度错了。我在实际项目里体会最深的是第一条和第十一条。很多人在第一个技能包上就想覆盖所有边界情况结果写了几天还没跑通也有人做完了技能包就扔在那里出了问题也不知道从何查起。把目录搭好、日志留好、测试集建好这个技能包才有持续进化的底座。最后再分享一个小技巧每次调试技能包时保留一份“失败输出快照”文件夹。把输入、当时技能包版本、错误输出都存下来。等过段时间回头看你会清晰地看到这个技能包是怎么一步步从不稳定走向稳定的这些快照比任何文档都更能帮你理解“Skills为什么有效”。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进