这两年做智能体开发最明显的一个感受是大家已经不满足于让模型“会聊天”了而是真正想让智能体去干活。但干活这件事光靠提示词堆功能清单效果极不稳定。你写了一大段“你可以调用A工具、B工具、C工具”模型在实际运行的时候要么不知道该调哪个要么调了之后参数传得乱七八糟。我自己的项目里早期光是修这种工具调用逻辑就占了一半的调试时间。直到我尝试了“agent-skills”这套技能化的组织方式把一个个能力封装成独立、自描述的技能包之后整个开发体验才真正顺了起来。这篇就聊聊我实践 agent-skills 的一些积累包括技能包怎么设计、怎么写描述、怎么编排、怎么排障。如果你正在做 agent 类应用或者想把现有项目改造成“技能驱动”的结构这篇文章应该能帮你省掉不少弯路。我尽量讲实际踩过的坑和验证过有效的做法而不是泛泛的概念。1. 理解 agent-skills从“工具列表”到“技能体系”1.1 工具与技能的根本区别很多刚接触这方面内容的朋友第一个疑惑往往是技能和工具不是一回事吗我的代码里写几个函数注册给 agent 调用这不就是技能吗从表面看确实类似但实际跑起来差别非常大。工具是最小的可执行单元比如“搜索文件”“读取CSV”“发送HTTP请求”它只做一件事不关心上下文也不关心目标。技能则是一个有输入输出约定的、完整的能力单元它通常包含了“什么时候用”“怎么用”“需要哪些参数”“产出什么结果”这些描述信息甚至还可以内嵌一小段执行脚本或提示词模板。我举个生活化的例子。工具好比螺丝刀和扳手单独给你一把螺丝刀你知道它是拧螺丝用的但没人告诉你这颗螺丝要拧在哪里、拧多紧、拧完之后怎么办。技能则是一个完整的“换门把手”操作卡上面写着适用场景、准备工具、操作步骤、验收标准。模型拿到操作卡之后不需要再去猜该怎么组合这些底层工具也不用在每一步都靠推理硬撑而是按技能包里的说明去执行成功率自然高很多。这种区别在 agent 实际运行中的体现尤其明显。注册一堆工具给 agent表面上选择很多但模型每次都要在几十个函数里做选择选择一多错误率就上来了。而技能包相当于提前帮模型做了“决策剪枝”——它只把当前任务真正相关的技能暴露出来而且每个技能自带详细的使用手册。说白了工具是零件技能是装配好了的组件agent 直接拿组件干活而不是自己临时去装配零件。1.2 技能包要解决的四个真实痛点我回顾自己做过的几个 agent 类项目发现技能化改造前后痛点非常清晰。这里整理成表格对比一下“裸工具调用”和“技能包体系”的差距痛点裸工具调用阶段的现状技能包体系的解法提示词膨胀把几十个工具说明塞进 system prompt上下文越来越长模型注意力被稀释技能描述按需加载用到了才进入上下文prompt 保持精简调用选择困难面对众多工具模型经常选错或犹豫产生无效循环调用技能是面向业务的语义化单元模型判断“该不该用”比“该用哪个函数”简单得多参数传递错误日期格式、单位、枚举值等细节频繁传错需要大量额外修正提示每个技能定义了严格的参数 schema 和校验规则错误在进入执行层之前就被拦截行为不稳定相同任务做两次模型可能走两条完全不同的调用路径技能将路径固化为标准流程模型按流程执行结果可复现这四个痛点我相信任何一个把 agent 推到生产环境的人都深有体会。尤其是参数传递错误这个问题看着小实际上非常磨人。有一次我做一个数据统计类 agent模型每次把“本月”理解成不同的日期范围我修了提示词、加了示例还是偶尔出错。后来我把统计逻辑封装成一个带明确日期参数和格式示例的技能包问题直接清零。对我来说agent-skills 的核心价值就是把“模型的临场发挥”变成“按技能卡执行”。模型当然还是需要一定推理能力但高度不确定的部分被技能包收敛了。这种工程化思路才是 agent 从 demo 走向生产的关键一步。2. 技能包的架构设计与定义规范2.1 一份技能包的核心结构拆解我实践下来一份标准的技能包通常包含三块内容描述文件、执行脚本、示例与校验文件。描述文件是给模型看的解释了技能是什么、什么时候被触发、需要什么参数执行脚本是实际干活的代码可以用 Python、Shell 或者任意语言但必须满足统一的调用约定示例与校验文件是给开发和测试用的确保技能的输入输出符合预期。用文件结构来表示大概是这个样子skills/ └── monthly-report/ ├── SKILL.md ├── generate_report.py └── examples/ ├── valid_case.json └── boundary_case.jsonSKILL.md 是核心。它就是给模型读的“使用说明书”我见过很多糟糕的技能包执行脚本写得很好但 SKILL.md 只有一句话“生成月度报告”模型根本没法判断什么时候该用这个技能。好的 SKILL.md 至少应该包含技能名称和一句话总结、适用场景和触发条件、输入参数说明、执行流程概述、输出格式和成功标准。我自己写 SKILL.md 的时候会先问一个问题如果把这个文件拿给一个完全不了解业务的新手他能根据这份说明顺利完成调用吗如果不能说明描述还不够。模型本质上就是一个阅读说明书做事的执行者说明书的质量直接决定执行质量。2.2 描述文件怎么写才容易被 agent 正确理解描述文件不是写给人看的文档而是写给模型看的“检索索引操作手册”。这里的写作技巧很讲究我总结了几个比较实用的原则。第一用行为动词开头而不是用名词分类。比如“分析销售数据并生成月度统计报告”就比“月度报告模块”好得多。模型对动作类语义的理解非常强因为它在预训练阶段见过大量“做某事”的指令式文本面对行为描述时触发判断会更准确。第二明确触发条件和排除条件。光写“用于生成月度报告”不够还要写“当用户询问本月销量、业绩汇总、环比变化等问题时使用如果用户需要的是实时数据查询不使用本技能请选用实时查询技能”。这种正反结合的描述能大幅降低模型误调用。第三提供参数说明和典型示例。每个参数都要标注类型、取值范围、默认值并且给出“好的示例”和“坏的示例”。比如日期参数直接声明接受“YYYY-MM-DD”格式示例写“2025-06-01”比在提示词里反复强调“用标准格式”管用得多。还有一个容易被忽略的点不要在技能描述里写太多无关信息。有些开发者喜欢在 SKILL.md 里写开发背景、作者信息、历史变更记录这些内容对模型理解技能没有帮助反而会稀释真正重要的触发信息甚至让模型在生成 token 的时候跑偏。极简、结构化、面向行为才是正确写法。2.3 技能粒度过大过小都是坑技能粒度是我曾经非常头疼的问题。最初我把整个业务线封装成一个超大技能包含十几个步骤模型虽然容易触发但执行过程中一旦某个中间步骤出错整个技能就废了排错也困难。后来我又走向另一个极端把“读取文件”“清洗数据”“计算平均分”这种细粒度操作都单独封装结果技能数量爆炸模型在几百个技能里选择反而比直接用工具函数还容易出错。不断调整之后我形成了一个判断标准技能的粒度应该对应一个“可独立验收的业务结果”。什么叫可独立验收就是这个技能执行完后产出物对用户来说是有意义的结果。比如“生成月度报告”是一个合适的技能“清洗数据”就不是因为清洗数据这个动作本身没有对用户交付价值它只是中间步骤。中间步骤应该放在执行脚本或技能内部的流程里而不是暴露给 agent 做选择。这个粒度选择本质上是在做职责划分。agent 的认知资源有限我们通过技能包帮它减少决策负担但又不能因为技能太大而引入单点风险。我现在的经验是单个技能的内部步骤尽量控制在三到五步执行耗时尽量控制在一两分钟内。如果技能内部流程变复杂了优先拆成多个子技能再用一个“编排技能”把它们串起来而不是把内容全塞在一起。3. 从零构建一个技能包的实操过程3.1 场景选取与目标定义方法论说再多不动手都是空的。这一节我拿一个真实做过的小项目来拆解整个构建过程。项目背景很简单我需要做一个周报生成智能体它要读取一周的项目数据自动汇总任务进展、风险点和下周计划输出成固定的周报模板。我平时负责的样板项目里这种汇报性质的任务占了业务方不少提问量每次都靠模型即时从零生成格式不稳定、数据口径还经常对不上。确定这个场景之后我没有急着写代码先做了两件事定义输入和定义输出。输入方面这个技能需要一个工作目录路径、一个统计时间范围以及一个包含任务状态表的数据文件。输出方面我希望它输出一个结构化的 Markdown 文档包含“本周完成”“存在问题”“下周计划”三大板块并且风险点必须以列表形式带优先级标注。把输入输出先定死后面的技能设计才不至于散掉。这个阶段最关键的经验是目标要具体到可验收。不要写“生成一份不错的周报”而要写“输出的文档必须包含哪些字段、按什么顺序排列、达到什么格式标准”。因为模型判断自己任务完成与否很大程度上依赖你给出的验收标准。标准越清楚它执行完毕后的自我检查就越有效。3.2 编写技能描述与参数定义场景和目标都明确了接下来就是写 SKILL.md 和执行脚本。我把 SKILL.md 的实际内容简化了一下保留核心结构给大家做个参考--- name: generate_weekly_report description: 根据项目任务数据生成结构化周报用于项目周会汇报 when_to_use: 当用户要求生成周报、汇总本周进展、整理项目风险时使用 when_not_to_use: 当用户只需要查询单个任务状态或需要实时进度时不使用本技能 parameters: data_dir: string, 必填存放任务数据文件的目录路径 start_date: string, 可选格式YYYY-MM-DD默认本周一 end_date: string, 可选格式YYYY-MM-DD默认本周日 output: format: markdown required_sections: [本周完成, 存在问题, 下周计划] risk_format: - [P0/P1/P2] 风险描述 --- # 执行步骤 1. 读取 data_dir 下的任务数据文件 2. 按任务状态字段过滤出本周已关闭和进行中的任务 3. 统计关闭任务数、延期率、P0风险数 4. 按输出模板生成周报 5. 将周报保存至 data_dir/周报/ 目录并返回文件路径这里有几个参数设计的细节值得说明。data_dir 我特意标注了“必填”因为技能脚本必须知道去哪里找数据start_date 和 end_date 则设成“可选”因为默认值可以直接写在执行逻辑里减少模型需要提供的参数数量。参数越少、约束越明确模型传入合法值的概率越高。把可以程序推断的东西写死在脚本里只把真正的用户意图暴露成参数这是技能设计里性价比很高的优化。参数类型我全部用了字符串这是刻意为之。不要试图让模型直接传复杂结构或者数组文本交互场景下字符串是最不容易出错的形态。真要传列表我会在字符串里用逗号或换行分隔脚本里再解析。模型在生成“2025-06-02,2025-06-08”这种数据时比生成嵌套 JSON 稳定得多。3.3 执行脚本与调用接口的实现执行脚本这块相对简单但它要严格遵守一个约定接收参数的格式和 SKILL.md 里定义的完全一致。我这里用一段简化版的 Python 脚本展示核心逻辑import json import sys from datetime import datetime, timedelta def parse_date(value): if not value: return None return datetime.strptime(value, %Y-%m-%d) def run(params): data_dir params[data_dir] start_date parse_date(params.get(start_date)) end_date parse_date(params.get(end_date)) if start_date is None: start_date datetime.now() - timedelta(daysdatetime.now().weekday()) if end_date is None: end_date start_date timedelta(days6) # 读取数据、聚合统计、渲染模板、保存文件 # ... return {status: success, report_path: output_path} if __name__ __main__: input_params json.loads(sys.argv[1]) result run(input_params) print(json.dumps(result, ensure_asciiFalse))我特别强调一点执行脚本的输出一定要结构化。无论成功还是失败都返回一个 JSON包含 status 字段和结果信息。失败的时候不要只抛异常而要返回一个带 error_code 和 error_message 的 JSON。这样做的好处是agent 拿到结构化错误之后可以直接依据错误信息决定下一步动作比如修正参数后重试一次或者主动告诉用户“技能执行失败原因是数据文件不存在”。我在实际项目中发现脚本如果返回纯文本或直接崩溃agent 经常会卡在同一个错误上反复重试白白浪费大量推理时间。而结构化的错误信息等于给了 agent 一条明确的问题线索让它能做出合理决策。这个细节对我的系统稳定性提升非常大。3.4 本地验证与效果评估技能写完不等于完事必须验证。我的验证流程分三步走。第一步用固定的测试参数直接调脚本确认脚本本身逻辑无误这一步排除了执行层的 bug。第二步把技能接入 agent 测试环境给模型一个典型用户请求比如“帮我生成上周的项目周报”观察模型能否正确触发技能、传入参数是否正确。第三步准备十个左右的边界输入包括空目录、日期范围无数据、文件格式损坏等情况逐一验证技能在异常输入下的表现。这里我建议记录一个简单但有效的指标——技能调用成功率也就是“一次调用中参数校验通过、脚本正常执行、结果格式符合要求”的比例。实践中我发现初期这个指标往往只有六成左右大部分失败原因都是参数问题日期格式传错、路径写错、把一周理解成自然周而不是项目周期。每次失败我都会回到 SKILL.md 去补充描述和示例直到给它一个更容易理解的表达方式。这本质上是一个“提示词工程技能调参”的磨合过程需要耐心但效果非常明显。4. 技能编排多个技能如何协作完成复杂任务4.1 串行编排与并行编排的取舍单个技能做多了之后你自然会发现一个新问题一个任务往往需要好几个技能配合才能完成。比如我的周报智能体除了“生成周报”还需要“拉取任务数据”“比对历史数据”“格式化输出”等技能。怎么把这些技能串起来变成一条稳定可靠的工作流就是编排层要解决的事。串行编排是最自然的方式。前一个技能的输出作为后一个技能的输入步骤清晰容易追踪。我早期做编排全部用的串行好处是 debug 特别方便一旦任务执行失败我能直接从链路上看出是哪个环节出了问题。但串行的问题也很明显链路长度会导致总体耗时线性增长而且前面的技能一旦失败整个任务就中断了。并行编排可以提升效率但复杂度成倍上升。多个技能同时跑彼此之间如果有共享依赖比如都要读写同一个配置文件就会产生冲突。我的建议是默认用串行只有当技能之间完全没有依赖关系时才考虑并行。举个安全的并行例子周报里需要同时读取任务数据和项目成员信息这两份数据互不依赖就可以并行读取让根因分析和大范围上下文读取同步完成最终再合并到同一个上下文里。4.2 技能之间的状态传递与数据共享技能编排绕不开一个问题状态和数据怎么在多个技能之间传递。我尝试过几种方案最终稳定下来的是“工作目录标准文件名”的模式。具体来说我在技能包体系里约定了一个工作目录每个技能执行后都可以把中间产物写入这个目录后续技能通过读取标准文件名的文件来获取上游数据。这种模式的好处是解耦。技能之间不直接传递内存对象而是通过文件系统交换数据任何一个技能都可以单独运行、单独测试。只要文件格式约定不变技能内部怎么改都不影响其他技能。坏处是多了一次磁盘读写性能有一些损失但在非高并发的 agent 场景下这点开销完全可以接受而工程健壮性提升却是实打实的。在传递数据时格式约定要非常严格。我会把每个技能的输出格式写进“数据字典”并把它作为 SKILL.md 的附件挂上规定所有中间文件统一使用带 schema 的 JSON禁止贪图方便直接传 Markdown 或者散乱文本。否则你就会发现模型为了拼接上下文把格式改来改去最终结果根本没法稳定解析。4.3 容错机制与降级策略编排层必须考虑失败的情况。技能跑挂了agent 至少要能给出一个可接受的反馈而不是整个任务直接崩溃。我常用的策略是分级降级关键技能失败时允许 agent 用替代技能或者简化流程完成部分目标非关键技能失败时跳过该环节但要在最终报告里注明缺失项。举个例子我的周报智能体会调用“风险分析”技能这个技能依赖项目日志数据。如果某次日志数据不完整技能无法正常运行我不会让整个周报生成流程停掉而是跳过风险分析生成一份带“本项目周期内风险数据不足未进行分析”标注的周报。用户拿到手知道缺失了什么agent 也没有白跑一趟。此外重试逻辑也不宜过度。我自己踩过坑给某一次执行配置了三次自动重试结果任务卡在一个必失败的错误上浪费了接近两分钟才抛出最终错误。现在的做法是技能执行失败时只允许 agent 根据结构化错误信息做一次“修正参数后重试”如果失败立即降级或交给用户决策。宁可早点失败也不要让用户一直等无效的重试循环。5. 常见问题与排查技巧实录5.1 现阶段最普遍的坑agent 选错技能技能多了之后最头疼的问题就是选错。比如我的系统里同时有“生成周报”和“生成月报”两个技能它们的参数几乎完全一样描述也相近模型经常把用户“看一下这周的进展”触发成月报生成。我排查下来发现根因是 SKILL.md 里的触发条件写得不够有区分度。针对这个问题我有两个行之有效的改进办法。第一在触发描述里加反向关键词明确写“当用户提到‘月’、‘本月’、‘月度汇总’时优先考虑月报当提到‘本周’、‘这周’、‘近七天’时优先考虑周报”。第二为容易混淆的技能互相加“排除引用”在周报技能的 when_not_to_use 字段里直接写“如果用户明确要求月度统计请调用 generate_monthly_report 而非本技能”。这种交叉引用能够显著降低选错率。还有一种比较隐蔽的选错原因技能名称不够语义化。我早期有个技能叫“processor”描述写的是“处理数据文件”结果模型在遇到任何数据类问题的时候都倾向于选择它因为它名字看起来“万能”。后来我改成“detect_outlier_in_task_progress”触发准确率立刻上升。技能名和描述一定要指向具体行为通用词汇是大忌。5.2 参数提取不准和信息缺失参数问题是技能调用失败的头号原因。模型从用户的一句话里提取出“data_dir”和“start_date”经常会有遗漏或者理解偏差。最常见的是日期用户说“上周”模型可能给出一个具体日期但也可能给出“2025-06-01 到 2025-06-07”这种范围字符串完全不符合参数定义。应对这个问题我有几个比较实用的技巧。一是定义宽松输入让参数解析函数兼容多种日期表达。在技能脚本里我先做一次模糊解析支持“自然周”“上个月”“本周一”这类相对表达解析失败才向 agent 索要明确日期而不是让 agent 一开始就去猜具体日期。二是给每个参数设置“缺失默认值”。我在实践中发现让 agent 去追问用户补齐参数往往比用默认值跑出结果更耗时而且追问多了用户会烦躁。除非是关键业务字段否则我都会在设计阶段定好合理默认值保证技能在信息不完整时也能产出可用结果同时在输出中标注“使用了默认值如需特定范围请在报告中修改”。5.3 执行环境不干净和执行超时技能脚本跑不起来、跑不动是另一种高频问题。我遇到过的场景包括Python 依赖版本冲突、数据文件编码不是 UTF-8、磁盘没有写权限。这些问题的共同特点是脚本在你自己电脑上验证时一切正常但一放到生产环境就歇菜。要解决这类问题首先要把技能的执行环境隔离起来不能让它和主程序共享所有依赖。我现在的做法是每个技能包自带一个 requirements.txt在技能首次执行时自动创建独立的虚拟环境并安装依赖。这样即便某个技能引入了比较冷门的第三方库也不会污染其他技能的运行环境。超时问题则需要给每个技能设定合理的超时上限。我经历过一次某技能正常情况两秒跑完但数据文件特别大的时候要跑十分钟结果 agent 一直傻等最终会话超时。后来我养成了习惯在技能注册配置里明确标注 timeout 值执行器严格按这个值做熔断。同时脚本内部定期打印进度日志方便中断之后判断跑到了哪一步。这个习惯在排障的时候给我省了非常多时间。6. 补充一些体会从技能库到技能文化技能包做到一定数量之后你会发现这套体系带来的最大收益反而不是单个技能的稳定性而是整个开发协作方式的改变。技能包是高度内聚的、可独立迭代的模块每个技能的维护边界非常清晰。某个技能出了问题只需要改那一个目录里的文件完全不需要担心影响 agent 的其他行为。这种“可以局部修改而不爆炸”的感觉对持续迭代的团队来说价值极高。我还有一个习惯每个技能包写完后我会补几颗“反例”示例文件记录这个技能曾经出过的典型错误。这些反例会作为描述文件的补充内容沉淀下来下一次模型再遇到类似场景时系统可以从这些反例中汲取经验避免重复踩坑。实际上追踪技能失败历史是打磨 agent 行为最有效的数据来源之一越往后期你调描述文件的次数越少因为这些历史数据已经把绝大多数边界情况覆盖了。最后再分享一个很实在的建议刚开始做技能包的时候不要追求大而全要先从当前 agent 最常被问到的、最需要稳定输出的那三个需求开始把三个技能包做扎实跑顺整个封装、注册、调用、排障的流程。实践过三轮之后你自然会有手感知道哪些描述该精简、哪些参数该下放、哪些环节需要脚本兜底。这套东西不难但它需要真刀真枪跑业务的时候才能积累出真正适合自己的规范。