ARTICLE · INTELLIGENCE

战地情报 · 详情页

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

Agent Skills实战指南:让AI Agent从“泛而不精”走向“专项熟练”

Agent Skills实战指南:让AI Agent从“泛而不精”走向“专项熟练”

1. 从一个恼人的场景说起:为什么 Agent 需要“技能”

先讲个我实际遇到的例子。几个月前我在做一个跨平台的数据整理项目,随手把任务丢给一个通用助手,让它去读一份格式相当混乱的报表,按固定模板输出汇总。助手确实“听懂了”需求,但干活的路径非常笨:先尝试调用某个通用函数解析,失败后换一种写法,又失败,再翻文档,最后靠猜才把字段对齐。整个过程耗时十几分钟,中间还产出了一堆无效调用。

这其实暴露了当前 Agent 类应用的一个通病:模型很聪明,但知识太泛,对特定问题的“熟练度”不够。它什么都会一点,但什么都不专。就好比你请了一个知识面很广的实习生,他什么都肯干,可遇到你团队内部那套复杂的报表规范,他还是要翻半天文档,试错几次才能上手。

“Agent Skills”解决的正是这个问题。它不是某个具体产品,而是一套让 AI Agent 具备“专项熟练度”的设计思路与文件规范:把某个领域的操作知识、脚本、提示词、资源打包成一个可被 Agent 按需加载的技能单元。模型平时不加载它,遇到对应任务时才把技能文件读进来,照着里面的流程、代码和约束去执行。

这套思路最打动我的一点是它还原了“人是怎么变熟练的”。人类员工入职后不会马上成为专家,而是通过不断接手项目、总结 SOP、沉淀工具,逐渐形成肌肉记忆。Agent Skills 想做的,就是把这种“肌肉记忆”显性化、文件化、可复用。它适合谁?适合所有正在做 Agent 应用、被“模型太笨、调用太乱、重复劳动太多”折磨的开发者;也适合只把 AI 当工具、想让它稳定完成某种固定任务的重度使用者。下面我把这套东西拆开讲清楚,最后附上我踩坑换来的经验。

2. Agent Skills 的核心设计:为什么“技能化”比“通用化”更靠谱

2.1 先搞清楚它和 Tools、MCP 的本质区别

很多人一听说 Agent Skills,第一反应是“这不就是 Tools 吗”“和 MCP 有什么区别”。我最初也这么想,但实践下来发现这是三件事,维度完全不同。

  • Tools(工具):是给 Agent 的“手”。一个函数、一个 API 调用,完成一个具体动作。它解决的是“能不能做”的问题。
  • MCP(模型上下文协议):是给 Agent 的“神经和血管”。它定义工具如何被发现、如何被调用、资源如何传输,解决的是“如何连接”的问题。
  • Agent Skills(技能):是给 Agent 的“经验和肌肉记忆”。它包含工具调用方式、领域知识、工作流程、注意事项,解决的是“做得好不好、专不专”的问题。

这样类比你就明白了:有工具等于给了 Agent 一盒螺丝刀,有 MCP 等于把螺丝刀接上了机械臂,而 Agent Skills 则是一本老师傅写的《如何拆装这台发动机》手册加上配套专用工具。它让 Agent 知道该用哪把螺丝刀、以什么顺序拧、拧几圈、遇到滑丝怎么处理。

拿我那个报表整理项目举例。传统做法是给 Agent 一个parse_report()工具,它知道自己“能解析报表”,但不知道“这份报表的特殊格式、常见脏数据长什么样、模板的字段映射规则”。而 Agent Skills 会把所有这些写进技能文件,Agent 遇到同类任务时加载技能,按里面预置的 Python 脚本直接处理,速度和准确率完全不在一个量级。

2.2 一次完整的技能生命周期:从打包到遗忘

