☰
Agent技能全链路解析:从工具、插件到编排的工程实践
2026/10/28 16:26:06 网站建设 项目流程

最近半年我几乎每天都在跟Agent打交道,最深的体感是:很多人把Agent理解成一个会对话的机器人,但真正让Agent产生价值的,是它背后那套技能系统——也就是标题里这个agent-skills要讨论的东西。

简单说,技能是Agent能力的"外挂模块"。没有技能的Agent只是一个空壳聊天框,有了技能,它才能查数据、调接口、操作软件、完成真实世界的任务。这篇文章我会把技能的定义、设计方法、落地实现、评测优化全链路讲一遍,适合正在做Agent应用、或者准备把LLM接入业务系统的开发者参考,也适合团队Leader用来评估技术方案。

1. 先搞清楚:Agent技能到底是什么,它和工具、插件的区别在哪

很多项目里,大家会把"技能"、"工具"、"插件"这三个词混着用,但在工程落地上,它们其实是三个层级的东西。我刚接触这个领域的时候也踩过这个坑,以为注册几个function calling就完事了,结果做出来的Agent换个场景就失灵。

1.1 工具、插件、技能的分层关系

工具(Tool)是最底层的原子能力,比如"发送HTTP请求"、"读取文件"、"执行Python代码",它只做一件事,没有业务语义。插件(Plugin)是把一组相关的工具打包,比如一个"天气插件"可能包含查当前天气、查未来三天预报、查空气质量三个工具。而技能(Skill)是在插件之上再加一层:它不光包含工具,还包含使用工具的策略——什么情况下调哪个工具、参数怎么填、结果如何解析、异常怎么兜底。

用生活化类比来说:工具是螺丝刀,插件是一整套工具箱,技能则是一个熟练工。熟练工知道拧不同位置的螺丝该用哪把刀、使多大劲、拧完怎么检查。这个"知道"就是技能的核心,它不是代码写死的,而是通过描述、示例、约束条件让模型学会的。

1.2 为什么说技能是Agent能力的边界

LLM本身不持久、不精确、不可控,它擅长的是理解和生成,不是计算和记忆。技能的存在就是为了补齐这个短板。设计得好的技能,能把不稳定的模型行为收敛到稳定路径上。

举一个实际例子:我做过一个财务分析Agent。如果让它直接对接财务系统API,模型经常会把net_income和revenue搞混,甚至自己编一个不存在的字段。但后来我们把"获取利润表"做成了一个技能,技能描述里明确写了字段含义、单位、汇率规则、以及数据缺失时的兜底策略,模型的表现立刻稳定了很多——因为它不需要"思考"该怎么查,只需要按技能里描述的方式执行。

这引出一个关键认知:Agent的能力上限 = 技能的质量 × 模型的调度能力。模型负责"在合适的时机调用合适的技能",技能负责"把模型的想法变成可靠的现实操作"。两者缺一不可。

1.3 一个技能应该长什么样

我在项目中会把每个技能定义成一个标准的结构,包含以下字段:

  • name:技能的唯一标识,英文小写加下划线
  • description:一段自然语言描述,说明这个技能能做什么、适合什么场景、不适合什么场景
  • parameters:调用参数的定义,包括类型、必填性、取值约束,直接复用JSON Schema
  • prompt_template:指导模型如何执行这个技能的额外指令,包括数据处理的规则、回复的格式要求
  • examples:一到三个输入输出示例,用于few-shot引导
  • fallback:技能执行失败时的兜底策略

这个结构看起来简单,但实际写起来有很多讲究。后面我会分章节详细说每个字段怎么设计。

2. 从业务需求到技能清单:拆解任务的完整方法论

很多人拿到需求就急着写代码,结果技能建了一堆,真正常用的没几个,不常用的永远没人调用。我在第二个Agent项目里就遇到了这个问题,最后花了很大力气做减法。现在我做技能设计,一定会先走一套完整的拆解流程。

2.1 使用RAS方法梳理任务集

