
刚把一个电商客服Agent从“什么都想干”改成了“按技能干活”效果变化非常明显。为了应对这个项目我整理了一套关于agent-skills的实践方法也就是给AI Agent建立可复用、可组合、可观测的技能体系——这也是目前Agent从“演示可用”走向“生产可用”最简单直接的一条路。如果你正在被“工具越来越多但Agent越来越蠢”“prompt已经堆到极限但问题还是反复出现”这类问题困扰这篇文章应该能给你一个清晰的方向。我从去年开始陆续做了几个Agent项目踩过的坑很多最核心的教训就是Agent的智能不取决于你塞了多少工具和指令而取决于你如何把能力“结构化”。agent-skills就是针对这个问题的一套设计思路它不依赖某个特定框架普通脚本项目也能用。下面我会从为什么需要技能体系、技能单元该长什么样、如何落地一套技能库以及维护过程中最常踩的坑这几个维度展开最后分享一些我在真实业务里的经验和教训。1. Agent为什么在“最后一公里”频繁翻车我一直觉得现在大多数Agent项目不是不够聪明而是“组织方式”出了问题。模型本身能力已经很强了但一旦你的接口、工具、指令堆到一定量级它就开始顾此失彼像个刚接手杂货铺的新人每样东西都知道在哪但顾客一多就手忙脚乱。1.1 工具越来越长模型越来越“花眼”我最早做Agent的时候习惯把所有能力都塞进function列表里。一个电商客服Agent刚开始只有3个工具查订单、查商品、创建售后单。这时候模型表现非常稳定用户问什么都能精准命中。后来业务扩展工具慢慢增加到20个优惠券计算、物流轨迹、发票申请、库存查询、客服备注……问题就来了。最明显的变化是工具选择的准确率下降。模型经常在用户问“能不能退货”时调用了“创建售后单”而不是先查一下订单状态用户问“什么时候到货”时模型先去调了“物流轨迹”却没带上订单号参数接口报错后它还会一本正经地告诉用户“您的包裹已到达配送站”——实际上接口根本没返回任何数据。我后来统计过一段时间的调用日志20个工具时工具选择准确率大概只有83%而3个工具时是99%。问题不在模型而在于我给了模型太多选择却没有帮它建立“什么时候该用什么”的清晰边界。function列表本质上只是一份接口清单它告诉模型“你有什么”但没有告诉模型“你为什么在这里”“你适合解决什么问题”“你的边界在哪里”。1.2 Prompt堆砌的极限指令越多越容易被忽略既然工具多了会混乱当时的想法是把每个工具的使用说明写进system prompt。结果system prompt越来越长从2k tokens加到12k tokens包含大量“当用户提到退款时请先调用订单查询接口确认订单状态然后调用售后接口……”这类规则。效果也有一点但很快遇到了新瓶颈模型是注意力机制的超长指令的末尾部分经常被“遗忘”。有用户问“我退款什么时候到账”模型记得退款规则但忘记要先查订单直接回车答复。更让人头疼的是不同工具之间的规则还可能冲突某个插件的文档说“退货必须在签收7天内”另一个插件的描述里写“生鲜类商品不支持7天无理由退货”模型在面对模糊场景时就开始了它的“自由创作”。这让我意识到问题已经不再是怎么写指令而是整个架构缺少了一层“能力封装”。就好比你让一个新员工直接看50条公司制度不如给他按岗位拆分好的SOP手册——什么时候走什么流程、碰到边界情况找谁确认清清楚楚。这就是我转向agent-skills思路的根本原因。1.3 从“工具清单”到“技能体系”一次架构思维的转变“技能”和“工具”有什么区别工具是一个可以被调用的接口技能则是一个完整的“问题解决单元”。一个技能通常包含这个技能是干什么的、在什么场景下被调用、什么时候不能调用、需要什么输入参数、输出什么结构、以及出错了怎么兜底。打个比方工具是一把螺丝刀技能是“更换面板的操作规范”。螺丝刀只负责拧螺丝而操作规范还告诉你什么时候需要先断电、拧到什么程度算到位、如果螺丝滑丝了该怎么办。对Agent来说后者显然比前者更有实用价值因为它把“判断”和“执行”的部分职责前置到了技能设计阶段而不再完全依赖模型的临场发挥。Agent-skills的核心价值就是让模型不是面对一堆零散接口而是面对一组有边界、有约束、自带说明的“能力单元”。模型只需要判断“现在这个场景应该用哪个技能”而不需要自己推演“用了这个工具之后下一步怎么办”。这样就大大降低了模型的决策负担。2. 拆解一个Skill的内部结构不仅仅是“一段代码”既然技能是Agent的能力单元那它到底长什么样我在自己的实践中逐渐沉淀出一个通用结构分成三部分技能定义文件SKILL.md、技能实现体脚本/命令/子Agent、以及一套明确的输入输出契约。三者缺一不可少了任何一个技能都会退化成普通工具。2.1 SKILL.md写给模型看的“使用说明书”一个技能首先必须有一份模型可读的说明文件。这份文件不是给人看的开发文档而是给大模型看的“使用说明书”它直接影响模型能否在合适的时机正确调用这个技能。我通常用Markdown格式命名为SKILL.md放在技能目录下。一个标准的SKILL.md至少包含以下内容name技能的唯一标识必须是简短、无歧义的英文短语比如order_status_query。description一段精炼的描述说明技能的作用重点是“什么场景下使用”和“什么场景下不要使用”。input输入参数的定义包括每个参数的名称、类型、是否必填、以及参数值的约束。output输出结果的结构化定义可以是JSON Schema也可以是简单的字段说明。examples一到两个典型调用示例帮助模型建立“什么时候调用”的直觉。error_codes技能可能抛出的错误码及其含义以及模型收到错误码后应该如何处理。这里我想专门强调一下description的写法。很多人写description容易写得太泛比如“查询订单状态”这其实是无效描述。更好的写法是同时包含“正向触发”和“反向抑制”两个维度name: order_status_query description: | 查询订单的当前状态和物流进度。 当用户明确询问“我的订单到哪了”“发货没有”“什么时候送到”“物流更新了吗”等物流/状态类问题时使用本技能。 注意本技能仅用于查询不能用于创建售后、修改地址、申请退款。当用户表达退换货或修改订单意图时严禁调用本技能。加上“严禁”这类反向描述后误调用率在我的实测中明显下降。因为模型在判断“该不该用”时不仅看到了正向触发条件还看到了明确禁止的边界这对减少“抢答式误调用”帮助极大。2.2 实现体脚本、命令、还是子Agent技能定义文件是“说明书”那真正的执行由谁来完成我见过三种实现体实现方式适合场景优缺点Python/Node脚本结构化数据处理、第三方接口调用、计算类任务确定性强单次执行成本低但需要编写代码命令行工具运维类操作、本地文件处理、环境管理复用已有CLI但输出解析需要额外处理子AgentLLM需要多步推理、语义理解、非结构化信息提取灵活度高但token成本高延迟大结果不稳定我的选型原则很明确能写脚本解决的绝不用子Agent因为确定性是生产环境的第一要素。比如订单查询这种技能就应该是Python脚本直接调接口返回JSON但如果是“判断用户投诉情绪并生成处理建议”这类开放性任务则适合用一个子Agent技能内部自己跑推理步骤。有一个容易被忽视的点是技能的“兜底逻辑”。脚本技能里一定要写try-except捕获异常后返回结构化的错误信息而不是让堆栈信息直接抛出来。因为模型的输入是干净的堆栈信息会给它造成极大的理解负担它很可能编一个不存在的修复方案告诉用户。2.3 输入输出契约谁边界清晰谁就稳定我强烈建议每个技能在实现体内做二次参数校验不要指望模型给你传的参数百分之百合法。模型擅长“理解意图”不擅长“精确填参”所以技能侧必须“防御式编程”。以订单查询为例模型从用户话术中提取订单号时可能把“订单号是20240515”中的日期也当成订单号传进来。如果技能脚本不校验参数格式就会带着一个非法订单号去请求上游接口返回错误后又把锅甩给用户体验非常差。正确的做法是实现体内先做参数合法性检查如果订单号格式不对返回一个明确错误码比如INVALID_ORDER_ID并在错误信息中附带“期望的格式是O开头的12位字母数字组合”。模型看到这个错误后就能理解自己参数提取错了会重新向用户确认或修正参数而不是对用户胡编。这个细节是我在踩了很多坑之后才加上的效果立竿见影。3. 实战从零到一搭建一套技能库让Agent真正“上手”有了设计思路之后接下来最关键的问题就是怎么在一个真实项目里落地我以自己做的电商客服Agent为例带你完整走一遍从场景梳理、技能编写到挂载使用的过程。这个流程是通用的你完全可以照搬到自己的场景里。3.1 先做“能力盘点”再动笔写技能很多人做技能一上来就写结果写到一半发现技能边界模糊、和已有功能重复。我的建议是先做一轮场景盘点把用户可能问的问题按照“触发频率”和“解决复杂度”两个维度画到一张表里再决定哪些场景值得做成技能。这是我当时梳理的结果部分用户意图频率现有处理方式是否值得做成技能优先级查询订单状态极高手动调接口是P0申请退款/退货高多步操作需判断条件是P0修改收货地址中需校验订单状态是P1咨询优惠活动中知识库检索是P1闲聊/情感陪伴高模型直接回复否-投诉威胁曝光低需人工介入是转人工技能P1关键判断标准是这个场景是否是一个“可复用的闭环任务”。如果某个场景需要多个步骤、依赖外部数据、且在不同会话中反复出现它就适合被封装成技能。闲聊这种纯模型行为就不值得封装做了反而是浪费。3.2 一个完整技能的编写过程从定义到实现以“订单状态查询”技能为例完整走一遍编写流程。第一步创建技能目录结构skills/ ├── order_status_query/ │ ├── SKILL.md │ └── query.py ├── refund_handler/ │ ├── SKILL.md │ └── refund.py └── address_modifier/ ├── SKILL.md └── modify.py第二步编写SKILL.md定义文件上面已经展示过类似版本。第三步编写实现体。下面是query.py的骨架重点展示参数校验和统一错误返回#!/usr/bin/env python3 import json import re import sys def validate_order_id(order_id): 订单号格式O开头后接11位字母数字 return bool(re.match(r^O[A-Za-z0-9]{11}$, order_id)) def query_order(order_id): # 这里替换为实际的上游接口调用 if not validate_order_id(order_id): return { status: error, error_code: INVALID_ORDER_ID, message: f订单号格式不正确: {order_id}期望O开头12位字符, hint_for_model: 请向用户核实完整订单号不要猜测或补全 } try: # 模拟真实接口返回 result { order_id: order_id, state: shipped, latest_event: 包裹已到达【北京转运中心】下一站发往【朝阳区配送站】, eta: 2025-01-20 18:00 } return {status: success, data: result} except Exception as e: return { status: error, error_code: UPSTREAM_TIMEOUT, message: 上游物流接口超时, hint_for_model: 请告知用户物流信息暂时无法获取稍后再试不要编造物流进度 } if __name__ __main__: input_data json.loads(sys.stdin.read()) print(json.dumps(query_order(input_data.get(order_id))))这里我特意在错误返回中加了一个hint_for_model字段它的作用是直接告诉大模型“拿到这个错误后该怎么跟用户解释”。这个设计让我后续处理错误时的体验好了很多——模型不再自由发挥而是沿着我们给的提示执行“安抚话术重试建议”的兜底动作。3.3 让Agent“看见”技能目录扫描与索引注入技能文件写好了怎么让Agent知道这些技能的存在我的做法是在Agent启动时扫描skills目录读取每个子目录下的SKILL.md将其摘要加载进system prompt的技能索引区。关键技巧是不要把所有SKILL.md的全文都塞进去。我给每个技能写了一个“摘要条目”只保留name、一句话description、以及关键参数完整定义保留在文件里等模型决定调用某个技能时再按需读取。这样system prompt的长度能被控制住。摘要注入的模板大致是这样## 可用技能列表 以下是你当前可以调用的技能每个技能包含技能名、触发场景和关键参数。完整说明在调用时通过工具获取。 1. skill_name: order_status_query 描述: 查询订单状态和物流进度用户询问“货到哪了”“发货没”时使用 主要参数: order_id (string, 格式O开头12位) 2. skill_name: refund_handler 描述: 处理退款/退货申请用户表达“要退款”“退货”时使用 主要参数: order_id (string), reason (string), refund_type (string) 3. skill_name: address_modifier 描述: 修改已发货订单的收货地址注意已签收订单不可修改 主要参数: order_id (string), new_address (string)摘要的作用是让模型在“技能选择”时有一个全局视图代价是每个技能大约占用30-50个token20个技能也就1000 token以内完全可以接受。而且摘要中包含了反向限制词例如“已签收订单不可修改”能提前过滤掉一部分误调用。3.4 技能组合从单技能调用到流程编排单个技能解决单点问题但真实的用户需求往往需要多个技能按顺序配合。比如用户说“我要退款订单号O20250119234”完整流程其实是先查询订单状态确认是否可退然后根据订单商品类目计算退款金额最后创建售后工单。我在实践中的做法是预设“流程模板”而不是让模型自己编排。原因很简单模型自由编排在多技能场景下极容易漏步骤或乱序而业务流程是确定的没有必要让模型去“创新”。FLOW_TEMPLATES { refund_request: [ {skill: order_status_query, args: {order_id: {{order_id}}}}, {skill: refund_calculator, args: {order_id: {{order_id}}, state: {{prev.state}}}}, {skill: refund_handler, args: {order_id: {{order_id}}, amount: {{prev.amount}}, reason: {{user_reason}}}} ] }模型只需要做两件事一是识别用户意图属于哪个流程模板refund_request二是从用户话术中提取模板需要的初始参数order_id、user_reason。模板之后的每个环节由流程引擎执行上一个技能的结构化输出自动透传给下一个技能。这样既保留了Agent的灵活性又大大提升了流程的确定性。4. 技能落地中的高频翻车现场我那三个月的排查心得上面说的是“理想路径”实际落地中会遇到一大堆问题。这一节我想单拎出来写因为我发现很多问题不是个例而是所有做技能化改造的人都会遇到的共性坑。4.1 描述写得太泛模型“想不起来”也“不该用”我遇到过最典型的一个问题是技能描述写得过于“宏大”。比如把优惠券技能描述成“处理所有与优惠相关的问题”结果用户问“你们最近有什么活动吗”时模型也调用了优惠券技能技能内部发现没有用户输入的活动识别逻辑直接返回空结果模型随即对用户说“暂时没有优惠活动”。但实际上店铺正在做满减。这个问题出在description的触发条件没有收敛。技能描述里应该写清楚具体触发该技能的用户话术特征而不是抽象的功能概括。你要让模型能通过“字面匹配”来判断。这是我后来反复打磨description之后得出的一条铁律。4.2 技能内部报错Agent依然面不改色地编造结果这是我在早期最头疼的问题。技能脚本抛异常了返回值是{status:error,error_code:UPSTREAM_TIMEOUT}但模型拿到这个结果后还是会给用户编一个正常的答案——它可能是下意识“修正”了异常信息或者根本没有认真看error字段。解决办法有两个层面。第一是技能返回信息必须要“简单粗暴”把hint_for_model放在错误返回的显眼位置明确指示模型“告诉用户什么话”。第二是在system prompt中加一条全局规则“当技能返回error时严禁生成任何业务结果必须严格按照hint_for_model中的话术向用户说明并给出重试或人工客服引导。”我加了这条规则之后模型编造结果的问题基本绝迹。4.3 技能索引长得太胖sysprompt被撑爆技能数量上来之后另一个严重问题是系统Prompt膨胀。即使只放摘要每个技能50 token60个技能就是3000 token。加上任务说明、规则、用户画像sysprompt很快就冲到10k以上。token消耗是一方面更重要的是模型对超长上下文的注意力衰减会让它“看不到”后面的技能。我的解法是“两级技能索引”。第一级只放技能“分类”比如“订单类”“售后类”“商品类”“用户类”每类一行描述第二级是“当前会话可能用到的技能”由前置的意图判断模块在会话开始时动态决定加载哪几个具体技能。比如用户进来就说“我要退款”意图分类器直接锁定售后类往后端技能表里只加载refund_handler、order_status_query、refund_calculator三个技能。实测效果sysprompt从12k降到3.5k工具选择准确率从83%回升到96%每个会话的token消耗降了约30%。4.4 技能调用的安全边界模型不是“授权”本身技能化带来的一个安全隐患是技能封装的“完整操作”一旦被调用会产生比单独工具更显著的副作用。比如一个“批量退款技能”用户只是说气话“这破店我要全部退款”模型如果判断为退款意图直接调用了批量退款技能后果不堪设想。所以我在技能设计上做了一个强制要求所有“写操作”类技能必须有二次确认机制不能在单轮对话中自动执行。实现方式很简单技能被调用后先返回“确认待办”状态模型需要向用户确认“您确定要申请退款吗”得到肯定回复后才继续执行。这个确认机制为我挡下了很多乌龙事件尤其是那些说话激进、实际并未真想退款的用户。同时技能内部要加白名单和额度校验。比如退款技能必须校验该订单是否在可退期限内、是否有过退款前科、退款金额是否超过单人月累计上限。这些规则不应该依赖模型判断必须在技能实现体内硬编码。5. 技能库的长期维护像养植物一样定期修剪技能不是写完了就一劳永逸的它会被业务变化、模型升级、用户话术漂移等因素不断影响。我早期的技能库维护基本靠“乱”直到有一次我统计了所有技能的调用数据才发现有些技能已经一个月没被动过而另一些技能疯狂被调用但成功率惨不忍睹。5.1 给技能建立“体检表”用数据剔除僵尸技能我给每个技能建立了一套指标跟踪表调用频率每天被模型选择并调用的次数成功率技能返回status: success的占比平均处理时长从技能被选中到返回结果的耗时平均token消耗包括技能描述、调用过程、结果返回的token量二次确认率写操作类技能被用户取消确认的比例这个表我每两周复盘一次。一个技能的调用频率越来越低通常说明它的触发描述和实际用户话术已经不匹配了需要更新SKILL.md里的description成功率持续低迷则说明技能内部逻辑或上游接口有问题需要看错误码分部。那些连续一个月零调用且无业务需求支撑的技能我会果断下架——技能库不是收藏夹留着冗余技能只会增加模型的选择负担。表格对比指标健康值危险信号可能原因与动作调用频率每周稳定连续几周下降触发描述与用户话术脱节需要更新description成功率90%70%上游接口不稳定或参数校验过严检查error_code分布平均处理时长1.5s5s内部逻辑过重考虑拆分技能或优化接口二次确认率20%-50%70%用户触发即后悔说明技能边界过宽应加前置条件5.2 技能版本管理与灰度发布别让你的一次小改动毁掉整个对话质量技能实现体的代码当然应该进git做版本管理但更重要的是一份技能内部的“设计文档”需要同步更新。我见过太多项目里技能代码改了SKILL.md描述还停留在旧逻辑结果模型按照旧描述调用新代码参数对不上直接右。另外一个值得养成的好习惯是改技能描述文件后要做回归测试。我把每个技能收集了20-50条标注好的测试问题每次修改description后跑到一个测试Agent上验证调用准确率和误调用率和上一版对比。只有两个指标都不变差才允许上线。灰度发布也很关键。技能代码的改动部分不要一把梭全量推先让5%的用户流量试跑几个小时确认成功率和错误码分布没有恶化再逐步放量。我知道很多个人开发者嫌麻烦跳过这步但只要你经历过一次“技能安全策略调错导致用户被重复扣款”或者“订单查询技能改崩导致全量客服不可用”你就会乖乖把这个流程补上。5.3 一些让技能库更健壮的小技巧最后分享几个维护过程中沉淀下来的土办法在SKILL.md里加“版本号”模型和你的调试工具都能快速感知技能是否过时。技能输出统一加trace_id每次调用返回一个唯一追踪ID后续排查问题直接用这个ID检索全部日志效率翻倍。定期跑一遍“异常话术”测试集专门准备一批边界话术比如“我不要了我要投诉”“你们这是诈骗吧”确认这些话术不会触发错误技能。我每次更新技能库都会跑这个测试集因为它防的是“情绪化误触发”。这几条都不是高深的技术但它们在我维护技能库的过程中实实在在地省下了大量时间。尤其是trace_id这个习惯有一次线上发生“技能执行成功但用户投诉未解决”的问题靠着trace_id半小时定位到是技能内部一个数据源配置错了如果没有它可能要翻一整天的日志。结构化的技能体系本质上是在给Agent立规矩——让它在合适的时候做合适的事同时给它们安排好兜底方案。它不是某个具体框架或某个复杂中间件而是一套人人都能干、干完就有收益的工程实践。如果你手上的Agent项目正处在“接口一多就失控”的阶段我的建议是你先别急着换模型也别急着加更多prompt老老实实把现有能力梳理成技能单元给它写清说明书、划清边界、做好兜底。这套功夫花下去效果可能比你换一个更强的模型还明显。