理解了区别,再看 Agent Skills 的工作方式就顺了。一次完整的技能使用过程,在我看来分为五个阶段:

  1. 注册:开发者把技能放进指定目录(技能库),Agent 启动时会扫描目录,生成技能清单。
  2. 索引:Agent 只读取每个技能的元信息(名称、描述、适用场景),并不加载完整内容,相当于只看了目录页。
  3. 匹配:用户提出问题后,模型根据技能描述判断是否命中。这里有个关键点:匹配不是关键词搜索,而是模型对自然语言的语义理解。
  4. 加载:命中后,Agent 把该技能目录下的 SKILL.md、脚本、资源文件读入上下文,然后才开始干活。
  5. 遗忘:任务结束或上下文清理时,技能内容从对话窗口中移出,释放 token 空间给后续任务。

这个“按需加载、用完即走”的设计是我认为整套思路里最聪明的地方。模型上下文窗口本就是稀缺资源,如果一次性把所有技能都塞进去,token 很快耗尽,而且信息过载反而会让模型抓不住重点。按需加载就像人脑的记忆机制——平时不需要想起的知识不会一直占用注意力,遇到相关场景才被激活。

3. 动手实践:从零构建一个可用的 Agent Skill

3.1 技能的标准目录结构与 SKILL.md 规范

我建议你直接按以下目录结构来组织一个技能,这是目前社区里比较通行的做法:

skills/ └── report-skill/ # 技能根目录,文件夹名就是技能名 ├── SKILL.md # 技能说明书,必选,Agent 首先读它 ├── scripts/ # 可执行脚本目录 │ └── parse_report.py ├── assets/ # 静态资源,比如模板文件、样例数据 │ └── output_template.xlsx ├── tests/ # 测试用例,验证技能可靠性的关键 │ └── test_parse_report.py └── references/ # 参考资料,非必选,用于补充领域知识 └── report_format_notes.md

SKILL.md 是这个技能的灵魂,Agent 靠它来决定“要不要用这个技能、怎么用”。我逐步踩坑后总结出 SKILL.md 必须包含的五个部分:

  • 元信息:技能名称、简短描述、适用场景。描述写得好不好,直接决定模型匹配准不准。
  • 依赖环境:需要哪些系统工具、Python 库、网络权限。
  • 使用说明:何时使用、何时不应使用、执行步骤。这是核心部分。
  • 输入输出规范:输入数据格式、返回结果格式、错误处理方式。
  • 注意事项与禁忌:必须遵循的规则,比如“不得删除源文件”“遇到敏感字段要脱敏”。

下面是我实际写过的一份 SKILL.md 骨架,你可以直接参考:

--- name: report-skill description: 解析混乱格式的销售报表,按指定模板生成汇总。适用于 Excel/CSV 格式的月度销售数据。 when_to_use: 用户提供销售报表并要求汇总、清洗或格式转换时 when_not_to_use: 用户没有提供具体报表文件,或需求与报表处理无关时 version: 1.0.0 --- # 报表处理技能 ## 环境依赖 - Python 3.10+ - pandas, openpyxl ## 执行步骤 1. 读取 scripts/parse_report.py,确认输入输出参数。 2. 用脚本解析用户提供的报表文件,处理缺失值和日期格式。 3. 按 assets/output_template.xlsx 的格式生成汇总结果。 4. 输出结果文件路径。 ## 输入输出 - 输入:Excel 或 CSV 文件路径 - 输出:按模板生成的 xlsx 文件路径 ## 注意事项 - 日期统一为 YYYY-MM-DD 格式 - 销售额为空时填 0,并备注 - 不得修改原始文件

注意几个细节:description和when_to_use要写得具体,不要泛泛地说“处理报表”。模型匹配技能靠的是语义,写得太抽象它会选错。我一开始写过“处理数据文件”,结果接表格任务时它选了技能,接文本任务时也选了技能,最后文本任务完全跑偏。后来改成“处理 Excel/CSV 销售报表”这类限定性描述,匹配准确率立刻上来了。

3.2 用渐进式调用实现核心逻辑:模型主导还是脚本主导?

关于“Agent Skills 的执行逻辑到底怎么设计”,实践中有两种路线,我分别试过,都有效,但适用场景不同。

