
前段时间我把自己维护了大半年的 agent-skills 技能库重构到了第四个版本趁着记忆还热乎把设计思路、实操过程和踩过的坑一起整理出来。如果你正在做 Agent 应用尤其是那种模型自己决定该调用什么能力的落地场景这篇应该能帮你少走不少弯路。先说清楚 agent-skills 到底是什么。它不是一个模型也不是一个具体的聊天机器人而是一套可复用的智能体技能体系。核心要解决的事情只有一件怎么让 Agent 稳定地执行一个多步骤的真实任务而不是停留在能聊天、能简单搜索的玩具阶段。这个项目适合三类人看一是已经用 function calling 但觉得效率不够的开发者二是想给团队沉淀一套 Agent 能力的架构师三是正准备从零搭建智能体应用的工程师。1. 项目整体设计为什么我把工具升级成技能早年做 Agent 的时候我和大多数人的思路一样先接一堆工具函数比如获取天气、搜索数据库、发送邮件。单个工具调用看起来没问题可真到了业务场景就不够用了。运营同事丢过来一句帮我把这个月的销售数据整理成周报模型需要先定位数据文件、再清洗字段、然后计算汇总、最后生成文档这一串动作光靠十几个原子工具来回拼凑成功率非常低。模型很容易在某个环节选错工具或者在参数上漏传信息链路越长越容易翻车。后来我决定换一种组织方式不再把能力拆成碎片而是把完成一件事的完整方法封装成一个整体。这个改变直接催生了 agent-skills 项目的第一版。1.1 工具函数与技能的本质区别工具函数是原子操作它回答的问题是我能做什么动作比如读取一个文件、发送一个 HTTP 请求。技能回答的问题则是我能完成什么任务比如整理销售周报、批量处理 Excel 报表。我用一个生活化类比来理解这件事。工具函数像厨房里的刀、锅、铲子每一件都是独立的器具技能则是一道菜的完整菜谱它把洗菜、切菜、热锅、下料、调味、装盘这些动作按固定顺序组织起来还包含了火候、时间这类经验参数。给模型一堆刀具它未必能做出一桌菜给它一份写清楚的菜谱它才能稳定地端出一道菜来。从工程实现上看两者的差异也很明显。维度工具函数技能粒度原子操作复合任务是否包含决策流程不含包含步骤和判断逻辑可复用性单一场景复用跨任务、跨项目复用维护成本低但数量庞大前期成本高后期收益大模型学习成本每个工具都要理解只读技能说明即可我并不是说工具函数不该存在而是要把它们收拢到技能内部。技能负责编排工具负责执行两者互相配合。1.2 技能与 Prompt 的关系把经验固化成资产在做 agent-skills 之前我也试过把所有指令写在系统提示词里。比如在 Prompt 里写当你需要整理 Excel 数据时请先调用 read_file再调用 clean_data然后调用 generate_report。这种方式在小规模验证时没问题但一旦任务多了Prompt 会膨胀到令人崩溃的程度。更难受的是每次调整流程都要重新改 PromptPrompt 本身就是一段文本既不好测试也不好版本管理。技能的本质是把这些流程和约束从 Prompt 里抽出来变成一个独立的、可测试、可版本化的模块。模型在使用技能时只需要读取一份结构化的说明文档照着文档执行即可。这样系统提示词保持精简能力的增删改也不影响主线逻辑。这也是 agent-skills 项目最核心的设计哲学让能力像插件一样即插即用而不是像胶水一样糊在提示词里。1.3 agent-skills 设计时锁定的三个目标项目重构的时候我给 agent-skills 定了三条硬性目标。第一能力可复用。同一个技能不能只为一个业务场景服务它应该能被多个 Agent 共用。比如Excel 报表合并这个技能运营在用财务也在用谁调用都行。第二门槛要够低。不是只有资深工程师才能添加技能。一个熟悉 Python 的普通开发甚至一个懂业务的数据人员只要按模板写好说明文档和脚本就能把新能力注册进技能库。第三行为可预期。技能不是黑盒它要有清晰的输入输出边界、错误处理方式和日志记录。这样模型调用技能后我们能追踪每一步执行情况出问题能找到原因。2. 技能的标准结构从目录到说明文档技能模块长什么样是项目第一个要确定的事。我见过很多团队把技能直接当成一个大 Prompt塞给模型用这不叫技能顶多算一段提示词模板。真正能落地的技能必须有一套完整的物理结构。2.1 一个标准技能的文件目录我最终定下来的目录结构长这样skills/ excel-report-consolidator/ SKILL.md scripts/ build_report.py utils.py assets/ template.xlsx requirements.txt每个技能都有一个独立目录目录名即技能名用短横线分隔。目录内必须有SKILL.md作为该技能的入口文档scripts存放实际执行脚本assets存放模板、参考文件或其他静态资源requirements.txt声明依赖。我在项目里是这样约定的SKILL.md给模型看的说明书包含技能的触发条件、使用方法、注意事项。scripts/可执行的 Python 或 Shell 脚本模型需要知道怎么调用它们。assets/技能运行时的辅助资源比如模板文件、配置文件、参考样例。requirements.txt技能运行需要的 Python 依赖与主项目依赖隔离。这套结构的灵感来自于平时写的开源项目。把说明文档和代码分开让模型主读文档让脚本真正干活两边都能保持干净。2.2 SKILL.md 是给模型看的产品说明书SKILL.md是整个技能的入口模型会在决定调用技能之前先读到它。写法上我强调三件事说清楚技能是干什么的说清楚什么场景下该用说清楚怎么用。一个相对合理的SKILL.md头部长这样--- name: excel-report-consolidator description: 将多个 Excel 分表合并为一张汇总表并输出缺失值报告。 适用于销售日报、部门周报等需要批量合并表格的场景不适用于生成图表或数据透视表。 version: 1.2.0 dependencies: - pandas - openpyxl --- # Excel 报表合并技能 ## 用途 将指定目录下的所有 .xlsx 文件按行合并统一列名后输出汇总表同时生成一份缺失值统计报告。 ## 输入 - INPUT_DIR存放分表的目录路径 - OUTPUT_DIR汇总结果的输出目录 ## 输出 - consolidated.xlsx合并后的汇总表 - missing_report.json各列缺失值统计 ## 执行步骤 1. 检查 INPUT_DIR 是否存在且非空。 2. 读取目录下所有 .xlsx 文件。 3. 统一列名映射将同名不同格式的列名标准化。 4. 按文件顺序追加合并保留数据来源文件名。 5. 统计缺失值输出 JSON 报告。 ## 注意事项 - 仅支持 .xlsx不支持 .xls。 - 如果 INPUT_DIR 下没有文件直接返回错误不要生成空报告。这种写法看起来很朴素但非常管用。模型读完之后能清楚知道这个技能接收什么参数、产出什么结果、有什么坑。关键一行不适用于生成图表或数据透视表其实是在帮模型做负向判断避免它拿这个技能去做不擅长的事。2.3 description 字段决定模型看不看得到这个技能如果模型是一个顾客技能列表就是菜单description就是菜单上那道菜的介绍。写得好不好直接影响顾客会不会点这道菜。我踩过最深的坑就是把 description 写得太宽泛。比如刚开始我给某个技能写的是处理 Excel 文件看似没问题实际上一堆场景都会命中它模型经常在需要做数据透视的时候也调它结果自然不对。后来我总结出一个描述模板前半句写清楚任务内容。中间写适用场景尽量具体。最后写一句不适用的边界。比如description: 将多个 Excel 分表合并为一张汇总表并输出缺失值报告。 适用于销售日报、部门周报等需要批量合并表格的场景 不适用于生成图表、数据透视表或单个文件的单元格修改。这句话通常控制在 50 到 120 个英文单词之间。太短了信息不够太长了模型会失去重点。我在项目中做过一次统计description 在 100 词左右时技能命中率最高超过 200 词后触发率明显下降。2.4 技能版本别让改坏了没有回退机会技能不是写一次就完事的。业务调整、脚本修复、依赖升级都会让技能发生变化。我要求每个技能必须带版本号并且版本变化要记录在SKILL.md的 frontmatter 里。版本规则参考语义化版本管理主版本任务流程重写输入输出可能不兼容。次版本新增功能保持原有功能兼容。补丁版本修复 bug、优化措辞、更新依赖。这样做最直接的好处是当模型调用某个技能出问题时我能通过版本号快速回退到上一版而不是面向一堆改动无从下手。另一个好处是技能索引系统可以根据版本决定是否要刷新缓存避免给模型推送过期文档。3. 实操演示从零构建Excel 报表合并技能光讲概念太虚我拿项目里最常用的一个技能做完整拆解大家可以直接照着做。3.1 先定义需求边界当时是数据部门提的需求每周要收 20 多张分表每张表结构不完全一致需要合并成一张总表还要统计哪些字段有缺失值。最初他们是人工用 Excel 一个个复制粘贴既慢又容易漏。我接到需求后第一件事不是写代码而是明确技能边界支持合并同目录下所有 .xlsx 文件统一列名按行追加输出合并表和缺失值报告。不支持数据透视、图表生成、单元格格式调整、跨目录合并。边界写清楚是为了避免技能被模型拿去干它不擅长的事。如果需求方以后要图表功能新建一个技能而不是往这个技能里塞。3.2 写 SKILL.md让模型知道何时用、怎么用写脚本之前我先把SKILL.md写好。这个是反向操作很多开发习惯先写代码但技能类项目应该先写说明文档因为说明文档定义了脚本的行为契约。我把最终的SKILL.md里的执行步骤细化成五步检查INPUT_DIR是否存在且非空。读取目录下所有.xlsx文件。对每张表做列名标准化比如销售额和销量元统一映射为sales_amount。按文件名排序后按行合并并追加一列source_file记录数据来源。统计各列缺失值数量输出 JSON 报告。模型读到这里就知道整个执行流程是什么样的了。它甚至可以在无法完整执行脚本时按照这个流程描述向用户解释我现在要做什么。3.3 脚本实现考虑幂等性和异常处理脚本使用 Python 和 pandas 实现核心代码量不大#!/usr/bin/env python3 import argparse import json from pathlib import Path import pandas as pd COLUMN_ALIASES { 销售额: sales_amount, 销量元: sales_amount, 销售金额: sales_amount, 区域: region, 渠道: channel, } def parse_args(): parser argparse.ArgumentParser() parser.add_argument(--input-dir, requiredTrue, help分表所在目录) parser.add_argument(--output-dir, requiredTrue, help输出目录) return parser.parse_args() def normalize_columns(df: pd.DataFrame) - pd.DataFrame: df df.rename(columnsCOLUMN_ALIASES) return df def main(): args parse_args() input_dir Path(args.input_dir) output_dir Path(args.output_dir) if not input_dir.exists(): raise SystemExit(f输入目录不存在: {input_dir}) if output_dir.exists(): # 幂等处理先清理旧结果避免重复执行时累加脏数据 for old in output_dir.glob(consolidated.xlsx): old.unlink() else: output_dir.mkdir(parentsTrue) files sorted(input_dir.glob(*.xlsx)) if not files: raise SystemExit(输入目录下没有 xlsx 文件) frames [] for file in files: df pd.read_excel(file) df normalize_columns(df) df[source_file] file.name frames.append(df) result pd.concat(frames, ignore_indexTrue) output_path output_dir / consolidated.xlsx result.to_excel(output_path, indexFalse) missing result.isnull().sum() missing missing[missing 0] report_path output_dir / missing_report.json report_path.write_text( json.dumps(missing.to_dict(), ensure_asciiFalse, indent2), encodingutf-8, ) print(f合并完成共 {len(result)} 行输出至 {output_path}) print(f缺失值报告输出至 {report_path}) if __name__ __main__: main()几个细节值得展开说。第一参数通过命令行传入不在代码里硬编码路径。模型需要通过技能描述里的输入约定来调用脚本硬编码路径会让技能换个环境就崩。第二输出前先清理旧文件。这是幂等性的关键。如果没有这一步重复执行技能会在同一个输出文件上叠加数据生成的报告根本没法看。第三normalize_columns做了列名映射。不同来源的分表列名可能不同这是真实业务里最常见的问题在脚本里定义别名映射比让模型在对话里做列名猜测稳定得多。3.4 造测试数据验证技能写完脚本后需要造测试数据验证。我在临时目录里建了三个测试文件sales_2025_01.xlsx包含列销售额、区域、渠道sales_2025_02.xlsx包含列销量元、区域、渠道、备注sales_2025_03.xlsx包含列销售额、区域、渠道其中某些单元格留空执行命令python scripts/build_report.py \ --input-dir ./test_data/raw \ --output-dir ./test_data/output输出结果符合预期consolidated.xlsx里共 45 行来源文件字段正确missing_report.json准确列出了备注列和部分空值的统计。这里有一个容易犯的错测试完的test_data不要扔进技能目录。否则技能索引扫描时会把测试文件也当成资源加载既浪费 token 又可能让模型误读样例数据。我在skills/根目录加了.gitignore来排除test_data。4. 技能注册、路由与上下文控制技能一个个建好了下一步是让 Agent 能够在合适的时机选中并调用它们。这一节是整个 agent-skills 项目里工程复杂度最高的部分。4.1 两级检索先看菜单再读详情如果把所有技能的完整文档一次性塞进系统提示词token 消耗会非常大。假设一个技能文档 800 词100 个技能就是 8 万词主流模型的上下文直接爆掉。我在项目里用了两级检索机制。第一级把每个技能的名称和一句话 description 汇总成索引列表注入系统提示词。模型看到的是类似这样的内容可用技能列表 - excel-report-consolidator将多个 Excel 分表合并为一张汇总表并输出缺失值报告。适用于批量合并表格场景。 - webpage-detail-extractor从指定 URL 提取正文内容并结构化。适用于文章采集和内容分析。 - image-resize-batch批量调整图片尺寸。适用于压缩图片、生成缩略图。模型判断某个技能可能适用时第二级再去读取该技能目录下的完整SKILL.md。这个步骤用代码控制模型只需要输出一个技能名系统就自动加载对应文档并注入到后续消息里。两级检索的好处是系统提示词始终很短模型负载低技能详情只在需要时出现。4.2 路由冲突当多个技能看起来都差不多技能多了之后最让人头疼的问题是描述重叠。做数据分析的技能可能同时有excel-report-consolidator、>description: 将多个 Excel 分表合并为一张汇总表。 适用于批量合并场景。如果你需要的是数据透视或图表生成 请使用 table-transformer 技能。这有点像是给模型一份纠错指南。实测下来路由准确率能从 70% 提升到 90% 以上。4.3 技能输出的上下文管理不是只有输入会撑爆上下文输出同样会。有些技能跑到最后会打印一大段数据比如把整个合并后的表格以文本形式输出给模型。模型拿着 5000 行的文本接着对话性能和效果都会断崖式下降。我在技能脚本里加了一条约定向模型返回结果时只输出关键摘要不输出完整数据集。比如合并脚本最后打印的是合并完成共 45 行写入 consolidated.xlsx 缺失值报告备注列缺失 12 个渠道列缺失 3 个详细内容模型可以通过文件读取但打开文件读取这个动作也要加限制只读取前 N 行作为预览。这相当于给技能的输出通道加了一个闸门保证上下文始终处于可控状态。如果某个任务确实需要完整数据我建议把完整数据写入中间文件技能返回文件路径让模型按需读取。这样既保留了信息又不污染对话上下文。5. 常见问题与排查技巧实录项目做到第四个版本大大小小的坑踩过无数遍。下面这些问题是出现频率最高的我整理成了一份速查表。问题典型症状排查方向解决办法模型不调用技能用户明确提出了某类需求模型却直接拒绝或泛泛而谈检查 description 是否准确、技能是否在索引列表里用专属场景词重写 description确保技能出现在候选列表模型选错技能调用了 A 技能但实际任务更适合 B 技能检查 A 与 B 的 description 是否重叠增加互斥说明引入关键词标签过滤技能执行报错脚本抛出异常模型不知所措查看脚本日志确认参数传递是否正确在 SKILL.md 中补充错误处理说明让模型能根据报错提示重试输出结果污染上下文对话历史中出现大段表格或长文本检查脚本是否打印了完整数据集修改脚本只输出关键摘要和文件路径重复执行产生脏数据同一技能执行两次结果文件内容翻倍检查脚本是否做幂等处理在输出前清理旧文件固定输出文件名依赖冲突技能需要 pandas 2.x主环境是 1.x检查 requirements.txt 是否独立为技能建立独立虚拟环境或使用容器隔离技能描述过长被截断模型只见到 description 的前半段导致误判检查 description 长度控制在 120 词以内把最关键信息放前 50 词5.1 模型一直不调用技能怎么办这是新手最容易遇到的问题。我见过一个案例开发者在技能描述里写的是处理数据文件结果用户说帮我把这个 Excel 里的数据整理一下模型完全没意识到要调用技能因为处理数据文件太抽象了和Excel没有任何直接的词面关联。排查思路是这样的先确认技能是否在索引列表里再确认 description 里的核心场景词和用户需求的语义距离有多远。如果用户说Exceldescription 里至少要有Excel 表格这样的词不能写电子表格或数据文件这种绕弯的表达。修改后的描述可能是将多个 Excel 文件合并为一张汇总表。 适用于用户提到 Excel、分表、合并、汇总、报表等场景。把用户可能用的词放进来命中率会大幅提升。不要担心描述啰嗦关键是让用户语言和技能语言能对上。5.2 技能执行成功了但结果模型没用上这个问题的隐蔽性很强。脚本正常跑完文件也生成了模型却在回答里说我没有找到整理后的数据。往往是技能输出格式不符合模型预期。解决方案是统一技能的结果返回格式。我规定所有技能必须向模型返回一段固定结构的结果摘要执行成功。 产出文件/path/to/consolidated.xlsx 行数45 字段sales_amount, region, channel, source_file 缺失值摘要见 /path/to/missing_report.json模型拿到这段摘要后就能准确向用户汇报结果也能根据字段信息决定下一步要不要做额外处理。5.3 新增技能后原有技能变笨了技能库越加越大模型反而越来越不会选。这也是正常的。当候选技能超过 30 个光靠 description 列表模型的选择难度会指数上升。我后面加了一层技能分组逻辑。比如所有数据处理类技能归为>