
前阵子同事问我agent-skills到底是套什么东西。我说你把它理解成给Agent配的技能树就成——模型负责思考技能负责干活两者一配合Agent才能从会聊天变成能办事。为了把这棵技能树种明白我把手头一个行业调研类Agent前前后后推倒重做了两遍中间踩了一堆坑也总结出一套相对完整的技能体系设计和落地方法。今天把这些内容整理出来给正在做Agent应用、对Function Calling的效果不太满意、想让Agent真正稳定产出结果的开发者做个参考。先说结论Agent的上限不在模型本身而在你给了它多少可用的手。模型再聪明如果没有一套设计良好的技能库它也只能在对话里兜圈子反过来技能库设计得足够扎实哪怕模型能力弱一档也能稳定完成复杂任务。这篇文章不聊花哨的概念全部围绕技能的定义、拆解、编排和排障展开尽量用能直接抄作业的方式写。1. 从会聊天到能干活Agent技能库到底解决什么问题1.1 大模型原生能力的三层边界大模型的强项是理解、归纳、生成弱项是确定性操作。你让它根据这五个报价单算出平均价并标注异常项模型可能给你一个看起来正确的数字但如果你用代码去算它一定是正确的。这里就涉及一个核心认知模型适合做决策和表达不适合做精确计算和动作执行。我们当时的目标很明确搭建一个能自动完成行业调研、生成分析报告的Agent。第一版只靠Prompt Engineering把所有指令都写在系统提示词里让模型自己想着完成任务。结果就是简单任务偶尔成功复杂任务经常中途跑偏。今天忘记调API明天把JSON格式写错后天干脆在报告里编造一个不存在的表格数据。如果用一个词总结第一版的体验那就是失控。后来我意识到问题不在模型而在架构。模型需要的不是更多指令而是一套能承载确定性动作的执行层。这就是agent-skills的出发点把那些可以固化的操作从模型脑子里拿出来放进可运行的代码里。1.2 Function Calling的一次性手套困境很多人会说这不就是Function Calling吗给模型定义几个函数它需要时会自己调用。确实Function Calling是Agent落地的第一步但它有两个绕不开的问题。第一它是无状态的。今天这个任务需要搜索明天那个任务也需要搜索每次都要重新定义搜索函数的参数调用完就结束了没有沉淀。函数是一次性手套用完即弃。第二不同模型对Function Calling的遵循程度差别巨大。同一个函数定义在A模型上能正确传参在B模型上就会漏参数、乱格式。这意味着每当你要切换模型或升级模型时所有函数描述都要重新调试。我并不是说Function Calling没用它是技能体系的底层载体。但如果你直接在业务代码里散落几十个Function定义维护成本会迅速爆炸。技能体系要做的是把这些散落的函数管理起来变成一个有命名、有版本、有描述、有校验、有复用机制的能力资产库。1.3 技能、工具与经验的三位一体工具、技能、经验这三个词乍一看很像其实层级完全不同。工具Tool单一能力比如发送HTTP请求读取文件执行SQL。技能Skill工具 使用场景 调参规范 输出标准化。例如搜索并提炼行业新闻要点这个技能内部包含搜索工具、去重逻辑、摘要策略对外暴露统一的输入输出协议。经验Experience技能的编排策略和迭代记录。哪些技能适合组合、哪些技能在什么情况下会翻车、不同场景下参数怎么调。agent-skills的本质就是把这三层打通底层是工具中间是标准化封装上层是调度编排。用生活化的比喻来说不是给Agent一把螺丝刀就完事而是给它一个完整的工具箱、一份使用手册、以及一本老师傅积累的维修笔记。维度纯PromptFunction Callingagent-skills技能体系稳定性低随机性强中依赖模型高代码兜底复用性差每次都写弱函数级复用强技能级复用可调试性差中高可单独测试可进化性低低高可版本迭代2. 技能拆解的三个粒度原子、复合、策略2.1 原子技能单一职责的最小执行单元原子技能是最底层的能力封装特点是单一职责、输入输出可预期、出错容易定位。比如执行HTTP GET请求读取指定路径文件发送企业微信通知这些都是典型的原子技能。我在设计原子技能时坚持三个原则。第一一个技能只做一件事绝不在一个技能里同时干抓取和解析第二参数必须完整且可枚举尽量给每个参数都提供约束条件第三返回结果必须结构化至少要包含success字段和data或error字段。原子技能虽然简单但它是整个体系的基石。如果这层没打牢上面再花哨的编排都是空中楼阁。有个小技巧原子技能的命名全部采用动词对象结构比如fetch_url、read_file、send_message这样模型在阅读技能清单时能快速建立直觉减少误调用。2.2 复合技能把固定流程固化成一条流水线复合技能解决的是多个原子技能按固定顺序协作的场景。举一个我们在调研Agent中实际用到的例子网页正文提取复合技能。它的内部流程是fetch_url抓取原始HTML然后调用HTML解析器提取主内容接着做噪声剔除去掉导航、广告、版权声明等最后把清洗后的正文转成统一格式的文本块。对外暴露的参数只需要一个url返回的是一段结构化的正文内容。复合技能的设计要点是内部流程可以固定但对外接口必须保持简单。调用方不需要关心你内部做了几步只关心给什么参数、拿什么结果。另一个要点是复合技能内部必须有明确的中间产物协议否则一旦某一步失败你很难定位到底卡在哪。我们的做法是每一步都记录执行状态和耗时如果流水线失败直接返回最后一步的日志。2.3 策略技能带决策分支的进阶封装策略技能是技能体系里最灵活也最容易失控的一层。它内部包含条件判断和分支选择根据输入数据的不同走不同的执行路径。举个例子我们做了一个行业资料收集策略技能。它接收一个行业关键词后会先判断资料类型需求如果是数据报告类走数据库查询官方统计网站采集路线如果是舆情观点类走新闻搜索社交媒体热榜收集路线如果用户没有明确指定则默认走综合路线先做一轮快速搜索再自行判断侧重。这个判断逻辑完全用代码写死不是在技能内部让模型做决策。这一点很重要技能内部尽量避免调用模型决策应该放在技能外部的编排层或者用简单的规则判断完成。2.4 技能粒度的把握太细累死调度太粗丧失灵活技能粒度怎么定是我实践中最纠结的问题。没有一个万能公式但有几个判断标准可以参考如果一个技能需要用户提供超过5个必填参数大概率是拆得太粗了考虑拆分。如果两个技能在任务中总是成对出现且顺序固定考虑合并成一个复合技能。如果某个技能内部开始出现超过两层的if-else分支考虑把决策逻辑上移到编排层。如果经常需要只用其中一部分能力而被迫传一堆空参数说明颗粒度不对需要拆细。经验法则很简单先用最直觉的粒度搭一版跑真实任务然后根据失败日志反推调整粒度。不要试图一步到位设计出完美的技能树这不现实技能体系是迭代出来的不是设计出来的。3. 一个可落地的Skill定义从JSON Schema到执行器3.1 Skill四要素描述、参数协议、执行器、输出协议一个标准技能包含四个部分技能描述、参数协议、执行器、输出协议。四者缺一不可但最容易被人忽视的是前后两端——技能描述和输出协议。技能描述是给模型看的它直接决定了模型在什么场景下会选择这个技能。我们的经验是描述要写清楚三件事——这个技能在什么场景下用、接受什么关键参数、参数应该怎么填。说得直白点你的技能描述是模型的使用说明书说明书写得含糊模型就会乱用。一个比较有效的技巧是在描述里明确写上不要用于XX场景的负面清单能显著降低误调用率。3.2 一个实际技能定义示例以提取网页正文技能为例下面是完整的参数协议定义{ name: extract_web_content, description: 抓取指定URL的网页并提取正文内容适用于新闻文章、博客、技术文档等以文本为主的长文页面。返回清洗后的纯文本正文去除导航、广告等噪声内容。不适合用于需要登录、包含大量表格数据或视频为主的页面。, parameters: { type: object, properties: { url: { type: string, description: 目标网页的完整URL地址必须包含协议头例如 https://example.com/article/123 }, max_length: { type: integer, description: 返回正文的最大字符数默认为5000最大不超过20000, default: 5000 } }, required: [url] } }这里有两个细节值得注意。第一description中明确写了不适合用于哪些场景这能有效避免模型在错误场景下调用。第二max_length给了默认值和上限避免模型因为参数选择纠结。3.3 为什么输出协议比参数协议更重要参数协议控制的是进去的东西输出协议控制的是出来的东西。在实际运行中输出协议对系统稳定性的影响甚至更大。原因很简单Agent是多步骤运行的一个技能的输出会成为下一个技能的输入或决策依据。如果每个技能的返回格式五花八门编排层就要为每个技能写适配逻辑系统复杂度会指数级上升。我们所有技能统一采用以下返回结构{ success: true, data: { ...: 业务数据 }, error: null, meta: { duration_ms: 3200, source_count: 5 } }success任务是否执行成功布尔值。data核心业务数据统一JSON对象不允许是字符串或数组方便后续处理。error失败时给出明确错误信息成功时为null。meta元信息包括耗时、来源数量、token消耗等用于日志和排障。这个规范看起来简单但统一之后给后续的编排和排障省了大力气。如果技能报错编排层只需要检查error字段如果技能慢看一眼meta字段就知道卡在哪如果结果不符合预期data字段负责提供完整上下文。3.4 执行器的具体实现要点执行器部分最常见的坑是只写了正常路径的代码没有处理异常和边界情况。我们在执行器里有几个强制要求参数预校验在执行前先检查参数格式、范围、必填项不合法就直接返回error不要等到调外部接口才暴露。超时控制每个技能必须有明确的超时时间外部HTTP请求尤其要设置。不能因为一个请求卡住整个Agent任务就挂在那里。降级路径主路径失败时是否有备选方案比如抓取失败后是否可以用缓存数据兜底这个在技能定义时要提前想清楚。幂等设计特别是对发送通知写入数据库这类有副作用的技能最好在执行器里做去重保护否则模型重复调用两次就会产生重复副作用。4. 技能编排为什么会写技能不等于会干活4.1 LLM自主编排的失控风险技能有了接下来要解决的是怎么安排它们干活。第一反应肯定是让模型自己决定调哪个技能、按什么顺序调。这在简单任务上可行但一到复杂任务就暴露问题模型可能跳过某个关键技能、可能在一个技能上反复横跳、可能编造一个不存在的技能去调用。而且token消耗不可控一个10步的任务模型可能来回调用20次接口。我们最终的策略是骨架固定、节点动态任务的整体流程由代码控制处于骨架位置具体节点上的参数选择、分支判断交给模型。这样的好处是任务主干可控、可预测同时保留了模型在局部节点的灵活性。4.2 一个实际的编排框架示例以行业调研报告生成Agent为例它的编排流程如下步骤执行方式说明1. 需求理解LLM把用户的一句话需求转成结构化参数行业、地域、时间范围2. 资料收集代码复合技能固定调用行业资料收集技能并行搜索多个信源3. 资料筛选LLM规则先规则过滤再由模型判断相关性输出摘要列表4. 深度阅读代码对筛选后的关键文档调用网页正文提取技能5. 结构化整理LLM按固定模板提取核心事实、数据点、观点6. 报告生成LLM基于结构化内容生成章节配合最终渲染合并这个流程中步骤2和4完全由代码控制步骤3、5、6由模型参与但输入输出都是标准化的JSON对象。整个流程的执行顺序和失败处理都由一个编排器控制不会因为模型发挥不稳定而乱套。4.3 失败恢复重试、降级还是终止多技能串联后一定会遇到某个技能执行失败的情况。失败处理策略要提前定好不能等出了问题再让编排层来做决策。我们在实践中总结了一套简单规则重试失败原因是超时或网络抖动重试1-2次。降级失败原因是目标网站不兼容或数据格式异常切换到备选技能或使用缓存数据。终止失败原因是参数错误或核心依赖不可用直接终止任务返回中断信息和已完成部分的中间结果。旁路某个分支失败不影响主流程时记录日志后跳过继续后续步骤。这套规则全部编码到编排器里模型不参与决策。因为模型在一次失败后的临场反应往往不可靠它更倾向于编造理由而不是承认失败。4.4 上下文管理别把每一步的原始输出都塞给模型最后是上下文管理这也是很多Agent项目跑崩的隐形原因。随着技能调用次数增多对话上下文越来越长token消耗越来越大模型注意力被无关信息稀释回答质量反而下降。我们采取的做法是只往对话里塞结构化摘要不塞原始输出。比如一个技能返回了8000字的网页正文编排器不会把这8000字直接喂给模型而是先做摘要控制在500字以内再传给下一步。如果后续步骤需要全文就把正文落盘在上下文里传递一个文件路径。这样的好处是上下文长度可控模型始终聚焦在关键信息上。5. 实测中暴露的问题与修正思路5.1 技能调用幻觉与参数编造一次完整的追查记录技能体系上线后最让人头疼的问题是模型编造参数。有段时间我们观察到一个诡异现象搜索技能的query参数经常出现一些奇怪的拼接词比如行业报告 2024 最新 数据 分析 市场 规模看起来像是模型把多个搜索意图揉在一起实际搜索结果质量很差。追查链路如下。第一步翻日志确认问题复现频率发现集中在上下文较长的任务中。第二步检查技能描述和参数Schema发现query参数的description写得太泛只写了搜索关键词没有说明应该如何构造、限制几个词以内。第三步检查模型版本确认是执行LLM自主编排的节点而不是Pipeline固定节点。第四步做实验验证把query参数的description改成简短的核心关键词3-5个词不要包含修饰语和时间词并在Schema里增加maxLength约束。修复后搜索质量明显提升编造参数的现象大幅减少。这个案例说明绝大多数模型不听话的问题根源都在技能描述没写清楚而不是模型本身的缺陷。5.2 上下文爆炸输出回灌引发的连锁失控第二个大坑是上下文爆炸。早期版本里每个技能的输出都会完整追加到对话历史中。做个10步的任务对话上下文轻轻松松突破2万token而且大部分是中间过程的信息。后果是任务后半段模型响应越来越慢质量越来越差还经常忘掉早期的关键约束。解决方法是前面提到的摘要化落盘策略。每次技能调用产生的原始输出先经过一个摘要技能压缩到要点再进入上下文原始数据全部落到文件系统或数据库需要时再读取。这样改造后同样任务的token消耗下降了约40%任务完成质量反而提升了。5.3 副作用重复执行与缺幂等保护有一次我们观察日志发现同一封邮件通知被发送了三次。排查之后发现模型在一个步骤中对发送报告完成通知这个技能连续调用了两次而编排器的重试机制又自动补了一次。原因在于模型不确定第一次调用是否成功所以选择重试。这个问题的根源是技能缺少幂等保护。修复方案分两层第一层在执行器内部通过request_id做消息去重相同request_id的直接返回历史结果第二层在编排器层面对带副作用的技能默认不自动重试重试必须经过人工或规则确认。这两层兜底之后副作用重复执行的问题就消失了。5.4 建立技能的回归评测集技能体系是个持续迭代的系统改一个技能可能会影响一系列任务。我们在过程中建了一个包含50个真实任务的评测集覆盖不同难度、不同信源类型、不同输出格式。每次修改技能定义或编排逻辑就跑一遍全量回归看三类指标评测维度合格标准说明技能调用成功率≥95%技能是否正确被调用无编造参数合法率≥98%生成的参数是否符合Schema约束任务完成度≥90%最终产出是否达到任务要求单任务token消耗相比基线不增防止优化一项、恶化另一项这个评测集的价值不在精确衡量智能水平而在于保证迭代过程中不要修好一个bug、带出三个新bug。技能体系的每一次改动都应该有回归数据支撑。5.5 其他小坑速览再整理几个低频但遇到过的问题模型在技能参数填URL时偶尔会丢掉https协议头需要执行器自动补全复合技能内部某一步失败但返回success需要在中间层增加严格的状态检查模型在任务描述里出现但技能清单里没有的功能时会倾向编造一个近似技能名这时候需要在系统提示词中强调只能调用列表中的技能。症状根因解决方案任务跑偏、步骤缺失技能描述不清晰打磨description增加负面清单上下文越来越长原始输出回灌摘要化落盘传递摘要与路径重复发送通知缺幂等保护执行器加request_id去重参数格式错误Schema约束不足增加枚举、格式、最大长度等约束调用了不存在的技能技能清单不明确强化系统提示词强制白名单6. 给新手的入坑建议6.1 从三个技能起步先把链路跑通如果你刚接触agent-skills我最大的建议是不要一上来就规划一整套技能树先挑三个最核心、最常用的技能落地把定义-注册-调用-编排-日志这条链路跑通再逐步扩展。我们的第一个可用版本只包含三个技能网页搜索、正文提取、结构化摘要。就靠这三个技能我们跑通了第一个端到端的调研任务。链路通了后续技能扩展就是复制粘贴改逻辑的事。6.2 Pipeline先行LLM编排后置刚开始不要急着让模型做完全自由编排。先把任务流程用代码固定成Pipeline模型只在关键节点参与决策。等Pipeline跑稳定了再逐步把一些决策节点开放给模型。这个顺序能让你始终有一个兜底版本模型发挥不稳定时还能退回到固定流程。我们到现在仍然保留着几个纯Pipeline的任务因为它们在稳定性和速度上确实比LLM编排有优势。6.3 可观测性要提前设计技能体系上线第一天就要有完善的日志系统。每次技能调用都要记录调用时间、输入参数、输出结果、耗时、token消耗、错误信息。没有这批数据后续迭代就是瞎子摸象。我们的日志系统后来生成了大量的数据成了评测和排障最重要的依据。尤其是模型编造参数这类问题没有日志你根本不知道它编造了什么、为什么编。6.4 心态调整这是一个持续迭代的过程把Agent想象成一个新来的实习生技能体系就是给它的SOP操作手册。SOP写得越具体、越细致、越有约束实习生的表现就越稳定但SOP不可能第一天就完美它需要在一次次错误中打磨完善。我现在看到任何一个Agent任务失败第一反应不是模型不行而是我的技能体系哪里还能改。这种心态转换是agent-skills实践带给我最大的收获。