ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

AI Agent技能工程:从Tools到Skills的设计实战与门道

AI Agent技能工程:从Tools到Skills的设计实战与门道

写 skill 的门道,比我想象中多得多

如果两年前有人告诉我,一个英文单词 "skills" 会成为 AI 应用开发里的核心概念,我大概率会觉得他在说职场培训。但 2025 年回头再看,这个词在 AI Agent 领域的分量,已经完全不亚于当年"插件"之于浏览器、"技能"之于游戏角色。

我最初接触 skills 这个概念,是在调试一个多智能体协作系统的时候。那时候项目里所有能调用的能力都被我们统一叫做"工具",写了一大堆 JSON Schema 描述函数签名,用起来倒也没什么问题。但做着做着就发现,有些功能根本没法用"一个函数"来表达——比如"帮我整理会议纪要并提炼行动计划",它不是一个原子操作,而是一套包含输入模板、处理流程、输出格式、异常情况的完整行为协议。你说它是函数吧,太狭隘;你说它是流程吧,它又带着很强的意图识别和规则约束。直到后来项目里开始用上 skills 这个叫法,我才意识到,这一类"通过自然语言描述来封装能力"的单元,确实值得单独拎出来设计和管理。

这篇文章不打算写那种"教你 30 分钟学会 XX"的速通教程。我想聊聊我在实际项目中折腾 skills 的真实体会:它跟 tools 到底差在哪、一个合格的 skill 内部应该长什么样、多 skill 协作时有哪些坑,以及把 skill 从"能跑"做到"好用"需要补哪些课。如果你是做 Agent 应用开发的,或者正准备给自家产品引入技能体系,这篇应该能帮你省掉不少弯路。

1. 先搞清楚 skills 到底是什么:一个被叫混了的概念

先说结论:skills 在大模型应用语境下,指的是通过自然语言描述,配合少量示例或规则,定义出来的一类可复用的能力单元。它的本质不是代码封装,而是"能力的逻辑封装"——你把"如何完成某件事"的步骤、约束、输入输出模板写清楚,让模型按这个协议去执行。

这个定义听起来还是有点抽象,我拿实际场景举例。

某天我接到一个需求:让项目里的 AI 助手能够处理用户上传的财务报表,自动提取关键指标并生成分析摘要。按我以前的习惯,第一反应是写一个analyze_finance_report(file_path)函数,然后把这个函数注册成 tool。但真的做起来就发现问题了:用户上传的报表可能是 PDF、可能是扫描件、可能是 Excel,内容结构五花八门;光"提取"就不止一个动作,还要先判断文件类型、做 OCR、读取表格结构、再决定哪些字段值得关注;最后输出的摘要格式也取决于用户到底想听"风险提示"还是"收益亮点"。

这一个"函数"根本装不下。正确的做法,是把整件事定义成一个 skill:明确它的触发条件(用户提到了财报/报表/财务数据)、输入格式(文件路径或文本内容)、执行步骤(格式识别 → 内容提取 → 指标计算 → 摘要生成)、输出模板(结构化 JSON 或固定 Markdown 表格),再附上几个典型输入输出的 few-shot 示例。模型读到这份描述,就知道"哦,遇到这种情况,我要按这个流程走"。

1.1 skills、tools、plugins 和 workflows 到底什么关系

这几个词经常被混用,但我建议把它们放成一条"粒度递进"的序列来看:

概念粒度核心特征类比
tools原子操作单一函数/API,输入输出确定一把螺丝刀
skills能力封装多个步骤+约束+示例,面向一类任务一套维修工艺
plugins分发单元一个或多个 skills 的打包分发格式一个工具箱
workflows流程编排多个 agents/tools/skills 串联成业务流一条产线

这张表解决了我之前特别长一段时间的一个困惑。tools 是"能调用的东西",skills 是"模型该怎么干活的行为协议",plugins 是"怎么把能力打包传给别人",workflows 是"多个能力怎么配合"。以前项目里我们讨论"要不要给 Agent 多加两个 tools",实际上很多时候要加的不是 tool,而是一个 skill——因为缺的不是那个函数,而是"遇到这种情况该怎么处理"的协议描述。

  • tools 关心的是"能不能调用"
  • skills 关心的是"该怎么执行"
  • plugins 关心的是"怎么分发"
  • workflows 关心的是"怎么编排"