RAS是我一直在用的方法,全称是Routine Action Script,就是把一个复杂任务拆成步骤化的行为脚本。它借鉴了认知科学里"程序性记忆"的概念——人类专家做熟练工作是不需要思考的,因为他们把整个流程内化成了肌肉记忆。Agent技能的目标,就是让模型对这些高频任务也形成类似的"肌肉记忆"。

具体做法分三步:

  1. 列出你希望Agent完成的所有终端任务,比如"根据用户问题生成SQL查询"、"将查询结果渲染成图表"、"检查数据质量并生成报告"。
  2. 对每个终端任务,拆解成若干原子操作序列,每个原子操作只做一件事,单独对应一个技能或者技能内部的一个步骤。
  3. 把原子操作归类去重,合并共性操作,形成技能清单。

拿一个内部知识库问答Agent举例。终端任务看起来只有一个:"回答用户关于公司制度的问题",但拆完之后其实是四个原子操作:R1检索相关文档、R2抽取关键条文、R3比对用户提问与条文的匹配度、R4组织语言生成回答。对应的技能就需要至少两个:一个文档检索技能,一个答案合成技能。

2.2 用任务树验证技能边界

拆解完之后,我会把所有技能画成一张任务树,验证两个问题:每个叶子节点是不是都有技能覆盖?有没有技能覆盖了多个不相关的叶子节点?前者是查漏,后者是查重。

一个常见的错误是,为了让Agent"更全能",给每个叶子节点都建一个独立技能,结果技能数量爆炸,模型在选择技能时的准确率直线下降。模型本质上是靠文本匹配来做工具选择的,技能越多,描述之间的相似度越高,选错的概率就越大。我测试过,10个技能以内,选择的准确率可以到95%以上;技能超过30个,准确率会跌到80%以下,这个衰减速度很惊人。

所以我的建议是:技能粒度控制在"一个技能完成一个完整交付物"的水平,宁可内部多写几个步骤,也不要拆成过细的原子操作。比如"生成周报"是一个技能,而不是"读取数据"、"分析趋势"、"生成文件"三个技能。

2.3 识别高频与低频技能,决定优先级

任务拆解完,还需要给技能排优先级。判断标准就两个:调用频次和失败代价。

高频且低失败代价的技能,比如"关键词提取"、"文本分类",可以先用最简单的方式实现,甚至直接用模型原生能力替代,不一定要做成独立的技能模块。低频但高失败代价的技能,比如"生成花式报销单"、"操作ERP系统",虽然用得少,但一旦出错影响很大,必须做重,包括详细描述、兜底策略、甚至人工审批环节。

我在做客服Agent时,发现"查询订单状态"和"修改收货地址"这两个技能调用频次极高,于是专门做了性能优化,包括预加载数据、缓存热点查询;而"申请发票"调用少,但涉及资金合规,就加了二次确认和人工审核的兜底。优先级不同,设计投入就不同,这是工程思维的体现。

3. 技能编排的正确姿势:顺序、并行与条件分支的实战取舍

技能定义好了,接下来是关键问题:多个技能之间怎么组织?这就涉及到Agent的编排逻辑。业界目前有两条路线:一是纯粹靠大模型自由发挥(Plan and Execute),二是用预先定义的工作流强行约束(Workflow)。我两种都实践过,各有适用场景。

3.1 纯模型编排为什么不可靠

纯模型编排的方案,就是你把技能列表丢给模型,让它自己决定调哪个、按什么顺序调。这种方案的优点是灵活,能处理大量不可预见的用户需求;缺点是不稳定,面对同一个任务,模型在不同次对话里可能走完全不同的路径。

我在测试一个数据分析Agent时做过对比:同一个问题"对比华东和华南区上季度的销售趋势",十次运行中,模型有六次先查销售总额再查区域明细,有三次先查区域明细再查总额,还有一次直接把两个查询拼成了一个。路径不一致导致的结果是,用户拿到回复的格式、信息维度每次都不一样,这在B端场景里是完全不可接受的。

3.2 结构化编排:把控制流写进声明式配置

为了解决稳定性问题,我现在的主力方案是结构化编排——把技能的触发条件、执行顺序、分支逻辑用JSON或YAML描述出来,模型只做决策点判断,不做全局规划。下面是之前项目的用户意图识别配置:

