Agent Skills实战:从Prompt封装到生产级工具的关键设计
2026/9/23 6:32:47 网站建设 项目流程

最近圈子里到处在聊 agent-skills,很多人以为它只是把 prompt 封装一下、变成可复用的函数这么简单。但实际上,一套设计良好的 skills 体系,能直接把一个“偶尔能用”的 demo 变成“稳定交付”的生产级工具。这篇文章我就从实战角度聊聊:skills 到底解决什么问题、底层机制长什么样、怎么从零手写一个属于你自己的 skill,以及我在实现和调优过程中踩过哪些坑。

1. 为什么说 Skills 是 Agent 从“玩具”走向“工具”的关键一步

先讲一个我反复遇到的场景。几个月前,我拿一个大模型 API 做了一个内部问答机器人,刚开始的效果很惊艳,能回答问题、能总结文档、能写周报。但用了一周之后,大家开始反馈“这东西时灵时不灵”——同一个问题换个说法,答案质量就断崖式下跌。问题就出在:我把所有能力都塞在一个巨大的 prompt 里,没有对模型的行为做任何结构性约束。

这时候我才意识到,单纯堆 prompt 是一件非常脆弱的事。你把“会用计算器”“会查数据库”“会做格式转换”这几种能力混在一个上下文里,模型的注意力会被分散,输出质量也会随之波动。因为你实际上是在要求一个概率模型同时记住并执行很多条隐式的规则,它不可能每次都精准命中所有要求。

Skills 的思路完全不同:它把每种能力拆成独立的模块,每个模块只负责一件事,并且有明确的输入、输出和执行逻辑。模型不再需要“猜”你要干什么,而是通过意图识别快速匹配到对应的 skill,然后按预设的路径去执行。这就像你不再让一个厨师同时管切菜、炒菜、摆盘,而是给后厨配了三个岗位,各管一段,出菜自然又快又稳。

是 prompt 不值钱了吗?当然不是。prompt 仍然重要,但它的角色变了——从“全能说明书”变成了“调度中枢”。它只需要负责理解用户的意图、决定该调用哪个 skill,并且把执行结果组装成用户能看懂的语言。真正的脏活累活,交给了每个 skill 内部去处理。

所以我给“是否应该把能力升级成 skill”定义了一个判断标准:当一个任务需要三步以上的固定操作,或者同一个逻辑需要被多个场景复用,或者这个逻辑涉及外部工具调用时,你就该考虑把它抽成 skill 了。别等 prompt 写到五千字、每次改动都像拆地雷的时候才动手。

2. 拆解 Skill 的底层骨架:意图识别、参数契约与执行策略

在我接触过的各种 agent 框架里,无论它叫 tool、skill、action 还是 capability,底层结构其实都有共通之处。搞清楚了这三个核心组件,你就不容易被任何框架绑架,甚至可以自己手写一套轻量的实现。

2.1 意图识别:让 Skill 知道什么时候该被触发

意图识别是 skill 体系里最容易忽视、也最影响体验的一环。你不是把所有 skill 的描述一股脑塞给模型就完事了,而是要提供一个清晰的“触发条件列表”。我在实际项目里,会为每个 skill 维护一份带权重的关键词和语义描述。比如一个“天气查询”的 skill,触发词可能包括“天气”“气温”“会不会下雨”“要不要带伞”,而语义描述则写明“用于查询某地某时的天气状况”。

更进阶一点的做法是加一层“排除逻辑”。我遇到过可怕的误触发:用户说“我想查一下明天的会议安排”,结果 agent 调用了天气查询。原因就是“明天”这个词在意图识别里的权重太高。后来我在 skill 定义里显式加了“不处理时间安排类请求”的负向描述,效果好非常多。在意图模块里,正例和反例同样重要。

2.2 参数契约:给大模型的“函数签名”

意图识别告诉你“该用哪个 skill”,参数契约则告诉你“这个 skill 需要哪些信息才能跑起来”。这一步非常像写函数的签名——定义参数名、类型、是否必填、以及每个参数的解释。