1.2 为什么说 skills 是给大模型"补全行为边界"的手段

我个人的理解是:大模型本身具备很多能力,但它不知道"在你这个产品里,这个能力要按照哪种约定来落地"。比如模型本来就会写会议纪要,但你的产品要求会议纪要必须按"背景—决议—待办—风险"四段式输出,且待办项必须带负责人和截止时间。模型不知道这个约定,除非你告诉它。

Skills 就是承担这个"告诉它"的载体。

它跟直接写死在系统提示词里的区别在于:系统提示词是"对所有对话生效的全局规约",而 skill 是"针对某一类任务的局部规约"。全局规约太多会把模型"压死",每轮对话都背上沉重的上下文负担;局部规约则是按需激活——用户不提财报,模型就不需要加载那一大段财务分析协议。

这个"按需激活"的设计,在实际项目里带来的收益非常明显。我们后来把十几个技能全部改成了独立 skill 文件,系统提示词的体量一下子降了 60% 多,而任务完成率反而提升了。原因很简单:模型不再被一堆跟自己无关的规则干扰了。

2. 从零到一写一个会"干活"的 skill:会议纪要实战全流程

光讲概念容易飘,我拿"会议纪要助手"这个 skill 举个例子,带大家完整走一遍设计过程。选它当例子是因为需求足够典型——几乎所有协作类产品都想做这个功能,但它又确实不是简单调一次模型就能搞定的,里面有很多隐藏的设计取舍。

2.1 第一步:先拆解任务,再动手写描述

很多人在这一步就开始犯错了。我见过不少方案,直接往系统提示词里塞一句"你是会议纪要助手,请整理用户提供的会议内容",然后就没下文了。这样的"skill"跑起来效果完全看运气,模型心情好时输出一份漂亮的纪要,心情不好时给你一段流水账。

正确的做法,是先把"做会议纪要"拆成人话能说清楚的行为链:

  1. 判断输入内容是否具备会议要素(参与人、时间、议题、讨论过程)
  2. 如果输入是流水账式的口语内容,先做信息清洗和结构化
  3. 识别每个议题的结论是什么
  4. 识别哪些内容是后续待办,并尝试提取负责人和截止时间
  5. 所有信息统一落到固定的输出模板里

拆完这些,你再回过头来写 skill 描述,就知道该往里面填什么了。

2.2 第二步:定义触发条件和输入输出协议

Skill 的描述文件里,最核心的部分就是"什么情况下该用我"和"我吃完输入后该吐出什么"。我一般会写成这样的一段配置(YAML 为例,是当前主流框架里比较常见的写法):

name: meeting_minutes description: 将会议口语记录整理为结构化会议纪要,适用于包含议题讨论、结论或待办的会议内容。 triggers: - "整理会议纪要" - "帮我记录一下会议要点" - "把下面这段对话整理成纪要" - 输入内容包含明显的会议特征(时间、参与人、讨论议题) input: format: text constraints: - 需要明确标识发言人与内容的对应关系(如"A:xxx") - 如果输入缺少发言人标识,需按"未知参与者"处理 steps: 1. 扫描输入,识别会议要素(时间、参与人、议题) 2. 将内容按议题维度分组,删除寒暄和无信息量语句 3. 对每个议题提取结论,标注达成方式(共识/争论/搁置) 4. 提取待办事项,尝试关联负责人和截止时间 5. 按输出模板生成纪要 output: format: markdown template: | ## 会议纪要 - 日期: - 参与人: - 主要议题: ### 议题一:{议题名} - 讨论要点: - 结论: - 状态:共识/争论/搁置 ### 待办事项 | 事项 | 负责人 | 截止时间 | 来源议题 | |------|--------|----------|----------| examples: - input: "A:这次活动方案怎么样 B:预算有点超了,需要砍一下 C:那就场地这块省点" output: |- ## 会议纪要 - 日期:未注明 - 参与人:A、B、C - 主要议题:活动方案预算调整 ### 议题一:活动方案预算调整 - 讨论要点:活动预算超出当前限制,需压缩开支 - 结论:对场地费用进行削减 - 状态:共识 ### 待办事项 | 事项 | 负责人 | 截止时间 | 来源议题 | |------|--------|----------|----------| | 重新核算场地费用预算 | 待定 | 待定 | 活动方案预算调整 |