{ "intent_classifier": { "description": "判断用户当前意图属于哪个技能链路", "skills_flow": { "query_order": { "if": "用户意图为查询订单状态", "then": ["parse_user_info", "query_order_db"], "fallback": "ask_user_confirm" }, "modify_address": { "if": "用户意图为修改收货地址", "then": ["verify_identity", "parse_new_address", "update_address_db"], "fallback": "notify_human_service" }, "open_ticket": { "if": "用户意图为提交售后工单", "then": ["collect_order_id", "collect_reason", "create_ticket"], "fallback": "append_missing_info" } } } }

这段配置表达的意思是:先让模型做一个简单的意图分类,然后根据分类结果走固定的技能链路。链路的中间步骤是确定的,模型只需要在进入链路前做一次判断,大大减少了自由发挥的空间。

3.3 并行调用与分支合并的最佳实践

并非所有编排都要线性走,实际业务里场景可以细分为顺序执行、并行执行和条件分支三类。

顺序执行的场景最典型,前一个技能的输出是后一个技能的输入,比如"解析用户问题→生成SQL→查询数据库→渲染答案"是一条依赖链。并行执行的场景,是多个技能互不依赖且需要同时取数,比如生成一份综合报告,需要同时调用销售数据技能、库存数据技能、配送数据技能,并行发起三个异步调用再合并结果,能大幅缩短响应时间。条件分支则用于"如果X则走A,否则走B"的场景,比如判断订单是否逾期,逾期走催收链路,未逾期走正常通知链路。

在实现并行时,我经常用一个简单的Python并发代码来调度:

import asyncio from agent.skill import SkillManager async def run_parallel_skills(skill_names, **context): manager = SkillManager(context) tasks = [manager.execute(name) for name in skill_names] results = await asyncio.gather(*tasks, return_exceptions=True) merged = {} for name, result in zip(skill_names, results): if isinstance(result, Exception): merged[name] = {"status": "failed", "error": str(result)} else: merged[name] = {"status": "ok", "data": result} return merged

这个代码简单但很实用,核心是两点:一是异步并发而不是串行等待,二是用return_exceptions=True保证某个技能挂了不会拖垮整条链路。

3.4 模型带工具的思维链:编排的补充形态

结构化编排之外,模型自主规划仍然有存在价值,只是要控制它的使用范围。我通常把模型自主调用限制在场景不固定的探索型任务里,比如"根据用户模糊需求,找数据、做分析、给建议",这种任务没法预先定义流程,只能让模型自己规划。

为了让模型规划得更好一点,我会特意在每个技能的description里写清楚"前置条件"和"调用结果",比如:

"skill_name": "search_web", "description": "用于搜索公开网页信息。当前置技能extract_keywords执行完毕后,本技能接收关键词列表并逐条搜索,返回包含标题、链接、摘要的结果列表。"

这样做比只写"搜索网页"四个字强得多——模型能看到技能之间的连接关系,规划的路径更合理。这个经验来自我的实测:描述里加了前置条件与调用结果之后,模型多步规划的准确率提升了约12%。

4. 技能描述与参数定义:决定模型调用成败的细节

很多人以为技能定义就是把函数签名丢给模型,实际上模型对description和parameters的敏感度远超想象。这一段说的全是踩坑总结出来的规律。

4.1 描述怎么写,模型才不掉链子

写技能描述有个核心原则:用模型的语言,而不是程序员的语言。模型理解自然语言的能力极强,但理解抽象符号的能力很差。所以描述里面要多写场景、多写意图、少写技术实现。

比如,一个搜索技能的描述,我见过两个版本。版本A:"Searches the database and returns JSON data by SQL query."。版本B:"当用户想查询交易记录、订单状态或账户余额时使用本技能。本技能会根据用户的自然语言问题和已登录的用户ID自动生成SQL并查询数据库,返回结果为JSON数组。"

同样的底层实现,版本B被模型正确调用的概率明显更高,因为它给出了明确的使用场景。

