
如果你最近在折腾 LLM Agent应该会明显感觉到圈内讨论的话题正在变化大家的关注点已经从“怎么让模型回复得更像人”转向“怎么把模型的能力沉淀成一套可以复用的工程资产”。我这两周刚好在打磨一个叫 agent-skills 的技能库项目把高频业务操作封装成带说明、带校验、带示例的技能文件再让 Agent 按需加载、按步骤执行。做完这一轮之后我基本改掉了以前那种“每个任务都现场写一段提示词”的坏习惯。这篇文章会把从技能库目录设计、核心机制拆解到真正接入业务系统、再到在真实环境里反复翻车的完整过程都整理出来给正在考虑做 Agent 技能化的同行一个参考。1. 从“提示词堆砌”到技能化agent-skills 要解决的第一个问题1.1 为什么我放弃了“万能提示词”方案在动手写 agent-skills 之前我的 Agent 项目走的是最朴素的路子写一个超级系统提示词把角色设定、业务规则、输出格式、举例全部塞进去然后期待模型面对任何请求都能稳定输出。现实很快打了脸。项目跑了一个月后系统提示词从最初 2000 字膨胀到了 6000 多字。里面塞了十几个业务场景的规则。结果就是A 场景的规则开始干扰 B 场景的判断模型在上下文过长时对提示词后半段的内容遵循度明显下降每次新增一个业务模块都要从头读一遍那坨提示词改一行可能引发另外三处行为异常。我后来做了一个统计6000 字里真正在每次请求中都会生效的可能不到 800 字。其他内容都是“防御性”的——为了防止某些边缘情况结果把所有情况都拖慢了。这让我意识到一个问题提示词不是不能写而是不能无限堆。Agent 的能力应该拆成一块一块的积木按需取用而不是一次性全部挂在主流程里。1.2 什么样的任务才值得沉淀成技能决定做 agent-skills 之后我第一个动作不是去写代码而是把项目里已有的任务清单拿出来做了一遍分类。分类标准很简单这个任务出现的频率高不高每周至少触发一次才值得做技能。任务的执行流程是不是相对固定的如果每次的逻辑都完全不同技能文件写出来也没法复用。任务对输出格式的约束是不是明确的比如“必须输出表格”“必须包含某个字段”这类强约束任务很适合技能化。按这个标准我当时筛出了两个高频场景一个是周度销售对账报告生成一个是客户工单的分类与流转建议。这两个任务流程稳定、输出格式要求严格、每周都会用到非常适合封装成技能。而一些探索型任务比如“帮我想想这个投放活动怎么做”不确定性太高技能文件写出来也只能是空话没必要硬套。所以 agent-skills 这套方案的核心判断是技能化解决的是“已知的重复问题”而不是“未知的一次性问题”。后者应该靠模型本身的推理能力而不是靠堆技能文件。2. 技能库的目录设计与技能文件规范2.1 为什么技能文件一定要有固定结构我是从其他项目里吃过亏的。最早我尝试用 JSON 结构来定义技能每个技能维护一个 JSON 配置工具调用、参数说明全部写在 JSON 里。结果有两个问题JSON 对注释的支持很差字段说明只能靠命名理解跨月之后自己都忘了某个字段是干嘛的。大语言模型对 JSON 的遵循度在复杂约束下不如自然语言指令稳定。让模型照着一段自然语言步骤执行比让它解析深层嵌套 JSON 可靠太多。所以 agent-skills 的技能载体用了 Markdown外层用 YAML frontmatter 写元信息正文用自然语言写执行步骤和规则。这样人看着清楚模型读着也顺。目录结构我最终定成了这样agent-skills/ ├── skills/ │ ├── weekly-sales-report/ │ │ ├── SKILL.md │ │ ├── scripts/ │ │ │ └── generate_report.py │ │ ├── resources/ │ │ │ └── report_template.xlsx │ │ └── tests/ │ │ └── test_skill.py │ └── ticket-classifier/ │ ├── SKILL.md │ └── scripts/ ├── index.json ├── loader.py └── README.md每个技能一个独立目录至少包含一个 SKILL.md 文件。脚本、模板资源、测试脚本都放在对应技能目录下互不干扰。这样做的直接好处是技能的增删改不需要改动主程序只要这个目录还在Agent 就能动态发现它。2.2 SKILL.md 的字段设计和描述写法SKILL.md 的 YAML frontmatter 部分我保留了这几个核心字段字段作用我的写作建议name技能唯一标识用短横线命名比如weekly-sales-reportdescription技能检索时被比对的关键文本写清楚“触发场景 输入 输出”这是整个技能库最重要的字段dependencies执行技能需要的 Python 依赖或外部工具加载时检查和安装避免运行时才发现缺失tools技能可能需要调用的脚本或 API显式声明避免 Agent 自己发挥去乱猜工具名正文部分我强烈建议分成几个小节目标、执行步骤、输出格式、注意事项、示例。其中“执行步骤”要写成编号列表每一条必须是明确的动作不能出现“视情况而定”这种话。模型在看到一个编号步骤列表时按序执行的概率远高于阅读散文式说明。关于description的写法我踩过很大的坑后面专门讲。这里先给一个正确示例--- name: weekly-sales-report description: 当用户需要生成周度销售对账报告时使用。输入为周一日期输出为包含销售额、退款率、异常订单三部分的 Markdown 报告。 dependencies: [pandas, openpyxl] tools: [scripts/generate_report.py] ---关键字是“当用户需要……时使用”这种以触发条件开头的描述在检索阶段命中率远高于“该技能用于生成销售报告”这种功能定义式写法。因为用户请求通常是“帮我写下上周的销售汇总”这里既有“销售”又有“上周”描述里只有“销售报告”其实匹配不到时间语义但通过“生成周度销售对账报告”这种完整句式模型能更好理解。3. 核心机制拆解技能注册、检索与执行怎么打通3.1 技能注册把 Markdown 变成可查询的索引Agent 要使用技能首先要能发现技能。我在 loader.py 里做了一个目录扫描器启动时会遍历skills/下的所有子目录读取每个 SKILL.md 的 YAML frontmatter生成一份index.json。import os import yaml import json SKILLS_DIR skills def load_skills_index(skills_dirSKILLS_DIR): index [] for skill_name in os.listdir(skills_dir): skill_path os.path.join(skills_dir, skill_name) skill_file os.path.join(skill_path, SKILL.md) if not os.path.isfile(skill_file): continue with open(skill_file, r, encodingutf-8) as f: content f.read() # 简化的 frontmatter 解析实际可引入 python-frontmatter 库 parts content.split(---) meta yaml.safe_load(parts[1]) meta[path] skill_path meta[content] parts[2].strip() if len(parts) 2 else index.append(meta) with open(index.json, w, encodingutf-8) as f: json.dump(index, f, ensure_asciiFalse, indent2) return index这个扫描动作必须和服务启动分离。技能文件是静态的不需要每次请求都重新扫描。我这边放在服务初始化时执行一次后面如果手动改了技能文件再通过一个接口触发重新加载。只有这样做Agent 主流程的逻辑才能保持简单不需要关心技能库内部的组织方式。3.2 检索策略为什么我只靠语义向量还不够技能注册好之后下一步是检索。最直觉的方案是把所有技能的description向量化然后对用户请求做语义检索。我一开始就是这么干的但在一个内部业务场景里出现了漏召回用户说的是“对一下上周的账”而技能描述里写的是“周度销售对账报告”语义相近但用了不同的词向量相似度排到了三四名开外Agent 就没选中它。后来我把检索改成了“向量 关键词”的混合模式def retrieve(query, top_k3): keyword_hits keyword_match(query, index) vector_hits vector_search(query, index) # 合并去重关键词命中加权重向量命中按分数排序 combined merge_and_rank(keyword_hits, vector_hits, top_k) return combined关键词匹配负责兜底把“账”“对账”“销售”这类强信号词直接命中向量检索负责处理“帮我看看上周的数据”这种语义表达。两者合并后取 TopK效果立刻上来了。这个策略可能不是最优解但胜在简单可控也方便排查问题。你可以根据实际把关键词命中分数调高一点我在项目里设置了关键词命中额外加 0.3 的权重。3.3 执行链路一次技能调用到底发生了什么agent-skills 的执行链路不长但每一步都有讲究。完整过程是用户请求进入主 Agent。主 Agent 先把请求和技能索引中的description做匹配选出候选技能。选中的技能正文被拼入当前上下文。Agent 按技能文件中的编号步骤逐步执行如果技能声明了scripts/下的脚本Agent 会在适当节点生成运行脚本的命令。脚本输出或查询到的数据回填到对话中Agent 整理成最终结果。输出校验模块检查结构是否合规不合规就回到第 4 步重试一次。这里有个关键设计技能文件不负责直接调用工具它只描述步骤和规则真正的脚本在需要时才被拉起。好处是技能文件保持结构简单Agent 的推理负担小坏处是脚本的输出如果不按预期返回Agent 可能没法顺利消化。所以我在脚本的返回里强制要求带一个status字段来标记这次执行是否成功。4. 把 agent-skills 接进业务项目的完整过程4.1 项目背景与第一个技能选型我改造的是一个内部销售运营看板项目。之前每次周会前运营同事都会在群里喊“帮忙出一下上周的销售对账”我当时的 Agent 每次都从零开始理解任务生成一段临时提示词去调数据库、算指标、出报告。结果就是输出的表格字段偶尔会缺列数字口径偶尔不一致。这次我决定先封装weekly-sales-report这个技能。选择它的原因很简单流程固定、输入明确、输出结构严格而且每周至少触发一次投入产出比最高。技能正文我写成了这样--- name: weekly-sales-report description: 当用户需要生成周度销售对账报告时使用。输入为周一日期输出为包含销售额、退款率、异常订单三部分的 Markdown 报告。 dependencies: [pandas, openpyxl] tools: [scripts/generate_report.py] --- ## 目标 根据销售数据库中的订单数据生成截至指定周的周度对账报告。 ## 执行步骤 1. 从用户输入中提取周一日期格式为 YYYY-MM-DD。 2. 调用 scripts/generate_report.py传入该日期和输出路径。 3. 等待脚本执行完成读取生成的 report.md。 4. 检查报告是否包含“销售额统计”“退款率统计”“异常订单列表”三个部分。 ## 输出格式 最终输出 Markdown 报告包含以下部分 - 销售额统计本周总销售额、环比上周变化、目标完成率。 - 退款率统计退款笔数、退款金额、退款率。 - 异常订单列表金额超过阈值或状态异常的订单最多 10 条。 ## 注意事项 - 日期必须用 YYYY-MM-DD 格式否则脚本会报错。 - 如果本周无数据不要编造数字明确说明“暂无数据”。4.2 与主 Agent 的接入方式主程序仍在用大模型驱动agent-skills 只提供技能发现和加载能力。接入接口很简单我给核心流程加了三个函数list_skills()、retrieve_skills(query)、load_skill(name)。业务代码不需要感知技能内部结构只要拿到技能正文塞进 prompt 就行。实际跑了两周之后效果最明显的变化是上下文占用下来了。以前每条请求就算跟销售报告无关系统提示词里也有一大段销售规则压着现在只有任务命中技能时相关的几百字才会进上下文。单次请求平均 Token 消耗下降了差不多 30%同时输出格式的稳定性提升明显报告缺列的情况基本消失了。5. 实测中的翻车现场与调优记录5.1 翻车一技能描述写得太“正式”Agent 根本检索不到刚把第一个技能放进去时我写的description是“销售对账报告生成技能用于处理销售数据的汇总与统计输出周度报告”。这个描述看着没毛病但实际调用时出现了很尴尬的情况用户说“帮我看下上周的销售情况”Agent 居然没召回这个技能而是自己现场发挥。排查下来发现问题出在描述里缺少“用户视角的触发场景”。模型做匹配时它是在把用户输入和技能描述做语义对齐描述里全是名词堆砌缺少“周度”“对账”“上周”这种和真实请求能直接配对的动作词。修复方式就是前面说的把描述改成“当用户需要生成周度销售对账报告时使用输入为周一日期输出为……”这种包含触发条件的完整句式。改完当天召回归零问题就解决了。5.2 翻车二技能步骤留了“自由发挥”的口子第一个版本的第 4 步写的是“对数据质量进行合理检查”。这句话现在回头看属于灾难级别的表达。“合理”是个非常主观的词模型根本不知道什么算合理。结果就是有时候它自己脑补数据有时候它跳过了异常校验直接把原数据输出。我后来把所有“合理”“适当”“必要时”这类词全部从技能文件里清掉改成可验证的行为描述比如“检查是否存在退款金额大于订单金额的记录如有则列入异常订单列表”。要让模型可靠技能步骤就必须是确定性的动作不是开放式的判断题。5.3 翻车三技能文件过长关键步骤反而被忽略另一个问题是技能文件越写越长尤其我在正文里加了很多说明性文字后文件到了两千多字。然后有个奇怪的现象模型对后面的“输出格式”部分遵循得挺好但前面的“执行步骤”第 3 步却偶尔会漏。这其实就是上下文注意力分布不均导致的。解决办法是瘦身技能正文里只保留模型必须执行的步骤和硬性约束原理说明、背景知识这类内容全部移到resources/目录下的附加文档里或者干脆删掉。技能文件的最佳篇幅在 500 到 800 字之间超过这个量级就该考虑拆分子技能了。5.4 调试方法给技能命中留痕前期调技能时最大的痛苦是“不知道 Agent 为什么没选这个技能”。我在检索模块加了日志把每次请求召回的前五个技能名称和得分都记录下来。这样就能对着日志看某次任务到底是因为描述不匹配没召回还是召回了但 Agent 没选定位问题的速度会快很多。这个排查链路强烈建议提前做别等技能多了再补。6. 和几种主流做法对比后我发现 agent-skills 的适用边界6.1 它与提示词模板、微调、工具函数有什么本质差异技能化不是唯一的路。市面上还有几种主流做法我实际都试过或调研过直接列一个对比方案维护成本可解释性扩展新能力效果稳定性适合场景全能提示词模板低但后期失控差长提示词难以审查只能继续加长中互相干扰流程简单的原型项目agent-skills 技能库中按目录维护好每个技能独立审查好加目录即可高按需加载多场景、多流程的生产项目微调模型高需要训练和评估差行为难以解释低每次迭代成本高高但只对固定格式好输出风格固定、无工具调用需求纯工具函数function calling中工具参数靠代码定义较好好高但对复杂流程弱单步原子操作如查天气、算价格agent-skills 的核心差异在于它把“技能”的载体从代码或模型参数变成了可读可写的文档。这意味着非工程师也能参与维护业务规则也意味着每次改动可以直接走代码评审流程行为变更完全可追踪。这一点在需要合规审查的场景里尤其值钱。6.2 哪些场景不应该硬套技能化虽然我在这个项目里收益明显但还是要泼点冷水。有三种场景不太适合 agent-skills第一种是频率极低的一次性任务。比如“帮我研究一下某个新竞品的定价策略”这种任务写成技能等下次用的时候需求早变了纯属浪费维护精力。第二种是高度依赖数值推理的任务。技能文件里的自然语言步骤对这类任务的约束力有限。比如涉及多表 join 和复杂计算的财务分析让 Agent 自己按步骤推理容易出错不如直接写一个计算能力强的脚本做后端技能文件只负责调度脚本。第三种是技能之间边界模糊的任务。如果你发现几个技能的 description 写出来高度相似说明业务场景本身没有清晰边界。这时候硬拆技能会导致召回混乱不如先把业务梳理明白。6.3 我下一步的计划版本化与回归评估agent-skills 目前跑得挺稳但我已经看到了新的痛点技能文件改起来很容易但你怎么知道这次改动没有让另一个场景变差所以我下一步准备给每个技能配一个小型评估集里面放几组“输入 预期行为”的测试用例。每次改完技能文件就自动跑一遍评估集看输出是否符合预期。再往后就是技能的版本化。给每个技能加上版本号让 Agent 在加载技能时可以感知当前版本并在技能行为变更时给出明确提示。最后说点我自己的体会。这次做 agent-skills 最大的收获不是把报告生成任务的准确率拉高了多少而是改变了我对 Agent 工程化的理解生产级 Agent 不应该把全部智能压在模型身上而是要把组织已有的最佳实践拆解成模型可以稳定执行的资产。技能文件就是这种资产的载体。如果你也在做类似的技能库我的建议是先挑一个最痛的高频场景跑通全链路再慢慢把其他技能沉淀进来。这套路走得通后面会顺畅很多。