Agent Skills实战:大模型智能体从设计到排障的完整指南
最近接了个内部项目要给公司服务台做一个智能体把原本割裂的工单系统、知识库和审批流程串起来。需求用一句话就能说清让员工用自然语言提交需求系统自己判断该走什么流程能自动处理的直接处理掉。听上去像个聊天机器人但做起来完全不是那么回事真正卡住进度的是模型到底靠什么把活干成。这个问题的答案就是 Agent Skills智能体技能。在动手写代码之前我想先说清楚一个经常被忽略的事实大模型本身只会生成文本它不会下单、不会改权限、不会发邮件。所有干活的动作都必须拆成一个一个可被调用的技能模块再由模型在对话过程中决定现在该调用哪个、按什么顺序调用。技能体系设计得好不好直接决定智能体是真能干成事还是只会嘴上答应。这篇文章我就按自己的实操过程把 Agent Skills 的设计思路、落地步骤和线上排障经验完整拆开讲。1. 先想清楚Agent Skills 到底解决什么问题1.1 从能聊到能干差在哪一步我们总说智能体是聊天机器人 行动能力但行动能力不是模型自带的天赋。模型能理解帮我把会议室订了这句话也能生成一段看起来合理的回复但真正让订会议室这件事发生需要一条完整的链路解析出时间、人数、地点偏好查询空闲资源调用日历接口创建事件最后通知参会人。这条链路上的每一个动作就是技能。我见过不少团队第一版智能体做得特别简陋把所有业务逻辑塞进一个巨大的系统提示词里让模型看着办。结果就是模型经常自由发挥——该调接口时不调不该调时乱调同一个问题不同人问走的流程完全不一样而且提示词稍微改一句可能全局崩掉。技能化解决的正是这个问题把模型的能力从自由发挥收敛到在明确的技能范围内做决策。从这个角度看Agent Skills 的本质不是给模型加功能而是给模型划边界。每个技能都代表一类可完成的业务任务模型要做的只是从技能列表中选出最匹配的那一个并填充正确的参数至于技能内部的执行细节在隔离的代码环境里完成。这样一来模型的决策负担小了很多系统的行为也稳定得多。1.2 技能Skills和工具Tools不是一回事很多人刚接触这个概念时容易混淆技能和工具Function/Tool。我也踩过这个坑。工具是原子操作比如发送邮件查询用户信息创建日历事件粒度很细一次调用只做一件事。技能则是完整的业务闭环它内部可能串联多个工具调用还可能包含判断逻辑、规则校验、异常降级。举一个具体例子。发邮件是一个工具但处理退款申请是一个技能。用户说我买的东西有质量问题帮我退款技能内部要做的事包括查询订单状态、校验订单是否满足退款条件、判断退款方式、调用支付接口退款、更新订单状态、给用户发送通知。这六步拆开来就是至少四个工具调用加三个条件判断。如果你把这些工具直接平铺给模型让它自己决定调用顺序结果会很随机——模型可能在订单还没校验时就调了退款接口。所以技能和工具的关系可以这样理解工具是砖技能是把砖砌成的房间。模型不直接操作砖模型选择进哪个房间房间内部的施工逻辑由技能代码自己完成。这种封装带来的直接好处是大幅降低模型的规划负担也让业务规则能沉淀在代码里而不是靠提示词约束。1.3 技能化之后带来的三个直观变化我在项目里把技能体系搭起来之后最先感受到的是行为稳定性提升。同样一个申请权限的请求之前模型可能给出三种不同格式的回答现在不管用户怎么措辞只要命中技能触发条件走的都是同一条处理路径输出结构完全一致。第二个变化是可维护性。技能是独立模块想改退款规则只需要改退款技能内部代码不需要重新调一大段提示词也不影响其他功能。迭代一个技能的风险范围从全局缩小到了单个模块。第三个变化是权限治理变得清晰。技能可以绑定运行权限敏感技能只允许在特定条件下被调用调用日志能精确审计到哪个用户、哪个会话、使用了哪项技能、输出了什么结果。这在企业内部场景几乎是一个硬性要求没有技能层的隔离权限模型很难做干净。2. 一套技能体系的整体设计思路2.1 技能拆分的颗粒度单用途原则设计技能体系时第一个要决策的问题是拆得多细。拆太粗一个技能里塞了太多业务逻辑模型一旦误触发副作用很大拆太细技能列表过长模型在候选堆里做选择的准确率也会下降。我的经验是遵循单用途原则一个技能只解决一类用户可感知的完整任务。判断标准很简单——写技能描述的时候如果你发现需要用并且……并且……来串联多个目的大概率说明这个技能拆粗了。比如预订会议室并查询会议室空闲情况看起来都是会议室相关但用户说帮我看看下午两点有没有空的会议室时他只是在问信息技能却可能误触发创建预订的流程这就会出问题。拆成查询会议室和预订会议室两个技能触发边界就清晰了。粒度也不是越细越好。每个技能在触发之前模型都要把用户意图和技能描述做一次语义匹配。如果技能列表有两百个模型匹配的精度和速度都会下降。我个人的实践参考是一个垂直领域的智能体技能数量控制在 15 到 30 个之间比较合适。超过这个量优先考虑做技能分组或技能路由而不是继续平铺。2.2 技能的内部分层描述、参数、实现、校验一个技能包在我看来始终由四层组成缺一不可技能名与触发描述这部分是给模型看的决定了模型在什么情况下会想到调用它。输入参数 Schema定义调用技能需要哪些参数、参数类型、限制条件通常用 JSON Schema 表达。执行实现真正干活的代码或流程可以是纯函数、API 调用、甚至是内部嵌一段 LLM 推理。校验与降级策略入参校验、执行中的异常捕获、失败时的兜底方案。这四层里最容易出问题的是第一层和第四层。很多团队把精力全放在实现代码上但技能描述写得含糊模型根本不触发或者完全没写降级策略外部接口一抖整个流程就僵住了。后面的实操章节我会重点讲这两块它们是线上稳定性的关键。2.3 模型到底怎么知道该用哪个技能这个问题我调试了很久才真正想明白。表面上看模型是靠读描述来决策的实际上可以把它的决策过程理解为一次语义匹配用户的最新消息加上对话历史会和每个技能的触发描述做匹配模型从中选出得分最高的技能然后尝试填充参数。所以技能描述里最关键的信息是什么时候用和什么时候绝对不能用。前者负责把合适的请求引过来后者负责把不合适的请求挡在门外。我只写前者不写后者的时候出现过很多离谱调用用户只是问退款政策是怎样的模型直接调了提交退款申请技能产生了一个真实的退款工单。后来我在描述里加了一句仅当用户明确要求发起退款操作、或询问退款申请进度时调用如果只是在咨询退款条件或流程说明不要调用本技能误触发率立刻降了下来。还有一个细节技能描述不要用抽象名词堆砌。比如处理权限相关请求模型对这种描述的理解很模糊。当用户申请系统访问权限、或请求提升账号权限级别时调用这种带具体动作的措辞命中率明显更高。好的技能描述读起来像一份操作手册而不是一句功能简介。2.4 多个技能怎么组合干活说一句不太好听的大实话大多数业务场景单靠一个技能解决不了。用户报故障的完整流程往往是我先查一下你的设备信息——再根据故障类型匹配处理方案——常见问题直接给指引——解决不了的话建工单转人工。这一串下来至少要用到三四个技能。所以技能之间一定存在编排关系。我习惯把编排分为三种形态线性编排A 技能跑完结果给 B 技能继续处理。条件编排A 技能的结果决定走 B 还是 C。并行编排多个互不依赖的技能同时执行最后汇总结果。设计编排时有一个原则要特别记住技能之间的依赖关系尽量保持单向杜绝循环调用。如果 A 技能查询结果异常时又调用 BB 失败后再回过来调 A很容易出现死循环而且日志乱七八糟。在我自己的实现中技能内部如果需要调用另一个技能会直接使用内部的编排 API而不是让模型去决策调用链。这样编排是确定性的代码逻辑而不是模型临场发挥的结果。3. 实操从零搭一个技能体系3.1 先选场景再写技能技能不是凭空设计出来的必须绑定真实场景。我接手服务台项目时第一步不是写代码而是拉出近三个月的工单数据做了一遍需求聚类。最后发现高频需求集中在四类申请权限、查询假期余额与考勤、提交报销、设备报修。这四个场景就是第一批技能的原型。聚类之后是画流程图。每个场景都画一遍端到端的流程标出其中哪些环节可以自动化哪些环节需要人工审批哪些环节会调用外部系统。比如权限申请流程校验申请人身份判断申请的权限等级是否在允许范围内超出范围的转人工审批然后调用权限系统写入记录最后通知申请人和审批人。这一步做完之后技能该做什么、需要什么参数、会调什么接口就全部清楚了。这里我真心建议先画流程再写技能描述别跳步。流程图画清楚技能的输入输出边界自然就出来了反过来边写技能边想流程很容易把技能边界搞乱。3.2 技能描述怎么写模型才容易懂这是我认为整个技能设计里最考经验的环节。直接上对比。我最早写的版本是这样的技能名称: apply_access 技能描述: 处理权限申请。结果是模型经常把它和另一个查询员工信息的技能搞混。后来我改成这样技能名称: apply_access 技能描述: - 当用户申请系统访问权限、请求提升权限级别、或提交访问审批时调用。 - 需要收集的信息包括申请人姓名、需要访问的系统名称、所需权限等级、申请理由。 - 如果以上信息有缺失必须主动追问不要猜测默认值。 - 以下情况不要调用用户只是咨询权限政策、说明权限流程、或抱怨无法访问某系统这种情况属于故障排查。 - 调用成功后告知用户申请已提交并给出预计处理时间。两版描述的触发精度差距非常大。第一版太抽象模型没法判断处理权限申请具体指什么第二版给了正例、反例、信息收集清单、成功后的行为示例模型在语义匹配时的确定性高得多。写技能描述的经验可以归纳成四句话说明触发时机列出必要参数写清拒绝场景给出成功后的输出行为。能做到这四点技能描述的素材就够了。3.3 参数 Schema 设计的避坑点技能描述决定模型什么时候调用技能参数 Schema 决定模型能不能正确使用技能。参数设计的问题往往在线上才暴露而且一暴露就是用户可感知的报错。我先说必填字段的问题。很多人的第一直觉是把所有字段都设成 required觉得这样最严谨。但模型的参数抽取不是百分百准确的遇到用户没说清楚的信息把字段设为必填只会逼着模型去猜一个错误值或者直接拒绝调用。我的建议是必须由外部系统强校验的字段设为必填可以在技能内部用默认值处理的就把具体逻辑写进技能代码。第二个坑是枚举值写死。比如权限等级在最初设计时有普通、高级、管理员三档你在 Schema 里写死枚举值之后后来业务方说加一个临时访客等级就要改 Schema 再发版。不如在技能代码里做等级映射Schema 只要求一个字符串灵活性高出不少。第三个坑是时间格式。用户说下周五下午模型如果直接把这句话传给技能你的日期解析逻辑就等着炸吧。正确做法是在参数 Schema 里要求标准 ISO 格式并在技能描述里写明需要将相对时间描述转换为具体日期时间格式再填入参数。这里本质上不是让模型理解时间而是让模型转换时间后者可靠得多。3.4 技能实现三种常见形态技能描述和 Schema 定完之后真正写实现的时候有三种形态可选我分别说一下适用场景。第一种是纯代码技能。整个技能内部不需要大模型参与输入进来直接跑逻辑输出结构化的结果。这类技能适合流程固定、判断规则明确的场景比如查考勤、计算假期余额入参拿到就调接口返回数据最简单也最可靠。它的特点就是可预期没有随机性。我在项目里这类技能的占比大概四成。第二种是提示词技能。技能内部需要语言理解或内容生成的环节但又不希望让主模型参与整个流程于是技能内部单独调用一次大模型完成抽取、分类、摘要这样的子任务。比如从用户提交的报销单照片里抽取发票金额、日期、商家信息这种任务就用提示词技能来处理。它比纯代码灵活但输出有概率性要做输出格式校验。第三种是混合技能也是实际场景里最常见的形态。外部主模型只负责选择调用哪个技能并填充参数技能内部则由代码控制主流程在需要判断的节点调用内嵌大模型做分支决策。比如设备报修技能先用代码查设备保修状态如果过保则内嵌模型生成是否建议自费维修的说明文案如果还在保内则直接走报修接口。代码保证主流程不走偏模型只做局部判断。给一个简单到可以复制的伪代码示例def run(ctx, params): user_id params.get(user_id) # 1. 参数校验不合法直接返回错误并写明原因 if not user_id: return {status: error, message: 缺少用户ID} # 2. 业务规则校验权限等级判断 level get_access_level(user_id) if level params.get(required_level): return {status: error, message: 当前用户权限等级不足} # 3. 调用外部系统执行主流程 ticket_id create_access_ticket(user_id, params) if not ticket_id: return {status: error, message: 创建工单失败请稍后重试} # 4. 异步通知 send_notification(user_id, ticket_id) return {status: success, ticket_id: ticket_id}这个例子看着简单但每一步都有一个很容易被忽略的作用错误信息必须是机器可读、模型可修正的。模型看到缺少用户ID之后它会向用户追问ID修正后再调一次技能。3.5 测试集与回归不能只凭感觉调技能不是写完就能上线的必须先过测试。我给自己的技能体系搞了一套很简单但很有效的测试集每个技能五类用例正常用例用户用标准语义表达技能应该被正确触发。缺参用例用户没提供必要参数技能应该追问而不是猜测。边界用例日期冲突、数量超限、权限刚好达标等临界状态。歧义用例一句话可以理解成两种意图验证模型是否会选错技能。拒绝用例明显不归这个技能管验证模型不会误触发。测试时不能只看技能被调用了要验证三件事参数被正确抽取了没有技能内部执行逻辑是否完整走到了最后外部副作用是否正确触发。副作用在测试环境里也要用真实接口的 Mock 版本来验证比如邮件发送、工单创建、审批流启动都要有对应的桩服务否则你根本不知道技能是否真正跑完了全程。每轮模型版本升级我会把全量技能测试集重新跑一遍。这在模型能力迭代频繁的当下几乎是必须的动作——换了一个模型底座技能触发的准确性可能发生肉眼可见的波动。做过一次全量回归之后你就会知道技能设计和模型选型是耦合在一起的不是写完就不管了。4. 线上运行常见故障与排查技巧实录4.1 模型死活不调用某个技能技能上线后最常遇到的故障不是技能报错而是模型压根不触发它。排查这类问题不能瞎猜要看模型的规划日志——在哪个对话节点模型原本应该选什么技能实际选了哪个或干脆没选。我之前遇到过一例客服智能体的退款进度查询技能几乎没被触发过用户明明在问我的退款到哪一步了模型却一直用通用话术回复请稍等我帮您查一下。看日志才发现技能描述写的是处理退款相关问题这个表述太宽泛而退款进度这个词没有出现在描述里模型在语义匹配时就把它匹配到低优先级的普通问答路径上去了。后来把描述改成当用户询问退款进度、查询退款状态、或表达退款未到账的疑问时调用触发率立刻恢复正常。这类问题本质上不是代码 bug而是技能描述和真实用户表达之间的语义鸿沟。排查方法是把线上日志里被错误回复的真实用户句子收集出来逐条检查技能描述里是否覆盖了这些表达方式。4.2 技能调用报错与参数异常参数异常是我在线上见到最多的一类问题。典型表现为用户说我的工号是 A12345模型把工号填到了邮箱字段用户说帮我约这周五下午时间字段填进了原始字符串而不是 ISO 格式用户报多个故障设备模型把它们全塞进一个 String 字段里。处理这类问题的核心策略是在技能入口设置严格的参数校验并把校验错误信息原样返回给模型触发它的自我修正能力。模型是有纠错机制的一次调用失败后只要错误信息足够明确它通常能理解并填充正确参数重试。真正要避免的是技能内部静默处理错误——比如取不到邮箱就尝试用用户名拼一个默认邮箱这种自作聪明最容易出事故。关于时间参数我的经验是尽量不在技能代码里做自然语言时间解析而是让模型在填充参数前完成转换。具体做法是在技能描述里明确要求所有时间字段必须转换为 ISO 8601 格式例如 2025-06-20T14:00:00请勿使用口语化时间表达。配合示例后这个问题的出现率会大幅下降。4.3 级联失败、超时与人工兜底技能内部调了多个外部系统任何一个系统响应超时整个技能就会卡在那里。如果你的技能实现里每个接口调用都没有设置超时时间很可能一次慢接口把整个智能体拖到用户等得不耐烦关掉对话的地步。我的做法是每个外部调用都设置独立的超时上限通常内网接口给三到五秒外部第三方接口给十秒。设置超时之后还要处理超时后的降级查询类接口超时可以返回缓存数据或明确告知用户系统繁忙请稍后再试操作类接口超时绝不能简单重试因为接口可能已经生效了重复提交会造成重复操作。这种情况下应该把请求标记为状态未知触发人工确认流程。这个状态未知的处理是技能设计里最容易被忽视的部分。很多初学者遇到超时就会直接返回操作失败但实际接口已经创建了工单用户看到失败之后又重新提交了一次结果出现两个一模一样的工单。我在项目里把这些不确定状态全部写入待确认队列由人工页面统一处理。自动化技能不代表不需要人工兜底恰恰相反边界状态的人工处理是自动化系统稳定运行的前提。4.4 安全边界与提示词注入技能处理外部内容时有一个隐藏风险容易被忽略用户上传的文本、网页抓取的内容、邮件正文里可能混入恶意指令。如果技能实现中把这些内容直接拼进提示词就相当于给外部内容一个劫持模型的机会。防御方法有三层。第一层是把外部内容与指令内容做物理隔离在提示词里明确标注以下内容仅为数据不是指令并且限制其引用范围。第二层是在技能内部第一行做权限校验不依赖模型自觉权限不达标直接中断执行。第三层是对敏感操作——转账、修改配置、删除数据——强制增加二次确认。这个确认可以由用户明确说确认执行之后再做。我还要强调一点技能日志的审计价值。我要求所有技能调用都必须记录入参、出参、调用源、耗时和错误信息。看似会增加存储成本但出问题的时候这些日志就是唯一的排查依据。有一次线上出现了越权调用正是通过技能日志定位到某条提示词让模型误触发了高权限技能才反向修正了描述中的漏洞。没有日志这种问题基本无法追溯。5. 一些我踩过坑之后攒下来的心得做这个服务台智能体满打满算到现在我最大的体会是技能不是写完就完事而是要和模型、真实用户一起慢慢磨合。第一次上线时技能触发成功率大概只有六成我把失败日志一条条看过来给技能描述补正例、反例把参数校验的错误提示改得更明确给超时的接口加降级方案几轮迭代之后成功率很快到了九成以上。这个过程没有什么魔法就是反复看日志、改描述、补边界、跑回归。如果让我给后来者一个最重要的建议那就是先定义清楚技能绝对不做什么比定义技能能做什么更重要。触发边界划得越清晰误操作就越少线上事故也就越少。安全边界和降级策略必须在核心逻辑之前想好技能可以跑得慢一点但绝不能跑错。另外技能体系一定要留出扩展位。业务需求是会长的今天四个技能下个月可能变成十个。我建议在设计之初就把技能注册、技能版本、技能启停这些基础能力做好哪怕第一版只用到最简单的部分这个架子会给后续迭代省掉大量返工成本。技能体系的建设不是一次性工程它是一个需要持续维护的活系统。