
我们团队最近在重构智能体核心把原先那堆乱七八糟的tool调用彻底梳理了一遍最终沉淀下来的东西就叫“agent-skills”。这名字看起来简单背后却是一整套关于技能定义、注册、编排、调用和排查的实践。如果你也在做AI Agent相关开发或者正准备给大模型接入工具能力这篇文章应该能让你少踩不少坑。1. 技能Skill到底解决了什么问题1.1 为什么突然都在聊技能抽象先说说这个概念的由来。大模型本身只会“说话”要让它干活必须给它接上外部能力。早期大家习惯把这层能力叫做tool也就是工具。一个函数、一个API、一个命令行脚本注册给模型去调。这种方式在Demo阶段没毛病但一旦项目进入生产问题就来了工具数量从十几个涨到上百个模型根本不知道该选哪个就算选对了参数格式、调用逻辑、结果解析也经常出幺蛾子。Skill这个概念的提出本质上不是发明了新东西而是把“工具”这一层做了更细、更规范的拆分。一个Skill可以理解为“一个完整的能力单元”它不仅包含可执行的函数还包含模型的调用意图描述、参数约束、触发条件、前置依赖甚至是后续结果的标准化处理。换句话说tool是零件skill是组装好的模块模型只管按需插拔不用关心模块内部怎么运转。这个概念之所以在业界被反复提及是因为它解决了一个核心痛点模型对工具的理解依赖描述文本而描述文本写得再长也很难覆盖所有边界情况。Skill通过结构化的元数据把这个边界问题固定了下来。比如模型需要查天气传统tool方案会给一个get_weather(城市, 日期)的函数模型会去猜参数是传“北京”还是“beijing”日期用什么格式。Skill方案则会把参数格式、枚举值、甚至常见错误示范都写进描述里让模型从“猜”变成“查”准确率自然就上去了。1.2 技能和工作流、工具的区别很多人容易把Skill和Workflow搞混。我在项目里也踩过这个边界。简单区分一下工具是单一函数Skill是单任务的完整闭环而Workflow是多Skill的编排组合。举个例子。一个发邮件的功能底层就是send_email这个工具函数。Skill层则把“写邮件并发送”整个流程封装起来包括收件人格式校验、正文模板处理、附件路径确认、发送结果回执解析。而Workflow可能是“每天早上9点自动整理昨日报表汇总成邮件发给出差同事”这种跨多个Skill的组合逻辑。区分这层关系很重要因为建模方式完全不同。如果把Skill当工具写容易把复杂度堆到模型身上让它去处理太多分支逻辑。如果把Skill当Workflow写又容易过度设计一个简单的查询功能也要走编排引擎延迟和成本都受不了。我在设计技能系统时采取了一个相对折中的原则能用单个函数说清楚的不拆成Skill能用一个Skill解决的不上升到Workflow。只有在需要多轮决策、跨系统调用或者状态管理时才引入编排层。这个原则帮我挡掉了很多不必要的复杂度。2. 技能系统的核心设计拆解2.1 技能的基本结构声明、逻辑、校验一个规范的Skill至少要包含四层结构。这四层缺一不可否则后面做技能编排时一定会返工。第一层是技能声明。它定义了技能的元数据包括技能名称、用途描述、适用场景、入参出参格式、错误码定义等。声明是给大模型看的模型决定是否调用一个技能完全依赖这段声明写得是否清晰。一个好的声明应当包含三部分技能能做什么、技能不能做什么、调用的必要条件。第二层是执行逻辑。就是实际运行的代码或API调用。这层没什么玄学但要注意一点技能内部的实现质量直接影响模型的信任度。如果模型多次调用某个技能都返回错误或超时它会倾向于不再使用这个技能转而编造答案。第三层是输入输出校验。这是很多团队忽略的部分。模型生成的参数往往会有格式偏差比如日期格式五花八门、字符串带了空格、数组只有单一元素。技能层需要在入口和出口都做严格校验入口校验不通过时应该返回清晰、可修正的错误提示而不是简单抛一场异常。第四层是反馈与迭代。每个技能调用后都要记录日志包括调用时间、输入参数、输出结果、模型反馈等。这些数据会直接决定你后续如何优化技能描述和实现。2.2 技能注册与发现机制技能系统的另一个关键点是注册与发现。在Agent启动时它会加载所有可用技能的声明构建一个技能列表。这个列表会随Prompt一起注入给模型模型据此决定调用哪个技能。一个常见的问题是技能多了以后Prompt会变得特别长模型的注意力容易被稀释。业内常用方案是分层技能发现高频技能常驻Prompt低频技能通过“技能检索”按需注入。我在项目里采用了一种更轻量的做法——技能描述摘要索引。系统维护一个小型索引表模型先看索引再根据相关性去加载完整描述。实测下来技能数量在五十个以内时直接用全量声明问题不大超过五十个就必须上动态加载了。技能注册的时机也很重要。建议在Agent启动阶段一次性注册完毕运行期间不要频繁变更技能列表否则模型对技能的记忆会混乱。如果确实需要热更新我会先停用旧技能再注册新技能并在下一次会话生效避免在同一个会话中来回横跳。3. 技能定义与实现的实战细节3.1 技能描述怎么写模型才爱用写技能描述是我觉得最考验功力的一件事。它不是写文档更像是写给AI看的“使用说明书”目的只有一个让模型在合适的时机、以合适的参数调用它。来看一个反例。假设我们有一个查询商品库存的技能描述如果写成“查询商品库存”模型确实会在用户问库存时调用但它不知道哪些商品可以查、库存返回的是什么状态码、缺货时该怎么办。真实场景下用户可能问的是“这款还有货吗”也可能问的是“补货周期多长”同一个技能不同问法对参数的覆盖完全不同。我总结了一套技能描述模板按这个结构来写模型的调用准确率提升特别明显技能名称query_stock 功能描述查询指定商品的当前库存状态 适用场景用户询问商品是否有货、库存数量、补货时间 不适场景用户询问商品价格、售后政策 入参格式 - product_id: 商品ID字符串必填 - warehouse_id: 仓库ID字符串选填默认值all 返回格式JSON对象包含库存数量、状态枚举in_stock/out_of_stock/low_stock 错误示例product_id为空时返回401错误码提示“请提供商品ID”这段描述里有几个关键点。功能描述要精炼能让人和模型都快速理解。适用场景和不适场景是对齐预期的关键模型可以据此排除误调用。入参格式要给出类型、必填性、默认值最好连格式示例都给出来。错误示例是低成本纠错手段让模型在参数不完整时主动询问用户而不是硬调。我试过纯写功能描述和带场景约束的完整描述同一个模型在高约束描述下的调用准确率能提升百分之四五十。这是因为模型在不确定时会倾向于选择约束更明确的选项。3.2 一个清晰的技能实现模板代码技能的实现代码没有金标准但一个清晰、规范的模板能省很多调试时间。我用Python写了一个通用模板核心逻辑是“声明、执行、解析、反馈”四段式。from pydantic import BaseModel from typing import Optional, Dict, Any # 1. 声明段定义技能入参模型和返回模型 class StockQueryParams(BaseModel): product_id: str warehouse_id: Optional[str] all check_delivery: Optional[bool] False class StockResult(BaseModel): product_id: str quantity: int status: str # in_stock / out_of_stock / low_stock estimated_replenish_days: Optional[int] None # 2. 执行段核心逻辑内部异常必须捕获并转成可读错误 def execute_stock_query(params: StockQueryParams) - Dict[str, Any]: try: no params.product_id.strip() if not no: return {error_code: 401, error_msg: 请提供商品ID} # 模拟库存查询 result {product_id: no, quantity: 62, status: in_stock} return result except Exception as e: return {error_code: 500, error_msg: f内部执行异常: {str(e)}}这段代码里有个容易被忽略的设计所有异常都在技能内部捕获并转成结构化错误码而不是抛给上层模型。模型只认文字和结构不认堆栈信息。如果你的技能直接把Python异常扔给模型模型大概率会胡说八道对排查没有帮助。3.3 参数约束与默认值的取舍参数约束是技能实现中最容易走向极端的部分。约束太死模型一遇到边界情况就不知道怎么办约束太松模型什么乱七八糟的参数都敢传。我的经验是给参数分层处理。必填参数只保留真正必要的比如查库存必须有product_id日期类查询必须有时间范围。可选参数根据业务场景设定默认值而默认值的选择要考虑模型调用的认知负担。比如日期查询用户说“查下周的排期”模型如果不知道“下周”是几号可选参数如果设置了默认值today那模型可能直接传今天导致查询结果完全错误。这种情况我更倾向于不设默认值让模型向用户追问具体日期而不是擅自假设。另一个经验是枚举约束要放在描述里不要只写在代码里。模型看到描述中的枚举列表比看到报错提示更容易提前规避问题。比如状态字段只支持“in_stock”和“out_of_stock”描述里写明模型生成的参数几乎不会错。4. 技能编排与调度如何让模型会用你的技能4.1 单技能调用与多技能协作技能到了一定量级就不能只做单点调用了必须考虑多技能协作。Agent的核心竞争力在于能像人一样组合技能完成任务。举个例子。用户说“把上周的销售数据和这周对比一下给我出一份简报”。这个需求至少涉及三个技能查询销售数据、查询本周数据、生成简报。三个技能按顺序执行前一个的输出要作为后一个的输入。多技能协作有两种实现路径。一种是预先定义好编排逻辑也就是工作流把技能按固定顺序串联。另一种是让模型自主决定调哪些技能、按什么顺序调这就是模型编排。我在早期项目里迷信模型自主编排结果发现模型经常会在简单任务上绕圈明明一个技能能搞定的事非要在三个技能之间来回切换超时率飙升。后来改成“关键路径固定编排 边缘路径模型自主决策”的混合模式执行效率和稳定性都好了很多。4.2 让模型自己选技能函数调用的温度与依赖让模型自己选技能本质上是给模型提供技能列表调用规则上下文温度参数和依赖管理是关键。温度设置上技能选择阶段建议比对话生成阶段略微调低。温度过高模型可能跳过明确的技能调用请求转而用自身知识硬答。我一般把技能选择相关的温度控制在0.1到0.3之间让模型尽可能确定性输出函数调用而不是发挥创造力。依赖管理方面一个技能内部可能依赖其它技能的结果。例如生成简报需要先拿到数据查询的结果。这时应该把技能设计为可组合的前一个技能的输出结构直接兼容后一个技能的输入结构避免在编排层做大量胶水代码。4.3 编排层如何避免技能冲突与死循环技能冲突是编排层最头疼的问题。多个技能都可能处理同一个用户请求时模型的随机性可能让这次选A、下次选B结果不稳定。我的应对方式是在编排层维护一个技能决策表把互斥条件写清楚。比如查询天气和查询空气质量看起来都是天气类请求但如果用户问“今天能适合跑步吗”这两个技能都有关系。这时候不应让模型二选一而是设计一个更上层的“运动建议”技能内部组合查询结果输出整合后的建议。死循环问题也不容忽视。模型可能反复调用同一个技能每次都因为参数不合法而失败。我通常在编排层设置调用深度上限和同技能连续调用上限达到上限后强制模型切换思路或直接向用户求助。这个兜底非常重要不然一个坏技能就能拖垮整个Agent会话。5. 常见问题与排查技巧实录5.1 模型总是选错技能怎么办这是被问得最多的一个问题。模型不调用你预想的技能不代表模型不行大概率是技能描述和参数设计出了问题。先排查技能名称和描述与用户真实意图是否匹配。模型是语义匹配的如果你的技能名叫“get_stock”而你在描述里只写“查询库存”当用户说“还有货吗”时模型的语义匹配可能优先命中“stock”这个词哪怕你描述里写了很多“库存”效果也不一定好。更好的做法是把用户可能问的自然语言问法直接写在描述里。比如适用场景用户问“有货吗”、“还库存吗”、“能发几天内发货”等和现货状态相关的问题时实测下来这种方式比抽象描述命中率高得多。另外可以检查一下技能在列表里的排序位置模型倾向于选择排在前面的技能把高频技能排在前面是低成本优化。5.2 技能返回结果容易解析失败技能执行成功不等于Agent调用成功。返回结果如果格式不标准模型在解析时就会出错导致用户看到的是“模型已调用但结果不对”的灵异现象。一种常见情况是技能返回了带单位或带格式的数字比如“62件”模型需要转成纯数字才能后续计算。我的建议是技能返回的结构要尽可能标准化为程序可读格式展示层的格式化放到最后一步。如果第一版输出就带上了人类可读的单位后续做聚合计算时还得先做清洗很容易埋雷。另外返回结构的字段命名要自解释。比如返回status: ok就不如status: in_stock直观。前者是程序状态后者是业务状态模型在后处理时更容易理解。5.3 技能调用超时与链路追踪技能调用超时在复杂编排里非常常见单个技能超过几秒钟整个链路就会堆积模型还会因为等待超时而重复调用。解决超时问题要从两个角度入手。一是技能本身要设置超时控制二是编排层要对整体链路做超时预算。我一般把单个技能的超时控制在两秒以内整个编排链路控制在五秒左右超过就直接降级返回部分结果而不是无限等待。链路追踪极其重要。每次技能调用我都要记录调用链ID包含技能名称、输入摘要、输出摘要、耗时、错误码。当用户反馈“结果不对”时我能直接查到是哪个环节出了问题。这个习惯帮我节省了数不清的排查时间。5.4 建议补充一个技能冲突排查表结合近半年的实际踩坑整理了一份技能冲突排查速查表问题现象可能原因排查方法解决方案模型调用了A技能但用户需要B能力技能描述语义覆盖不足对比用户问法与技能描述在描述中补充自然语言问法两个技能都能处理同类型问题技能范围重叠列出互斥条件设计上层技能或调整描述边界模型反复调用同一技能失败参数约束与描述不一致检查入参描述与代码校验统一描述与实际校验逻辑编排链路执行顺序错乱依赖关系不明确查询调用链日志预先固定关键路径顺序长技能描述让模型混淆描述冗余无重点精简描述突出核心字段使用描述模板压缩信息密度这张表我贴在办公位前每个新技能的评审都会过一遍这些检查项。技能系统的稳定性就是这样一点点抠出来的。6. 一些让我额外收益的配置细节6.1 用持久化记忆给技能叠加“经验buff”我后来做了一个优化每次Agent成功解决一个复杂请求后把这次的技能组合方式和参数配置整理成“经验片段”存到记忆库。下次遇到类似请求时系统会把这段经验注入到Prompt中。效果非常明显。比如第一次用户让Agent整理周报Agent花了几轮才搞清楚要拉哪些数据、怎么排版。这个经验记住之后后续遇到同类请求几乎一次就能完成。本质上是在给技能系统叠加一层上下文增强让Agent越用越顺手。6.2 定期对技能做“瘦身”与合并技能系统运行一段时间后会产生大量低质量或过时技能。我的建议是每两周做一次技能审计。优先级排序是调用次数从高到低排列调用全失败的优先修复调用次数极少且与其它技能高度重合的优先合并。不要怕删除技能。技能多了Prompt会变长模型选择成本会变高最终调用准确率反而下降。少而精的十五个技能比杂乱无章的五十个技能好用得多。7. 不同场景下技能设计的侧重点7.1 客服问答场景重校验、轻编排客服问答类Agent对技能准确率要求极高用户问法千奇百怪技能设计重点是参数校验和场景覆盖。这种场景下技能数量不求多但要保证每个技能的描述清晰、边界明确。误调用一个技能比不调用更糟糕因为用户会得到一个看似合理但完全错误的答案。我倾向于把客服场景的技能设计成大颗粒度一个复杂的工单查询技能而不是拆成十个细碎的小技能。模型面对的选择越少出错概率就越低。7.2 数据查询场景重结构、轻描述数据查询类技能很容易出现“模型理解了意图但参数传错”的情况。这类技能的描述可以稍微精简但数据结构设计必须严谨。返回值要标准化、可计算、可聚合字段命名要自解释内部异常要转成结构化错误码。数据查询场景我还会特别关注权限问题。技能层必须内置数据权限校验不能把权限判断完全交给模型。7.3 多步骤实操场景重编排、组合胜于堆叠多步骤实操场景比如“帮我下单买咖啡并安排配送”中间涉及选品、支付、配送地址等多个环节。这种场景必须用编排层来固定关键路径不能指望模型每次都能随机应变地组合技能。我的经验是把所有必须按顺序执行的步骤固化成Workflow只有可选的操作才暴露给模型自主决策。这样既能保证核心路径不出错又保留了对用户特殊需求的弹性。8. 写在最后的个人体会做Agent技能系统这半年最大的体会是技能设计不是一锤子买卖而是一个持续打磨的过程。模型在变、场景在变、用户问法也在变技能系统必须跟着迭代。我养成的一个习惯是每次用户反馈结果不好的时候第一反应不是骂模型而是先去看技能调用日志十次里有七次问题出在技能定义或参数约束上。另一个特别想分享的经验是控制技能数量。很多人喜欢把能想到的功能都堆进去觉得技能多代表能力强。但模型的能力边界是有限的过多的选择会让模型决策质量急剧下降。少而精、覆盖面准才是技能系统长期稳定的关键。如果你正在做Agent开发我建议你从五个以内的小技能入手把调用链路完整跑通再把技能数量慢慢加上去。过程中多积累描述优化的经验慢慢你就能感受到Agent好不好用很多时候真的就取决于那几行看似不起眼的技能描述。最后再分享一个小技巧给每个技能接一个简单的使用统计面板记录调用次数、平均耗时、失败率。这个面板的数据会比你想象的更早帮你发现问题。根据我个人项目经验一个技能如果连续一周调用失败率超过百分之十五大概率是该优化或下线了。