1. 为什么 Agent 需要技能化
1.1 从"模型直接回答"到"模型调用工具"的演进
我最早接触 Agent 时,用的还是纯提示词方案:把所有业务逻辑写进 System Prompt,让大模型"记住"该怎么做。小规模 demo 还好,一旦业务复杂起来就彻底失控——Prompt 动辄几千字,模型经常记错步骤,偶尔还会凭幻觉编造执行结果。那段时间我最大的感受是:模型不是数据库,也不是流程引擎,你没法靠"叮嘱"让它稳定地完成多步骤操作。
后来转向工具调用(Function Calling)方案,情况有所好转。把一个个独立操作封装成函数,模型自己决定什么时候调用。这个思路的核心是把"模型不确定的部分"交给代码,而"该做什么"的决策权仍保留给模型。实际跑下来能明显感觉到:凡是封装成工具的环节,准确率大幅提升;而留在 Prompt 里的逻辑,依然容易出岔子。
但工具方案也有它的天花板。工具之间是平级的,模型每次只能选一个来调用,缺少"多个工具按顺序配合"的机制。比如我想让 Agent 完成"查询订单→判断是否超时→发送催付消息"这个流程,工具化之后 Agent 可能只调了第一个工具就停住了。这不是模型笨,而是工具本身没有"流程感"——单个操作很清晰,组合逻辑却没人管。
真正的转折点是我开始尝试"技能化"。把一组相关的工具、执行逻辑、参数规范、使用场景打包成一个可以被 Agent 调用的整体单元,这就是我理解中的 agent-skills。技能不是工具的替代品,而是工具之上的组织层:一个技能可以包含多个步骤、多个工具调用,甚至嵌套其他技能。它把"模型要做什么"和"模型该怎么做"彻底分开了。
1.2 技能与普通工具、插件到底有什么区别
很多人分不清技能、工具、插件这三者,我在项目初期也踩过概念混淆的坑。简单说:工具是最小可执行单元,通常对应一个函数或一次 API 调用;插件是工具的集合包,偏重"集成";技能则是"面向任务"的逻辑封装,描述的核心是何时用、怎么用、用完之后要产出什么。
用一个生活化的类比来理解:工具像螺丝刀,插件像一整套工具箱,而技能像"换灯泡操作手册"。手册告诉你需要拿螺丝刀(工具)、需要先断电(条件)、拆几个螺丝(步骤)、装好后怎么验证(结果)。Agent 调用技能时,其实是在执行一份"操作手册",而不是单纯举着螺丝刀到处拧。
这个区别带来两个关键变化。一是抽象层级不同:工具调用只解决"怎么执行单个动作",技能解决"怎么完成一个目标"。二是决策负担不同:工具模式下,模型需要自己规划每一步调用顺序,容易出错;技能模式下,模型只需决定"这个任务匹配哪个技能",执行细节由技能内部代码接管。实测下来,后者对长尾任务的成功率提升非常明显。
1.3 技能化解决的核心痛点
我总结下来,技能化主要解决了四个让我头疼很久的痛点。
第一个痛点是提示词膨胀。以前每个任务都要往 Prompt 里塞一堆步骤说明、边界条件、输出格式,塞多了模型反而不听话。技能化之后,这些细节从 Prompt 移到了技能定义文件里,Prompt 只保留"哪些技能可用"的索引。Prompt 从 3000 字缩到 300 字,模型选择准确率反而上升了。
第二个痛点是逻辑复用困难。很多任务之间存在公共步骤,比如"获取当前用户上下文"在多个 Agent 流程里都会用到。普通工具虽然也能复用,但整个"如何基于上下文判断下一步"的逻辑没法复用。技能允许嵌套,把公共逻辑抽成子技能,改动一次全局生效。
第三个痛点是测试没有边界。没有技能化之前,改一个环节要回归整个流程,测试成本极高。技能化之后,每个技能独立测试,再测技能之间的集成关系,边界一下子清晰了。第四个痛点是可观测性差。技能内部有明确的步骤结构,每一步的执行时间、成败、输入输出都能记录,排障时不用再对着模型的一整段输出瞎猜。
2. 技能库整体设计:从入口到调用的分层架构
2.1 技能描述层:让 Agent "看懂"技能
我早期做技能化时只写实现代码,结果发现模型根本不理解这个技能什么时候该用。后来意识到,技能必须先解决"可被理解"的问题——模型看到技能列表时,需要快速判断哪个技能匹配当前任务。我把这部分设计成独立的描述层,和实现代码完全解耦。
描述层包含技能名称、用途说明、参数模式、返回格式、使用约束、示例。难点在于"用途说明"怎么写才有效。写得越抽象,模型越容易误匹配;写得越具体,覆盖的场景就越窄。我试过几十种描述风格,最后总结出一句话经验:描述里要写清楚"预期效果"和"触发条件",必要时给一个真实输入输出样例。比如一个订单催付技能,与其写"处理订单催付",不如写"用于订单已支付但未发货且距支付时间超过 48 小时的情况,调用后发送催付消息并记录通知状态"。
参数模式也要在描述层明确。我习惯为每个参数标注类型、必填性、取值范围、示例值。这里最容易踩的坑是:模型调用时会把自然语言里的数字转换成参数值,如果参数定义含糊,它就会传错格式。有一个真实教训:某时间参数我定义成了"字符串"类型,没有限定格式,结果模型一会儿传"2024-03-15",一会儿传"十五号中午",下游解析直接崩了。后来统一改成标准时间戳格式并在描述里反复强调,问题才消失。
2.2 技能实现层:可编排的执行逻辑
描述层解决"该不该用",实现层解决"用了怎么执行"。我把实现层设计成有限状态机加步骤管线:一个技能内部由若干步骤组成,步骤可以是代码函数、外部 API 调用、甚至是另一个技能。步骤之间支持顺序执行、条件分支、循环和并行分发。
状态机的优势在于每个步骤的输入输出都明确,Agent 拿到节奏的掌控感。比如"催付技能"内部可能分为:校验订单状态→生成催付文案→选择发送渠道→发送→记录结果。每一步都可能失败,我需要在设计上明确失败后的处理策略:是直接中断并返回错误,还是走降级分支。这里我强烈建议不要让技能内部静默吞掉错误,一定要把失败原因结构化地抛给上层。
实现层的另一个设计重点是上下文传递。步骤之间共享哪些数据、哪些数据需要隔离,都要提前想清。我吃过一次亏:两个并行步骤同时读写同一个全局字段,结果出现数据竞争,Agent 返回的结果时不时就串了。后来的方案是每个步骤声明自己的"输入上下文"和"输出上下文",由调度器负责安全地传递和合并,并行步骤之间只能通过显式声明的共享区交换数据。
2.3 技能注册与索引层:动态发现与版本管理
技能数量少的时候,全量加载到 Prompt 里没毛病。一旦技能超过 20 个,Prompt 塞不下了,模型的选择准确率也会明显下降。解决思路是加一层技能注册与索引:每个技能注册时带上索引标签,模型先通过检索找到候选技能,再在候选集内做选择。我用的是双阶段流程:粗粒度标签过滤 + 细粒度语义匹配,把候选技能控制在 5 个以内。
索引标签的设计直接影响召回效果。我给标签分了几类:领域标签(如电商、客服、数据)、动作类型标签(如查询、发送、计算)、实体标签(如订单、用户、商品)。实际使用中发现,标签体系不要太追求完美覆盖,宁可多打几个粗标签,也不要漏打关键标签。漏标签意味着技能永远不会被检索到,等于白写了。
版本管理这块我吃过亏。迭代技能时直接覆盖定义,结果线上 Agent 的行为忽然变了,还找不到原因。后来我强制引入语义化版本号:技能定义里的每个变更都要升级版本,并在注册中心保留历史版本。某个版本出问题可以快速回滚。另外,上线前我会用一组"金标用例"来回归测试,用例集固定不变,技能版本更新后必须跑通全部用例才算合格,这个习惯帮我挡住了不少低质量变更。
3. 实操:定义你的第一个技能
3.1 技能定义文件怎么写
先说结论:技能定义最好用独立文件管理,不要硬编码在代码里。我目前采用的格式是 YAML,原因是可读性好,非开发同事也能参与维护。一个技能定义文件的核心字段包括name、description、version、author、input_schema、output_schema、steps、tags、examples。其中steps是技能的核心逻辑,其余是元信息和契约。
一个最简单的例子,定义一个"获取商品详情"技能:
name: get_product_detail description: 根据商品ID获取商品详情,包含标题、价格、库存、上架状态。适用于用户查询商品信息或下单前核对商品状态的场景。 version: 1.2.0 tags: [电商, 商品, 查询] input_schema: product_id: type: string required: true description: 商品唯一标识,形如 SKU-2024-001 output_schema: success: type: boolean title: type: string price: type: number stock: type: integer on_sale: type: boolean steps: - id: validate_id type: function function: check_product_id_format - id: fetch_detail type: api endpoint: /api/products/{product_id} method: GET - id: format_result type: function function: format_product_payload examples: - input: 查看商品 SKU-2024-001 的信息 output: "返回商品标题、价格、库存、上架状态"这个文件直接告诉模型三件事:这个技能做什么、需要什么参数、执行完给什么结果。模型不需要理解内部fetch_detail到底怎么请求,只要知道传入product_id就能拿到结构化商品数据。
实际操作中有一个很重要但容易被忽略的点:description的措辞不要和别的技能太像。我曾经定义过两个技能,一个叫"获取用户近期订单",另一个叫"查询用户购物记录",描述高度相似,结果模型频繁选错。后来把两者合并成同一个技能并加了维度参数,准确率立刻回升。
3.2 参数设计要注意的细节
参数设计是技能可用性的命脉,我在这一块吃的亏最多。总结几个值得反复检查的细节。
第一,参数名要直白。product_id一眼能看懂,但pid、prodId这种缩写就很容易让模型困惑。虽然模型能猜,但猜就有出错的可能。技能是给模型用的接口,命名风格要贴近自然语言,而不是贴近程序员习惯。参数名和描述中的用词保持一致,模型定位参数的语义准确率会高很多。
第二,必填参数尽量少。模型传参的本质是从对话中抽取信息,信息缺失时它要么编造,要么报错。如果某个参数只是增强功能,就给它一个默认值,不要设为必填。例如查询订单接口的time_range参数,默认近 30 天即可。
第三,枚举值一定要显式声明。比如send_channel参数只有sms、email、in_app三个可选值,就一定要在 schema 里写成enum。不写的话,模型可能传一个push_notification进去,下游匹配不上直接抛异常。显式枚举还有一个附带好处:模型对"候选选项"的理解比"自由填空"更可靠,这也是某种意义上的选择困难缓解。
第四,参数描述要写"业务含义",不要只写"类型"。product_id的描述写成"商品唯一标识,形如 SKU-2024-001",比"商品ID"准确得多。模型靠描述做语义映射,描述越具体,映射越稳定。
3.3 技能间依赖与复用技巧
技能可以嵌套调用,这是它比工具强很多的地方。我有两个习惯:一是把高频公共逻辑沉淀为底层技能,让上层技能引用;二是用依赖声明明确技能间的关系,避免隐式调用导致的混乱。
依赖声明很简单,在技能定义里加一个depends_on字段,列出依赖的技能和版本范围。调度器在加载技能时检查依赖是否满足,不满足直接拒绝注册。这个机制帮我省掉了很多莫名其妙的线上故障——以前某个底层技能改了参数格式,调用它的 20 个上层技能全部悄悄报错,排查了整整两天才定位到。加了依赖版本声明后,底层技能升级会触发上层技能的回归测试提醒,问题能在发布前被发现。
复用技巧方面,我强烈建议把"决策类逻辑"作为子技能抽出来。比如一个"判断订单是否满足催付条件"的技能,内部封装了超时判断、金额判断、用户偏好判断,上层技能只需要调用它并拿到should_pay_remind布尔值。这样做的好处是:判断逻辑只维护一份,不同上层技能得到一致的行为。模型也更容易理解——它不用自己推理复杂的条件组合,只需要调用一个"很会判断"的技能。
依赖过深也有风险。我遇到过最长的嵌套链路有 5 层,一次调用牵动几十个子技能,单次执行耗时撑了近 20 秒。后来我给技能调用加了最大深度限制,并提请设计者反思:链路超过 3 层时,是不是该把某些中间层合并?技能粒度不是越细越好,核心标准是"这个技能是否具备独立的业务语义"。
4. 技能编排:让 Agent 学会"组合拳"
4.1 线性编排与条件分支
技能编排是我后期投入精力最大的部分。最初每个技能只做一件事,Agent 经常完成了第一步就停下来,没有"接下来该做什么"的意识。后来我意识到,必须把"多步动作"本身也交给技能来做,于是开始设计编排型技能。
最基础的是线性编排:步骤 A → 步骤 B → 步骤 C,顺序执行。这种模式适合步骤之间没有依赖分支的流程。我在定义文件里用steps的数组顺序表达,每个步骤引用一个子技能或内部函数。一个典型的"客户投诉处理"技能就是线性编排:读取投诉内容 → 核验订单 → 判断责任方 → 生成回复。
条件分支稍微复杂一点。我增加了condition表达式和branch定义,让技能根据中间结果走不同分支。举一个实际例子:"售后审核"技能里,如果订单金额小于阈值,走快速退款分支;如果金额较大,走人工复核分支。条件表达式的判断结果来自前序步骤的输出字段,调度器会基于这些字段决定下一步走向。
条件分支有个设计陷阱:分支条件必须互斥且完备。我遇到过两个分支条件重叠的情况,同一笔订单同时满足"超时"和"可退款"两个条件,调度器不知道该走哪个分支,最后默认走了第一个,而第一个分支的逻辑恰好是错的。解决方案是给条件表达式增加优先级,并在设计阶段强制检查所有分支条件的联合覆盖是否等于全集,这样就不会出现"无路可走"的输入。
4.2 递归调用与子技能
编排里最有价值的设计之一是递归调用。一个技能可以调用自己,这在处理"树形结构任务"时特别有用,比如组织架构查询、多级菜单遍历、评论回复链路分析。
给你一个场景:用户问"这个商品包含哪些子规格"。商品体系有多级类目,直属子类和孙类结构不同。我设计了一个"展开子类目"技能,它先查询当前类目的直接子类,再对每个子类递归调用自身。递归深度到叶子节点后返回,最后把所有层级的子类汇总。这样技能代码只需写一次,却能处理任意深度的类目结构。
不过递归调用一定要写终止条件。我最开始忘了限制最大深度,结果商品类目里有个循环引用(管理员手动配错导致的),技能直接陷入死循环。后来我加上两条防线:一是在调度器层面限制全局最大深度的硬编码;二是在技能实现里显式检测"已访问节点集合",发现重复节点立即终止并返回错误。这两条防线缺一不可,因为调度器的最大深度只能在极端情况下兜底,而业务层的重复检测才能真正阻止逻辑错误蔓延。
子技能还有一种用法是"并行扇出"。比如要批量核对 100 条订单状态,如果逐条调用,耗时是串行的;我把它设计成一个编排技能,内部将订单列表分组后并行调用"单条核对"子技能,全部完成后汇总结果。这个优化把处理耗时从 300 秒压缩到 40 秒左右,收益非常可观。注意,并行扇出的子技能之间必须没有数据依赖,否则就得改成有依赖关系的两轮并行。
4.3 编排冲突的处理:循环检测与超时
编排技能多了之后,最怕出现循环依赖和调用冲突。我曾经设计了一套"自动推荐"编排:先调"获取用户兴趣"技能,再根据兴趣调"推荐商品"技能,而"推荐商品"又调了"获取用户兴趣"作为辅助信息。这在逻辑上并不是死循环,因为后一次调用只是取缓存,但还是让我惊出一身冷汗。实现层我再三确认后,决定在调度器里统一做循环检测:用有向图记录所有技能调用关系,每次部署前跑一次拓扑排序,发现环就拒绝上线。
超时机制也必须有。每个编排型技能都应设置总体超时时间,超时后统一返回"执行超时"错误,而不是让调用方无限等待。我常用的是超时阈值 = 预估正常时长的 3 倍,保证足够宽裕,又不会卡死整个流程。子技能也应有自己的超时,且子技能超时要能被父技能捕获并触发降级分支,而不是直接崩掉整个编排。
我还遇到过一个很有意思的问题:Agent 在技能编排中产生了"选择冲突"。同一个任务被两个技能都声称可以处理,模型犹豫不决,返回了模棱两可的响应。排查后发现问题出在技能描述上——两个技能都写了"适用于用户退款相关咨询"。我给其中一个技能加上更明确的边界条件描述,并降低另一个技能的匹配优先级,冲突就消失了。这个经验值得记住:技能描述里使用"仅当……时使用"句式,可以显著减少模型的选择摇摆。
5. 技能测试与评估:别让 Agent 乱调
5.1 单技能单元测试
技能化之后,测试变得前所未有的清晰。每个技能都是独立单元,可以给它构造输入、断言输出。我的测试策略分两层:第一层是纯逻辑测试,不经过模型,直接调用技能实现函数;第二层是"模拟模型调用"测试,用预设的自然语言输入作为 Agent 用例,验证描述层的匹配是否准确。
单元测试的用例设计要覆盖几种典型路径:正常输入、边界值、缺失必填参数、非法枚举值、依赖技能抛错。尤其要测"参数缺失时的报错信息"是否友好,因为模型可能会在漏信息时收到这个报错,清晰的报错能让模型自己纠错并重新调用。我在报错信息里加上了可操作的纠正建议,比如"缺少参数 channel,请从 [sms, email, in_app] 中选择"。实测下来,模型看到这种错误后自动重试并成功的概率非常高。
模拟模型调用测试是另一道保险:我去真实对话环境里采集了一批用户问题,作为固定测试集,输入给"技能选择器",看模型最终选择哪个技能、传入哪些参数。测试不关心答案内容,只关心两点:选择是否命中预期技能、参数是否映射正确。这个测试集每次技能版本更新都会全量跑一遍,跑不过就不允许发布。
5.2 多技能集成测试
单个技能正常不代表组合起来正常。集成测试主要验证三件事:技能间数据传递是否正确、依赖版本是否兼容、编排路径是否按预期执行。
数据传递问题最常见。上层技能调用子技能后,需要从子技能的输出里提取字段。字段名不匹配、类型不一致都会导致下游步骤失败。我在集成测试里专门加了一层"契约检查":每个子技能调用完成后,自动校验输出是否符合被调方声明的output_schema,不符合就直接标记测试失败。有了这层契约检查,字段名写错的问题基本能在集成阶段暴露。
依赖版本兼容测试也值得投入。我会在测试环境里把底层技能升级到最新版本,然后跑一遍所有依赖它的上层技能测试集。如果某个上层技能断言失败,就说明两个版本的契约发生了破坏性变更,需要排查是底层技能改了输出格式还是上层技能用了旧字段。这个流程保证技能可以放心升级,不会被隐藏的兼容性问题绊倒。
编排路径验证上,我用了"追踪记录"的方式:测试执行时,把每个步骤的实际走向记录下来,跟预期步骤序列做比对。例如预期"售后审核"技能应该走"快速退款"分支,但实际走向了"人工复核",测试就会捕获这个差异。这种方式能帮我快速发现条件表达式逻辑问题,尤其是边界条件判断错误。
5.3 评估指标:成功率、召回率、误用率
光靠测试用例还不够,产品上线需要衡量技能调用的整体表现。我习惯跟踪这四类指标:技能选择准确率、参数映射准确率、任务完成率、误用率。
技能选择准确率 = 正确命中的技能数 / 总请求数。这个指标反映描述层写得好不好。参数映射准确率 = 正确映射参数的请求数 / 命中技能的总请求数,反映input_schema的定义质量。任务完成率 = 用户问题得到有效解决的请求占比,可以通过用户的显式反馈或运营回访来估算。误用率 = 技能被调用但实际不符合应用场景的请求占比,这个指标非常关键,因为 Agent 一旦"乱调"技能,虽然后续还能兜底,但用户体验会明显变差。
我拿过一组真实数据:优化描述层之前,选择准确率只有 78%,误用率高达 15%。我把描述重写了一遍,改用"仅当……时使用"的句式并补充真实样例,准确率提升到 93%,误用率降到 4%。这说明大部分问题出在"模型理解偏差",而不是模型能力不够。好的技能描述就是模型的"照明灯",越具体越亮,越含糊越容易撞墙。
评估不是一次性任务。我建议把评估作为日常流程固定下来:每两周跑一次全量测试和指标统计,对比上次结果,找出回退项。回退通常意味着某次修改引入了隐性副作用,比如改了一个子技能的输出格式,导致依赖它的父技能逻辑失效。有了固定评估节奏,这类问题最长两天内就能暴露。
6. 常见问题与排查实录
6.1 Agent 不选技能:描述和触发条件问题
遇到最多的线上问题是"Agent 明明有技能可用,就是不调用"。排查这个问题的思路要分三步:先看技能是否被成功检索到,再看描述是否让模型觉得当前任务匹配,最后看调用示例是否有说服力。
检索不到的情况常见于标签打了太多"没人搜的词"。比如电商场景里打标签fulfillment,但用户问题提的是"发货",模型语义检索时匹配不到。解决办法是标签贴近真实业务用词,并且做同义词扩展。描述不匹配的情况也很多,尤其当描述写得过于抽象,比如"处理订单生命周期相关操作",模型可能搞不清楚"催付"到底算不算订单生命周期的一部分,干脆不调用了。我把描述改成包含具体动作的词,如"催付需要给已下单但未付款的用户发送提醒消息并跟踪发送状态",模型识别准确率立刻改善。
还有一个很容易忽略的原因:技能调用的"前置条件"没交代清楚。模型倾向于在"它判断该做"的时候调用技能,如果技能描述里没写清楚使用的前提条件,模型可能觉得自己手头信息不足就退缩了。我的做法是在技能描述里明确写出"当出现……情况时,使用本技能;如果……则不要使用",这能大幅减少模型的犹豫。
6.2 技能粒度太粗或太细
技能粒度是设计层面的老难题。粒度太粗,一个技能包罗万象,内部代码里全是 if-else,Agent 调用时也搞不清它到底能做什么。粒度太细,技能数量爆炸,每个技能描述都很短,模型选择时信噪比太低,检索也困难。
我的判断标准是:如果一个技能的内部逻辑已经需要大量条件分支去区分不同"子任务",就该拆了。如果有人改了这个技能要小心翼翼担心影响别的场景,也说明该拆了。反之,如果两个技能经常被 Agent 同时调用以完成同一目标,就该考虑合并成一个编排技能来代替它们各自独立出现。
举一个实际案例:早期我的技能库里"查询订单"和"查询售后单"是两个独立技能,但几乎所有售后场景里 Agent 都需要同时调用它们来对比信息。后来我新增了一个"售后订单全景查询"技能,内部一次查询两个单据并合并输出。这之后,模型选择更干脆了,相关任务的一次性成功率提高了不少。技能粒度调整是常态化优化,不是上线时就定型的事。
6.3 上下文爆炸与结果污染
技能调用会带来上下文管理问题,主要体现在两方面:一是技能返回结果过大,塞进上下文把最重要的信息挤掉;二是不同技能返回的同类数据格式不一致,互相污染。
上下文爆炸的处理经验是:所有技能输出都要"瘦身"。设定输出 schema 只保留必要字段,杜绝整表返回。一个查询订单的技能如果默认返回 30 个字段,而真正需要的只有 5 个,其余 25 个字段就是噪音。我统一做了字段裁剪,并把完整原始结果放在可查询的存储里,需要时再通过另一个"详情查询"技能取回,而不是一次性全塞给模型。
结果污染问题的根源是字段命名冲突。两个技能都返回status字段,一个表示订单状态,一个表示用户状态,上层编排的步骤从两个子技能取数据时,就很容易取错。我在设计规范里强制要求:每个技能的输出字段加上业务前缀,比如order_status和user_status,并在output_schema里注明来源。这样字段名一旦冲突,测试阶段就能发现,不会等到线上才出问题。
我还遇到过技能之间通过全局存储间接"污染"的情况:A 技能写了某个缓存键,B 技能读取时拿到了 A 写入的中间值,导致 B 的输出漂移。排查时极其隐蔽,因为两个技能表面上没有依赖关系。解决方案是给所有技能中间数据加上命名空间隔离,并且默认禁止读写未声明过的共享键。这个强制规范实施之后,类似的隐性数据污染问题就再没出现过。
7. 最后分享几点做 agent-skills 的经验
前前后后折腾了挺久,踩了不少坑,有几条比较深的体会想单独说一下。
第一,技能化不是银弹。它解决的是"模型能力边界和业务流程的匹配"问题,但如果业务流程本身混乱、字段没定义清楚,技能化只会把混乱结构化地放大。我建议在开始技能化之前,先花时间梳理业务步骤和数据结构,哪怕用文档手写出来,都比直接写代码强。
第二,技能描述值得花最多的心思打磨。我见过不少人把精力全放在技能实现和代码优化上,却忽略了对模型说话的那几行字。实际上,模型能不能正确使用技能,描述质量起着决定性作用。我的习惯是每写一个技能,先拿 20 条真实用户问题做模拟选择测试,选择准确率不过 90% 就继续改描述,直到满意再写实现。
第三,从少数几个高频技能开始,不要贪多。技能库里前十个技能往往能覆盖 80% 的高频场景,先把这些技能做到极致,比追求技能数量重要得多。技能一旦超过几十个,维护成本会指数级上升,评测、回归、版本管理全都变重。我后来有意识地"合并同类项",把相似技能收编成带参数的更抽象技能,让技能库保持精简。
第四,日志和追踪是排障的生命线。每个技能调用、每一步执行、每个分支选择、每次参数映射,都应该有结构化日志。初期我觉得麻烦没做全,出了一个难查的现场事故之后,我补全了全链路追踪。从那以后,再遇到问题,基本能在几分钟内定位到具体技能和具体步骤,效率提升非常多。特别是当你准备把技能库逐渐开放给更多 Agent 共用时,全链路追踪几乎是必需的基础设施。
最后再分享一个小技巧:技能定义文件里加一个changelog字段,记录每次版本变更的原因和时间。这个字段不需要给模型看,但对你维护技能库非常有帮助。半年后回头看某个参数为什么被改成这样,那个字段会给你答案。