1. 项目概述:当“技能”成为 Agent 的第一公民
如果你最近半年一直在关注大模型应用层的动态,应该会频繁撞见“agent-skills”“tool-use”“function calling”这些词。老实说,tool-use 已经不算新鲜,但 “agent-skills” 把“工具调用”这个动作上升到了“技能体系”的层面——这背后的思路转变,才是这个项目标题真正值得拆解的地方。
简单理解:传统 tool-use 是大模型在需要时临时调用一个外部函数,用完就完;而 agent-skills 更像给 Agent 配备一套“职业技能树”,每个技能包含触发条件、执行步骤、失败回退、状态管理等完整逻辑。它解决的核心问题是:光有工具列表的 Agent 像拿着锤子的新手,而拥有技能体系的 Agent 才像真正上过工地的老师傅。
这个方向适合三类人看:一是正在做大模型应用落地的工程团队,二是想理解 Agent 架构演进的产品/技术负责人,三是对自主 Agent 感兴趣、想自己动手搭一套技能框架的个人开发者。这篇文章我会从设计思路、框架拆分、核心实现、踩坑记录四个层面展开,尽量把“技能化”这个抽象概念落到可复现的实操层面。
提示:本文不依赖特定框架,核心思路可以用任何支持结构化输出的模型配合自定义执行器实现。文中的代码片段是伪代码级别,重点讲设计和逻辑,不绑定具体库。
2. 为什么“技能”比“工具”更适合 Agent
2.1 工具调用的三个尴尬现场
先回忆一下你实际开发中遇到的场景。第一个尴尬:模型接了十来个 API,但每次都是“问一句调一次”,像极了刚入职的新人,做什么都要请示领导,毫无自主性可言。第二个尴尬:有一次调用链特别长,前面一步的返回结果要作为后面一步的入参,结果模型在中间某一环理解偏了,后面全部跑偏,排查了半天才发现是“工具选择”那一步出了问题。第三个尴尬:某个外部服务偶尔超时,模型没有重试逻辑,直接在错误结果上继续运算,产出了一份看着像模像样、实际完全错误的分析报告。
这三个场景指向同一个问题:工具只是“零件”,缺少“使用零件的规程”。真实世界里,熟练工人不会每次拿到扳手都研究怎么握,而是形成了一套“拧螺丝”的肌肉记忆——什么时候该用扳手、用多大力、拧到什么时候停、拧不动怎么处理。agent-skills 想解决的就是这个:把“用什么工具”和“怎么用工具”打包成一个完整技能,Agent 拿到技能后不仅知道“能做什么”,还知道“该怎么做、做不下去怎么办”。
从架构角度看,这套思路的价值在于把控制逻辑前置。传统 tool-use 模式下,控制逻辑散落在模型每次推理中,模型需要自行判断“下一步调什么”,这对模型的推理能力要求极高,也因为不可控而经常翻车。而 agent-skills 模式下,技能本身携带了流程编排信息,模型更多是“选择技能”而非“实时编排步骤”——相当于把一部分决策从模型运行时转移到了技能设计期,这大大降低了运行时的复杂度。
2.2 技能体系的三个核心构成
一个完整的 agent-skills 方案,在我看来至少要包含三个层面。
第一层是技能描述。这不仅仅是“一句话说明这个技能做什么”,而是要写清楚适用场景、输入要求、输出格式、副作用、失败条件。好的技能描述就像招聘 JD,把“岗位职责”和“任职要求”写明白,模型才能精准匹配。我在实际项目中发现,描述太泛的技能模型乱用,描述太死的技能模型又不敢用——这个度的拿捏需要反复迭代。
第二层是技能实现。这就是传统的 tool function,但要求更高。普通的工具函数只管“输入参数返回结果”,而技能实现需要包含状态管理、重试策略、错误码规范。举个例子,一个“获取天气”的工具可能只需要查 API 返回数据,但一个“城市出行规划”的技能,内部可能涉及天气查询、交通路线、时间预算、备选方案生成等多个子步骤,这些步骤之间的状态流转必须由技能内部管理,而不是靠模型临场发挥。
第三层是技能评估。这是最容易忽略也最关键的。技能设计得再好,如果无法评估“这次执行得好不好”,迭代就无从谈起。评估维度至少包含完成率、成功率、副作用率、token 开销。我在实践中用一套简单的量化表来评估技能质量,后面会详细展示。
三层合起来看,技能体系本质上是一种“半成品决策框架”——把可以由人工设计的逻辑固化下来,把必须依赖模型的推理部分留给模型。这个边界划在哪,是每个 agent-skills 项目最核心的设计决策。
3. 动手设计一套技能框架:关键决策拆解
3.1 先定边界:哪些逻辑该进技能,哪些留给模型
这一步是设计中最需要经验的地方。我总结了一个三步筛选法,你可以直接拿来用。
第一步:看频率。某个流程如果经常重复出现,比如“多文档对比阅读”“定时任务编排”,就值得固化成技能;如果是一锤子买卖,那直接让模型临时调用工具就行,不要过度设计。频率判断可以从对话日志里统计,也可以靠业务经验拍脑袋,前期不用太精确,跑一阵子再调整。
第二步:看确定性。流程中的步骤是否相对固定?比如“发票信息抽取”虽然每张发票内容不同,但抽取步骤基本固定:识别图片→提取结构化字段→按模板输出→异常字段标记。这种流程确定性强,适合技能化。反过来,“写一篇方案”这种开放任务,步骤高度依赖具体需求,强行技能化反而会限制模型发挥。
第三步:看容错成本。执行失败会不会带来严重后果?比如涉及支付、权限变更之类的高危操作,哪怕逻辑确定,也不建议完全自动化,而是设计成“半自动技能”——技能负责准备条件和预检,关键确认动作留给人。这个边界踩过坑的人都懂:全自动的权限管理技能,一次误判的代价可能让你后悔设计这个技能。
我自己的经验是:初期宁可少技能化,也不要追着把所有流程都固化。技能框架搭起来之后,新增一个技能成本很低,但技能和技能之间的交互复杂度会指数级上升,一上来搞十几个技能,最后调试交互逻辑的时间远超省下的时间。
3.2 技能描述规范:让模型“看得懂、用得准”
技能描述是技能体系里最有技术含量也最容易被低估的部分。同一个技能,描述方式不同,模型的使用效果可以差出一大截。我来展示一下我在实践中的模板:
技能名称:技术方案对比评估 适用场景:用户给出两个或多个技术方案,要求从成本、风险、实施周期、团队能力匹配度等多维度评估 触发条件:用户明确要求“对比方案”或“评估哪个方案更好”;上下文包含两个及以上候选方案的完整描述 输入要求: - 方案列表(必填):每个方案需包含技术选型、预估成本、人力投入、时间计划 - 评估维度(可选):缺省时使用全维度评估 - 权重配置(可选):可自定义各维度权重,格式为JSON 执行流程: 1. 解析方案列表,校验字段完整性 2. 根据维度生成评估模版 3. 逐项填写评估信息,维度缺失时标注“待确认”,不得臆测 4. 生成加权得分表 5. 输出[结论段落]+[得分表]+[风险提示列表] 输出格式:结构化Markdown表格 + 结论段落 失败条件: - 方案描述不完整,影响评估可信度 → 输出“信息不足”状态,列出缺失字段清单 - 超出单次评估上下文窗口 → 分批次评估,输出阶段报告 副作用:无外部写操作,纯分析类技能注意到几个关键点:一是“触发条件”写得比较宽,允许模型根据上下文自行判断,而不是死板地等用户说“请对比方案”——实际对话里用户很少说得这么规整;二是“失败条件”要给出具体动作,模型才知道处理路径;三是一切描述都要能落到输出格式上,不可验证的描述等于没写。
描述写好后,一定要做“假面试”测试:把描述喂给一个随机模型,给几个真实对话场景,看它能不能正确触发和使用技能。我见过太多写得漂亮但模型根本不会用的技能描述,原因多半是描述里用了领域黑话或者隐含了上下文依赖。
3.3 技能执行器的架构选型:单轨还是多轨
技能执行器是承载技能运行的框架,这里有一个关键分叉:顺序执行还是并行调度。
顺序执行最简单,就是一个技能跑完再跑下一个,状态通过内存传递。优点是逻辑清晰、好排查、token 开销固定;缺点是慢,遇到独立任务时浪费窗口。适合技能间有强依赖、需要逐步确认的场景,比如“数据清洗→特征提取→模型训练”这种流水线。
并行调度让多个技能同时跑,主 Agent 汇总结果。优点自然是快;难点在于结果聚合:两个技能输出格式不一致怎么办?时序不同怎么办?部分技能失败要不要整体中断?这块设计不好,你就会陷入“并行一时爽,合并火葬场”的境地。
我的建议是:异构技能之间老老实实串行,同构技能(比如多文件批量处理)才做并行。并行时给每个技能的结果包一层统一的 envelope 格式,带上任务ID、状态、输出、耗时,聚合层才好统一处理。下面我给出一个极简的执行器伪代码:
class SkillExecutor: def __init__(self): self.registry = {} # skill_name -> skill_instance self.parallel_batch = {} # batch_id -> [task_ids] def run_serial(self, skill_names, initial_state): state = initial_state outcomes = [] for name in skill_names: skill = self.registry[name] skill_result = skill.execute(state) outcomes.append(skill_result) state = skill_result.state # 技能内部维护状态流转 return outcomes def run_parallel(self, task_list): # task_list: [(skill_name, params), ...], 仅限同构技能 results = [] for skill_name, params in task_list: skill = self.registry[skill_name] results.append(skill.run_with_retry(params)) return self.aggregate(results)run_with_retry 是技能实现里很关键的小机制:捕获异常后按配置重试,重试几次、退避策略如何,由技能自己定义。我见过有人在执行器层面统一做重试,但不同技能对重试的容忍度不一样——比如幂等类技能可以大胆重试,非幂等类技能重试可能导致重复扣款,所以重试策略必须放在技能内部,而不是全局统一。
3.4 状态管理:被低估的“隐性架构”
双状态管理的设计原则:
第一类状态是执行状态,包括当前技能批次的进度、每个技能的任务ID、已产生的中间数据。这类状态跟着执行器走,职责单一,理解成“流水线工单”。
第二类状态是长期记忆状态,包括用户偏好、领域知识、历史决策记录。这类状态不跟着单次执行走,而是沉淀下来跨会话复用,理解成“老师傅的经验积累”。
最大的坑就是把这两类状态混在一起存。一旦混存,单次执行的临时数据会污染长期记忆,长期记忆的偏好又可能影响不该影响的单次执行决策。比如用户上一次说“格式简略一点”,如果你不加区分地把它写进长期记忆,下一次做复杂技术方案对比时模型也可能“简略输出”,结果关键信息全丢了。
状态存储方案上,我自己的选择是:执行状态放内存或临时 KV,长期记忆放向量数据库或结构化库。关键原则是这两者的读写路径必须拆开。再往深一层说,技能内部也要按这个逻辑区分——技能执行时的局部记录存内存,技能完成后需要沉淀的结论才写长期记忆。
4. 核心实现:从零搭一个可运行的技能系统
4.1 技能注册与发现机制
技能系统首先要解决“模型怎么知道有哪些技能”的问题。最简单的方式是把所有技能描述拼进系统提示词,模型运行时全量可见。这个方案在小规模(5个以内)可行,技能一多,token 开销很快就把窗口撑爆,而且无关技能会干扰模型选择。
升级方案是技能检索层:先把技能描述向量化,运行时根据当前对话上下文做检索,只把相关的几个技能描述注入上下文。这就像你打开软件不会同时看到所有菜单,而是根据你点的按钮弹出对应面板——按需加载。
检索方案我实测下来要注意两点:一是描述文本的向量化要用短文本优化过的模型,用通用长文本模型反而效果一般;二是检索结果里要保留“不匹配”的信号,如果检索结果相关度普遍低于阈值,就空着不要硬塞,让模型回归无技能状态。自动技能检索的音色可以配合技能描述一起注入,格式上做个简单的 rating 标注:
可用技能: - [技术方案对比评估](匹配度:0.92) - [代码评审](匹配度:0.87) - [数据分析报表](匹配度:0.31,因输入缺失跳过)注意,我示例中省略了“不可用技能”的注入,市面上不少检索器会把全部情况都写出来,我觉得意义不大,反而浪费 token。让模型明确知道“哪些技能当前可用”,比知道“哪些技能当前不可用”更重要。
4.2 技能内部的“子技能”设计:层级调用的威力
单个技能如果实现成一个巨大的函数,写起来爽,但复用性和调试性都很差。我后来习惯了“技能内部再拆子技能”的层级设计。
举一个实际例子:我做过一个“竞品调研报告”技能,表层看是一个技能,内部拆成五个子技能:信息采集、信息去重、竞品指标提取、对比分析、报告渲染。每个子技能只完成一件事,子技能之间通过标准数据接口传递。这样一来,单个技能的代码量被压缩,而且子技能可以在不同技能之间复用——比如“信息去重”这个子技能,我在做“文档综述”技能时也直接引用了。
层级设计带来的另一个好处是便于黑盒测试。我可以单独测一个子技能,而不是每次都跑整个链路。这在调试时帮了我大忙:有一次报告渲染乱码,我花了一个小时才定位到是某一段文本的转义问题,如果技能是一个 500 行的巨函数,定位成本会翻好几倍。
子技能之间传递的数据结构一定要提前定义清楚。我用的是 JSON Schema 附带注释,每个子技能的输入输出都做严格校验,不符合直接报错,不许“差不多能用就行”。这种字段级约束看起来繁琐,但在层级调用时能省下大量对接调试时间。尤其注意日期格式、金额单位这类容易“差一点”的字段,早统一早安心。
4.3 可观测性与调试手段:技能执行的黑匣子必须打开
技能系统跑起来容易,调起来难。你遇到的情况多半是:结果不对,但不知道是模型理解偏了、技能逻辑错了、还是外部 API 返回了脏数据。没有可观测性的技能系统,排查就是大海捞针。
我的经验是,每个技能执行至少记录四类日志。
第一是决策日志:模型为什么选了这个技能?触发依据是什么?这一步可以通过记录注入时的提示词上下文和模型输出中的“技能选择理由”来实现。
第二是执行轨迹:技能内部每一步子技能的顺序、耗时、输入输出大小。格式上记录成 JSON lines,方便后续回放和统计。
第三是失败快照:哪一步失败了?报错信息是什么?重试了几次?最终是放弃还是降级处理?这个对定位“偶发问题”特别重要——很多技能是外部依赖导致的偶发故障,一次性日志看不出规律,但汇总一段时间就能发现“每天晚上八点前后失败率明显偏高”之类的规律。
第四是代价统计:token 开销、耗时、API 调用次数。先有数据,才能谈优化,我见过很多团队做技能迭代全靠感觉,一问“这个技能成功率多少”,答不上来。
这四个维度整理下来,可以做成一张简单的监控面板。不需要复杂的可观测性系统,一个日志采集脚本加几张聚合表就能跑起来。但前提是,在技能实现里就得埋好日志节点,事后补日志是最痛苦的。
4.4 技能评估闭环:数字驱动的迭代节奏
技能写出来不等于能用,能用不等于好用。我建立了一套打分机制,每次技能迭代后跑一轮评估,用分数决定是否合并改动。
评分维度分五块:
| 维度 | 权重 | 说明 |
|---|---|---|
| 完成率 | 40% | 技能在正确触发后正常走完全流程的比例 |
| 结果准确率 | 25% | 输出内容经人工核验后的正确率 |
| 副作用率 | 15% | 执行中是否产生了额外副作用(如重复写库、误发请求) |
| Token 开销 | 10% | 单次执行的平均 token 消耗 |
| 用户体验分 | 10% | 用户侧反馈(点赞点踩、满意度评分) |
完成率权重最高,是因为一个经常跑不通的技能,质量再高也没用;副作用率单独一栏,是因为很多技能的坑不是“没做完”而是“多做了不该做的事”——比如一个查询技能顺带写了缓存,虽然结果看起来没问题,但副作用在复杂交互中会积累成刺。
评估数据怎么产生其实很简单——每次执行后的日志自动流进评估表,加上一个简单的人工抽检流程。我大概每两周做一轮:拉出一周数据,逐技能过一遍指标,把低于阈值的技能列进修复列表;修复后立即重跑评估,连续两轮达到阈值才能算“达标”。这套节奏看起来朴素,但在很长一段时间内都保证了我的技能体系是稳健迭代而非拍脑袋优化。
5. 实操过程:完整走一遍技能从设计到上线
5.1 案例背景:一个“会议纪要与任务拆解”技能
选这个案例是因为它足够典型:高频、流程相对固定、但又有需要模型发挥的部分,而且对副作用敏感(不能把任务拆错、不能漏掉关键事项)。先说业务背景:某团队每周有大量跨部门会议,每场会议结束后需要整理纪要、提取行动项、分配给责任人、设定截止时间,还要跨会议追踪之前行动项的完成情况。
传统做法是人工整理,费时且容易遗漏。我这里就把它做成一个“会议纪要与任务拆解”技能。
5.2 技能设计阶段:四步确定方案
第一步,明确输入输出。输入是会议原始记录(文本或转写稿)、参会人列表、历史行动项库;输出是标准格式的会议纪要、新增任务清单、关联历史任务的进展标记。注意:输入里“历史行动项库”就是长期记忆状态,需要从知识库读取,而非每次会议重新输入。
第二步,拆解内部流程。我拆成五个子技能:
- 纪要结构化子技能:把原始记录按“议题→讨论要点→结论→遗留问题”整理,这一步模型自由度较高,尽量保留原意不做缩写。
- 行动项识别子技能:从结构化纪要中提取涉及“人+事+时间”的句子,转成候选行动项;这里用固定提示词模板,减少模型自由发挥。
- 责任人匹配子技能:将候选行动项里的负责人与参会人列表对齐,模糊名称做归一化。
- 历史任务关联子技能:读取历史行动项库,将本次新增的同类任务与已有未完成项建立关联,输出“继续跟进/新开任务/已完结”标记。
- 产出渲染子技能:最后按模板输出纪要、任务清单和进展汇总。
第三步,定义状态流转。子技能之间是串行关系,每个子技能输出都是一个标准 JSON 数据结构。特别是“行动项识别子技能”的输出,要求字段必须是{负责人, 动作描述, 截止时间, 状态}四件套,缺一不可;字段缺失时直接走校验失败分支,不让数据带病流向下游。
第四步,设计失败回退。最现实的两个失败场景:一是原始记录质量太差,行动项识别不出关键字段;二是责任人名称和列表对不上。处理方案分别是不合格文本先经一轮清洗子技能、责任人匹配失败则挂起待人工确认而非自动指派,宁可不生成也不能乱指派。
设计阶段在这里停了下来,我不急着写代码。先输出一份设计文档,同时把技能描述喂给模型做假面试测试,发现“行动项识别”子技能的描述里“时限描述”定义得不够清晰,模型会把“尽快”“下周内”这类模糊时间也硬填一个截止日期——这是个典型问题,后面在描述里加了约束:模糊时间标注为“待确认”,绝对不许猜。
5.3 实现阶段:核心代码与关键决策
进入编码阶段后,我按子技能分别实现,每个子技能独立可测。这里贴两个核心片段,一个是行动项识别子技能的核心逻辑:
def extract_action_items(structured_notes): # 输入: 结构化会议纪要 # 输出: 候选行动项列表 system_prompt = """ 你是一个行动项抽取器。从给定的会议纪要中抽取所有隐含行动指令的句子。 规则: 1. 只抽命令句/建议句/分派句,不抽状态描述句 2. 每条结果必须包含:负责人、动作、截止时间、状态 3. 截止时间为模糊说法(如“尽快”“下周内”)时,填"TO_BE_CONFIRMED" 4. 负责人无法识别时,填"UNASSIGNED" 5. 不得合并多条指令为一条 输出严格为JSON列表格式。 """ messages = [{"role": "system", "content": system_prompt}, {"role": "user", "content": structured_notes}] response = call_model(messages, response_format="json") validated = validate_action_items(response) # 校验四件套是否完整 if not validated["valid"]: return {"status": "failed", "details": validated["errors"]} return {"status": "ok", "items": validated["items"]}逻辑不复杂,但注意“validate_action_items”这一步很关键。我在实际开发中做了字段级校验,比如负责人是否为字符串、动作描述长度是否大于 2、截止时间是否满足 ISO 格式或 TO_BE_CONFIRMED。不符合的直接拒绝,而不是尝试修复——因为模型的输出一旦需要修复,往往意味着提示词有问题,遮住问题只会让它一直存在。
另一个核心片段是责任人匹配子技能,这里要对模糊名称做归一化。模型输出“王工”要和参会列表里的“王建国”匹配上,还要处理重名的情况。我的做法是先全量匹配,再模糊匹配,最后人工确认队列兜底。代码如下:
def match_assignee(raw_name, attendee_list, alias_map): if raw_name == "UNASSIGNED": return None # 1. 精确匹配 if raw_name in attendee_list: return raw_name # 2. 别名匹配 if raw_name in alias_map: return alias_map[raw_name] # 3. 模糊匹配(去除头衔、职称、空格等) normalized = normalize_name(raw_name) for attendee in attendee_list: if normalized in normalize_name(attendee) or \ normalize_name(attendee) in normalized: return attendee # 4. 无法匹配,返回 None 交人工确认 return None模糊匹配的规则要非常克制,只做“包含”级别,不做拼音、语义联想级别的猜测。我吃过亏:模型输出“小李”,参会列表里有“李伟”和“李晓东”,模糊匹配匹配不上其实还好,最怕的是系统自作聪明匹配了其中一个,结果任务派错人。所以宁可返回 None 走人工队列,也不要“猜一个”。
最后是实现“历史任务关联子技能”,这个子技能要读取历史行动项库,判断新任务与旧任务的关系。这步我用了一个轻量规则:按负责人+动作关键词做相似度,超过阈值标记为“继续跟进”,否则新建。这里嵌入了一个小的向量匹配函数,但阈值设得很保守,宁可多标“新开任务”,也不要错误地把独立任务合并进旧任务。
5.4 上线验证:小流量灰度与回归测试
技能上线之前,我先用历史会议数据跑回归:挑出过去十场会议的原始记录,跑完技能后人工核验输出质量。这一步非常值得做,能在真实流量进来前暴露绝大部分设计问题和实现 bug。
回归中我发现了一个很有意思的偏差:模型在处理讨论中出现的“反问句”时经常误判成行动指令。比如“不如下周我们同步一下进度?”这种句式,人类一听就知道是提议而非指令,但模型会把它识别成“安排同步任务”。我加了规则,对“?”结尾的句子默认降权,这个误判率立刻降下来。这说明技能内部不是纯靠模型提示词就能完美,往往还需要一些前置或后置的规则来兜底。
灰度阶段,我把技能以 20% 流量的方式放出去跑了一周,对比人工整理的对照组。主要指标是行动项召回率(是否漏掉了关键任务)和准确率(是否有错误或杜撰的任务)。一周后数据稳定,完成率超过 90%、人工确认时间减少约四成,才全量放开。
5.5 反馈闭环与持续迭代
技能上线之后不是终点,持续迭代才是。我把每次执行后的用户修正作为最重要的迭代源。比如有用户手动改了任务截止时间,说明模型自动填的时间经常不靠谱,我就把“截止时间生成”的前置规则改掉,改为优先识别记录中显式时间,没有再让用户填。
迭代的节奏我自己固定成每两周一轮,把所有修正行为聚类一次,逐类决策是改提示词、改规则还是改数据结构。这套闭环跑下来,技能的效果是逐步上升的,而且每次改动都有数据支撑,不是拍脑袋。
6. 常见问题与排查技巧实录
6.1 模型总是用不好技能:触发率低、误用率高
触发率低,说白了就是技能描述和模型认知对不上。排查步骤:先记录模型在什么语境下该用而没用,收集 10 到 20 个这样的反例,逐条看描述里哪句话有误导性。常见原因是描述里用了“必须”“一定”之类的绝对词,让模型觉得条件不满足就不该用。
误用率高,往往是触发条件写得太宽。比如一个“智能体规划”技能,触发条件写了“用户需要完成任务规划”,结果任何聊到“计划吃饭”“明天安排”的普通对话也触发了这个技能。解法是加“约束触发条件”,写明“用户明确要求自动规划行动步骤、且包含多于一个执行工具调用的场景才触发”。
另外一个隐藏原因:技能在模型上下文里的排序位置会影响使用率。放最前面的技能容易被选中,这算“位置偏见”。如果你有几个同等重要的技能,不要固定排序,每次随机打乱,或者通过权重机制做干预,不然模型永远偏爱第一个技能,统计上会产生虚假的“好坏”结论。
6.2 技能执行到一半失败:失败恢复怎么做
技能失败是常态,关键是失败之后的状态处理。三种典型失败:
| 失败类型 | 典型场景 | 推荐处理 |
|---|---|---|
| 输入校验失败 | 模型产出了缺字段的 JSON | 不修复,直接中断,返回“需要补充信息”的状态 |
| 外部依赖超时 | API 调用 3 次都超时 | 技能内定义重试上限,达到上限降级为“部分完成+警告” |
| 逻辑内部异常 | 子技能间数据格式不匹配 | 打错误快照,保留现场,等待人工介入 |
第一个“输入校验失败”我特别要强调:很多工程同学看到模型输出格式不对,“顺手”就帮它修正一下,这其实是掩盖问题的最好方式。模型输出格式错误往往提示提示词有歧义,或者模型对输出格式理解出现偏差,你修一次,它下一次还会错,甚至可能因为你“宽容”而越来越随意。正确做法是返回错误状态,记一条失败日志,供迭代提示词使用。
外部依赖超时这一块,我踩过最多的坑不是“重试多次失败”,而是重试成功后没有正确处理“幂等性”。举个反例:一个发通知的技能,超时后重试,结果之前那次其实已经发出去了,重试又发一次,对方收到两条。所以技能内只要有外部写入类操作,重试前一定要确认操作是否生效——查询对账优先于直接重发。
6.3 技能之间“打架”:状态污染和输出冲突
当技能数量上到十几个之后,技能间冲突就开始浮出水面。最典型的是长期记忆污染:技能 A 执行时往记忆里写了一条“用户偏好格式简略”,技能 B 下次读取时直接把这个偏好当成通用规则,输出结果过度简化,关键字段缺失。
解决这类问题我总结了两条铁律:
第一,技能写入记忆时必须带“上下文标签”。比如“偏好-输出格式-简略”和“偏好-交互风格-高效”分开存,读取时按标签匹配,不带标签的记忆一律不进长期库。这本质上是给记忆做结构化隔离,让“别人家的偏好”不会跑到不该用的场景里。
第二,每个技能执行前应该做一次“环境检查”:检查当前记忆中有没有与本技能冲突的标签,如有,本技能自行决定是否忽略该记忆。检查逻辑写在技能内部,不能指望模型靠提示词自觉。
6.4 技能评估里“指标很好但体验很差”的悖论
我经历过一次:技能完成率 95%,结果准确率 92%,但用户满意度只有 60%。数据解读时我一度很困惑。后来逐个看执行日志才明白,问题出在“完成了但过程绕远路”——模型选了技能,但用了大量内部推理去修正自己不合适的输入,执行时间过长,用户等得不耐烦了;还有一部分是把本来一句话能解决的查询搞成了重型技能全流程,用户想简单拿个信息,被“详细报告”淹没。
这个教训让我改变了评估的维度,不再只盯着“技能本身”指标,加上了“非必要触发率”和“响应耗时”两个辅助指标。非必要触发率高说明技能描述里触发条件还是太宽泛,应该收窄;响应耗时偏高说明技能内部子步骤太多,该考虑拆成两个独立技能或者加并行。
6.5 日志爆炸与排查困难:怎么快速定位“哪一步出错”
技能体系跑起来之后,日志数据量很大,尤其并行技能多的时候。我用了一个“trace_id”贯穿法:每个技能批次生成一个全局 trace_id,子技能、外部调用、模型调用日志全部带上这个 ID。排查时只要拿 trace_id 一搜,整条链路的日志就全出来了。这是最基础也最有效的排查手段,没有之一。
另外,日志里的“失败快照”一定要记录完整上下文。只记“调用超时”这种信息等于没记,要记录当时的输入参数、前几个子技能的输出、模型返回的原始内容。很多时候失败原因藏在“前一步输出的细节”里,没有上下文根本无从查起。
7. 技能体系的扩展方向与个人体会
这个项目做到后面,我发现 agent-skills 真正的价值不在单点任务的自动化,而在让多个技能之间形成“组合拳”。比如“会议纪要”技能和“项目周报”技能可以串起来:会议生成的行动项自动汇入周报数据源。这种技能间组合带来的生产力提升比单一技能翻倍还要多。
技能市场这个概念也很有意思。如果技能描述和实现都标准化,技能本身就可以像插件一样在团队甚至跨组织间流通。你团队调好的一个“技术方案评审”技能模板,别的团队拿来改一改就能用。当然前提是技能描述的质量足够高、实现足够标准化——这两点,恰好就是整个项目最花时间的地方。
我个人在实际操作中最大的体会是:别急着让技能“聪明”,先让技能“可靠”。一个稳定完成 90 分任务的技能,远比一个有时 100 分有时 60 分的技能有价值。可靠性的来源不是更好的模型,而是更清晰的技能描述、更完善的状态管理、更严格的数据校验,以及更系统的观测和迭代闭环。
最后再分享一个小技巧:技能描述里不要写“让模型自己判断”这类话,永远给出可执行的规则。比如“若信息不足,应请求补充”比“根据情况决定是否请求补充”有效得多。把模棱两可的部分消灭在技能设计阶段,运行时的不确定就少一分——这一条,贯穿了我整个 agent-skills 项目的始终。