另外,描述里最好写清楚"不能做什么",也就是负向约束。比如一个处理货币换算的技能,要在描述末尾加一句"本技能不负责汇率趋势预测,如需趋势分析请调用trend_analysis技能。"这一条非常有用,能显著减少模型的错误调用。

4.2 参数Schema的三种典型错误

参数定义是另一个重灾区。很多项目直接复用后端接口的参数定义,我就见过三个高频问题。

第一个问题是必填参数过多。模型调用一个技能时,如果发现必填参数缺失,通常会自己编一个值填上去——这一点要特别注意,模型宁可编也不会说"参数不够"。所以能设为可选的参数尽量设为可选,并给默认值。

第二个问题是枚举值描述不清。比如状态字段status枚举是[0, 1, 2],模型根本不知道哪个是"已支付"。正确的做法是给每个枚举加说明,我一般用这样的格式:

{ "status": { "type": "string", "enum": [ "PENDING", "APPROVED", "REJECTED", "CANCELED" ], "description": "审批状态。PENDING=等待审批中,APPROVED=已通过,REJECTED=已驳回,CANCELED=已取消。" } }

第三个问题是没有提供default。模型在不确定参数值时,如果有default它会倾向用默认值,没有的话它会发挥自己的"创造力"。一个保险的做法是,所有非关键参数都设置default或nullable,给模型留一条最不容易出错的路。

4.3 通过few-shot示例稳定模型输出格式

描述写得好,参数定义得严谨,模型大部分时候输出是对的,但仍然有输出格式不规范的情况。这时候就要上few-shot示例了。我的做法是在每个技能的prompt template里放两个示例,一个正常场景,一个边界场景。

举个文本摘要技能的示例写法:

技能执行示例: 输入文本:今天华为发布了新款Mate 60 Pro手机,该产品采用卫星通信技术,起售价为6999元。 输出: {"summary": "华为发布Mate 60 Pro,支持卫星通信,售价6999元。", "keywords": ["华为", "Mate 60 Pro", "卫星通信"]} 输入文本:会议纪要,讨论下季度预算,市场部建议增加线上投放,研发部反对。 输出: {"summary": "会议讨论下季度预算,市场部主张增加线上投放,研发部持反对意见。", "keywords": ["预算", "线上投放", "市场部", "研发部"]}

里面的边界示例专门选的是没有明显"文章"特征的短文本,让模型知道这个技能不仅给长文用,也给对话、纪要用。示例的价值在于它们比一百行描述都直观。

5. 技能上线后的观测与迭代:从能用到可靠的一整套闭环

技能写完、Agent能跑,这只是第一步。真正拉开差距的是后续的迭代。我维护的Agent上线三个月,迭代了十几个版本,这个过程中有一套相对成熟的观测与评估体系。

5.1 三张核心观测报表

我会对每个技能维护三张报表:调用统计报表、失败归因报表、用户反馈报表。

调用统计报表记录每个技能被调用的次数、成功率、平均耗时可观测。通过它可以快速发现两个问题:很多技能长期零调用说明设计时有冗余;某些技能调用成功率持续走低,则需要优先排查。失败归因报表需要记录失败的类型,根因分为四种:技能逻辑Bug、输入参数异常、模型路径选择错误或外部依赖超时。这四类的处理方式完全不同。用户反馈报表记录的是用户对最终回复的满意率以及投诉关键词,B端产品里用户反馈往往是最真实的晴雨表。

5.2 如何评估一次技能调用的成功还是失败

评估技能调用是否成功,不能只看返回码,还要看结果质量。我在项目里会用LLM-as-a-Judge的方式做自动化评估,让一个评估模型从完整性、准确性和格式合规三个维度给技能的输出打一个总分,低于60分的打到待优化列表里。

新技能上线前,除了跑通Happy Path,我强烈建议补一个对抗性测试:故意给Agent提带有歧义、缺参数、含陷阱的问题,看它会不会掉链子。比如"帮我把昨天那个单子改个地址"——这里面"单子"和"地址"都没说清,好技能应该在参数不足时主动追问,而不是瞎猜。

5.3 迭代技能的版本管理策略