举个例子,一个发邮件的 skill,参数至少包括收件人、主题、正文,可能还有抄送和附件。如果收件人是必填的而模型没提取出来,你不能直接让它硬跑,而是应该返回一个“缺参提示”,引导用户补充信息。我在实现里常用 JSON Schema 来做这层约束,这样既能校验参数完整性,又能让大模型按照固定格式返回提取结果。

这里有一条关键经验:参数描述一定要写清楚“从用户原话里怎么找到这个值”。模型不是真正理解“日期”这个抽象概念,你说“会议时间”它可能提取成“明天上午”,但你的业务系统需要的是“2025-06-15 09:00”。所以你最好在参数描述里给出转换规则,比如“必须将相对时间转换为绝对时间,格式为 YYYY-MM-DD HH:mm”。很多参数提取失败,不是模型太笨,而是你没告诉它该怎么转换。

2.3 执行策略:LLM 调度、确定性代码与混合编排

拿到参数之后,skill 内部怎么执行?我在实践中总结出三种模式,分别适用不同场景。

第一种是纯代码执行,也叫确定性执行。比如写一个“计算两个日期之间相差多少天”的 skill,完全没有必要让模型来算日期,直接用 Python 的 datetime 库处理就行。这类 skill 输出的结果是 100% 可预期的,可以放心大胆地用。

第二种是纯模型执行,比如“总结一篇长文的核心观点”。这种任务没有标准答案,必须依靠模型的语义理解能力。这时候 skill 内部其实就是一段优化过的独立 prompt,只专注于做好总结这一件事,不受其他上下文干扰。

第三种是混合编排,也是实践中最常见、最能体现工程难度的模式。比如一个“舆情分析”的 skill,可能要先去搜索引擎抓数据、再调用模型做情感分类、最后用代码把结果聚合渲染成报告。这种链路里,我一般用代码控制主流程,在需要语义理解和判断的节点才调用 LLM,最大化确定性的比例。

这三种策略没有哪个更好,关键在于你对自己的任务的确定性要求有多高。我的习惯是:能确定的部分绝不让模型碰,需要发散理解的部分才交给模型。这会让你的 skill 整体非常可靠,而不是在某些 case 上惊艳、某些 case 上翻车。

3. 手把手搭建一个 Skill:从需求拆解到注册调用

理论说再多,不如动手做一个。接下来我以“生成规范化 Git 提交信息”为例,完整走一遍 skill 的设计与实现。选这个例子是因为它足够小、足够常见,但又完美涵盖了意图识别、参数提取、模型调度和确定性校验这几个核心环节。

3.1 需求拆解:明确输入、输出与边界

任务的输入是用户的一段话,比如“我改了登录页面的 bug,还优化了数据库查询性能,分别是两个 commit”。输出则是一段可执行的 git commit 命令,或直接生成符合 Conventional Commits 规范的提交信息。

在动手写代码之前,先划清边界。这个 skill 只负责“分析 diff 或描述并生成规范的提交信息”,它不负责替你执行 git 命令(避免权限风险),也不负责理解你把代码改成了什么样——如果你不给 diff 内容,它就只能基于你的描述来生成。

边界划得越清楚,后面做意图识别和参数设计就越轻松,模型的发挥空间也越可控。

3.2 设计意图描述与参数 Schema

我给这个 skill 起的名字是generate_commit_message,它的触发条件包括“生成提交信息”“写 commit”“提交消息”“git commit”“一句话/一段话描述我的改动”等。

然后定义参数 Schema。我的思路很简单,用户至少要提供“改动描述”或“diff 内容”其中一种,所以我做了两个可选字段,但要求至少有一个非空。如果两个都为空,就返回提示让用户补充。

{ "name": "generate_commit_message", "description": "根据用户描述的代码改动或提供的 diff 内容,生成符合 Conventional Commits 规范的 Git 提交信息。", "parameters": { "type": "object", "properties": { "change_summary": { "type": "string", "description": "用户用自然语言描述的改动摘要,例如:修复了登录页在移动端样式错乱的问题" }, "diff": { "type": "string", "description": "用户粘贴的 git diff 内容,如果提供了该字段,优先基于 diff 分析改动" } }, "required": [] } }