路线一:模型主导、脚本辅助

模型读完 SKILL.md 后,自己规划步骤,按需调用 scripts 目录下的脚本。脚本只负责具体的、确定性的计算任务。这种方式的优点是灵活,遇到 SKILL.md 没覆盖到的边界情况,模型能临场发挥;缺点是模型可能“自由发挥”过度,偏离技能本意。

路线二:脚本主导、模型监督

脚本把整个流程自动化,模型只负责接收用户输入、调用脚本、把结果翻译成人话。比如上面这个报表解析场景,我直接写好一个parse_report.py,它能自动读取指定路径的文件、清洗数据、输出模板文件。模型实际上只做两件事:把用户给的路径传进脚本,把脚本输出的结果告诉用户。

我个人的经验是:流程越标准化,越应该走路线二。原因很简单,模型处理长流程时容易在中间步骤“忘事”,而脚本只要调试好了,每次执行结果都是确定的。但脚本主导也有缺点:一旦输入格式超出预期,脚本可能直接报错,此时模型需要具备“把报错信息转化为用户可理解的提示”的能力,所以模型监督的角色不能省。

这里我贴一段实际的脚本示例(简化版),演示“脚本主导”时脚本应该怎么设计才能让模型更好地配合:

#!/usr/bin/env python3 """报表清洗脚本:读取 Excel/CSV,清洗后输出标准格式。""" import argparse import pandas as pd from pathlib import Path def clean_report(input_path: str, output_path: str) -> None: # 根据扩展名选择读取方式 if input_path.endswith(".csv"): df = pd.read_csv(input_path) else: df = pd.read_excel(input_path) # 统一列名,去除首尾空格 df.columns = [c.strip() for c in df.columns] # 日期标准化为 YYYY-MM-DD if "日期" in df.columns: df["日期"] = pd.to_datetime(df["日期"], errors="coerce").dt.strftime("%Y-%m-%d") # 销售额为空填 0 if "销售额" in df.columns: df["销售额"] = df["销售额"].fillna(0) # 生成统计汇总 summary = { "总销售额": df["销售额"].sum(), "订单数": len(df), "日期范围": f"{df['日期'].min()} ~ {df['日期'].max()}" } # 输出到目标路径 df.to_excel(output_path, index=False) print(f"处理完成,输出文件:{output_path}") print(f"汇总:{summary}") if __name__ == "__main__": parser = argparse.ArgumentParser() parser.add_argument("--input", required=True, help="输入文件路径") parser.add_argument("--output", required=True, help="输出文件路径") args = parser.parse_args() input_path = Path(args.input) if not input_path.exists(): print(f"错误:输入文件不存在 {input_path}") exit(1) clean_report(str(input_path), args.output)

脚本刻意加了很多print输出,这是为了给模型提供“可观察的执行结果”。如果脚本闷声执行不输出任何过程信息,模型就只能猜它干得怎么样,一旦出错,排查成本极高。让脚本主动汇报,是对模型最友好的设计。

3.3 技能测试:不想上线后被 Agent 坑,就必须做这一步

技能文件写好后,很多人直接丢给 Agent 去用,结果翻车了都不知道是技能的问题还是模型的问题。这里的正确做法是:先离线验证技能文件本身是可靠的,再把包装后的技能交给 Agent。我自己的测试清单长这样:

  • 脚本层:用真实样例数据跑一遍脚本,确认输出格式符合预期。
  • 边界数据:测试空表、全脏数据、超大文件,脚本是否报错或超时。
  • 说明层:让模型只读 SKILL.md,不看任何其他资料,问它“这个技能是干什么的、怎么用”。如果模型能准确复述,说明文档写得够清楚。
  • 端到端:在 Agent 环境里实际提一个典型任务,观察技能是否被正确匹配和加载。

除了一次性测试,我强烈建议你用脚本做回归。技能文件后续一定会改,每次改完都跑一遍,避免改坏了自己还不知道。

4. 机制拆解:模型是如何“看见”并调用技能的