技能迭代是有风险的,改了A场景可能影响B场景。我在项目里采用影子模式与灰度发布结合的方式。影子模式是新的技能版本与旧版本并行运行,新版本的调用结果记录但不返回给用户,攒够一定量数据后做离线对比评估。灰度发布更适合高失败代价场景,比如财务相关的技能,先放量给内部用户,观察一两周再全量。针对每次技能变更,记录变更点的规则是:描述改了哪一句话、参数加了哪个字段、示例换成了什么,并在下一次迭代评审里复盘这些变更对调度准确率和输出质量的影响。

5.4 快速反馈回路的工程化

最后是我自己总结的一个经验:给Agent建立"技能运行记录"的存储机制。我通常会在技能执行前后各记录一次上下文,包含输入参数、执行结果、模型当时的推理摘要和最终输出。这样当用户反馈一个Bad Case时,我可以快速回放整个过程,而不是靠猜。

这个存储不需要复杂架构,项目早期我用一个日志系统就够用,每天往里面写全量运行数据,再加一个简单的检索接口。Once回放链路走通,排查问题的效率至少提升一倍。很多团队花了大价钱上监控告警系统,但忽略了这个最基础的"可回放"能力,我是建议先把基础打牢再上系统。

6. 跨场景复用与安全边界:技能资产化的最后一步

技能做到成熟之后,我还会考虑它们能否在团队内部跨项目复用,以及这些技能是否触碰到了不该碰的边界。这两点一块说,因为它们都决定着技能的生命周期能走多远。

6.1 把技能打包成共享资产

技能本质上是一段"经验编码",当你在A项目把"生成周报"技能打磨得很成熟时,B项目大概率也需要类似能力。如果没有资产化机制,B项目的同学会重新开发一遍,走一遍你踩过的坑。

我在团队内部的做法是建了一个技能仓库,用Git管理,每个技能一个目录,里面包含技能定义文件、测试用例、使用文档和变更日志。新项目接入时,先到这个仓库里查找可复用技能,而不是从零开始。这块的收益非常大,我们团队大概做了三十几个技能之后,新项目的Agent开发周期从平均三周降到了十天以内。

6.2 识别技能的越权风险与降级方案

高权限技能是最需要被盯紧的。比如一个能操作数据库的技能,如果被模型在错误场景调用一次,可能造成数据事故。我把它视为"越权风险"并制定了降级方案。

我的做法是在技能调用链路上加一道权限拦截层,包含三层:

  • 技能级权限:整个技能是否允许当前用户调用
  • 参数级权限:技能可以调用,但某些高危参数必须经过审批
  • 操作级权限:允许调用的技能只能以只读方式执行,禁止写操作

层层拦截的效果十分明显。举一个真实的例子:我们曾经出现过一次拐点,运营同学让Agent"把上周的活动数据加到我上个月的报告里",Agent差点把月报数据库里的历史表覆盖了,幸好参数级权限拦截住了写入参数并触发人工审批,避免了一次事故。从那之后我把所有写操作类技能默认改成需要审批字段,不再用普通参数传递敏感操作。

6.3 技能过期管理与维护机制

最后一条是关于养护。业务会变,流程会变,技能绑定的业务规则也会变。如果技能描述里写的规则与当前业务不一致,模型按旧规则执行,结果是"正确但错误"——程序没Bug,但结果已经不是用户想要的了。

所以技能也要有生命周期管理。我在仓库里给每个技能状态标记为稳定、待更新与已废弃。每个迭代周期走一遍技能仓库,把调用量低、准确率下滑、规则过期的技能挑出来,该更新的更新,该合并的合并,该下架的下架。这套流程不需要太多的成本,但能防止技能库渐渐变成一堆"僵尸能力"。

做Agent技能设计到现在,我最大的体会是:真正难的不是让模型"理解"任务,而是把你的业务经验转化成模型能执行、能复用、能兜底的那套技能系统——这背后是工程,是方法论,更是对业务场景的深度理解。希望这篇内容对你做自己的技能体系能有一些真正的帮助。

需要专业的网站建设服务?

联系我们获取免费的网站建设咨询和方案报价,让我们帮助您实现业务目标

立即咨询