这个 Schema 看起来简单,但有一个坑我在早期经常踩:required字段如果写了["change_summary"],用户在对话中只贴了一段 diff 而没写任何描述,意图识别模块就会卡住,或者反复追问用户“请描述您的改动”,体验非常差。后来改成“二选其一”的可选参数,再用代码做非空校验,才彻底解决。

3.3 用 Prompt 模板实现 Skill 内部逻辑

由于这个 skill 的核心是语义理解,所以内部逻辑以 LLM 调用为主。但它不是简单地把参数拼进 prompt 就完了,关键在输出约束。我要求模型返回一个 JSON,里面包含type(提交类型)、subject(标题)、body(正文,可选)和breaking_change(是否有破坏性变更)。用 JSON 结构化输出,后续才能顺利做自动化处理,而不是让模型直接吐一段散文。

你是一个专业的 Git 提交信息生成器。根据用户提供的改动描述或 diff 内容,生成一条符合 Conventional Commits 规范的提交信息。 要求: 1. type 必须是以下枚举之一:feat, fix, docs, style, refactor, perf, test, build, ci, chore, revert 2. subject 不超过 50 个字符,使用动词开头,如“修复”“新增”“优化”“重构” 3. 如果存在 breaking change,请在 body 中明确标注 BREAKING CHANGE 及说明 4. 直接从 JSON 输出,不要任何解释或 Markdown 代码块标记

在真正实现的时候,我还会在 prompt 的末尾加上一则 few-shot 示例,因为单纯给规则而不给样例,模型很容易在 type 选择上任性发挥。比如用户说“改了下按钮颜色”,如果你不给例子,模型可能返回style,这从规范角度其实不算错,但很多团队会把这类改动归为style之外的fix或者chore,所以示例可以帮你把团队的约定固化下来。

3.4 用代码做确定性兜底与参数校验

模型生成结果之后,不能直接信,要做校验和兜底。我用 Python 代码对模型返回的 JSON 做几件事:

  • 尝试解析 JSON,失败时重试一次,且要求模型只输出纯 JSON;
  • 校验type是否在枚举列表内,不在则映射到最接近的值,比如“bugfix”映射成“fix”,“update”映射成“chore”;
  • 校验subject长度,超过 50 个字符时截断,并把被截掉的内容自动放到body里,这样信息不丢。

这一步看似简单,但正是它让 skill 从“演示可用”升级成了“生产可用”。大模型的输出天然带有随机性,如果你不做这种系统性的兜底,同一段 diff 跑十次,可能有一两次结果就是坏的。

3.5 注册到 Agent 并完成端到端调用

写好了 skill 的核心逻辑,最后当然要把它注册进 agent 主调度系统。如果你用的是 LangChain 之类的框架,注册方式通常是装饰器或字典注册;如果自己维护调度,就是维护一个 skill 列表,把 name、description、参数 Schema 和可调用函数关联起来。

完成这一步之后,我建议立刻跑三个端到端的用例:正常描述、仅提供 diff、什么都不给。第三个用例应当主动引导用户补充信息,而不是直接报错。这本质上是一个体验兜底,很多用户第一次接触 agent 时,根本不会按照你的预期把信息一次性给全,引导话术就非常重要。

4. 实战踩坑记录:Skill 设计中最容易翻车的四个细节

在真实项目里,skill 不是写出来就完事,而是要扛住各种奇怪输入的考验。曾经吃过的亏太多了,这节我挑四个“一踩一个准”的坑来详细聊聊。

4.1 参数设计过宽把意图识别带偏

早期我在设计一个“发送 HTTP 请求”的 skill 时,把 URL、方法、请求头、请求体的描述写得特别宽泛,结果模型动不动就把简单的“帮我查一下某网站情况”理解成要发 HTTP 请求,完全跳过了正常的上下文判断。后来我把 skill 的描述收窄成“仅当用户明确要求调用外部 API 或抓取指定 URL 时才使用”,并在系统 prompt 里加了一条“优先使用已选技能,不要主动请求外部接口”,误触发率大幅下降。

这里有个教训:skill 的参数描述不是写给开发者看的,而是写给意图识别模块看的。你写得越具体,模型越知道边界在哪里。模糊的描述等于把判断权交给了随机的注意力分布,不翻车才怪。