4.1 技能清单与系统提示词:控制台的快捷键

前面说了,Agent 不会在一开始就把全部技能内容读入上下文,它会先扫描技能库,生成一份简洁的清单。这份清单很像 IDE 里的快捷键面板——上面只写命令名和一句话说明,具体功能等你按下去才展开。

在我的实现里,技能清单是拼进系统提示词里的,每行一个技能,格式如下:

技能列表: - report-skill:解析销售报表并生成汇总文件(输入 Excel/CSV) - log-analyzer:分析服务日志并提取错误摘要 - translation-checker:中英互译准确性校对

有人可能会问:“技能多了怎么办?全部列出来会不会撑爆上下文?”实际上一条技能描述平均只需要 20~40 个 token,哪怕上百个技能,也只要几千 token,完全可接受。真正要控制的是技能内容本体,而不是清单。

这里有个优化技巧:清单里的描述要“从用户需求出发”而不是“从技能实现出发”。比如“log-analyzer:分析服务日志并提取错误摘要”是从用户视角写的,比“log-analyzer:封装了 pythongrep 命令的封装脚本”更好理解。模型也是靠这段描述做语义匹配的,描述写得越贴近用户会说的话,命中率越高。

4.2 命中机制:为什么说这是语义匹配而不是关键词搜索

当用户提出“帮我整理一下这个月的销售数据”时,Agent 内部大致发生这样几步:

  1. 系统提示词里有技能清单,模型知道存在report-skill这样一个技能。
  2. 模型把用户问题与技能描述做语义比对:“整理销售数据”和“解析销售报表并生成汇总文件”语义高度相关。
  3. 模型决定加载该技能,于是把SKILL.md和必要的脚本文件读进上下文。
  4. 模型按技能说明逐步执行,完成后再把技能内容从上下文中清理掉。

你可以盯着第 2 步想一个问题:为什么开发者要强调“描述要写得具体”?因为语义匹配依赖模型的理解能力,而“解析销售报表”和“整理销售数据”不是字面重合,是靠语义联想搭上的。如果描述写得太笼统或太技术化,模型就联想不过来。我试过一次把技能描述写成“用于 pandas DataFrame 后处理”,完全是从代码角度写的,结果用户说“帮我看看这些数据有啥问题”,模型死活不加载这个技能,因为“有问题”和“后处理”之间隔着两层语义。

4.3 上下文预算与遗忘机制:不是所有内容都该常驻

很多 Agent 应用做着做着就发现对话变“笨”了,原因往往是上下文被无关内容塞满,模型注意力分散。Agent Skills 的“按需加载”对这个问题有显著的缓解作用,但它不是银弹——你需要主动关注上下文预算。

我实践中的经验数字是:

  • 技能清单占用的上下文控制在 1000 token 以内;
  • 单个技能加载后的内容控制在 4000 token 以内(SKILL.md 加脚本);
  • 技能内容加载后,与对话历史合计不超过上下文窗口的 50%,必须给模型的“工作记忆”留空间。

如果你发现一个技能动辄上万 token,那就该拆分了。比如“报表处理”技能拆成“报表解析”和“报表可视化”两个独立技能,按需加载,比塞成一个超大技能稳定得多。

5. 常见问题排查与避坑实录

实操了一段时间,我整理了一份高频问题速查表,都是我真实踩过的坑:

问题现象根因分析解决方案
Agent 在多个技能之间反复横跳技能描述语义重叠度过高检查技能描述,划清边界,明确 when_not_to_use
明明加载了技能,Agent 还是按自己的方式干SKILL.md 里的指令不够强,模型认为“建议”而非“必须”用祈使句写步骤,必要时用“必须”“不得”加强约束
技能脚本跑通了,但 Agent 报错脚本输出格式与模型预期不一致在 SKILL.md 里写清楚每个脚本的输出格式,并在脚本里用 print 明确汇报
技能匹配失败技能描述与用户自然语言表达跨度太大重写描述,从用户视角描述“什么时候该用我”
上下文很快耗尽技能一次性加载过多,或技能内容过大缩减单个技能体积,细粒度拆分技能
技能在换一种说法提问时失灵了依赖关键词匹配,语义泛化不足在技能描述里补充同义短语和典型问法

