Agent Skills实战:从超级提示词到可复用技能库的完整路径
我最近一年在带一个客服自动化项目发现了一个特别值得聊的东西Agent Skills。先说个结论如果你正在做AI Agent而且业务里有一堆反复出现、规则复杂的操作流程与其继续把提示词越写越长不如把这些流程拆成一个个独立、可复用的“技能包”。这就是Agent Skills的核心思路——把Agent经常要干的专业动作提前封装成标准模块需要时就调用不需要时就放在旁边而不是让模型每次都临时临场发挥。这篇文章我想围绕agent-skills这个方向把我从踩坑到搭建技能库的完整过程分享出来包括结构设计、实操例子、工程化经验和一堆排查技巧。1. 先搞清楚Agent Skills到底解决什么问题1.1 从“超级提示词”到“技能封装”很多人刚上手做Agent的时候习惯把所有指令写进一个巨型系统提示词里。今天加一条“遇到退款走流程A”明天加一条“遇到发票校验先调用接口”最后提示词变得又长又碎改起来提心吊胆模型还经常把后面的指令忘掉。说白了这就像让一个新来的实习生记住一整本员工手册然后指望他遇到任何情况都能从中翻到正确动作——他连翻都来不及更别提炼出关键步骤了。Agent Skills的思路完全不同。它把“员工手册”里某一块专业内容抽出来做成一张独立的标准操作卡。Agent平时根本不用记这张卡等到任务真的匹配上了它再去读这张卡、执行配套脚本做完之后还能把结果拿回来继续对话。这样做的好处非常直观单个技能可以单独修改、单独测试不会因为改了一个流程就把其他流程搞坏上下文里也不用塞一大堆用不上的规则模型反而更容易抓住重点。我自己的体感是技能封装的拐点出现在提示词超过一定长度之后。团队之前在一个内部知识问答Agent里堆了接近两万字的管理规则效果不升反降模型经常互相矛盾。后来把考勤、报销、权限申请这些动作全部改成独立技能系统提示词压缩到只有意图路由和通用对话规范准确率一下就上来了。这就是Agent Skills存在的第一层理由不是花活而是为了降低复杂系统的维护成本。1.2 Agent Skills与Tools、RAG的边界聊Agent Skills很容易和Tools、RAG搞混。我在社区里也看过不少讨论其实这三者的边界挺清晰的理清楚之后做设计时就不会纠结“这个能力该放哪”。先说Tools它是最小的原子操作。比如“查询订单状态”“发送邮件”“读取某个文件”本质上是把某个确定的函数或API暴露给Agent。Tools只有一个动作有明确的输入输出不该包含复杂的业务判断。RAG则是知识检索它提供的是“信息”回答“是什么”的问题比如企业制度里怎么写、产品规格是什么Agent检索出来之后再进行判断。Agent Skills处在两者之间又往上走了一层。它可以包含多个Tool调用、一段判断逻辑、一套业务规则甚至内嵌一段脚本。它的本质是“过程性能力”解决“怎么做”的问题。举个例子一个“发票合规校验”技能里面会先读取发票图片再调用OCR工具再比对金额规则最后还要走一遍税务规则判断这中间可能涉及三四个工具调用和几十条分支规则——这种多步骤的流程不应该散落在提示词里也不应该硬编码成单个工具最合理的载体就是Skill。为了避免说混我习惯用一个简单判断标准如果这个能力可以一步完成且输入输出稳定做成Tool如果核心是查资料做成RAG如果它需要“按步骤做事”中间还有决策和分支那就做成Skill。这个分类不一定绝对但足够指导日常架构了。1.3 适合用Agent Skills的场景不是所有场景都值得套Agent Skills。我见过有人把“打招呼”也做成一个技能纯属过度设计。真正适合技能化的场景我认为至少有这么几类场景类型典型例子为什么适合Skill多步骤业务流程退款审核、工单分派、报销初审业务规则多、步骤固定适合沉淀成标准流程专用格式处理会议纪要提取、报告生成、日志解析输出格式严格需要配套脚本做结构化处理高成本操作保护批量删除数据、发正式公告、对外支付需要多重校验必须用技能挡住模型“乱来”周期性任务每周生成销售汇总、每日巡检告警重复度高参数固定适合固化成模板判断标准也很简单同类任务一个月内出现多次且每次的流程差异不大就值得花时间封装。反过来如果任务千奇百怪、每次流程都不一样强行做成Skill只会让Agent在一个错误技能上一路狂奔。这个判断在项目早期尤其重要因为一旦技能库开始膨胀清理成本可比写提示词高多了。2. 一个标准Skill长什么样2.1 Skill的最小可用结构我先说一下我们现在常用的最小目录结构。无论是什么项目Skills的骨架基本都长这样skills/ task_list_parser/ SKILL.md scripts/ main.py assets/ prompt_templates/ extract_template.txt requirements.txtSKILL.md是整个技能的核心负责告诉Agent“你什么时候该用我、你要怎么用我、你要注意什么”。scripts/里放技能真正要执行的代码通常是Python脚本输入输出都走标准参数。assets/放一些辅助资源比如固定的模板、参考样例不是必选项。requirements.txt记录脚本依赖方便部署时隔离安装。有人会问为什么不把脚本内容直接写进SKILL.md里我在早期确实这么干过把Python代码贴在技能说明里同事用起来也行但后来发现两个问题一是代码更新频繁每次改代码都要连带改说明很容易漏二是模型在执行复杂逻辑时可能“参考”代码而不是“执行”代码结果输出不可控。把代码独立成脚本Agent只需要读取SKILL.md然后用工具调用脚本反而是最稳的。2.2 设计原则显式接口优于隐式记忆Skill设计的核心原则我总结成一句话把接口显式地写出来别指望Agent靠读你几段自然语言描述就猜出正确用法。Agent不是人它看到“帮我整理一下会议记录”时不会自动脑补出你要的字段它只会照着技能描述里那个输入输出结构去填。所以SKILL.md里最好有一个很明确的前置说明类似下面这种形式--- name: task_list_parser description: 从非结构化文本中提取任务清单。当用户提供会议记录、聊天记录或邮件 并要求整理待办、责任人、截止时间时使用该技能。 如果文本中没有任何任务相关内容直接返回空列表不要编造任务。 input: text: string原始文本内容 output: tasks: array每个元素包含 task、owner、due_date、priority 四个字段 --- # 技能说明 当前技能用于把杂乱的文字变成结构化任务列表。执行顺序 1. 确认输入文本非空否则返回空列表。 2. 调用 scripts/main.py 进行识别与结构化提取。 3. 对返回结果做字段校验缺失字段使用默认值。这里最关键的不是花哨的格式而是把“输入”和“输出”定义清楚。字段类型、默认值、空值处理规则都要写明。因为在Agent眼里接口就是契约契约不清晰后面所有环节都不可信。再补充一个设计习惯尽量让Skill输出标准JSON而不是让Agent读一段文本自己理解。标准结构化输出既好校验也方便后续流程继续处理。我们在做多技能编排时几乎所有Skill的返回结果都定义成JSON Schema方便统一走一层校验器不合格的调用直接重试或回退。2.3 写清Skill边界触发条件与拒绝逻辑这是我最想强调的一点。很多团队写Skill时只写“什么时候该用”却忘了写“什么时候绝对不该用”。结果Agent看到一个长得差不多的用户请求就强行套用技能出了错还很自信。要解决这个问题SKILL.md里必须有一块显式的边界说明。以退款技能为例我会在description里写description: 处理订单退款申请适用于未发货订单或已签收但存在质量问题的订单。 仅当用户明确表达退款意愿且订单状态可查时使用。 以下情况必须拒绝执行订单不存在、退款金额超过用户账户余额、 用户没有提供订单号、或者当前会话中没有人工审核授权标识。这段看起来像在啰嗦实际上非常有用。它教会Agent“什么时候不做”比“什么时候做”更重要。我管这个叫“拒绝边界”在客服场景里这条边界直接决定Agent会不会乱退款。我们后来还在技能里加了一个“escalate_if_unsure”标志凡是边界条件判断失败的场景统一走人工升级流程效果立竿见影。另外一个容易踩的坑是不要在description里写太多抽象形容词。像“智能地”“合理地”这种词模型根本不知道怎么执行。应该换成可检查的条件比如“订单金额小于1000元”“任务截止时间不早于当前时间”。规则越可计算Agent的行为就越稳定。3. 手把手实现一个Agent技能任务清单解析器3.1 场景与目标光讲理论比较虚我挑一个大多数团队都会遇到的例子任务清单解析器。背景是这样的我们团队每天大量沟通发生在聊天工具和邮件里有需求变更、临时安排、会议纪要最后真正需要跟进的待办事项散落在各处。以前每个人自己手工整理漏项是常事。后来我们决定给Agent加一个技能让它可以自动从一段杂乱文本里提取任务、负责人、截止时间和优先级输出成结构化清单。这个技能的目标很具体输入一段真实世界里的沟通文本输出一个JSON数组每个任务带四个字段。难点在于自然语言里的表达非常灵活“周五前给财务发报表”“等老王确认”“这个不急下周再说”这些话都需要被解析成相对标准的结构。如果全靠提示词让模型离线处理结果不稳定所以我们决定把它做成一个带脚本的Skill。3.2 SKILL.md内容设计第一步是写SKILL.md。在这个文件里我们尽量把“触发条件”和“处理逻辑”压缩到前端让Agent一眼就能决定是否调用。--- name: task_list_parser description: 从会议记录、聊天对话或邮件中提取待办任务列表。适合回答“整理一下待办”、 “提取任务清单”这类需求。如果输入文本没有明确任务返回空列表。 input: text: string原始非结构化文本 output: tasks: array - task: string任务描述 owner: string负责人姓名未知则填待确认 due_date: stringISO日期未知则填 priority: string可选 high/medium/low未知则填medium --- # 技能逻辑 1. 判断文本是否为空。为空直接返回 {tasks: []}。 2. 将输入文本原样传给 scripts/main.py等待JSON输出。 3. 校验输出字段task字段不能为空owner缺省补“待确认”。 4. 解析完成后用自然语言把任务列表逐条反馈给用户。注意我们没有在SKILL.md里写具体怎么识别责任人和日期这些规则全放在脚本里。原因是识别规则属于算法细节会频繁迭代不希望模型每次读提示词时还要消化一堆正则和规则说明。让SKILL.md保持简洁只负责“决策”和“边界”脚本负责“执行”和“计算”。3.3 配套脚本与核心逻辑配套脚本main.py我采用了“规则预解析 模型兜底 结构校验”三层方案。第一层先用正则把明显的“谁谁谁做什么”“几号前完成”提取出来能覆盖六成情况第二层对剩余文本调用大模型API补全第三层统一做字段校验和修正。import json import re from typing import List from pydantic import BaseModel, Field class TaskItem(BaseModel): task: str owner: str Field(default待确认) due_date: str Field(default) priority: str Field(defaultmedium) class TaskList(BaseModel): tasks: List[TaskItem] def pre_parse(text: str) - List[TaskItem]: # 只处理最规则化的表达任务、负责人、截止时间 results [] pattern r(?Ptask.?)(?:由|\s)(?Powner\S)(?:负责|处理)(?:|\s|)?(?Pdue\d月\d日)? for m in re.finditer(pattern, text): results.append(TaskItem(taskm.group(task).strip(), ownerm.group(owner))) return results def llm_fill(raw_text: str, candidates: List[TaskItem]) - TaskList: # 调用大模型API补充缺失字段这里略去具体请求代码 ... return TaskList(tasks...) def main(input_text: str) - str: candidates pre_parse(input_text) final_items llm_fill(input_text, candidates) return final_items.model_dump_json()这段代码不是要给你一个完整生产版本而是展示Skill脚本应有的边界输入输出都走标准结构内部可以有规则、有模型调用、有校验。脚本要能被命令行或函数调用直接跑通不要在脚本里写大量print应当返回结构化JSON。实际运行中我们还加了超时控制和失败回退。如果模型调用超时或者返回了非法JSON就退回到纯规则解析结果。这样保证技能不会因为模型波动而完全不可用。Agent调用技能就好比按下一个自动化按钮这个按钮得保证尽量少失灵。3.4 接入Agent后的调用流程技能写好后接入Agent的流程其实不复杂。我以我们现在用的模式举例Agent主循环收到用户消息先判断意图当意图命中“任务整理”时从技能库加载task_list_parser把SKILL.md注入上下文让模型理解使用方式然后把用户原始文本作为text参数调用脚本脚本返回JSON后Agent再负责把JSON转成一段自然语言回复。这个流程的关键在于Agent只负责“决策”和“表达”不负责“计算”。它不需要知道脚本内部用正则还是模型只需要知道输入输出。这样设计有一个额外好处技能导出以后可以被其他Agent复用。同一个任务解析Skills既能放在内部项目助理里也能放在客服工单系统里因为接口是标准化的。我特别建议你接入后跑一遍端到端日志确认模型确实在正确时机触发了技能。我们第一次接的时候模型老是不调用查了半天发现是description里没有写清楚触发词后来把“整理待办”“提取任务”“列出行动项”这些常见说法都写进去调用率才上来。这部分后面还会详细讲。4. 从单个Skill到技能库工程化要点4.1 技能仓库的目录与命名规范当技能数量超过五个你就会发现管理方式决定了项目能不能继续跑下去。我们早期的技能目录很随意什么test_parse.py、utils_tool都出现过后来Agent经常找不到正确的技能因为名字根本没有语义。重新整理以后我们定了两个硬性规范目录名统一用“动词对象”比如generate_report、check_invoice、assign_ticket禁止出现“utils”“misc”“test”这类无意义名称。目录内部结构也统一起来。每个技能下面必须有SKILL.md可选有scripts/和assets/。如果一个技能没有脚本、只有文档说明我们也允许但它依然要遵循同样的目录结构。统一结构最大的价值是让枚举和加载变得很简单一个目录就是一个技能目录清单就是技能清单。做技能路由的时候直接扫描一层目录就够了不用维护额外的注册表。4.2 动态加载与自动路由技能库一大Agent面临的核心问题变成100个技能该选哪一个我们试过三种方案简单说下感受。第一种是基于描述向量的检索。把每个技能的description做向量化用户请求也做向量化用余弦相似度选出Top K个候选技能然后把候选技能描述交给Agent决策。这种方式对描述质量要求高适合技能间语义区分明显的场景。第二种是纯靠Agent自动路由。把技能列表全部写进系统提示词让模型自己选。这个做法在技能少于20个时非常有效简单直接超过30个以后模型会开始漏选因为上下文里的技能列表太长了注意力会被稀释。第三种是分层路由。先在系统提示词里只放“路由规则”按业务域分成客服域、数据分析域、办公协作域等Agent先选域再在域内选择具体技能。这是目前最稳的方案也是我们在生产环境里采用的方式。它牺牲了一点灵活性但换来了很高的确定性特别适合业务边界清晰的内部Agent。顺便提醒一句不管用哪种路由每个技能description里的关键词都要用心打磨。这个描述不是给人看的是给模型看的。你写“该技能用于从文本中提取行动项”和“该技能用于把会议纪要里的待办事项整理成清单”效果完全不一样后者明显更容易被召唤。4.3 技能版本管理与兼容性代码有版本管理技能也应该有。我们为每个Skill引入了version字段并按语义化版本维护变动记录--- version: 2.1.0 changelog: - 2.1.0: 新增对逾期任务的提醒字段 - 2.0.0: 输出结构升级owner字段改为可选 - 1.0.0: 初始版本 ---版本管理真正的意义在于兼容性控制。底层大模型API升级后很多旧技能的prompt文本可能不再适配输出也可能发生偏移。我们每个月都会跑一遍技能回归测试如果发现某技能在新版本模型下输出字段丢失就要进行大版本升级。另外技能上线后尽量不要频繁小改因为Agent在多个会话里的行为一致性也很重要今天能触发明天不能触发用户会觉得系统变傻了。4.4 如何做技能评测与回归技能评测是很多人忽略的一块但它恰恰是Agent项目从“demo能跑”到“生产可用”的分水岭。我们的做法是给每个技能建一个测试集包含几十条典型输入和对应的期望输出。测试集里不仅要覆盖正常场景更要覆盖边界情况空输入、非法字段、模棱两可的表达、明显不在技能范围内的请求。系统每天会跑一遍所有技能的评测用准确率和字段完整率两个指标看变化。准确率指的是输出结果和期望结果的一致程度字段完整率指的是每个JSON字段是否都按规范填写。一旦指标下滑超过阈值系统会告警我们再去判断是技能描述问题、脚本问题还是模型升级导致的。这套流程看起来重但对保持长期稳定非常有效。5. 实战中的坑与排查技巧5.1 Agent不调用Skill怎么办这是被问得最多的问题。辛辛苦苦写好技能Agent就是不调用一直自己用自然语言回答。排查思路我建议按顺序来先确认技能路径有没有被正确扫描到再看description是否包含用户请求中的语义关键词然后看技能名称有没有和系统内置工具或常见术语冲突。有一次我们给一个“查询余额”技能起了balance_query的名字但Agent总是调用一个旧工具后来发现旧工具也在技能库里名字叫check_balance两者的description高度重叠。模型抢工具靠的是相似度谁的名字更贴切就选谁。我们把旧工具下线后问题立刻消失。所以遇到不调用第一件事不是增加提示词而是检查是不是有另一个技能在“截胡”。5.2 输入输出触发不稳定的排查如果技能有时触发有时不触发问题大概率出在输入输出定义不够严格。我们曾在SKILL.md里用自然语言描述“用户需要提供订单号”但没规定格式。结果同一个技能有的Agent把订单号放在order_id字段有的放在orderNo下游脚本直接崩溃。排查时翻了日志才看清。解决办法是把输入参数定义成交互式确认如果Agent认为信息不完整先向用户询问必填项再调用技能。我们在脚本和Skill文档之间加了一层“参数预检”参数不齐就返回一个“需要补充字段”的结果让Agent继续追问用户。这样既减少了调用失败也让模型不会硬猜值。5.3 依赖注入与沙箱执行问题Skill里的脚本不会凭空运行。如果脚本需要数据库连接、HTTP请求或第三方库环境问题就会变成主要坑。最常见的情况是开发环境能跑部署后就报错原因通常是依赖没有锁版本。requirements.txt里必须严格指定库的大版本号和小版本号不要直接用这种宽松写法。另一个坑是安全边界。我们内部规定所有Skill脚本必须在独立沙箱进程里执行设置好资源限制和网络策略。这是因为Agent传入的参数是不可信的如果直接把用户文本拼进shell命令风险很高。早期我们在一个实验项目里试过直接在系统环境里跑脚本结果某个技能拿到一段带特殊字符的文本后硬是把一个临时目录删了。从那以后所有脚本执行都走沙箱这件事必须认真对待。5.4 多Skill协同时的优先级冲突技能之间描述重叠是迟早的事。比如“会议纪要总结”和“待办提取”都会处理会议记录模型经常拿不准该用哪个。解决这类冲突我建议在SKILL.md里写清“互斥关系”而不是指望模型自己懂。在description里加一句“如果目标仅是把会议记录整理成待办事项请使用task_list_parser而非meeting_summarizer”这句话看着简单却能把选择错误率降低一大截。更复杂的情况下可以在编排层直接用规则决定比如某个业务域内先判断是否包含“截止时间”包含则走任务提取技能不包含则走纪要总结。用规则兜底比让模型在多个相近技能里自由博弈要稳得多。6. 一些反直觉的经验6.1 Skill文件不要太长我刚起步时总想把技能写全一个SKILL.md塞了几十行规则结果Agent反而抓不住重点。后来想明白了Skill文件是给模型看的“说明书”不是给开发者看的“需求文档”。太长的说明书会让模型注意力分散关键信息被淹没。我们内部标准是SKILL.md尽量控制在百行以内超过这个长度就要考虑拆分规则或把规则挪进脚本。6.2 让Agent“拒绝”也是一种技能这也是我们后期加的一个通用技能。很多Agent失败不是因为能力不够而是因为不会拒绝。用户问了个边界问题模型硬答结果错得离谱。所以我们给Agent加了一个“无法处理”技能当它判断请求不在任何技能范围内、且自己没有能力回答时就明确告知用户“这个问题不在我的处理范围”并给出下一步建议。不要小看这个技能它让整个系统的可信度提升了不少。6.3 先写几个烂Skill再追求完美技能设计这件事纸上谈兵永远不如先跑一遍。我见过不少团队在架构阶段反复推演“这个技能该怎么设计”“那个技能会不会冲突”最后项目进度都被拖死了。我更推荐的做法是手工整理一段真实样本先随便写一个能跑的简单脚本哪怕是正则硬匹配也好然后立刻接进Agent跑一周看实际调用情况和失败案例再迭代完善。先有烂版本才会有好版本。6.4 长期沉淀的方向把Agent Skills当成一个持续积累的内部资产库而不是一次性开发任务这个心态很重要。现在我们在每次项目复盘时只要发现某个问题反复出现而它又适合封装成固定流程就会把它补成一个新Skill。刚起步的时候可以只做三五个高频技能后面每隔一两周增加一个半年过去你会发现自己手头已经有一套覆盖核心业务的技能库。这个库的复用价值远大于任何一段优化过的提示词。最后再分享一个小经验。我在实际维护这套技能体系时最深的体会是Agent Skills能不能用好不取决于你写了多少花哨的框架代码而取决于你是否真的把“技能边界”和“接口契约”想清楚了。每次遇到奇怪的生产事故追到最后基本都是描述写模糊了、字段没定死、或者没有提前写好拒绝条件。把这三件事做扎实Agent Skills这套模式在普通工程团队手里也能玩得很稳。如果你也在搭自己的Agent不妨从一个小技能开始试试先跑起来再去慢慢打磨。