4.2 过度迷信大模型,没有留确定性后门

另一个翻车点是把所有操作都交给模型,比如让模型去解析日期、做数字计算、处理 JSON。当时觉得“大模型这么强,肯定没问题”,结果发现它在简单计算和格式规范化上经常出错,而且每次错的还不一样。后来我改成:能用 datetime 库算日期就绝不让模型算,能用json.loads解析就绝不让模型写解析逻辑。模型只负责理解意图和生成自然语言文本,剩下的脏活全交给代码。

这也是我在设计 skill 时定下的一条铁律:做加法时多问一句“这一步能不能用代码实现”,如果能,就不要用模型。一个 skill 里的模型调用越少,它的行为就越可控,测试成本也越低。

4.3 上下文污染:Skill 之间“互相打架”

多个 skill 共存时,最大的问题不是单个 skill 不好用,而是它们之间会互相干扰。具体表现是:用户提了一个问题,A skill 和 B skill 都被触发了,agent 不知道该走哪条路,于是把两边的逻辑混合着执行,输出四不像。

这个问题我从两方面解决。第一,skill 的触发条件写得更具排他性,比如天气查询和日程查询都涉及“明天”,那我就在天气 skill 里明确写“不处理会议、日程、待办类请求”,在日程 skill 里明确写“不处理天气类请求”。第二,在调度层面加了“互斥技能组”的概念,同一组技能共享一次意图打分,得分最高的才执行,其他自动屏蔽。这样模型不会纠结。

4.4 没有评测集,改一处崩三处

这个坑其实最要命。中期迭代时,我优化了一个 skill 的 prompt,结果它自己的效果变好了,却导致另一个不相关 skill 的调用率暴跌。如果没有一套回归评测集,你根本发现不了这种“连带伤害”。

后来我花了两天时间,把项目里所有 skill 的高频输入和典型边界输入整理成了一个 JSON 评测集,每一条都标注了期望输出。每次改任何一个 skill 的 prompt 或参数逻辑,我都会先跑一遍全量评测集。只有这种回归测试通过,才敢把新版本放进实际环境。

评测集本身不复杂,难的是你有没有这个意识。很多项目死在“这次改完好像挺好的”这种错觉上,过了两周才发现线上积累了一堆坏结果,全都无从追溯。

5. Skill 的测试、评估与迭代:建立越用越稳的飞轮

Skills 真正落地以后,能不能持续变好,主要看你的测试和迭代机制。哪怕你只有一个 skill,我也建议从第一天就把这套机制搭起来。这节我来展开讲讲如何建立评测集、如何做回归测试、以及在实际迭代中如何利用日志驱动改进。

5.1 构造评测集:从真实日志里挖边界用例

我构造评测集的方法非常简单粗暴:把上线后的真实用户输入全部记录下来,然后人工筛选出有代表性的样本。样本分成三类:高频正常请求、同义改写请求、边界/恶意请求。比如对提交信息生成 skill 来说,高频正常请求是“修复了登录页面闪退的 bug”,同义改写请求是“登录页崩了,帮忙搞一下 commit”,边界请求是“我啥也没改,就是想试试这个功能”。

每一类样本能帮你抓住不同的问题。高频正常请求保证核心功能不退化;同义改写保证意图识别足够鲁棒;边界请求保证系统不会崩溃或者出现幻觉操作。有人会觉得维护评测集很麻烦,但比起每次线上翻车后花半天去排查,提前把这几十条用例跑一遍的成本低太多了。

5.2 回归测试与断言:不是“跑一下看看”而是“自动判定”

有了评测集还不够,你得能自动判定结果好坏。我看过很多团队的做法是“跑完人肉看一遍”,这本质上还是不可持续的。

我的做法是给每个评测样本配一个断言函数。有些是硬断言,比如输出 JSON 的type必须在枚举内、subject长度不能超过 50;有些是软断言,比如用语义相似度判断生成结果和期望结果的关键词是否一致。可自动化判定的部分尽量用代码判定,只有涉及主观质量的部分才保留人工抽检。

这样,每次改动 skill 后,一条命令跑完所有用例,输出通过率报告。如果通过率低于上一次,说明改动引入回归;如果持平或提升,再进入下一步实际测试。

