年初和几个做Agent应用的朋友聊落地瓶颈大家反复提到同一个词agent-skills。聊到最后基本能形成共识——模型本身的智力水平已经不是主要矛盾了真正卡住项目进度的是怎么把五花八门的工具调用、业务逻辑、异常处理组织成一整套能让Agent稳定复用的“技能”。这不是写几个function再塞进系统提示词那么简单的活儿而是需要一套完整的设计规范、注册机制、评测体系和工程化保障。这篇文章我就把过去几个月在模拟项目X里搭建技能层的完整过程整理出来从抽象设计到代码实现从踩坑复盘到测试评估尽量讲透。这套内容适合谁看两种人收益最大一是正在做Agent应用的开发者你迟早会遇到“提示词越写越长、模型还是经常做错”的困境二是想从纯提示词工程往工程化方向转的算法工程师技能层就是那个最值得投入的抽象层次。1. 光有聪明的模型Agent还是干不成活很多人第一次接触Agent的时候直觉是“既然模型这么聪明给个目标它就能自动完成”。真上手做才发现模型聪明归聪明干起活来经常像刚入职的实习生——理解力不错但对业务流程、边界条件和异常场景一无所知每次都要从头教还教完就忘。1.1 从“工具调用”到“技能编排”的落差先区分两个容易混淆的概念工具调用和技能编排。工具调用是最底层的能力本质是把一个函数暴露给模型比如search_web(query)、send_email(to, subject, body)。模型需要自己决定“什么时候调、调几次、参数怎么组合”。这是通用的、无状态的。技能编排则是在工具之上增加了一层业务封装。一个技能内部可能包含多个工具的协作顺序、前置校验、后置处理、异常兜底。比如“周报汇总”这个技能内部的完整流程可能是拉取本周代码提交记录、汇总各模块负责人填写的进展、按固定模板格式化、最后发送到指定频道。模型面对的是一个generate_weekly_report(team_id, week)技能而不是十几个零散的工具。从工具到技能中间差的不是代码量而是工程抽象。没有这一层抽象模型每次执行任务都要重新理解业务流程既慢又容易跑偏有了这一层模型的任务就从“理解业务并规划”降维成“根据描述选技能、填参数”。1.2 Skills要解决的三类典型卡点在实际项目里我会把“没有技能层”导致的典型症状分成三类你可以对照一下自己项目是不是也有类似情况第一类提示词爆炸。为了教会模型一个复杂流程系统提示词从500字涨到3000字还在继续膨胀。每加一个新业务场景提示词里就多几十行指令最后模型根本抓不住重点指令冲突反而更频繁。这不是提示词工程能兜住的是抽象层级不对。第二类工具失控。平台里的工具函数越来越多模型选择工具的准确率直线下降。今天把get_user_info当成update_user_info调用明天把send_reminder用在完全不该用的场景。工具多了之后选择本身就成了模型最重的负担。第三类不可评测。改动一个提示词或者一个工具实现影响范围有多大完全靠上线后看用户反馈根本没有前置回归手段。原因就是缺少一个稳定的、可测试的中间层——技能。1.3 一个形象的类比模型像大脑技能像肌肉记忆我经常用这个类比跟团队讲技能层的价值模型是大脑负责决策技能是肌肉记忆负责执行。一个人再聪明如果每次开锁都要现学“怎么握钥匙、怎么对准锁孔、怎么用力”那效率一定很低。真正熟练的人开锁这个动作已经内化成肌肉记忆大脑只需要下达“开门”这个指令。Agent技能层就是干这个事的。把那些高频的、有固定套路的操作固化成肌肉记忆让模型大脑只做最上层的结果判断和目标拆解。这样模型推理开销大幅下降、行为稳定性显著提升业务人员也终于敢让Agent去处理真实任务了。2. 我没有一开始就抽象技能后来不得不重构说实话我第一次做Agent项目的时候也没想这么清楚第一版方案就是典型的“堆提示词”路子。这节我把当时的演进过程写出来不是为了展示“我做了多好的设计”而是说明“技能层这个东西往往是被现实教训逼出来的”。2.1 第一版把所有逻辑堆在系统提示词里模拟项目X的需求是做一个面向内部运营的智能助手要能查询数据、生成报表、推送消息、处理审批。第一期功能不多我用了最直接的办法在系统提示词里写清楚“当你收到某类请求时依次执行以下步骤”然后给模型挂上几个现成的API工具。一开始效果还可以因为场景少、指令清晰模型基本能走通。但随着运营团队提出更多需求——多条件筛选报表、定时提醒、跨系统数据比对——系统提示词里的规则越加越多问题开始密集爆发。最典型的一个场景我写了规则“当用户要求生成报表时先检查数据源是否完整再调用报表工具”。“数据源是否完整”这个校验模型有时候会主动调用一个检查接口更多时候它会跳过这个步骤直接生成。结果就是报表生成成功率只有七成剩下的三成需要人工补救。那段时间我和团队几乎每天都在做同一件事看对话日志找模型漏掉的步骤给提示词打补丁。补丁打完这个场景好了另一个场景又坏了。那时候我就意识到把业务流程写在提示词里本质是在用最脆弱的载体承载最需要确定性的逻辑。2.2 触发重构的四个信号回头看有四个信号是非常明确的如果你项目里也出现了说明你也该考虑引入技能层了信号一系统提示词超过1500字且还在增长。大部分模型对超长提示词中后段内容的遵循度会明显下降越加越多反而越不听话。信号二同一个流程在不同对话里被模型以不同顺序执行。业务步骤没有固化模型每次“自由发挥”的轨迹都不一样。信号三新增一个工具后旧场景反而开始出错。工具多了之后选择准确率下降典型表现就是工具互相干扰。信号四线上问题只能靠“看日志猜原因”无法在开发环境稳定复现。因为模型的执行路径不固定错误自然也不可复现。这四个信号里第四个最致命。不可复现的问题意味着你连修都没法修只能祈祷下次模型换个走法。我从信号二明显出现的时候就开始设计重构方案了因为流程不可控是这一切劣化的根源。2.3 重构后的目录结构与命名规范重构的第一步不是写代码而是把已有的流程盘出来形成一份技能清单。做这份清单时我定了一套约定后来发现这套约定对后续开发帮助巨大分享给你技能目录按“场景域/技能名”两级组织。场景域就是业务模块比如report、message、approval。技能名是动词短语比如generate_daily_report、send_group_notification、create_leave_approval。每个技能对应一个目录目录下固定放四个文件skills/ ├── report/ │ ├── generate_daily_report/ │ │ ├── SKILL.md # 技能描述与使用场景 │ │ ├── schema.json # 入参定义 │ │ ├── execute.py # 执行体 │ │ └── validate.py # 验证器SKILL.md是给模型看的描述这个技能是什么、什么情况下用、行为边界在哪。schema.json是参数协议定义模型需要提供哪些入参。execute.py是实际逻辑把工具调用编排成业务步骤。validate.py是技能自检器在执行前检查参数合法性在执行后校验结果完整性。这套目录结构我做了一次重构后就再没改过。四个文件的职责划分清楚开发、测试、模型三方的协作界面都变得非常明确。3. 技能的四件套描述、入参、执行体、验证器一个技能要有资格进入Agent平台四个文件必须是完整的。很多项目死于“只写执行体”SKILL.md和验证器被当成可有可无的东西结果技能上线后模型乱用、结果错了都不知道。这一节逐个拆解每个文件讲清楚怎么写、为什么这么写。3.1 技能描述怎么写才不会让模型“看不懂”SKILL.md是模型理解技能的窗口。很多开发者不重视它随手写两行“生成日报”就完了。模型拿到这种描述确实也能跑但经常在模糊场景里误用用户明明问的是上周数据模型却调了generate_daily_report用户想查昨天的报表模型却咬定没有这个能力。我总结了一个三段式的描述模板触发条件 执行边界 典型示例模板包含第一段“何时使用”写清楚这个技能覆盖哪些请求类型以及哪些相似但不属于本技能的请求类型。第二段“行为边界”写清楚技能会做什么、不会做什么特别是涉及外部副作用时比如发消息、改数据要有明确禁令。第三段“示例”给出2-3个真实的输入和对应输出帮助模型建立模式匹配。下面是我实际项目里的一个SKILL.md片段脱敏后# generate_daily_report ## 何时使用 当用户要求生成“日报”“每日数据汇总”“当日运营情况”时使用本技能。 如果用户要求的是周报或月报不要使用本技能转用 generate_period_report。 ## 行为边界 - 只读取数据不修改任何线上配置。 - 若数据源接口超过 30 秒未响应直接返回失败不要重试超过 2 次。 - 日报中的指标只包含“新增用户数、活跃用户数、转化率、异常告警数”四项。 ## 示例 输入生成今天的日报 输出返回包含上述四项指标的一个表格并附上各项环比昨日变化率。这段描述我看过很多模型跑出来的效果遵循度相当高。关键在“何时使用”里写清了“用错场景的负例”模型选择技能的准确率会明显上升。这也是我特别想强调的描述里写“什么时候用”重要写“什么时候不用”更重要。3.2 入参设计宁可多写约束不要盲目相信模型schema.json直接决定模型能不能正确填充参数。我发现很多项目失败不是模型不会选技能而是填参数时总是丢三落四。根本原因通常是入参设计得太随意。举个例子一个send_group_notification技能参数content是必填的。模型在从用户原话里提取内容时可能只提取了核心句子丢了称呼、丢了下文。如果schema.json里只有content: string那模型大概率会按自己的理解截取一段不会追问用户。正确做法是把约束写细。下面是我的一个入参定义{ type: object, properties: { content: { type: string, description: 完整通知正文必须以开头必须包含具体事项和截止时间。长度不超过500字。 }, target_type: { type: string, enum: [team, all], description: 接收范围。team为指定团队all为全员。默认team。 }, send_time: { type: string, description: 发送时间格式HH:MM。不填则立即发送。 } }, required: [content], additionalProperties: false }这段JSON里藏着几个容易被忽略的讲究每个字段的description都具体到“格式”“长度”“包含元素”级别。模型读到这种级别的约束填出来的参数质量高得多。additionalProperties: false一定要设。不设的话模型可能脑补出schema里不存在的参数后端一解析就报错。enum约束尽量给。给模型越少的选择空间它出错的概率越低。3.3 执行体与验证器让技能可测、可回滚execute.py是核心逻辑所在。我在项目里习惯把所有外部调用都封装成统一的接口这样技能里只描述“做什么”不关心底层实现。后面如果想换数据源、换消息通道只改封装层技能本身完全不用动。验证器validate.py容易被忽略但它其实是技能质量的守门员。我设计验证器时参考了“防御性编程”的思路不信任任何输入也不信任外部服务的返回。具体来说验证器做三件事执行前校验参数格式、取值范围是否符合规范。不符合就直接拒绝并返回人类可读的错误信息。执行后校验结果结构是否完整。比如日报技能必须返回四个指标缺一个就视为失败。副作用确认涉及发送消息、修改数据等操作时用确认机制兜底避免Agent“替用户做主”。这样设计的好处是技能的可测试性大幅提升。后来我写自动化回归时只需要准备好入参和期望结果执行execute.py再跑validate.py就能批量验证。这套机制帮我省了至少一半的排障时间。3.4 技能模板代码示例把四个文件的代码骨架放出来结构上可以供你直接参考改造# execute.py 骨架示例 def execute(params: dict, context: dict): # 1. 参数预处理 payload normalize_params(params) # 2. 数据获取 # data data_service.fetch(payload) # 3. 业务处理 # result transform(data) # 4. 结果封装 return {status: success, data: result}# validate.py 骨架示例 def validate_input(params: dict) - dict: errors [] if not params.get(content): errors.append(content is required) if len(params[content]) 500: errors.append(content too long) if errors: return {valid: False, errors: errors} return {valid: True}这段代码不复杂重要的不是代码本身而是“先验证、后执行”的执行顺序必须固定。我会在技能框架里强制所有技能都走validate - execute - post_check的流水线防止有人图省事跳过校验。4. 技能注册表与生命周期管理技能写好了怎么让Agent平台认识它如果只是每个技能一个目录平台启动时扫描一遍然后全部加载这种朴素方案在技能数少的时候没问题一旦超过几十个就会遇到三个坎没法单独禁用一个坏技能、没法灰度新版本、没法处理技能之间的冲突。所以需要一个注册表机制。4.1 为什么需要注册表而不是一堆散文件注册表的核心作用有三个发现、路由、治理。发现平台启动时从注册表加载技能元信息而不是全盘扫描文件系统。后者慢且不可控前者快且可以精确控制哪些技能生效。路由当模型选择技能时平台把候选技能列表从注册表里取出来而不是把全部技能描述都塞给模型。这能显著降低模型的上下文负担。治理注册表里可以记录每个技能的状态、版本、负责人、最近修改时间。出问题时能快速定位到“哪个技能、哪个版本、谁改的”。我在项目里用的是一个非常轻量的实现一张技能注册表本质上就是一个JSON索引文件包含name、version、path、status四个字段。扫描目录后生成索引平台只认索引里的技能。4.2 动态加载、禁用热修复的具体做法技能上线后出bug最怕的就是要重启整个服务。注册表机制可以很好地解决这个问题平台内置一个“技能文件监听器”每隔几秒检查技能目录的哈希值发现变化就重新加载对应技能。这样改完技能代码几秒内就能生效不需要重启Agent服务。禁用一个坏技能也非常简单。把注册表里status改成disabled平台在下一轮路由时就不会把它返回给模型。线上出现“这个技能一直选错”的情况时我可以一边禁用一边看日志分析原因不用慌慌张张改代码。不过有一点必须提醒动态加载虽然方便也要谨慎使用。如果技能正处于执行中你直接把代码文件替换掉了旧进程可能引用到旧版本和新版本混杂的状态。我的做法是“三不替换原则”执行中不替换、验证未通过不替换、没有灰度环境不替换。宁可晚几分钟生效也不要引入线上不可预期的状态。4.3 版本化与冲突处理技能版本化的粒度我建议至少到“变更记录”级别。具体做法是注册表里加一个changelog字段每次发布新版本时记录变更描述。这样排查问题时能回答“这个技能为什么从2.0升到2.1”而不是对着代码diff猜。冲突处理主要发生在两个场景同名不同目录两个技能目录撞了同一个name注册表构建时必须报错或按版本规则收敛不能静默覆盖。技能描述互斥两个技能的“何时使用”段落描述重叠导致模型选哪个都像对的。这属于设计层面的冲突需要在技能评审时发现。我后来加了一条硬性规则任何两个技能之间不能对同一个用户意图给出重叠的触发条件。新技能提交时必须检查描述相似度超过阈值就打回。从实操反馈来看这种注册表机制虽然没有引入多复杂的系统组件但解决了日常维护里最琐碎又最致命的问题——技能到底有没有效、是谁改的、能不能快速下线。5. 没有评测体系的技能库越用越失控很多人以为技能层搭好了Agent就能稳定干活了。事实是技能库是会退化的。你改了一个技能模型行为变了你加了一个技能旧技能选择率降了你调整了一个参数约束某条业务链路断了。没有一套自动化的评测体系这些问题只能等到线上用户报错后才发现。5.1 技能评测集搭建的三层结构评测集不能随便堆一批对话记录就完事。我建议分三层搭建第一层单技能单元评测。每个技能一组用例用例里覆盖正常输入、边界输入、错误输入三大类。正常输入验证“做对了”边界输入验证“做得稳”错误输入验证“知道拒绝”。其中错误输入很容易被忽略但它恰恰防止了技能被滥用。第二层多技能路由评测。准备一批混合意图的请求验证模型能不能正确选择技能。不仅要测“该调用A时有没有调A”还要测“该调用A时有没有误调成B”。后者是整个评测里最值得关注的指标。第三层端到端任务评测。模拟一条完整的业务任务从用户提出目标到任务完成中间可能一次或多次调用技能验证最终结果是否满足预期。这层评测会暴露技能编排和模型推理之间的协作问题。三层评测集加起来我在模拟项目X里准备了大概800条左右的用例。数量看起来不少但基本都是通过积累真实日志后脱敏标注而来投入产出比非常高。5.2 成功率之外的指标召回率、误触发率、耗时一个技能好不好不能只看“成功率”。我建议至少看四个指标成功率调用后结果被验证器判定为通过的比率是基础指标。召回率应该调用该技能的场景里模型实际调用的比率。召回率低说明技能描述写得太窄模型认不出来。误触发率不该调用该技能但模型调用的比率。误触发率高说明技能描述写得太宽或者和其他技能描述重叠。平均耗时技能从开始调用到返回结果的时长。在Agent任务链路里一个技能耗时过长会直接拖垮整体响应体验。我拿项目里两个技能的真实数字来说明技能A的误触发率是3%技能B是26%。技能B的SKILL.md里触发条件写了“当用户需要查看数据时”这个描述等于没说任何数据分析请求都会优先选它模型被带偏得厉害。后来我把它改成“当用户需要查看实时监控指标、且指标来自XX系统时”误触发率降到了6%。5.3 回归测试如何在每次迭代后自动跑评测集搭好之后关键是要“每次迭代都跑”。我把这套评测做成一个简单的命令行回归工具输入一个分支名工具自动拉取最新代码、执行三层评测集、汇总指标报告。这个工具本身不复杂无非就是调API、跑用例、汇总统计但它的价值在于把“技能变更影响面”从玄学变成了可见的数字。有一次我想给“通知发送类”技能统一加一个“敏感词过滤”功能改完后回归测试发现“周报汇总”技能的成功率下降了12%原因是周报内容里包含了几个测试专用的代词被过滤规则误杀了。如果没有回归测试这个问题会在上线后被真实用户撞上。正是这种“改A坏B”的体验让我深刻体会到技能库是一个高度耦合的系统任何变更都必须过评测体系这关。6. 实战踩坑那些文档里不写的细节这节写几个我在项目里真实遇到、卡了很久、最后想明白的细节问题。这些内容基本查不到现成文档属于“踩完坑才总结出来的经验”。6.1 同一个技能被多个任务调用时的上下文污染早期我发现一个诡异现象同一个技能单独测试一切正常一旦放进多轮对话里经常出现参数串场。比如用户先问“本周的日报”再问“顺便把上周的也生成一下吧”第二个请求竟然复用了第一个请求里的日期范围。根源在于我把“技能执行”和“会话上下文”耦合得过紧。模型在生成参数时会参考对话历史里的旧参数。这在某些场景下是优点但在“日期”“筛选条件”这类参数上就是灾难因为用户的新请求往往隐含“重新指定”。修复方案有两个层面一是在SKILL.md里明确写“所有参数以当前请求为准不要沿用历史参数”二是在请求Agent平台时关掉技能参数对上下文的自动继承。双管齐下之后参数串场的频率降到了几乎为零。6.2 模型总是误解“可选参数”该怎么治当schema.json里同一个技能有五个字段、其中三个可选时模型经常忽略可选字段。但很多业务的烦人之处在于可选字段恰恰是决定输出质量的关键。比如“发送通知”技能里的send_time是可选的但运营希望模型能智能判断“紧急通知现在发、普通通知明天早上九点发”。模型几乎从不主动填这个字段导致通知经常在下班时间轰炸用户。这一开始把我搞得很头疼后来我换了一个思路不要用“可选/必填”来传达业务意图而是把业务意图写进字段描述里。我把send_time的description改成“若通知内容含‘紧急’或‘立即’则必须设为当前时间若通知内容为每日例行播报则必须设为次日09:00其他情况可省略。”改完之后模型填充这个字段的频率提升了。这说明模型不是不听话而是大多数时候是真的不知道你的业务偏好是什么。描述写得越具体行为就越可预期。6.3 技能执行慢比不执行更致命一个技能从模型选对、填好参数、到执行完返回结果整个链路如果超过15秒用户的体感就是“卡死了”。我在项目早期没太关注耗时结果被运营吐槽“Agent比我手动做还慢”。后来我做了两件事改善耗时。第一件给技能加“快速失败”机制数据源接口2秒不返回就切换备用通道5秒不返回就直接失败返回不无脑重试。第二件把耗时长的计算步骤异步化。日报生成里的“环比计算”要跑十几秒我把它拆成两步先返回基础指标再异步补充环比数据。模型可以先给用户一个可用的初步结果等补充计算完成后再推送给用户。经过这两点优化技能的平均执行耗时从14秒降到了4秒左右。用户体感好了很多模型也愿意更频繁地调用技能整体任务完成率反而提升了。这个方向容易被忽视但它对Agent应用的实际落地至关重要。6.4 权限边界是最后一道安全阀最后说一个和技能逻辑无关、但非常关键的细节权限边界。技能本身再智能执行层也必须有一个刚性权限边界。我在模拟项目X里给技能层做了“最小权限”约定每个技能只能访问它完成职责所必需的数据和接口不能越权。具体落地到代码上是给每个技能配一个permissions声明{ skill_name: send_group_notification, permissions: [ read:contact_group, write:notification_message ], deny: [ write:system_config ] }这样即使模型的决策出错比如误选了某个技能执行层也会因为权限不足而拒绝操作。我见过太多Agent事故根源不是模型傻而是权限边界太松。技能层是最后一道防线这是架构层面的基本原则不建议省。我在复盘这轮技能层改造时最大的体会是agent-skills的核心不是“会调用工具”而是“有一套完整的机制确保调得对、调得稳、坏了能快速发现、改了能快速验证”。技能描述、入参协议、执行逻辑、验证器、注册表、评测集每一块看起来都不起眼但它们合在一起决定了Agent应用是“能用”还是“好用”。我也还在持续迭代这套体系比如下一步准备把技能之间的依赖关系做成可视化拓扑让“改A会影响哪些下游”一眼就能看清楚。这些方向等有了结论再来分享。