做Agent功能开发这段时间,我最大的感受是:模型能力决定下限,技能体系决定上限。同样是接GPT或Claude的API,有人做出来的东西像个玩具,有些人能稳定处理复杂业务流,差别全在技能设计上。
所谓“agent-skills”,简单说就是给智能体设计一套可复用、可编排、可维护的专业技能库。它不是一个单独的技术名词,而是一整套工程方法:从技能的定义、触发、编排到故障恢复,每一步都有讲究。这篇文章不打算讲框架配置和抽象理论,只讲我在真实项目里怎么设计技能、踩过哪些坑、最后形成的一套能落地的方案。
1. 技能体系在Agent框架里的位置
1.1 为什么单独的Prompt不够用
很多人刚上手做Agent时,习惯把需求全塞进System Prompt里。比如做一个日程管理助手,就在提示词里写“你是一个日程助手,你负责解析用户意图、查询日历、添加事件、提醒用户”……一开始效果还行,场景一复杂就全线崩溃。
我在项目里实测下来的问题很典型:
- 上下文越长,意图识别越漂。用户说一句“帮我看看周五下午有没有空”,模型在超长系统提示里找了半天,还是把“查询”和“修改”搞混。
- 所有行为都绕着一个大提示词转,改一个场景细节就得动全局,测试一次回归成本特别高。
- 多轮对话里经常出现重复执行。模型上一轮刚调完查询工具,下一轮不知道结果状态,又调了一遍。
技能体系的出现,就是把这些“模型需要临时决策的事情”变成“提前定义好的可执行单元”。每个技能是一个独立模块,有自己的触发条件、参数结构和执行逻辑。模型要做的事被大幅简化:识别当前该调哪个技能、填对参数、把结果组织成用户能懂的回答。
1.2 技能的三个核心构成要素
我自己在设计技能时,不管场景多复杂,最终都收敛到三个东西:
- 技能描述(Skill Description):一段短而精的文字,说明这个技能管什么、不管什么、在什么情况下使用。这段文字是模型选技能时的唯一依据,所以必须把边界写清楚。举例说,日程助手领域的“查日历”技能,描述应该包含“查询指定日期或日期范围的日程安排,支持按关键字过滤”,同时补一句“仅查询,不执行创建或修改操作”。
- 入参Schema(Parameter Schema):定义模型调用这个技能时,需要提供哪些结构化参数。这层做得扎实,后面做参数解析才省力。我的经验是参数名要尽量贴合领域术语,比如
start_date、keyword,别用arg1这种没有语义的命名。 - 执行逻辑(Execution Logic):技能真正跑起来的那段代码,可以是API调用、数据库查询、Python脚本,甚至可以是另一个子Agent。执行逻辑的返回格式要固定,最好是一个结构化的JSON,包含
success字段和data字段,这样编排层才好看状态。
我的建议是:任何技能的设计文档都围绕这三个要素写,缺哪个都不算完整。
1.3 传统工具调用和技能编排的差异
不把技能体系跟传统工具调用混淆很重要。工具调用(Function Calling/Tool Use)的粒度通常很细,一个工具就干一件事,比如get_weather、send_email。技能则是带状态和编排逻辑的更大粒度单元。
举个我实际做过的例子。一个智能客服技能,如果拆成工具层,可能是search_order、check_refund_policy、create_refund_ticket这三个独立函数。但技能层应该设计成一个售后处理技能,内部自己判断“用户是查订单还是申请退款”,自己维护一个流程状态机,按规则决定先查什么、再调什么。
这套抽象带来了明显收益:
- 模型侧决策负担降低,只在“该调哪个技能”上做选择,而不是在“该调哪个函数”上做选择。
- 上层编排策略可以复用技能,不会因为API列表变长导致提示词爆炸。
- 测试更干净,每个技能可以独立喂一批样本验证准确率。
这也是为什么现在主流Agent框架都往“技能树”“技能库”方向走——因为工具调用是基础,但可编排的技能才真正有业务价值。
2. 技能生命周期管理:从创建到退役的完整闭环
2.1 技能创建阶段:先定义好边界
我在动手写技能代码前,会先花一个小时做边界分析。核心是搞清楚三件事:这个技能解决什么问题、不解决什么问题、与相邻技能怎么交接。
这里说的是“相邻技能”的边界,特别容易忽略。比如内容创作场景里,“生成文案”和“润色文案”看起来是两件事,但实际事件里,模型经常不知道该调哪个。有一次线上用户说“帮我把这段产品介绍改得更高级一点”,系统里同时有“文案生成”和“文案优化”两个技能,模型在两者之间疯狂摇摆,测试集上切换成功率不到七成。
后来我把两个技能合并成一个“文案创作”技能,内部再分两步走:先是判断现有文本要不要保留,再决定是生成还是改写。看似技能数量减少了,实际准确率上来了。这就是边界设计的价值——技能粒度不是越细越好,而是越稳定越好。
2.2 技能测试校验:样本集思维
技能做完以后,我强烈建议建一个小的测试样本集,不要等集成测试时再头疼。每个技能至少准备20条典型请求,覆盖正常场景、异常场景、模糊场景三类。
- 正常场景:用户意图明确,参数齐全,技能应该直接命中。
- 异常场景:用户输入缺参数,技能要能识别缺失并引导补充。
- 模糊场景:用户输入有歧义,可归属相邻技能,此时要有明确的兜底逻辑。
这个样本集在后续每次修改技能描述时,都要完整跑一遍。实测下来,很多调整描述导致精度回退的问题,都是因为回归测试没做到位。我自己吃过亏:有一次在技能描述里多写了一句话“支持批量查询”,结果模型在用户只问单个日程时也走了批量分支,返回格式完全变了。
2.3 技能上线与灰度发布
技能上线不是小事,我的流程是分三步:
- 先在离线环境用历史对话记录回放,看技能命中率和参数抽取准确率。
- 再到内部群小范围灰度,人工抽查20%的线上调用日志。
- 最后逐步放量到全量,头两天盯紧错误日志,一旦某项技能连续报错,立即切回旧版本。
灰度过程中有个关键参数叫做“技能优先级降级阈值”。当某个技能的置信度分数低于设定值(比如0.4)时,系统应该自动降级到通用模型直接回答,而不是硬着头皮执行。这个策略能避免很多生产事故。
2.4 退役归档:技能不是越多越好
技能库膨胀是所有Agent项目都会遇到的问题。上线三个月后,我的技能数量从12个涨到37个,模型每次做技能选择时,光读一遍技能描述就要消耗大量token,而且选择准确率明显下降。
后来我定了一个规矩:每个季度做一次技能退役评审。连续一个月调用量低于1%的技能,一律打上“deprecated”标签,从调度候选列表里摘除,只保留在历史库里做存档。需要时再恢复,也不需要重写成新技能。
实测效果很显著:候选技能从30多个降到20个以内,选择准确率回升了约8个百分点。
3. 推理编排层:怎么让智能体选对技能、用对参数
3.1 技能路由的选择策略
技能路由是编排层的核心,我试过三种方案,效果差异很大:
- 纯模型选择:把所有技能描述丢给模型,让它选一个。优点是灵活,缺点是随着技能数量增加,准确率下滑明显。实测在候选技能超过30个时,准确率在75%徘徊。
- 基于规则预筛选 + 模型选择:先用关键词和意图分类模型粗筛,把候选技能缩小到5个以内,再交给模型精选。这套方案目前最稳,准确率能做到90%以上。
- 向量检索 + 模型排序:把技能描述做向量化,用户输入也向量化,用余弦相似度召回Top5,再让模型排序。这个方案在技能描述语义区分度高的场景下好用,但如果两个技能描述语义特别接近,容易召回错误的候选。
我目前主推第二种方案。理由很简单:成本低,可控性强。预筛选规则层可以单独维护,模型只做最终的判断题,而不是开放题。
3.2 参数解析的工程化处理
技能选对了,参数抽取出问题的概率其实更高。我踩过的坑主要是这几种:
- 时间表达识别不全。“明天下午三点”要转成具体时间戳,中文的自然语言表达太复杂,“明天”“下周一”“周五之前”各有各的解析逻辑。
- 枚举值归一化。用户说“约会”“会议”“出差”,背后可能对应同一个日程类型,需要预先定义映射表。
- 省略主语的情况。多轮对话里,用户说“那改成周三吧”,实际完整意思是“把刚才定的周五事件改到周三”,光靠单轮上下文是解析不出来的。
我的处理方案是:参数解析不依赖单一模型调用,而是先做规则清洗,再做模型补全。时间、日期、数字这类强规则信息,尽量用规则代码处理;模糊的语义信息才交给模型抽。这样既快又稳。
下面是技能路由和参数解析的简化代码逻辑(Python示例):
def route_and_execute(user_input, session_context): # 第一步:预筛选候选技能 candidates = prefilter_skills(user_input, session_context) # 第二步:让模型从候选中选出最合适的技能 selected_skill = model_select_skill(user_input, candidates) # 第三步:参数解析 params = parse_parameters(user_input, session_context, selected_skill.schema) # 第四步:执行技能 result = selected_skill.execute(params) # 第五步:结构化输出 return format_response(result)这段代码看起来简单,但每一步后面都有细节。预筛选这块,我建议维护一个同义词典,比如“查”“看看”“安排”“订”这些动词与技能触发词的映射,能大幅提升粗筛召回率。
3.3 多技能协作与状态流转
现实场景里,很少有一个技能单打独斗的。我在做一个“周报生成助手”时,流程涉及“收集工作动态”“分析工作要点”“生成周报正文”三个技能,而且三个技能之间有严格的先后顺序。
这种情况下,技能编排必须引入状态机概念。我在设计时用一个plan字段记录当前进度,每个技能执行完后更新状态,只有状态匹配的技能才会在下一轮被允许触发。
举一个具体流程:
用户说:帮我把本周的工作情况整理成周报 状态:initial → 触发技能1“收集工作动态” 状态:collected → 触发技能2“分析工作要点” 状态:analyzed → 触发技能3“生成周报正文” 状态:done → 返回最终输出如果中途用户改了需求,比如“先只写研发部分”,状态就要回退到collected,重新分析。这种状态流转如果靠模型自己记忆,几乎必然出错,必须由编码层显式维护。
3.4 回退策略与兜底逻辑
技能调用不是百分之百成功。模型选错技能、参数抽取失败、外部API超时,每类问题都该有自己的兜底策略。
我在线上环境常遇到的场景是:用户输入太短,技能路由模型给的置信度极低。这时候我不让系统硬猜,而是设置了一道“澄清关卡”——模型反问用户一句“您是想查已有日程,还是想新增日程?”。一次澄清,准确率能又提升十几个百分点。
还有一种常见情况:虽然技能选对了,但参数不够,比如用户说“帮我安排会议”,没说时间地点。兜底逻辑是在执行前自动生成一个参数补充模板,只补用户缺的那一项,而不是一堆抽象问句。
4. 实战演练:两个典型场景的技能拆解
4.1 企业内部日程管理助手
这是我第一次完整落地技能体系的场景,技能结构如下:
| 技能名 | 触发场景 | 核心入参 | 执行逻辑 |
|---|---|---|---|
| 查日程 | 用户想了解已有安排 | date,keyword | 查询日历服务并返回日程列表 |
| 建日程 | 用户要新增安排 | title,start_time,end_time | 创建日历事件,返回确认信息 |
| 改日程 | 用户要调整已有事件 | event_id,updates | 判断冲突并更新事件 |
| 删日程 | 用户要取消安排 | event_id | 删除事件,返回确认信息 |
这里最值得说的是“改日程”技能里的冲突检测逻辑。用户想改时间,如果新时间段已有其他安排,技能不能直接改,而是要返回冲突明细,让用户确认是坚持还是要顺延。这个逻辑写在技能内部,而不是写在模型提示词里,执行起来特别可靠。
从指标上看,这套技能体系上线后,意图识别准确率从最初的76%提升到92%,用户平均对话轮次从6.2轮降到了3.8轮,体验提升非常明显。
4.2 个人知识库问答助手
第二个场景是个人知识库问答,技能不太一样,包含“语义检索”“摘要归纳”“来源溯源”三个核心技能。这里最难的坑是“摘要归纳”和“语义检索”的先后顺序。
很多人觉得应该先检索再归纳,但实测下来,对于关联信息分散在多个文档里的问题(比如“这个项目用了哪些数据库技术”),模型容易只检索到第一段提到关键词的内容,后面的关联内容根本没被召回。
我的解法是:在“语义检索”技能里直接内置“多轮召回”逻辑,一次查询会生成三个不同角度的子问题,分别检索,最后合并去重。这个设计让知识库问答的答案完整性提升很大。
4.3 技能拆解对Prompt设计的影响
做了技能体系以后,Prompt设计思路也会跟着变。以前是一份“做什么都行”的超级提示词,现在是“最小化调度提示词+技能描述库”。
调度提示词要做的事只有:
- 基于候选技能列表,选择最合适的一个。
- 按技能的Schema填充参数。
- 整理技能执行结果为最终回答。
调度提示词越薄,模型每次请求消耗的token越少,响应速度越快,准确率反而越高。
5. 关于技能扩展、性能优化和维护成本
5.1 如何设计可扩展的技能接口
技能体系的优势在于可扩展,但前提是接口必须统一。我定过一套内部规范,所有技能执行入口返回的结构必须长这样:
{ "success": true, "data": {...}, "error": "", "trace_id": "xx-xx-xx" }可以说这是最容易被忽略却最重要的一条规范。如果没有统一的返回结构,编排层就得给每个技能写适配器,技能一多就乱成一锅粥。统一结构之后,新技能接入只需要注册描述和Schema,编排层一个字节都不用改。
5.2 控制token消耗与延迟的关键策略
技能体系虽然稳定,但成本确实比裸调API高一些,因为每次请求要携带技能描述和候选列表。我的优化手段有三个:
- 技能描述压缩,用一句话说清楚“是什么+什么时候用”,能控制在50个字以内。
- 候选技能预筛选前置,模型实际只需要看5个候选,而不是全部30个。
- 结果缓存,对高频且参数稳定的请求,比如“查今天日程”,直接命中缓存返回。
实测中,这三招加起来能让单次请求成本降低约38%。
5.3 日志可观测性的重要性
Agent项目的排障比传统后端项目难很多,因为错误发生在“模型决策”那一层,而不是代码执行那一层。所以我在日志设计上要求:每次技能调用都要记录trace_id、技能命中前的候选列表、每个候选的得分、最终选中的技能、参数解析结果、执行返回结果、响应耗时。
这套日志体系帮我在排障时省了大量时间。有一次用户反馈“助手总是不记得我上次说的项目名”,我看日志发现,虽然参数解析结果正确,但会话上下文里的历史信息在组装时被截断了。没有trace日志,这个问题根本无从定位。
5.4 技能迭代时容易踩的坑
技能体系上线后,迭代是常态。常见的坑有三个,分享出来希望能帮到后来者:
- 只改技能描述,不改样本集,导致回归时漏测。
- 新增技能时,没考虑跟已有技能的边界,导致模型在新旧技能间摇摆。
- 上线后没有灰度,一次性全量,出问题影响面太大。
每一项我都亲身体会过教训,现在每一条都被写进了团队开发规范。技能体系和写代码有一点相通:越往后越考验工程纪律,而不是单点的模型调优能力。
6. 常见故障排查与调优实战记录
6.1 技能误触发的归因方法
误触发是我收到最多的一类线上反馈。排查时,我会按“先看路由日志,再看参数解析,最后看上下文”的顺序定位问题。
有一回用户问“那个项目下周截止吗”,系统错误地触发了“查日程”技能,其实应该走“项目进度查询”。看日志发现是预筛选阶段,“下周”这个时间词命中了日程类关键词,候选列表里混入了错误技能,模型也就跟着做出了错误选择。修复方法是在时间词命中后增加一道“主语类型判断”——主语是人/日历事件,才进日程技能;主语是项目/任务,进项目查询。
这种问题靠调Prompt很难根治,因为根因在预筛选规则的优先级上。
6.2 参数抽取不准的针对性优化
参数抽取的常见问题有三个:缺失、冗余、格式错误。我的经验是缺参时不要直接让技能执行失败,而是触发一次澄清;冗余参数增加校验逻辑;格式错误则用规则层做二次清洗。
举一个实际修过的案例:用户说“提醒我明天早上九点给王总发邮件”,技能是“定时提醒创建”,入参需要time和action。第一次参数解析把“王总”误判为action的一部分,导致邮件动作内容变成了“给王总发邮件”。我调整了Schema内容,在action描述里写明“不要包含收件人名字”,并加了实体识别规则,问题就解决了。
6.3 技能响应过慢的处理经验
技能响应慢的根因一般不在技能本身,而在上游。有一次排查“语义检索”技能,发现慢在调用向量数据库前还额外做了一次全文检索,数据量一大就超时。绕过去以后,单次检索从1.8秒降到0.6秒。这里想说的是:Agent项目的性能分析,要敢于把“技能执行”拆到最底层,而不是笼统归咎于“大模型太慢”。
6.4 技能改进的整体调优路径
做技能调优时,我走的路径很固定。每个月做一次样本集复盘,把线上最近500条日志拉出来,人工标注技能命中情况和参数抽取情况,找出错误集中点,再针对单一技能做描述或规则层修正。修正后全量回测,用数据说话,而不是凭感觉改。
调优这套工作流跑顺以后,技能库的准确率会是稳步上升的曲线,而不是每天修修补补的混乱状态。
7. 复盘与建议:给做Agent技能体系的几点经验
7.1 三条最值钱的经验
回顾这段实操经历,有三条经验最想说给后来者听:
- 技能设计先于模型选型,很多人先定了模型再想技能,但技能抽象能力直接决定模型能发挥多少。
- 调度层要和执行层解耦,调度层管选择与编排,执行层管业务逻辑,两端独立演进,才不会互相拖累。
- 样本集是技能体系的灵魂,没有持续维护的测试集,一切调整都可能让系统变得更差而不是更好。
7.2 适合快速启动的最低成本方案
如果你想在自己的项目里快速体验技能体系带来的变化,不用先搭完整框架。我推荐的做法是先找三个高频场景,手写三个技能,每个技能只包含描述和返回结构,然后改造你的调度提示词,让模型先做“选技能”这一步。跑一周,对比一下准确率和排障效率,你会立刻感受到差距。
搭这套最小闭环,一天时间就够了,成本极低,收益却很直观。技能体系从来不是只有大厂才用得上的技术,它本质上是一套做工程的思考方式,把复杂场景拆好、定好边界、配好兜底,Agent的能力就能稳定释放出来。
我个人在实际项目里的感受是,做Agent技能设计最迷人的地方不在于调模型调得多好,而在于把一个庞大模糊的需求,不断拆细、拆清晰,最后变成一整套可靠的小模块。后续如果你的场景涉及多模态数据、任务型对话或者复杂工作流编排,这套技能方法论可以顺畅地迁移过去,值得持续投入。