Agent技能封装实战:从提示词堆砌到可复用Skills体系

发布时间:2026/10/7 4:17:53
Agent技能封装实战:从提示词堆砌到可复用Skills体系 1. 为什么Agent要会技能而不是会写代码1.1 把逻辑全部写死在Agent里的下场做AI Agent应用时间久了你一定会碰到这样一个阶段项目刚开始时往系统提示词里塞几个任务描述模型跑得还挺好。但功能一多问题就来了——今天加一个查库存的规则明天加一个写日报的模板后天再加一个异常指标的处理逻辑系统提示词越来越长长得像一篇小型论文。我上一个项目就栽在这上面。团队做了一个内部数据助手最初只有查订单和算销售额两个功能写在提示词里勉强够用。后来业务方要求加权限判断、加趋势分析、加周报自动生成、加老客户回访提醒……每次加功能我都要在主提示词里追加一大段当用户提到XX时你应该YYY。结果模型经常顾此失彼用户问一个简单的昨天销量多少它反而开始思考是不是要生成周报更麻烦的是某个功能出了bug你要在一大坨提示词里定位到底是哪一段规则出了问题那种排查体验跟在一座垃圾山里找一枚硬币差不多。这个问题的本质是你把能力和决策混在了一起。Agent的主提示词应该是大脑的决策中枢而不是一个功能仓库。大脑只需要知道有什么工具可用、在什么情况下用哪个至于工具本身怎么实现应该放到大脑之外的独立模块里。1.2 Skills的本质可复用的能力封装agent-skills解决的就是这个矛盾。你可以把它理解成一个能力工具箱每一个skill都是一个独立的、自包含的能力单元里面写清楚这个能力是干什么的、在什么场景下用、需要哪些参数、脚本怎么运行。Agent本身不关心技能内部怎么实现它只关心该调哪个技能、传什么参数。用一个生活化的类比你饿了不会把整个厨房的菜谱背下来你只需要知道——西红柿炒蛋是一道菜原材料是西红柿和鸡蛋然后照着步骤做就行。Skills就是那道菜的菜谱而Agent就是那个根据现在手头有什么食材、想吃什么来翻菜谱的厨师。比起传统的把所有指令写进提示词Skills有四个实打实的好处可复用同一个技能可以在不同Agent项目里使用换项目不用重写逻辑。可扩展新需求只需要新增一个技能目录不用动Agent的核心逻辑回归风险小。可维护技能内部出了问题单独改一个文件夹就行不像以前要在一大段提示词里做手术。省上下文主提示词只需保留所有技能的描述卡片模型根据描述决定调用谁不用把每个技能的完整说明书都塞进上下文。我当时重构的方案说起来很简单把Agent变成一个空的决策器所有业务能力全部拆成skills。每个技能有独立的目录、独立的说明文档、独立的执行脚本Agent只负责路由和调度。重构完的第一个感受就是——代码的新增速度反而变快了。以前加功能要反复调提示词试错成本特别高现在加功能就是建一个目录、写一份SKILL.md、放一个脚本跑通就完事了。2. 一个真实Skill从规划到落地异常告警分析技能2.1 需求场景定义让Agent帮我盯异常指标光说概念容易飘我用一个真实项目里的技能来拆解。当时我们需要一个能力Agent定期检查业务监控指标发现异常波动时自动输出告警摘要。以前这个逻辑是写在一个定时任务的代码里的判断规则全部硬编码业务方每次改阈值都要找我改代码。把它改造成一个agent skill之后整个流程变成了Agent收到帮我看看今天有没有异常指标的任务自动决定调用这个技能技能脚本负责从时序数据库拉数据、跑异常检测算法、把结果格式化返回给Agent最后由Agent根据返回结果生成自然语言告警。这个技能的核心需求拆解下来就三条拉取指定时间范围的指标数据支持多个指标。用简单可靠的异常检测方法识别波动不依赖复杂模型因为运行环境未必有GPU。输出结构化的异常结果包括指标名、时间点、波动幅度、可能原因备注。2.2 Skill文件结构与关键字段一个规范的skill目录通常长这样skills/ └── anomaly_alert_analysis/ ├── SKILL.md └── scripts/ ├── fetch_metrics.py └── detect_anomaly.py其中SKILL.md是灵魂它决定了Agent能不能正确理解并调用这个技能。我习惯把SKILL.md写成带YAML front-matter的Markdown结构如下--- name: anomaly_alert_analysis description: 分析监控指标数据并输出异常告警摘要。当用户询问今天有没有异常某个指标波动原因或需要定时巡检时使用。不适合用于生成图表或预测未来数据。 version: 1.2.0 require: - python3.10 - taosrest keywords: [监控, 告警, 异常检测, 指标分析] parameters: - name: time_range type: string required: true description: 查询的时间窗口如 last_1h、today、2024-06-01 00:00:00 ~ 2024-06-01 23:59:59 - name: metrics type: array[string] required: false description: 需要分析的指标名列表默认读取 watchlist 配置 --- # 异常告警分析 本技能按时序指标数据使用移动平均 标准差的方法识别异常波动。这里每个字段都有它的道理不是凑数的name技能的全局唯一标识Agent在内部记日志、排错时都靠它。description这一段是给模型看的它决定了模型什么时候想到用这个技能。描述里一定要写清楚什么场景用以及什么场景不要用很多误调用都是因为描述写得太宽泛。require运行依赖的声明Agent执行前可以检查环境避免脚本跑到一半才报缺少库。parameters参数协议相当于是Agent和技能之间的接口契约。模型根据这段声明生成调用参数脚本侧按这个契约解析入参。2.3 执行脚本让模型只做决策不做计算脚本方面我的经验是能用普通代码解决的问题不要让模型自由发挥。异常检测这种有明确逻辑的事情就老老实实写在Python里模型只负责传参和解读结果。import json import sys def load_params(raw): # Agent框架通常会把参数以JSON字符串传入这里做一层容错 try: params json.loads(raw) if isinstance(raw, str) else raw except json.JSONDecodeError: params {time_range: last_1h, metrics: []} return params def main(): params load_params(sys.argv[1] if len(sys.argv) 1 else {}) time_range params.get(time_range, last_1h) metrics params.get(metrics, []) # 实际工程中会在这里连时序数据库按时间范围拉取数据 raw_data fetch_metrics(metrics, time_range) anomalies detect_anomaly(raw_data) # 输出标准化JSON方便Agent解析后组织语言 print(json.dumps({anomalies: anomalies, checked_metrics: list(raw_data.keys())}, ensure_asciiFalse)) if __name__ __main__: main()这段代码的关键设计在于输入和输出的双向标准化。输入侧模型传来的参数可能格式不太稳定脚本要做容错输出侧统一输出JSON而不是自由文本这样Agent拿到的是一份结构化数据它基于这份数据去组织异常摘要就非常自然。3. Skill被调用的完整链路匹配、注入与执行3.1 意图匹配模型是怎么找到对的技能到目前为止我们把技能做出来了。但这是否意味着Agent拿到任务就会正确调用它天真了。真正让Agent想起来用这个技能靠的是语境匹配。主提示词里通常会给Agent一份技能清单每个技能对应一段short description。当用户说帮我看看今天指标有没有异常模型的注意力机制会把这句话和我们技能描述中的今天有没有异常指标波动这些关键词建立语义关联然后决定调用。这里的核心是描述质量决定了匹配准确率。你写分析监控指标数据并输出异常告警摘要模型就明白这是告警场景如果你写数据分析工具模型碰到任何数据问题都可能来碰瓷——用户问分析一下用户反馈它可能也调用这个技能但脚本里根本没有处理文本数据的能力结果自然是一团糟。所以我在每个技能的description里都会加一句不适合用于……的负向说明这个做法帮我挡住了大量误调用。3.2 上下文注入技能库大了之后怎么办技能数量少的时候把所有技能描述都放在主提示词里没毛病。但技能库一旦突破三四十个情况就不一样了。每次请求都要把三四十段描述一起发给模型token消耗大而且描述太多会导致模型注意力稀释反而降低选型准确率。我们后面做了两层优化技能索引层平时只把技能名和一句话摘要放在主上下文全文描述放到一个单独的索引里。检索增强注入用户问题进来后先用向量检索在技能索引里召回到top 5技能只把这几段完整描述注入上下文让模型从中选择。这种做法比较像你在一个大型图书馆里先通过检索系统找到3本书而不是把整个图书馆的目录全部背下来再决定看哪本。两层的效果非常明显技能选型准确率至少提升了十个点token开销也降了将近一半。3.3 调用过程中的常见中断点技能执行不是每次都一秒完成的中间会有一些必须处理的中断情况。我在实际项目里碰到比较多的是这几个参数缺失模型调技能时漏了必传字段。我们的策略是脚本侧做缺省值兜底而不是直接报错。比如说time_range没传默认查最近一小时。技能内部异常脚本崩溃或返回了非预期格式。好的做法是让Agent能感知到这个异常然后告诉用户监控分析技能执行失败原因可能是时序数据库连接超时正在重试而不是沉默。超时处理有些技能要跑十几秒Agent会等不及。这种情况下可以让Agent先返回一句我正在分析指标数据大约需要20秒给模型一个异步交互的空间。我在做异常告警技能时还踩过另一个有意思的点模型在拿到结构化结果后并不一定会直接转述有时会自作主张补充一些分析结论。比如脚本返回CPU使用率从30%升到80%模型可能会补一句这可能是流量突增导致的。虽然多数时候补得对但偶尔也会胡诌。为了控制这一点我在SKILL.md里加了一条说明Agent只能基于技能返回的数据组织语言不得编造未包含在结果中的原因分析。4. 技能迭代与质量控制让技能库越用越准4.1 先跑通一条完整路径再谈优化很多人在做技能库时容易犯一个毛病一开始就想把标准规范定得非常完善每个技能都要有测试用例、有版本号、有复杂依赖——结果热情都消耗在整理文件格式上了。我的建议是反过来的一个技能只要能解决一个具体问题哪怕它的SKILL.md只有三行字先让它跑起来。我们团队从头到尾遵循一个原则一个技能的最早版本一定是特定场景下、用最简单粗暴的方式、端到端跑通。比如告警分析技能的第一版就是——一个Python脚本参数写死跑完打印结果。先确认它能在一个真实案例里输出让人满意的结果然后再去补SKILL.md、补参数定义、补容错。因为这时候你已经知道合理输出长什么样了定义description就不会太虚。4.2 用不合理的调用案例反过来修正描述技能跑通后真正的提纯过程在测试和灰度里。我常用的办法是记录每一次调用每周复盘一次Model调用历史专门找那些调用不合理的情况。这些不合理大致分成三类每类都对应一种修正手段不合理情况根因修正手段该用的时候没用description里缺少触发关键词把用户真实问法中的高频短语加进去不该用的时候用了description过度宽泛、缺少负向说明补一句不适合用于……参数传错parameters的description有歧义把字段说明改成带示例的描述比如time_range示例last_1h、today举个例子告警分析技能的初期描述没有写不适合用于预测未来趋势结果用户问下周QPS会涨到多少模型也调用了它而脚本根本没有预测能力。这事被我发现后在description末尾加了一句仅用于当前/历史数据波动分析不具备预测能力。从那以后这类误用基本绝迹。4.3 技能库的版本管理轻量但要坚持技能越来越多之后版本管理就绕不开。我不推荐给每个技能套一整套Git Flow那对这个场景来说过重了。我们的做法更轻每个技能在SKILL.md里维护一个version字段改动时递增。在技能目录下放一个简短的CHANGELOG.md记录大改动的原因不用事无巨细地写。用一份tests/目录做关键路径验证一个技能至少要有1个标准输入 1个边界输入比如空数据、参数缺省。任何人改动技能必须更新description和version否则code review打回去。这套机制虽然很轻但扛住了我们半年内将近两百次技能更新的压力。核心价值在于你永远能从CHANGELOG追溯到某个行为变化是哪个版本引入的排查问题时有据可依。5. 高发踩坑与规避方案这些坑我全都踩过5.1 技能命名太抽象模型经常选错第一个教训来自命名。我早先给技能起名叫data_processor涵盖了一堆数据处理逻辑。结果模型只要遇到任何和数据沾边的问题都倾向于调用它哪怕用户只是问了一句这周销售额数据怎么样它也试图调用这个技能来处理。但那个技能内部其实只能处理原始日志的清洗和转换根本不懂业务指标。改名的经验是技能名宁可长一点也不要模糊。后来我把它拆成log_cleaning和sales_report_analyzer两个独立技能description也各自写清楚触发条件误用率立刻掉了下来。命名准确还有一个额外的好处——调试的时候搜日志技能名能直接告诉你当时模型的决策意图省去很多猜谜时间。5.2 没有给模型留拒绝调用的出口这个问题容易被忽略。假设Agent收到了一个跟所有技能都不匹配的任务比如用户问你会写诗吗你没有给任何不调用技能的行为定义那模型会硬挑一个最接近的技能来调用。结果就是一个干数据监控的技能被拿去做自然语言创作。解决方案是在主提示词里明确写一条兜底指令如果用户需求不在任何技能的能力范围内直接告诉用户当前不具备这个能力。这也是为什么我在第2章强调每个技能的description都要写不适合用于什么场景——因为模型需要在技能匹配时有一票否决的空间。5.3 依赖项说明缺失脚本跑起来才报错还有一次印象很深的经历。我写了一个需要连接ClickHouse的技能本地测试一切正常到了服务器上一次都没跑成功始终报ModuleNotFoundError。排查了半天原来部署环境里没有装clickhouse-driver这个依赖。问题的根源在于我在SKILL.md里没有声明依赖框架也没法提前帮你装好。从那以后所有技能目录都强制加一个requirements.txt并且在SKILL.md的require字段里写明运行环境要求。执行引擎在调用技能前会检查环境是否满足依赖不满足就直接报一个清晰的错误信息而不是等到脚本运行中段才炸出来。5.4 参数协议设计得太死反而限制了灵活场景参数设计也是一个需要拿捏分寸的地方。有一阵子我把每个技能的参数都做成严格必填类型精确到枚举值。结果是模型在真正调用时经常因为拿不到某个必填字段而反复询问用户对话体验很差有时候模型还会为了凑参数自己瞎编一个值传进来。后来我调整了策略必填参数只保留那些没有它技能逻辑根本无法运行的字段其余全部给默认值。比如告警分析里的metrics不是必填缺省时读watchlist配置time_range如果没传就默认查最近一小时。这样既保证了脚本的健壮性也让模型在大多数场景下可以一句话发起调用用户体验好很多。6. 技能库的角色划分与团队落地节奏6.1 把技能分成三类原子技能、复合技能、策略技能技能库规模做大之后如果所有技能堆在一个层级里很快又会变乱。我们最后形成了一个三层分类可以拿来当参考类型特点例子原子技能完成单一、不可再分的基础操作查指标数据、发告警通知、读写配置文件复合技能编排多个原子技能完成一个完整业务动作异常分析取数检测格式化策略技能根据一定规则决定走哪条流程告警分级轻度异常发通知严重异常走人工介入流程这三个层次的分工对应到团队协作上也很清楚算法工程师主攻原子技能业务开发同学负责复合技能的编排产品和运维则关注策略技能的规则维护。层级分明之后评审技能改动的效率提高了很多——因为每个人只需要在自己那一层负责。6.2 小团队怎么逐步沉淀技能库如果是小团队我不建议一开始就铺开做一整个技能库平台。我的实际建议是分三步走阶段一1-2周挑最有价值的三个场景做成三个粗糙但能用的技能跑通Agent决策—技能执行—结果返回的闭环。阶段二第3-4周把技能规范补起来统一SKILL.md模板、依赖声明和参数协议。每个技能补上测试案例。阶段三第2个月起正式启用技能索引和检索流程开始做调用历史复盘跟description的持续打磨。我们当时也就是这么一步步走的没有在任何阶段追求一步到位。好处是每一阶段都有完整的可用产物团队能感觉到进展而不是在白纸上画大饼。6.3 技能库会不会发展成Agent自己维护聊到最后说一下我最近在琢磨的一个方向。既然技能本质是可复用的能力封装那么技能的开发—测试—登记流程理论上也可以让Agent自己完成一部分。现在已经有项目在做自我改进技能库Agent在运行过程中发现某个任务反复出现、但现有技能都覆盖不好就自动生成一个候选技能草稿请求人类审查后再注册进库。这种半自动的模式我认为会比全自动更稳妥——毕竟技能描述和参数协议这种东西最终还是要人来把关的。我对agent-skills这套设计整体的判断是它重新定义了Agent项目的生长方式——从一个不断堆叠提示词的单体应用变成一个可以插拔、可以协作、可以持续演进的能力生态。这中间没有什么高深莫测的东西只要多设计、多记录、多复盘你的技能库也会越来越聪明。最后再分享一个小习惯每隔一段时间回头读一读你最早写的那份SKILL.md你会很直观地看到自己和这个项目一起成长了多少。