这套写法的核心逻辑是:触发条件告诉模型"什么时候激活",steps 告诉模型"按什么顺序干活",output template 告诉模型"交作业的固定格式",examples 则给模型一个"照猫画虎"的参照物。

2.3 第三步:单测和调优,别急着接业务

Skill 写完之后,我强烈建议先用一批离线样例测一遍,而不是立刻接进生产环境。我自己的习惯是准备三种测试输入:

  • 理想输入:结构清晰、信息完整的会议记录,验证主流程是否顺畅
  • 边缘输入:只有两句话的对话、全篇没有发言人标识的文本、重复内容很多的冗长段落
  • 恶意输入:看起来像会议记录但其实是闲聊的文本、带有情绪化争吵的内容

跑完一组测试后,你会发现模型大概率在两三类地方出问题:一是触发误判,把普通聊天也识别成会议内容;二是待办提取过猛,明明只是讨论时顺口说了一句"回头看看",它也给提取成了待办;三是输出格式不稳定,有时不遵循 markdown 模板。

每一类问题都有对应的修法:

  • 触发误判 → 在 triggers 里增加负例说明,明确"仅当内容包含明确讨论结构和结论意图时才触发"
  • 待办提取过猛 → 在 steps 里加一道校验:"待办必须包含动作动词+可执行对象,否则归入备注"
  • 格式不稳定 → 增加一条约束:"严格按 template 输出,不要添加额外章节"

3. 藏在细节里的稳定性:让 skill 每次都能交差不多的作业

如果说上一章解决的是"怎么把 skill 做出来",这一章解决的就是"怎么让它稳定得像一个老员工"。我见过很多 skill 的第一次版本效果惊艳,但换几个输入就开始翻车,根子往往不在模型能力,而在 skill 本身的描述设计存在模糊地带。

3.1 把"模糊要求"翻译成"机器可校验的标准"

这是我认为 skill 工程里最值钱的一步。

举个例子,很多人在写会议纪要 skill 时会写这样一句话:"对讨论内容进行总结"。这句话看着没问题,但模型的"总结"有无数种理解——它可以保留 90% 的细节做缩略版复述,也可以提炼成三行高度抽象的结论。同一个 skill,面对不同输入时输出风格就可能飘忽不定。

更好的写法,是指定"总结的粒度和范围":

  • 删除重复表达,保留所有不同观点
  • 每个议题的讨论要点不超过 5 条
  • 每条要点必须是一个完整陈述句,不能是短语

再比如,你希望模型在不确定负责人时不要瞎编。与其写"不确定时请标注待定",不如写得更严格:"若原文未明确提及负责人,一律使用'待定'字段,禁止根据上下文猜测人员姓名"。这两句话对模型行为的影响差别非常大,前者留下了猜测的余地,后者直接封死了窗口。

3.2 给 skill 加一层"防呆设计":输出自检和人工兜底

Skill 的执行结果没法保证 100% 正确,所以好的 skill 设计一定要有"自我检查"和"降级兜底"这两个环节。

自我检查这一环,可以在 skill 的 steps 末尾加一条:"输出前自行检查:1) 是否包含全部议题;2) 待办事项是否有动作动词;3) 是否遵循了输出模板。若未满足,修正后再输出。"这种"让模型自己给自己当编辑"的做法,实测能把格式符合率拉高不少。

降级兜底则是应对"输入内容实在没救"的情况。比如用户就丢了一句话:"今天开会讨论了下个月的计划",没有任何细节。这时候 skill 不应该强行产出一份五个章节的会议纪要——它应该明确告诉用户"提供的信息不足以生成完整纪要,请补充参会人、讨论内容和结论"。这个"拒绝生成"的分支如果不写进 skill 里,模型很可能会自作主张编出一堆内容,那就是事故了。

