
今年做AI Agent应用最明显的感受就是模型越来越聪明但活儿不一定干得漂亮。让它聊天、总结、写文案是轻松可一旦要求它“按一套固定流程走完、再按指定格式交付结果”它就经常在某个环节给你跑偏。我们团队从年初开始认真对待“技能化”这件事绕了很多弯之后把重心落在了一个叫agent-skills的项目思路上。简单说就是给Agent准备一组可复用的“技能包”每个技能包含一份操作手册加一组脚本模型接到相关任务时按手册执行。这篇文章不讲空概念直接拆解agent-skills里面的设计逻辑、目录结构、实操细节和踩过的坑给正在做Agent应用或者准备把重复流程交给大模型的团队一个可复用的参考。1. agent-skills 到底解决了什么问题1.1 传统Agent工具箱的局限在哪现在大多数Agent项目都有“工具调用”的能力本质上就是给模型暴露一批函数查天气、算个价格、调一下数据库。这套机制解决的是“点状能力”模型需要什么就调什么就像给一个新人配上独立的工具他需要哪个就临时拿哪个。但对稍微复杂一点的任务问题马上就来了。比如我要一个“生成销售周报”的流程先读取数据源清洗掉异常值按模板汇总再输出成指定格式的Excel最后校验一遍文件是否能正常打开。用传统的function calling做你得把查数据、清洗、汇总、写Excel、校验拆成五个独立函数然后祈祷模型每一步都记得按正确的顺序调用、传递正确格式的中间结果。实测下来的感受就是偶尔能跑通一次但换一组数据、换一个措辞的请求它就开始自由发挥。不是你Prompt写得不好而是“多步流程 强约束规范”本身就是传统工具调用不擅长的事。这里要提一个很多团队忽略的点大模型在短上下文里执行单步操作非常稳定但在长上下文里同时管理“流程顺序、数据格式、输出要求、异常处理、业务规范”这五件事时注意力会被稀释。你会发现它该调用的脚本忘了调用不该用来凑字段的规则偏偏凑上了。这不是模型变笨了而是你让它同时承载了太多没有结构化的职责。1.2 技能包如何把“动作”升级成“流程”agent-skills的核心思路是把“某个岗位的整套干活方式”打包成一个自包含的模块。每个模块不是单纯的一堆函数而是“操作手册 脚本工具 参考资料”三件套。模型接到任务时先判断自己需要哪个技能然后读取对应的操作手册按手册里的步骤走需要执行脚本时再调用脚本。我习惯用一个类比来解释这件事以前你是在给新同事发一堆零散工具用时现找技能包则是给新同事一本《岗位操作手册》里面写了什么情况该做什么、具体怎么做、做到什么程度算合格工具箱就挂在手边。模型拿到技能包之后不再是“我自己想一套流程”去完成任务而是“按这套成熟流程”去完成任务。这个转变非常关键因为你在技能包里沉淀的是测试过、优化过、踩过坑之后的稳定流程而不是每次让模型临时发挥。用这个思路来做项目之后有几个很直观的好处。第一Agent的主Prompt可以保持精简不用塞一堆行业规则和操作细节。第二每个技能可以独立开发、独立测试、独立版本管理像是维护一个小型API库。第三一个技能能跨项目复用团队里A项目打磨好的报表技能B项目直接拿过去就能用。我们后来把同一套技能从月度报表迁移到财务口径报表只改了几行说明文档整个迁移成本极低。2. 深入拆解一个技能包的内部结构2.1 目录结构与三个核心部件的分工一个标准技能包长这样skills/ └── excel_report/ ├── SKILL.md ├── scripts/ │ └── generate_report.py └── reference/ └── report_template.xlsxSKILL.md是整个技能包的大脑也是给大模型读的操作手册。它解释这个技能在什么场景下用、执行步骤是什么、输出规范是什么、有哪些坑不能踩。scripts/目录放可执行的脚本是技能的“手”。reference/目录放模型执行复杂任务时需要参考的资料比如模板文件、样例数据、业务规则补充说明充当“参考书”。这套三层结构有一个内在的取舍逻辑把“描述性知识”放进SKILL.md和reference把“确定性计算”放进scripts。模型负责做判断和流程控制脚本负责做精确计算。举例来说你不可能让模型手动把1000行数据汇总成报表格式但你可以让模型判断“当前数据源是CSV符合技能使用条件”然后调用generate_report.py完成转换。这种分工让模型发挥它擅长的语言理解与任务拆解能力同时避开它不擅长的机械计算。2.2 SKILL.md是写给大模型看的产品说明书SKILL.md的写法与传统技术文档差别很大。传统文档默认读者是人你会写“本模块用于生成报表”而SKILL.md的读者是语言模型你需要写成“当用户请求符合以下条件时使用本技能执行时严格遵循以下步骤如果遇到XX情况执行YY处理”。一个可用的SKILL.md至少包含四个部分技能识别信息、适用条件、执行步骤、输出规范。技能识别信息写在文件头部的YAML区域包括技能名称和一句话描述。适用条件和输出规范则直接影响模型能否正确决策这个部分我建议写得像“准入条件”一样明确。举一个我们在实际项目中用过的桌面端报表技能示例--- name: excel_report description: 生成标准Excel报表支持CSV数据源转换、表头规范化、基础样式设置 when_to_use: 用户要求生成xlsx报表、周报、月报且提供或可获取到表格数据 when_not_to_use: 用户只是需要纯文本形式的结果或仅需要简单几句话的汇总分析 --- # Excel报表生成技能 ## 前置条件 - Python 3.10及以上版本 - 已安装 openpyxl 库 - 数据源文件必须为CSV格式且至少包含 date、channel、orders、sales、margin 五列 ## 执行步骤 1. 先确认数据源文件路径如果路径含糊先向用户确认 2. 用 scripts/generate_report.py 生成报表 3. 测试生成的xlsx文件是否可以正常打开 ## 输出规范 - 输出文件名为 report_YYYYMMDD.xlsx - 第一个Sheet名称固定为“汇总” - 表头顺序固定为日期、渠道、订单数、销售额、毛利率 - 数字列保留两位小数 ## 常见问题处理 - 如果CSV中有空值统一按0处理并记录到日志 - 如果文件编码不是UTF-8优先尝试utf-8-sig解码你可能会发现这份文档对“人的阅读体验”其实不算特别友好它更像一份给机器执行的规则声明。这正是SKILL.md该有的样子它不是给人欣赏的文档而是给模型看的行为准则。我在实际编写中有一条硬性约束凡是模型“必须绝对遵守”的内容放到“输出规范”和“执行步骤”里用明确的“必须/固定/统一”等词凡是模型可以灵活处理的内容才放到描述段落里。这样模型在解析文档时能轻松区别强约束与弱约束。2.3 scripts和reference怎么配才算顺手脚本的设计原则可以用一句话概括做得足够小、足够专、输入输出足够明确。一个技能下可以放多个脚本但我不建议写一个几百行的“万能脚本”。脚本应该像函数一样输入是明确命名的参数输出是确定格式的结果。比如generate_report.py接受一个输入CSV路径、一个可选输出路径成功时打印生成路径失败时打印清晰的错误信息并返回非零退出码。这样模型能根据脚本输出自行判断“接下来该干什么”。reference目录我通常用来放“模型可能需要查但不需要全部读入上下文”的资料。比如一个业务技能涉及十几种内部报表字段的口径定义一次性全塞给模型会占用大量上下文窗口影响它对主任务的注意力。正确的做法是在SKILL.md里写“如果遇到字段口径问题查阅reference/field_definitions.md”让模型按需去读。这相当于给模型一个“工具箱里的参考书”只在需要的时候翻开而不必从头背到尾。3. 从零搭建一个报表生成技能完整实操记录3.1 需求定义与验收标准先行我们实际操作时会先写一段“需求定义”把它当成技能的规格说明书。这里用销售周报生成技能举例。需求定义如下用户提供一份包含原始销售记录的CSV数据Agent需要把数据清洗后生成为标准Excel表格要求格式统一、字段规范、文件可正常打开。验收标准有三条第一输出文件名为report_YYYYMMDD.xlsx日期取当天第二Excel中第一个Sheet名是“汇总”表头依次为日期、渠道、订单数、销售额、毛利率第三用测试CSV跑通后文件用Excel或WPS打开不报错且每列宽度可读。很多时候项目做到一半出问题回头一看都是需求定义阶段没抠细节。比如“输出文件可正常打开”这一条听上去很基础但真到CSV带编码问题、字段带小数点精度问题的时候少一个校验环节就会让模型产出一个打不开或者打开后乱码的文件。所以我把验收标准这一步看得很重每一项都是后面测试的检查点。3.2 编写SKILL.md与脚本的技术细节SKILL.md按照前文的结构写好后脚本这边我给出了一个精简但足够支撑流程的实现。核心逻辑分成三块读取并解析CSV、格式化数据、写入Excel并设置基础样式。#!/usr/bin/env python3 generate_report.py - 根据CSV数据生成标准Excel报表 import csv import sys from pathlib import Path from datetime import datetime from openpyxl import Workbook from openpyxl.styles import Font, PatternFill, Alignment from openpyxl.utils import get_column_letter HEADERS [日期, 渠道, 订单数, 销售额, 毛利率] def load_csv(path: Path): rows [] with open(path, encodingutf-8-sig) as f: reader csv.DictReader(f) for row in reader: rows.append(row) return rows def format_rows(rows): result [] for r in rows: result.append([ r.get(date, ), r.get(channel, ), int(float(r.get(orders, 0))), round(float(r.get(sales, 0)), 2), round(float(r.get(margin, 0)), 2) ]) return result def write_excel(rows, output_path): wb Workbook() ws wb.active ws.title 汇总 header_font Font(boldTrue, colorFFFFFF) header_fill PatternFill(solid, fgColor4472C4) for col, header in enumerate(HEADERS, 1): cell ws.cell(row1, columncol, valueheader) cell.font header_font cell.fill header_fill cell.alignment Alignment(horizontalcenter) for r, row in enumerate(rows, 2): for c, value in enumerate(row, 1): ws.cell(rowr, columnc, valuevalue) for i in range(1, len(HEADERS) 1): ws.column_dimensions[get_column_letter(i)].width 14 wb.save(output_path) print(f报表已生成: {output_path}) if __name__ __main__: input_csv sys.argv[1] output_xlsx sys.argv[2] if len(sys.argv) 2 else freport_{datetime.now():%Y%m%d}.xlsx data_rows format_rows(load_csv(Path(input_csv))) write_excel(data_rows, output_xlsx)脚本本身不需要复杂关键在于它和SKILL.md的约定保持一致。CSV列名date、channel、orders、sales、margin是谁定的是SKILL.md里约定的。输出表头“日期、渠道、订单数、销售额、毛利率”是谁定的也是SKILL.md里约定的。这说明脚本不是独立的程序它是整个技能流程的一个环节它必须严格遵循技能包内部定义的接口契约。开发过程中脚本处理的是“确定性逻辑”而模型处理的是“判断和调度”两边边界越清晰整个技能越稳定。3.3 与Agent框架集成后的实测情况有了技能包之后还需要把它注册到Agent的技能列表里。这一步在实现上比较简单Agent启动时扫描skills/目录提取每个技能的name和description生成一份索引交给模型。模型拿到用户请求后根据描述判断该调用哪个技能一旦命中再把完整的SKILL.md内容加载进上下文并指示模型执行。这种“先看列表、后读全文”的设计是为了避免开头就把所有技能文档灌进上下文毕竟一个大项目的技能包可能有十几个全部塞进去会稀释注意力。我们做了三轮实测。第一轮给模型一句请求“帮我把今天的销售数据做成周报Excel”模型正确选择了excel_report技能并按SKILL.md里的步骤完成了报表生成。第二轮把请求改成“用数据算出周销售额发我个总结”模型判断这是纯汇总分析需求没有调用Excel技能直接给出了文字结果。第三轮故意给一份列名不完整的CSV模型的处理是先检查到了缺失列然后按SKILL.md里的“常见问题处理”规则向用户确认而不是自作聪明地用假数据填充。这三轮正好分别验证了技能命中、技能拒绝、异常处理三个关键行为整体表现符合预期。4. 落地过程中遇到的典型问题与排查实录4.1 SKILL.md写太长模型反而抓不住重点我们第一次写技能时生怕模型看不懂把背景说明、业务逻辑、历史变更、参考案例全写了进去成品大约有300行。一测发现模型经常忽略最后的输出规范生成的Excel字段顺序是乱的。排查后发现一个规律模型对文档开头和结尾的内容注意度较高中间夹着的信息容易被略过。解决办法非常简单把必须遵守的规则压缩到80行以内并把“输出规范”和“执行步骤”这两个最强约束的章节放在文档最核心的位置一大段背景说明挪到reference目录里去需要时再查。4.2 输出格式不稳定生成的Excel在边界条件下打不开早期我们还遇到过一类问题脚本逻辑没问题但测试数据里某些订单数字段为空int()转换直接抛异常导致报表生成中断。模型在遇到这类异常时如果SKILL.md中没有预设处理方案就会自己“想办法解决”比如用空字符串替代数字结果产出的Excel打开后表格样式混乱。后来我们在SKILL.md里明确写了“CSV中有空值时统一按0处理”同时在脚本中以容错方式实现模型和脚本双保险输出才稳定下来。这类问题本质上不是技术实现难而是“异常分支没有在技能定义阶段想清楚”。写SKILL.md时一定要把可能出现的脏数据情况、失败重试方案、无法处理时的用户反馈策略一并写进去。我用一张表来整理这次排查中比较高频的问题方便团队查阅现象根因解法模型忽略步骤直接给结论SKILL.md过长注意力被稀释精简到80行以内核心约束前置生成的Excel打不开脚本异常未处理 模型自行补数据规定空值按0处理脚本增加容错多个技能同时匹配用户请求description和when_to_use写得含糊明确技能边界补充when_not_to_use模型反复读取reference长文档上下文规划不当增加“仅某字段口径不明时查询”的触发条件升级脚本后旧缓存被复用技能未做版本管理为SKILL.md追加version字段并更新日志4.3 技能匹配冲突最隐蔽的坑技能多了以后会出现一个新的问题用户请求同时命中两个相似技能。比如我们有“销售周报生成”和“运营日报生成”两个技能的description都包含“生成报表”模型可能随机选了一个结果输出格式不符合业务预期。这类问题排查起来很痛苦因为不是每次必现是概率性的。解决思路是给每个技能的SKILL.md补充场景关键词让描述之间形成明显的区分带。同时要在when_to_use和when_not_to_use里写清楚“排除性”条件。比如销售周报技能里写明“仅当用户明确提到销售、订单、渠道时使用”运营日报技能里写明“不适用于销售口径数据”。这样一来模型可以根据用户请求里的业务关键词做出更确定的判断。另一个辅助手段是把高频场景的示例话术直接写到description里比如“适用于帮我把今天的销售数据做成周报”这明显比“生成报表”这类抽象描述更容易让模型命中。4.4 上下文占用高多技能连调时注意资源分配另外还有一个容易被忽视的问题就是多个技能连续调用时的上下文管理。比如用户先要求生成周报再要求把报表通过邮件发送出去这就涉及两个技能。每个技能加载SKILL.md和脚本说明都会占用上下文连着加载两份文档主任务的注意力会被分散。我们的做法是在两个技能环节之间增加一个“任务状态摘要”让模型用一段简短文字记录当前已完成的任务、生成的文件路径、需传递给下个技能的参数然后释放掉前一个技能的完整文档再加载下一个。这个“摘要切换”的机制在长链路任务中非常管用。5. 团队化技能库建设与进阶玩法5.1 一套可落地的技能评审流程当技能从一个扩展到十几个之后就面临和代码库一样的管理问题谁来维护、怎么保证质量、如何避免别人改坏了你的技能包。我的建议是把技能当成API来管理。命名规范上统一用“动词_对象”的结构比如fetch_user_list、generate_excel_report避免起一些玄乎的名字。评审流程上新增技能至少要过三关第一关是需求确认确认场景真实且重复度高第二关是技术评审看脚本边界是否清晰、SKILL.md的约束是否可执行第三关是灰度验证先用20条测试请求跑通过才允许接入正式环境。版本管理方面我们直接在SKILL.md的frontmatter里加了version字段任何改动都更新版本号并且保留一份变更记录。这样出问题时能快速回滚。有一点值得强调技能包的修改影响面比普通代码更大因为它是直接作用于模型行为的东西。你改一行描述模型对技能的理解可能就变了所以技能改动要走单独的测试流程不要顺手改完就上线。5.2 多技能协同与更远的编排思路技能真正发挥威力是多个技能串成一条流水线的时候。还是以报表加邮件为例技能A负责从数据库导出CSV技能B负责把CSV变成Excel技能C负责把Excel作为附件发送。每个技能保持单一职责技能之间通过明确的任务状态来衔接。这种设计让单个技能容易测试也让整个流水线可以灵活调整顺序。我们在实践中还尝试过让一个技能内部引用另一个技能的脚本效果也不错但前提是两个技能的接口说明都足够清晰否则模型容易在中间步骤上“迷路”。回到这整套思路的起点我最大的体会是不要把Agent当成全能执行者要把它当成一个“会阅读手册并严格执行的老师傅”。你的职责是把老师傅的手册写好、工具备好、边界划好。agent-skills解决的不只是“让模型学会一个新任务”更是“让一个已经会做很多事的模型稳定地做对你指定的那件事”。这个转变比再调多少次Prompt都重要。先把团队里最高频的三个重复流程技能化跑通一个完整闭环你就会明显感觉到维护成本和出错率同时降下来了。