5.1 排查技巧:一个常用的三板斧

遇到技能调用异常,我一般按下面顺序排查:

第一步:看技能有没有被加载。在 Agent 调试窗口里检查用户提问后,上下文是否出现了 SKILL.md 的内容。没出现,说明匹配阶段就失败了,问题出在技能描述上;出现了,才能排除匹配问题。

第二步:把技能文件单独喂给模型。注意这里不是让 Agent 调用技能,而是直接把 SKILL.md 粘进一个单独的对话里,问模型“按这个技能,你应该怎么处理某任务”。这一步能精确地把“技能写得不好”和“Agent 装配得不好”区分开。

第三步:把脚本--help和一次样例输出贴回 SKILL.md。很多脚本出错的场景,其实不是脚本错了,而是模型不知道脚本怎么调。在 SKILL.md 里补上明确的调用示例和输出样例,命中率会有显著提升。

5.2 一个特别容易踩的坑:脚本报错信息太“程序员化”

有一次我的技能里有个脚本抛了FileNotFoundError,模型原封不动地把这个错误抛给了用户:“FileNotFoundError: [Errno 2] No such file or directory: ...”。正常用户看到这种报错基本是懵的。问题出在脚本的异常处理没有面向用户设计。

我把脚本改成捕获异常、输出礼貌的中文提示后,效果立竿见影:

try: clean_report(args.input, args.output) except FileNotFoundError: print("抱歉,我找不到你提到的文件,请检查路径是否正确。") except Exception as e: print(f"处理过程中出现异常:{e},请确认文件格式是否支持(xlsx/csv)。")

记住一点:Agent Skills 的输出,最终是人看的。脚本的中间输出、错误信息,都应该以“人能看懂”为标准,而不是以“程序员方便定位”为标准。两者并不冲突,但后者要放在调试日志里,而不是直接透出给用户。

6. 踩坑之后,我对 Agent Skills 设计原则的个人总结

把这套东西反复实践了几轮之后,我沉淀出四条自己的设计原则,供你参考。

原则一:技能的边界要“一问题一技能”。一个技能只解决一类问题。哪怕两个技能共享部分代码,也不要在一个技能里塞两个场景。技能一旦臃肿,匹配准确率就会下降,调试难度也会上升。

原则二:把确定性的逻辑交给脚本,把不确定的判断交给模型。算日期、格式化字符串、数据处理,这些都是确定性逻辑,脚本解决,又快又准;判断用户意图、决定走哪条分支、把结果转化成自然语言,这些需要灵活性,交给模型。不要把这两件事的职责搞混。

原则三:每个技能都要有“不适用清单”。我在 SKILL.md 里专门加了一节when_not_to_use。为什么要这个?因为模型是概率性的,它可能过度匹配——用户问“怎么生成一份报表模板”,它也去加载“报表解析”技能,虽然这俩完全是两码事。有了“不适用清单”,模型在犹豫时就有了明确的排除依据。

原则四:先离线,再在线。技能的调试要尽可能脱离 Agent 做,比如直接跑脚本、直接让模型读 SKILL.md 回答问题。等到离线确认技能本身可靠,再放回 Agent 里集成,否则你会把“技能的问题”和“Agent 的问题”混在一起,排查到怀疑人生。

我个人现在的工作习惯是:任何重复超过两次的任务,我都会想想“能不能沉淀成一个 Agent Skill”。这个习惯带来的收益是复利式的——积累的技能库越丰富,后续新项目的交付速度越快,而且质量稳定,因为验证过的技能不那么依赖临场发挥。这也是我做 Agent 应用以来最有价值的一个认知转变:别指望模型时刻都是天才,给它配一套好用的“肌肉记忆”,它才能稳定输出专业水准。

RELATED READING

延伸阅读

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