3.3 复杂 skill 的拆解策略:组合优于巨无霸

还有一种常见病,是把 skill 写得太大,一个大文件里塞了七八个职责。比如某些产品经理喜欢把"会议处理"做成一个大 skill,里面包含了会议纪要、任务跟踪、日程建议、参会人分析……看起来功能全,实际用起来特别难受:模型面对一个简单请求时,也要在满屏的规则里搜索自己该执行的那一部分,容易抓错重点。

我的建议是:一个 skill 只解决"一类问题的完整闭环"。如果确实需要多职责,就把它拆成多个 skill,然后在主 skill 里通过排列组合来引用。比如:

  • meeting_minutes:负责把会议整理成纪要
  • action_item_extractor:负责从文本里提取待办事项
  • date_parser:负责解析时间表达并标准化日期格式

写纪要的流程先调meeting_minutes,出来后如果还需要结构化任务信息,再调action_item_extractor。每个 skill 保持单一职责、独立测试,组合起来的系统反而更灵活。

4. 多 skill 协作场景下的冲突处理与上下文管理

当系统里只有一个 skill 时,你基本不用担心"打架"问题。但一旦 skill 数量上了两位数,事情就开始变得微妙。这里分享一下我踩过的一个典型坑,以及解决方案。

4.1 典型冲突:两个 skill 的触发条件重叠了

我们项目里同时有email_draft(写邮件草稿)和tone_adjuster(调整语气)这两个 skill。当时的设计是:用户在写信助手界面输入内容后,系统根据用户指令决定调哪个 skill。结果实际应用中频繁出现一个问题——用户说"帮我把这段话写得更客气一点",按理说应该调tone_adjuster,但email_draft的触发条件里也写了"当用户请求与邮件内容修改相关时激活",结果两个 skill 同时被触发,系统先让 email_draft 重新生成了一遍邮件,又让 tone_adjuster 调了一遍语气,输出结果完全乱了套。

排查链路是这样的:

  1. 先从调度日志定位到两个 skill 同时进入激活列表
  2. 逐一检查触发条件,发现email_draft的条件写得过于宽泛,里面赫然有一条"内容涉及修改"
  3. 把重叠部分缩窄:email_draft只在"用户要求创建新邮件或重写整封邮件"时触发,tone_adjuster专门承接"修改已有文本的语气风格"
  4. 重新跑回归测试,冲突消失

这个问题表面上是触发条件写得不精细,底层其实是"职责边界划分不清"。两个 skill 做的事情有交集,模型当然不知道该怎么选。解决办法是把交集区域明确归属到其中一个 skill,让另一个 skill 的触发条件显式排除掉这部分。

4.2 上下文状态的丢失:被拆碎的 skill 拼不回完整流程

多 skill 协作的另一个大坑,是"流程状态断裂"。

有一次我们搭一个研究助手,处理流程是:用户给一个研究主题 → skill A 负责拆解成子问题 → skill B 负责检索资料 → skill C 负责生成报告框架 → skill D 负责填充内容。单看每个 skill 都挺正常,但连起来跑的时候经常出现严重的信息丢失——skill C 生成报告框架时,已经不记得 skill A 拆出来的子问题是哪些了,于是生成的框架跟前面的研究方向对不上。

根因在于:技能之间的信息传递只靠对话历史,而对话历史一旦被截断或压缩,早期 skill 产生的关键中间结果就丢了。解决思路有两个,可以配合使用:

  • 显式状态传递:skill A 的输出除了返回给用户之外,还要写入一个持久化的中间结构(比如 JSON 格式的研究计划),skill C 激活时直接读取这个结构,而不是指望从对话上下文里翻
  • 在 skill 描述里显式声明依赖:"本 skill 将在执行前从 workbench 产物区读取 research_plan.json,若文件不存在则先调用 researcher 生成"

这套改动落地之后,长流程任务的完成稳定性提高了非常多。核心经验是:每个 skill 的输入输出尽量"显式化",不要让中间产物只存在于上下文窗口里。