5.3 日志驱动迭代:记录触发失败与隐性失败

最后也是我最看重的一点:线上日志是 skill 迭代的最佳燃料。除了记录正常调用日志,我还会额外记录“意图识别不确定性高”的请求——也就是模型给出的意图打分非常接近阈值的情况,这些样本正是最容易误触发的场景。

另一种值得关注的是“隐性失败”:skill 明明被调用了,也返回了结果,但用户看完后又换了一种问法再问一遍。这通常说明结果没满足需求。想捕捉这一点,我通常会给每个 skill 输出加一个 trace id,关联到会话上下文,再关注同一会话中是否出现对同一主题的二次请求。

这些日志数据积累到一定数量后,每两周我固定做一次分析,把误触发和隐性失败的 bad case 整理出来,针对性地调整意图描述、参数 schema 或 prompt 模板。这样 skill 的准确率就能沿着真实使用反馈一路往上走,而不是靠拍脑袋优化。

6. 更进一步:Skill 的复用、组合与演进方向

当你的 agent 里积累了十几个、几十个 skill 之后,面对的就不再是单个 skill 的调优问题,而是整个技能体系如何组织、如何组合、如何持续演进的问题。这一节我从实际工程角度聊聊长期规划的思路。

6.1 从单体 Skill 走向技能库

每一个 skill 本质上都是一小段“行为资产”。如果你换了一个项目或者换了一个基座模型,好的技能库可以直接平移过来,因为你只需要让新模型理解每个 skill 的意图描述和参数契约,而无需重新定义这些任务本身。我现在会把常用的、与具体业务无关的 skill(比如生成 commit、格式转换、URL 抓取、摘要总结)单独整理成一个公共技能库,大有“一次编写、处处复用”的好处。

组织技能库时有一条重要的原则:命名和描述要做到“自解释”。当你技能数量超过 20 个时,意图识别模块会把它们全部过一遍,如果你的描述含糊,新接入的模型根本不知道该选哪个。我调试后的经验是:描述第一句写清楚“这个 skill 能做什么”,第二句写清楚“什么时候不要用”,这两句比任何复杂配置都能提高命中率。

6.2 技能组合:从一个 Skill 编排成一条 Workflow

单点 skill 解决的是单一任务,但真实场景往往是一条链路。比如“分析一份 PDF 里的财务数据并生成周报”,这涉及抓取文件、解析表格、抽取指标、生成周报文本等多个步骤。你可以把每一步都做成一个 skill,再写一个编排层按顺序调用它们,这其实就是最简单的 workflow。

在这个编排层里,我倾向于把一个 skill 的输出直接作为下一个 skill 的输入,但一定要做“中间结果的校验”。比如解析表格这一步可能返回空数据,如果直接往生成周报的 skill 里塞,它会一本正经地生成一段毫无依据的周报。有了中间校验,空数据就能被及时发现并触发重跑或向用户求助,而不是一路错下去。

6.3 演进方向:从规则编排到模型自主规划,但不盲目

现在很多框架都在宣传“让模型自主决定调用哪些 skill、以什么顺序调用”,听起来很美好,但我个人的经验是:对于已有明确流程的业务场景,写死编排链路往往比让模型自由发挥更可靠。模型适合做“开放域的探索”,比如你也不知道用户最终想干嘛;而生产系统更适合“固定流程的高执行度”。

所以我的建议是分而治之:在确定性要求高的核心链路上用代码编排,把技能负责的灵活判断限定在局部;等到模型本身的可靠性进一步提升,再去尝试更大胆的自主规划。这种思路也许不够“酷”,但在真实业务里,它才是能持续交付价值的选项。

回到开头那个问题——agent-skills 是不是只是“给 prompt 换个名字”?我觉得答案已经很清晰了。它把大模型从“什么都能聊”变成了“什么能干好”,它是行为模块化、工程化和资产化的过程。这中间没什么神秘魔法,无非是把意图、参数、执行、校验、评测这些环节一个不漏地做扎实。希望这篇文章能帮你少走几步弯路,早点摆脱 prompt 一把梭的处境。

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

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

立即咨询