
最近半年我几乎每天都在跟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 Schemaprompt_template指导模型如何执行这个技能的额外指令包括数据处理的规则、回复的格式要求examples一到三个输入输出示例用于few-shot引导fallback技能执行失败时的兜底策略这个结构看起来简单但实际写起来有很多讲究。后面我会分章节详细说每个字段怎么设计。2. 从业务需求到技能清单拆解任务的完整方法论很多人拿到需求就急着写代码结果技能建了一堆真正常用的没几个不常用的永远没人调用。我在第二个Agent项目里就遇到了这个问题最后花了很大力气做减法。现在我做技能设计一定会先走一套完整的拆解流程。2.1 使用RAS方法梳理任务集RAS是我一直在用的方法全称是Routine Action Script就是把一个复杂任务拆成步骤化的行为脚本。它借鉴了认知科学里程序性记忆的概念——人类专家做熟练工作是不需要思考的因为他们把整个流程内化成了肌肉记忆。Agent技能的目标就是让模型对这些高频任务也形成类似的肌肉记忆。具体做法分三步列出你希望Agent完成的所有终端任务比如根据用户问题生成SQL查询、将查询结果渲染成图表、检查数据质量并生成报告。对每个终端任务拆解成若干原子操作序列每个原子操作只做一件事单独对应一个技能或者技能内部的一个步骤。把原子操作归类去重合并共性操作形成技能清单。拿一个内部知识库问答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_exceptionsTrue) 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_exceptionsTrue保证某个技能挂了不会拖垮整条链路。3.4 模型带工具的思维链编排的补充形态结构化编排之外模型自主规划仍然有存在价值只是要控制它的使用范围。我通常把模型自主调用限制在场景不固定的探索型任务里比如根据用户模糊需求找数据、做分析、给建议这种任务没法预先定义流程只能让模型自己规划。为了让模型规划得更好一点我会特意在每个技能的description里写清楚前置条件和调用结果比如skill_name: search_web, description: 用于搜索公开网页信息。当前置技能extract_keywords执行完毕后本技能接收关键词列表并逐条搜索返回包含标题、链接、摘要的结果列表。这样做比只写搜索网页四个字强得多——模型能看到技能之间的连接关系规划的路径更合理。这个经验来自我的实测描述里加了前置条件与调用结果之后模型多步规划的准确率提升了约12%。4. 技能描述与参数定义决定模型调用成败的细节很多人以为技能定义就是把函数签名丢给模型实际上模型对description和parameters的敏感度远超想象。这一段说的全是踩坑总结出来的规律。4.1 描述怎么写模型才不掉链子写技能描述有个核心原则用模型的语言而不是程序员的语言。模型理解自然语言的能力极强但理解抽象符号的能力很差。所以描述里面要多写场景、多写意图、少写技术实现。比如一个搜索技能的描述我见过两个版本。版本ASearches 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技能设计到现在我最大的体会是真正难的不是让模型理解任务而是把你的业务经验转化成模型能执行、能复用、能兜底的那套技能系统——这背后是工程是方法论更是对业务场景的深度理解。希望这篇内容对你做自己的技能体系能有一些真正的帮助。