4.3 让调度更聪明:分层决策与可见性控制

还有一个值得聊的问题,就是"该把 skill 列表交给模型自动选择,还是由上层逻辑硬编码路由"。这两种做法我都试过:

调度方式优点缺点
模型自动选择灵活,能处理用户绕来绕去的表达技能一多容易误触,可能选到不合适的 skill
上层硬编码路由稳定可控,适合业务场景已经非常明确的情况扩展新 skill 时要改代码逻辑
分层混合先按领域粗分类过滤,模型只在候选集里选需要搭建领域分类层,前期成本稍高

从效果来看,分层混合最稳。具体做法是给每个 skill 打上领域标签(比如"沟通辅助""数据分析""日程管理"),配置一个轻量分类器,先把用户请求归到某个领域,再只把该领域的 skill 定义暴露给模型。这样模型看到的候选集永远只有几个,误触概率大幅下降。

可见性控制同样重要。不是所有 skill 都需要随时处于"可被激活"状态。比如一些专业领域的 skill(财务分析、代码审查),在用户没明确表露出相关需求时,干脆就不加载进模型上下文。这既减少了干扰,也省了 token。

5. 从"能跑"到"好维护":skill 的工程化管理经验

当一个项目里的 skill 超过 20 个之后,管理成本会急剧上升。我见过团队里出现"技能孤儿"——某个人写的 skill 没人维护、没人用,还时不时被模型激活一下,产出质量感人。要避免这种情况,得靠工程化管理,而不是靠自觉。

5.1 目录结构设计和版本管理

我现在在项目里采用的 skill 目录结构长这样:

skills/ meeting_minutes/ SKILL.md # skill 主描述文件 examples/ normal_case.md # 正常输入输出样例 edge_case.md # 边缘输入样例 negative_case.md # 负例,防止误触发 assets/ # 引用的静态资源,如提示词模板、字典 tests/ test_cases.json # 自动化测试用例 expected_outputs/ # 预期输出 email_draft/ SKILL.md examples/ tests/

每个 skill 一个目录,SKILL.md 是技能的唯一入口,examples 提供正反例,assets 放依赖资源,tests 挂自动化验证。这套结构的好处是:任何人接手一个新技能,打开目录扫一眼就知道这个技能是干嘛的、长得什么样、跑没跑过测试。

版本管理上,我给每个 skill 都维护独立的变更记录,并尽量遵循语义化版本号。行为描述有改动就意味着大版本变更,因为这会直接影响模型的实际执行结果;只是补充了几个示例则算小版本。

5.2 自动化回归测试:这是最容易被偷懒但最不该偷懒的部分

Skill 的测试不同于普通代码的单测——它不是在验证逻辑对不对,而是在验证"描述是否足够引导模型稳定产出符合预期的结果"。

我们的做法是维护一批测试用例,每个用例包含输入文本、预期行为特征、评分要点。跑测试的时候,让模型按 skill 描述执行,然后把输出喂给一个自动评判器检查关键要素是否满足。比如会议纪要 skill 的测试要点包括:

  • 输出是否包含全部议题
  • 待办事项是否都有动作动词
  • 是否严格遵循了模板结构
  • 是否对信息不足的输入进行了拒绝

这个流程看起来简单,但实测价值极大。之前每次改动 skill 描述都心惊胆战,生怕影响别的场景;有了回归测试后,改完直接跑一遍测试套件,1 分钟内就能知道改动是否引入回归。

5.3 技能运行时观测:别等用户骂了才知道 skill 坏了

最后想说一下观测问题。Skill 是模型驱动的,它的行为存在不确定性,所以它比普通代码更需要运行时观测。我们在项目里给每个 skill 调用都打上了结构化日志,记录以下字段:

skill_name, 触发方式, 输入摘要, 输出摘要, 执行耗时, token开销, 自检是否通过

数据积累到一定程度后,可以做两类分析:一是找"低质量输出"共性的入口,看看是哪些 skill 频繁产出不符合预期的结果,优先优化它们;二是做 token 成本分析,看看哪些技能调用成本过高,是不是描述写得太啰嗦。

