做 AI Agent 这一年多我最大的感受是真正让一个智能体从“能聊天”变成“能干活”的不是模型选得多大也不是 Prompt 写得多花哨而是你有没有一套像样的agent-skills体系。简单说就是把你希望 Agent 完成的任务拆成一个个边界清晰、可复用、可测试的“技能”再让模型像工人按图纸施工一样去调用它们。这篇文章我想聊聊我在这套思路上的完整实践怎么理解技能、怎么设计技能、怎么编排技能以及我踩过的几个比较典型的坑。适合正在做 LLM Agent、自动化助手、企业内部 Copilot 的开发者参考也适合刚从 Prompt 工程往 Agent 工程转型的朋友。1. 重新理解“技能”Agent 不是聊天框而是工具箱1.1 一句话说清 agent-skills 要解决的问题很多人在做 Agent 时会陷入一个误区把 Agent 当成一个更强的聊天机器人什么都靠模型临场发挥。但在真实业务里用户要的不是“聊得开心”而是“把事情办成”。比如让 Agent 帮我查昨天的销售数据、帮我约一个会议室、帮我把客户反馈整理成工单——这些事如果每次都靠模型现场编流程结果一定是不稳定的。技能Skill的本质是把某一类任务的执行逻辑、判定条件和边界约束打包成一个可复用的单元让 Agent 在这件事上不再“自由发挥”而是走一条确认过可行性的路线。我用一个生活化类比解释一下你不可能要求一个刚入职的实习生靠常识帮公司搞定财务报销、差旅预订和合同审核但你可以给他一本操作手册告诉他什么场景翻哪一章、每一步怎么做、遇到异常找谁。agent-skills 就是这本操作手册Agent 就是那个照着手册干活的实习生。1.2 从 function calling 到 skill能力封装的三个演进阶段我在项目里经历过三个阶段算是比较典型的演进路径第一阶段直接在业务代码里写死接口调用。用户在对话框说“查一下 A 客户”代码就用正则或关键词匹配去触发某个 API。这阶段的问题很明显——换个说法就失灵加个需求就改代码本质上只是披着 AI 外衣的传统指令系统。第二阶段用 Function Calling / Tool Use。把每个可调用的函数交给模型让它根据用户意图自己选择并填入参数。这比硬编码灵活很多但很快你会发现一个函数解决的是“单次动作”而真实任务往往是“一串动作”。比如“约会议”至少涉及查日程、创建会议、发通知、设提醒四个动作如果只暴露四个函数让模型自由组合它经常会在中间环节出错。第三阶段技能化。不是把函数直接裸露给模型而是把“约会议”这整个工作流封装成一个技能连同一份清晰的触发描述、输入输出契约、执行策略和失败处理一起打包。模型只需要判断“用户想约会议”剩下的流程由技能内部处理。这才是 agent-skills 真正带来的价值把模型的智能用在“决策”上把确定性的逻辑从模型那里剥离出来交给可控的代码层执行。1.3 通用 Prompt 的局限为什么闲聊式指令撑不起复杂任务你可能会想既然模型这么聪明我把所有步骤写在 Prompt 里不就行了吗我也这么干过结果在任务稍微复杂一点时就崩了。原因有三1Prompt 是建议性约束不是确定性执行。模型可以选择性遗忘、自由发挥重组步骤尤其是 Token 上下文越长越容易在细节上走样。2Prompt 难以做状态管理。多步任务往往有中间状态比如“会议已创建但还没通知参会人”这种状态保存在哪里写回 Prompt 里会让上下文越来越脏最终影响模型判断。3Prompt 不能单独测试。一个几十行的大 Prompt 出了问题你很难定位是第几步逻辑出错。而一个技能可以单独跑、单独测、单独迭代。所以我的结论是通用 Prompt 适合探索期技能化适合生产期。当你的 Agent 功能还在验证需求时用 Prompt 快速迭代没问题一旦需求明确、要稳定上线了就该把核心路径逐条技能化。2. 技能的四层设计法从自然语言到可执行资产这一节是整篇文章的核心。我会讲我实际使用的技能设计方法概括为“四层设计法”每一步都有对应的产出物。2.1 第一层能力盘点——把“能做什么”翻译成动词很多人设计技能时犯的第一个错是按系统模块来列。比如“用户管理模块”“订单模块”“日程模块”这种划分是写给程序员看的不是写给 Agent 看的。正确做法是按“用户目标动作”来列。每个技能应该是一个动词短语例如“创建会议邀请”“查询订单物流状态”“导出客户对账单”“更新项目里程碑”这背后的逻辑是Agent 的意图识别本质上是在做“动词匹配”。用户说“帮我约个会”“明天下午安排个会”“约一下张经理的时间”这些句子的共同点是动作意图“创建会议”而不是“用户管理”。你清单上写的是动词短语模型才更容易把自然语言映射过去你写的是模块名模型要费劲翻译翻译就会出错。我在团队里做过一次统计把所有技能描述统一改成“动词对象”格式后意图路由的准确率从 76% 提升到了 89%这个提升纯粹来自描述格式的规范化。2.2 第二层输入输出契约——技能边界的物理实现技能不能像函数一样只写“传入用户 ID返回结果”因为它的输入来自自然语言可能缺值、可能模糊、可能带歧义。所以输入输出契约本质上是在你和模型之间定一个明确的交接协议。我的做法是给每个技能定义结构化参数用参数来约束模型。下面是我常用来定义一个“会议安排”技能输入部分的模板{ params: [ {name: title, type: string, required: true, description: 会议标题}, {name: start_time, type: string, required: true, description: 开始时间ISO 8601格式}, {name: duration_minutes, type: int, required: false, default: 30}, {name: participants, type: array, required: true, description: 参会人邮箱列表}, {name: room_keyword, type: string, required: false, description: 会议室偏好关键词} ] }这个契约有几个容易被忽略的点字段描述要带格式约束比如时间必须写 ISO 格式。否则模型会给出“明天下午”这种自然语言值需要二次解析。default 值很重要。模型不是每次都善解人意用户没提时长时你不能报错给个默认值比让 Agent 反问用户更顺滑。输出也要定义不能只返回一个“成功”。我通常会定义输出结构包括状态、结果摘要、下一步建议。这样 Agent 拿到结果后可以继续和用户对话而不是返回一段底层数据。提示参数清单不要太长超过 8 个字段模型就容易填错。如果超过 8 个字段说明你的技能拆得太大应该切分。2.3 第三层执行策略——技能肚子里装的到底是什么技能不是简单“挂一个函数”就完了它内部可以有三种执行策略我根据任务复杂度来选择策略一单次工具调用。技能收到参数后直接调用一个外部 API 或数据库查询返回结果。适合查天气、查汇率、查订单这类“一问一答”型任务。它不复杂但需要注意超时和失败时的默认回复。策略二内部多步编排。技能内部包含多个动作但不需要模型参与由代码确定性执行。比如“取消订单”可能需要依次调用“验证订单状态→发起取消请求→发送退款通知→记录日志”。这些步骤在一个技能内部串联对外部模型黑盒化。它的优点是稳定缺点是如果业务规则变了需要改代码而不是改提示词。策略三子技能复用。技能内部再调用其他技能。这是 agent-skills 最有威力的形态。比如“安排一次客户拜访”这个技能里面会复用“查找客户联系人”“查询我的日程空档”“创建日程”“发送确认邮件”四个子技能。子技能之间通过技能编排层协调而不是让外层模型陷入细节。我建议你从策略一开始每个技能先做成单次调用跑通了再升级成多步或复用。一上来就想做复杂编排八成会在调参的路上崩溃。2.4 第四层可测试性——技能不能只靠“看着像能跑”我见过很多 Agent 项目死在“上线没几天就被业务方投诉”核心原因是技能从没被单独验证过。技能的可测试性决定了它能不能从实验品变成生产资产。我给每个技能配套三类测试单元测试制造各种输入包括正常值、缺省值、异常值验证技能本身的参数校验和错误处理。意图测试准备一组典型用户句子验证意图路由能否把它们准确对到技能上。我一般每个技能准备 20-40 条真实语料覆盖说法变化。端到端回归把技能接上模型和工具后模拟完整对话检查链路是否正常。刚开始做这些测试时最烦人的是意图测试里的“相似但不同”的句子比如“帮我查一下明天的日程”和“帮我加一条明天的日程”一个查一个写差一个字但技能完全不同。后来我在每个技能的描述里都加了“触发条件”和“不触发条件”两部分这个做法让意图测试的准确率大幅提升。一个技能如果经历了这三层测试它的稳定性会远远超过同等复杂度的纯 Prompt 方案。这也是为什么我强调与其维护几个庞大提示词不如维护几十个小而精的技能。3. 技能编排与路由单技能是点多技能才是网当技能数量超过十几个之后真正的问题不再是单个技能怎么实现而是系统怎么决定在哪个场景用哪个技能、技能之间怎么协作。3.1 意图路由用户输入怎么找到对的技能路由是 agent-skills 体系里承上启下的关键层。它的任务很简单根据用户当前的输入和上下文选出应该激活的技能。我这里说的路由有两层含义。第一层是技能选择第二层是参数填充。模型在这里的工作是“分类信息抽取”这是 LLM 最拿手的任务比让它自由发挥安全得多。我常用的结构化路由输出格式如下{ skill: meeting_scheduler, confidence: 0.95, params: { title: 产品周会, start_time: 2025-06-15T10:00:0008:00, participants: [devexample.com, pmexample.com] } }路由设计的几个心得每个技能必须有一个“触发描述”明确说明什么时候用它。这个描述的质量直接决定路由准确率。我写触发描述的模板是本技能处理什么类型的请求、常见的表达方式有哪些、什么情况下不要触发。兜底技能必须有。当所有技能置信度都低于阈值时不能直接报错要进入一个通用的“澄清/转人工”技能继续追问或说明能力边界。没有兜底技能用户觉得 Agent 一下子就变蠢了。路由的结果最好带置信度。我设了一个阈值通常 0.7 以上直接执行0.4-0.7 进入追问0.4 以下走兜底这样可以在“优先自动化”和“优先不犯错”之间调到合适的平衡点。3.2 依赖与冲突技能不是孤岛技能数量多了以后会出现两类问题依赖关系和意图冲突。依赖关系是指一个技能的结果是另一个技能的输入。比如“帮我安排一场跟李总的会议”先要“查找李总的联系方式”接着“查询李总可用的时间”然后“创建会议”最后“给李总发邀请”。这些技能之间有明确的先后顺序。我一般用一个轻量级编排器把它们串起来而不是让模型自己“凭感觉”拼顺序。编排器内部维护一个小的任务队列每个任务执行完就把输出映射成下一个技能的参数输入。意图冲突更隐蔽。举个例子用户说“帮我看看明天有没有空”它既可能是指“查询日程”也可能是在为“创建会议”做前置询问。我的处理方式是优先激活信息获取类技能把“要查什么”和“要做什么”区分开。如果语义含混就多追问一句“你是要查空档还是约时间”比随便押一个技能从而搞错方向的代价小得多。3.3 一个完整场景从“帮我安排下周的会议”到技能链执行用具体例子来演示编排过程。假设用户输入是“帮我安排下周三下午三点跟王经理开个项目会”。这个输入落进路由层后我的系统会走这么一串流程路由层识别主技能meeting_scheduler同时抽出参数时间周三 15:00、对象王经理、标题项目会。编排器发现技能内部需要子技能先用contact_lookup查王经理的邮箱和可用状态再用calendar_check验证下周三 15:00 是否空闲。contact_lookup返回王经理邮箱但标注“该时段王经理可能不在需要二次确认”。编排器决定不直接创建会议而是把“待确认”状态返回给 AgentAgent 用自然语言回复用户“下周三 15:00 王经理似乎有冲突我再确认下他的备用时段可以吗”用户确认后再走meeting_scheduler创建会议、发送邀请、设置提醒。这整个过程里模型真正做的只是路由判断和一小段对话管理其余全部是技能内部的确定性流程。这正是 agent-skills 的核心收益让确定性的逻辑回归代码让模型只做它擅长的语义理解。4. 实测中的三大步坑边界模糊、上下文污染、调试困难前面讲的都是方法论这一节聊聊实际落地时我踩过的比较深的坑每个坑都花了不少时间才爬出来。4.1 坑一技能描述写得像函数文档路由频繁误判刚开始我把技能描述写成“该函数用于查询订单状态参数包含订单号返回订单状态和物流信息”结果路由准确率惨不忍睹。原因很好理解模型不是程序员它看不懂“函数用于查询”这种描述它需要知道的是“用户在什么情况下会说这句话、你会怎么回应”。后来我把所有技能的触发描述全部改写成“用户话术模板反例”效果立竿见影。比如一个技能描述我改成当用户提到下单、购买、买入、想要、订购 XX 商品且目标是完成购买行为时触发该技能。 当用户只是询问商品信息、比价、查看评价时不要触发该技能应使用 product_inquiry 技能。这个改动很枯燥但确实是把路由准确率从 80% 拉到 90% 以上的关键一步。我的建议是给每个技能写 3-5 个正例和 3-5 个反例正例告诉模型“什么时候该用我”反例告诉模型“什么时候别用我”。这个钱省不得。4.2 坑二多技能复用时的上下文污染技能化之后子技能之间经常需要共享信息比如联系人 ID、订单号、会议室编号。一开始图省事把这些信息全塞进一个全局上下文变量里结果出现了一个非常诡异的问题用户说“帮我查一下张经理的日程”系统返回的却是李经理的日程。排查之后发现原因在于前一个技能执行时把“当前操作对象”设定成了李经理这个状态留在了共享上下文里下一个技能读取时没做校验直接把李经理当成了用户当前要求的人。这属于典型的上下文污染。我的解决方案是给每个技能定义明确的输入作用域技能只能读取自己声明过的输入参数不能访问其他技能的中间状态。执行完一个技能后立即清理临时状态只保留编排器约定的输出映射。简单的隔离规则却彻底解决了串数据的问题。注意上下文隔离是技能化设计的底线原则。你可以为了性能做部分共享但必须显式声明“谁可以写、谁可以读”否则迟早会出跨任务的脏数据事故。4.3 坑三技能调试比函数调试难一个量级传统函数调试输入输出都是确定的打断点看变量就行。技能调试不一样同样的用户输入两次运行可能走不同的分支因为模型天生有随机性而且技能跨模块协作时问题可能出在路由层、参数提取层、执行层或者外部 API定位链路很长。我现在的调试方法有三个详细 trace 日志。每次请求打全链路日志记录路由结果、置信度、提取的参数、执行步骤、每一步的耗时和 token 数。没有日志技能出错就是黑盒猜谜。单技能沙箱。每个技能都支持脱离完整系统单独测试。我用一个内网页面输入技能名和参数直接跑技能并查看输出方便快速确认问题出在技能内部还是出在路由层。固定采样比对。遇到模型行为不稳定时我会固定几十条测试样本反复跑十次来统计成功率而不是相信某一次运行的结果。这样能分辨出是偶发波动还是系统性缺陷。吐槽一句技能调试的难点其实不在技术而在心智模型你不能再像调试普通代码一样“预期一个结果”而是要建立“概率性思维”去接受和统计不确定性然后在设计上把不确定性控制到最低。这也是 agent-skills 方案相比纯 Prompt 方案的一个重要优势——它把不确定性收敛到了路由层和少量对话管理处而不是散落在整个执行链路里。最后再分享一个小技巧技能化不是一蹴而就的。我习惯先跑通一个最小闭环再做技能的“切片化”和“沉淀化”。也就是先用一段能用的 Prompt 跑通业务再分析用户真实请求里的高频动作把这些动作切成技能最后把稳定的触发规则写进技能描述里。这个过程听起来慢但每沉淀一个技能后续的迭代速度就会快一截。我做完第一版用了两周但之后每加一个新功能基本只需要半天到一天整体收益远大于前期的投入。