我个人还会定期抽样检查模型的输出日志,直接肉眼看案例——这个习惯虽然原始,但往往能发现自动化评估发现不了的问题,比如模型开始用一种我没见过的格式输出,或者语义理解跑偏了。

6. 从"单点技能"到"能力网络":skill 体系的进阶设计思路

如果一个项目里的 skill 已经积累到了一定的量级,你可以开始考虑把它们织成一张网,而不是一个个孤岛。

6.1 组合式技能的两种模式:流水线与路由

Skill 之间不是只能互相独立,它们可以组合成更大的能力单元。我常用的组合模式有两种:

第一种是流水线模式,适合流程固定、顺序明确的场景。比如"竞品分析报告"这个复合技能,内部就是固定的三段流水线:data_collector抓取信息 →analysis_framework套用分析模型 →report_writer按照模板产出报告。由于顺序完全确定,可以直接写成一个 orchestrator skill,它负责依次激活下游技能并汇总产物。

第二种是路由模式,适合"根据输入情况动态选择下一步"的场景。比如"客户支持"这个复合技能,它要先判断用户是哪一类问题——账号问题走account_troubleshooter,支付问题走payment_handler,使用指导走tutorial_guide。路由 skill 本身的职责很单一:分类,然后分派。

这两种模式可以组合,甚至形成多层嵌套。但要注意一点:嵌套层级不宜过深,每多一层就多一次状态传递损耗。我踩过的教训是,三层以上的 orchestrator 链路,一旦某个中间环节输出质量不稳,整个链路都会崩,排障还特别麻烦。

6.2 Skill 的"自我进化":拿用户反馈反哺技能描述

这是我觉得最接近"自我进化"的一个环节,也是运营成本相对高但收益可观的方向。

具体做法是:把用户对 skill 输出的反馈信号引入技能迭代闭环。当用户对某个技能的结果点了"不满意"或主动修改了输出内容时,系统记录下这次失败的输入和原始输出。运营人员定期汇总这些失败案例,找出模型反复出错的规律,再针对性地调整 skill 描述、补充负例、增加约束。

举个例子,我们的meeting_minutes技能上线初期收到了不少"待办事项提取不准"的反馈。分析失败样本后发现,模型经常把反问句"我们要不要考虑一下备选方案?"也提取成待办。修复方法也很简单:在 skill 描述里加了一条约束——"待办事项必须是明确指派或承诺的动作,疑问句中的建议不属于待办;如果是疑问句中的建议,归入'待讨论'栏"。补上这一条后,同类反馈明显减少。

6.3 规模化的最后一步:把技能沉淀成团队资产

当 skill 体系在公司或团队内部慢慢长成之后,它的价值就不只是某个产品功能了,而是一份可复用的团队知识资产。新人接手项目时,翻阅 skill 目录就能快速理解这个系统"会做什么事、按什么方式做事";其他产品线做类似功能时,也可以直接拿来复用。

当然,这也意味着要对 skill 的跨项目复用保持克制。每个业务场景都有自己的特殊性,直接复制别人的 skill 往往需要适配修改。我的建议是:复用技能时可以通用能力为主,比如时间解析、文本摘要、格式转换这类的"底裤型技能"尽量标准化;而那些带着强业务规则和领域知识的技能,还是要做到"一品一策",别偷懒。

说到底,skill 工程本质上是"如何把人的做事方法翻译给模型听"的工程。它比写代码更微妙,因为你面对的是一套概率模型而非确定性逻辑;但它的上限也高得多——一份设计得当的 skill 描述,能让模型的发挥稳定度提升一个数量级。我这几条经验,都是真金白银踩出来的,希望能帮你少走几步弯路。

如果你正准备搭建技能体系,我最后再分享一个真实的建议:从最小但完整的场景起步,别急着铺规模。挑一个你最有把握的场景,把 skill 写细、测透、跑到稳定,再复制到下一个场景。技能体系这东西,宽度远没有深度重要,一个能持续稳定输出的技能,胜过十个看起来花哨但偶尔翻车的花架子。

RELATED READING

延伸阅读

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