聊到Agent开发很多人第一反应是这不就是给大模型套个循环再挂几个工具函数吗我一开始也这么想直到亲手把一个Demo性质的Agent往真实场景里推才发现最大的短板根本不是模型能力而是那堆散落在代码里的“技能”——它们没有体系、没有标准、没有治理加一个功能就要改一遍主流程排查一个问题要翻遍所有分支。今天围绕agent-skills这个主题把我实际构建技能体系时踩过的坑、沉淀下来的设计方法完整拆一遍。这篇内容适合谁如果你正在做Agent类应用手上有三五个工具函数想沉淀成可扩展的技能库或者你被“模型不按预期调用工具”“参数老是传错”“技能越加越乱”这些问题折磨过那这篇应该能给你一套可以直接落地的参考方案。不涉及具体厂商API所有设计思路基于通用实践你拿回去套自己的场景就行。1. 为什么Agent需要一套“技能体系”而不是一堆函数先聊一个很实际的问题函数和技能到底差在哪很多人觉得不就是把函数声明一下、描述写清楚让模型调用就完事了吗。如果只有两三个函数确实够用。但一旦函数多起来你会很快发现三个尴尬的事实。1.1 模型真正消费的是“技能描述”不是代码大模型在调用工具时本质上做的是一个“意图匹配”动作它读到用户的问题再读你给的函数描述判断哪个函数能解决当前问题。这里的关键在于函数签名里的参数类型、返回值结构是给编译器看的而那段自然语言描述才是给模型看的。我见过太多人把函数描述写成这样“搜索函数参数query返回列表”。这种描述交给模型效果几乎等于没有描述。模型只能在参数名上猜“query是什么”猜错了调用就不对。真正的技能描述至少要回答三个问题这个技能是干什么的边界在哪里什么情况下应该用什么情况下不应该用参数之间有没有依赖关系有哪些隐含约束这本质上就是把“函数功能说明”升级成“使用说明书”。技能体系的第一步就是强制每个技能都有结构化的、面向模型的使用说明书。1.2 技能的三个核心特性可复用、可编排、可治理为什么叫“体系”因为单个技能没有价值技能之间的关系才是价值。我一直把Agent技能体系类比成乐高积木。单个积木就是一个技能比如“搜索网页”“解析PDF”“发送消息”。积木越多能搭出来的形态就越丰富。但如果没有统一的接口标准A积木和B积木根本拼不到一起那再多积木也只是散件。具体到技术上可复用意味着同一个技能可以被多个Agent或多种业务场景调用而不是写死在某个流程里。可编排意味着技能之间可以组合形成一个复杂任务的处理链路。可治理意味着每个技能的版本、权限、调用量、错误率都是可控可观测的。这三点合起来才配叫“体系”。1.3 我在项目里踩到的第一道坎说个真实经历。模拟项目X的初期我直接在Agent主循环里硬编码了五六个函数。当时觉得挺香逻辑清晰跑得也顺。直到第七个函数进来问题暴了两个函数功能相近但命名不一致一个叫search_web一个叫fetch_web_content模型经常选错还有个函数涉及敏感操作被我随手挂在一个通用分支下差点放出一个越权调用。那次重构之后我彻底放弃了“堆函数”的思路开始把技能当“一等公民”来设计。后面所有新增能力都走技能注册、技能声明、技能测试的流程。现在回想那是这个项目最值得的一步。2. 技能的核心要素Schema设计决定了模型能不能用对如果说技能体系是大楼那每个技能的Schema结构声明就是地基。地基歪了楼上全歪。这一节直接讲Schema设计里最关键的几个点。2.1 技能描述怎么写模型才愿意点你一条被反复验证的经验技能描述要用“动词开头 明确边界 反例提示”的结构。正确示例“搜索公开网页信息返回网页标题和摘要。适用于查询事实性信息、查找资料、获取最新动态。不适用于计算数学题、解析本地文件。”错误示例“搜索功能。”前者不仅有正面引导还给了反例。模型在面对模糊意图时反例能有效减少误调用。比如用户问“3乘以5等于多少”模型看到“不适用于计算数学题”就不会强行调用搜索技能。我还会在描述里加上“与同类技能的差异”。比如系统里同时有“搜索网页”和“搜索数据库”我会在搜索网页的描述里写“与数据库搜索不同本技能面向公网信息无法查询内部数据”。这能帮模型在多个相似技能之间做区分效果非常明显。2.2 参数声明的玄机枚举值、默认值、依赖关系参数是模型最容易出错的地方。我总结了三类高频问题以及对应的Schema解法第一类参数范围失控。模型生成一个超出业务边界的参数值比如搜索类型传入一个不存在的枚举值。解法是在JSON Schema里显式声明enum把可选值锁死。第二类必选参数缺失。模型认为某个参数不重要就不传。解法是区分required和optional并且把真正关键的参数设为required。但注意required字段不宜太多超过四五个会显著增加模型生成难度。第三类参数之间的隐形依赖。比如“大数据量导出”技能格式参数设为“xlsx”时就必须同时传sheet名称。这类依赖无法直接用标准JSON Schema表达我会在技能描述里用一句话写明“当格式为xlsx时必须提供sheet_name参数”然后在代码侧做强校验。2.3 一个可直接套用的技能Schema模板下面是我在项目里用得很顺的JSON Schema模板字段不多但每个都有用途{ skill_id: web_search, version: 1.2.0, name: 搜索公开网页, description: 搜索公开网页信息并返回标题与摘要列表。适用于查资料、找新闻、核实事实。不适用于数学计算、数据库查询。与内部数据检索技能不同本技能只访问公网内容。, parameters: { type: object, properties: { query: { type: string, description: 搜索关键词建议不超过20个字, minLength: 2, maxLength: 50 }, max_results: { type: integer, description: 返回结果条数默认5最大10, enum: [3, 5, 10], default: 5 } }, required: [query] }, returns: { type: array, items: { type: object, properties: { title: {type: string}, url: {type: string}, snippet: {type: string} } } } }注意几个细节skill_id和version是技能管家的标识不是给模型看的description里明确写了差异和反例max_results这种参数直接给enum模型就不会生成一个“7条”这种尴尬值。这套模板我在三个项目里复用稳定性和可维护性都很好。3. 技能注册与发现让Agent知道自己会什么有了单个技能的Schema下一步就是怎么让Agent“拥有”这些技能。这一节讲技能注册中心的设计思路以及动态加载、冲突处理这些在实操里绕不开的问题。3.1 集中式技能注册表我在项目里的做法是维护一个集中式注册表所有技能统一登记、统一管理。注册表就是一份技能清单包含技能ID、版本、Schema、对应的本地处理函数、权限等级、调用统计等字段。注册表解决了“Agent到底会什么”的问题。没有注册表的时候每加一个技能你都要去改主流程代码有了注册表主流程只认注册表里的内容新增技能只需要往注册表里登记一条记录。这让系统的扩展路径从“改代码”变成“加配置”质的变化。注册表用数据库表还是配置文件取决于团队习惯。我的建议是原型阶段用YAML文件就够了字段清晰、可读性好一旦技能数量超过20个、需要多人协作就迁移到数据库配上简单的管理后台。3.2 动态加载与懒初始化技能注册表里登记的只是一个“声明”真正被调用时才需要加载实际执行逻辑。我采用动态加载机制Agent收到模型返回的tool_calls后先查注册表再根据技能ID找到对应的处理函数然后执行。这里有个性能细节不要把所有技能都加载到内存里。一个Agent挂着几十个技能其中可能只有三五个是高频使用的。在注册表里标记lazy_load字段只有被实际调用时才import对应模块能明显降低启动开销和内存占用。有一种情况要额外注意技能的执行逻辑如果依赖外部资源比如数据库连接、第三方API密钥建议在技能内部做延迟初始化而不是在全局启动时统一建立连接。否则会出现“技能注册了一堆其实只用到两个但所有连接都占着”的资源浪费。3.3 技能冲突与优先级裁决技能多了冲突是必然的。我遇到过的冲突主要有两类第一类是技能ID冲突两个模块想注册同一个技能ID。解法是建设统一的命名规范用“模块名.技能名”的二级命名比如knowledge.search、file.extract_text。从源头杜绝重名。第二类是意图重叠两个技能都能处理同一类用户请求。比如“发送邮件”和“发送短信”用户说“通知一下张经理”模型可能两个都选。我的经验是在技能描述里明确各自边界同时给技能设置优先级权重。当模型的置信度不高时优先返回权重高的技能。冲突裁决机制的本质是在模型能力之上加一道工程护栏。不要指望模型每次都选得准系统设计要允许“选错了也能被约束住”。4. 技能编排从单点调用到复杂任务流程单个技能解决单点问题真正体现Agent价值的是多个技能组合完成一个复杂任务。这一节讲技能编排的三种基本模式以及我常用的一个结构化编排方法。4.1 串行、并行和条件分支技能编排的核心模式就三种理解了这三种复杂流程都是组合出来的。串行模式最简单技能A的执行结果作为技能B的输入。典型场景是“翻译并总结”“先翻译英文文档再总结中文要点”。注意串行模式下前一个技能的输出必须直接可喂给后一个技能的参数这要求在技能设计时就考虑接口兼容性。并行模式用于互不依赖的子任务。典型场景是“市场调研”并行搜索产品信息、竞品信息、行业动态最后统一汇总。并行能显著缩短复杂任务的耗时但对代码要求高要处理好多个结果的汇聚顺序和冲突。条件分支根据执行过程中的中间结果决定下一步走哪个技能。典型场景是“客服工单处理”先判断工单类型如果是退款类走退款流程技能如果是技术类走排障技能。条件分支的核心是“决策变量”的设计——模型在每一步都需要足够的上下文来做判断。4.2 用“任务清单”驱动编排我在这里分享一个我自己用得比较多的方法任务清单编排法。它的核心思路是不让模型一口气生成整个执行计划而是让模型先拆解任务清单再逐项执行。具体流程是这样的Agent先接收用户的复杂请求利用一个“任务规划”技能将请求拆解成有序的子任务列表。每项子任务明确关联一个具体技能以及所需的前置依赖。Agent逐项执行子任务每执行完一项将结果追加到上下文。所有子任务完成后用“结果汇总”技能生成最终响应。这个方法的优势在于可观测性极强。用户能实时看到“当前正在执行哪一步”出现错误时也能精准定位到具体子任务而不是笼统的一句“Agent出错了”。我做过一个内容生产Agent一篇文章的生成流程拆成了搜集素材、提取数据、生成大纲、分节成稿、风格优化五个子任务全程可跟踪效果比一把梭式的生成稳定太多。4.3 编排时的错误处理策略技能编排最容易翻车的地方是某个子任务失败后整个流程直接崩掉。我在实践里总结了三个必须处理的策略子任务失败要支持重试瞬时故障超时、网络抖动重试一到两次成功率能提升不少。失败要能降级主技能失败时判断有没有可替代的备用技能。比如搜索技能挂了可以用缓存查询技能兜底。失败要能记录每一次编排失败都要有结构化日志记录哪个技能、什么参数、什么异常。这是后续优化最重要的一手资料。5. 实操从零构建一个“检索摘要”复合技能理论讲了这么多来一个完整案例。假设我们要做一个“检索摘要”技能用户提一个问题Agent先搜索相关资料再对结果做摘要最后输出一段有条理的回答。这个案例能覆盖技能定义、注册、编排的完整闭环。5.1 第一步拆解复合技能的原子能力“检索摘要”这个需求一上来听起来很简单但直接把它塞进一个技能里反而难写。我的习惯是先拆成原子能力再组装。拆解之后这个复合技能由三个子技能组成search_web搜索公开网页返回标题和摘要。extract_content根据URL抓取网页正文内容。summarize_text对长文本做要点摘要。三个子技能各司其职可以被其他复合技能复用。比如summarize_text不仅能处理网页正文还能处理粘贴的文本。这就是前面说的“积木化”。5.2 第二步定义技能Schema并注册为每个子技能定义JSON Schema这里以summarize_text为例{ skill_id: text.summarize_text, version: 1.0.0, name: 文本摘要, description: 对长文本生成简洁要点摘要。适用于总结文章、报告、会议纪要。输入文本长度大于200字时效果最佳。不适用于翻译、改写润色。, parameters: { type: object, properties: { content: { type: string, description: 需要摘要的原始文本, minLength: 50 }, summary_length: { type: string, description: 摘要长度偏好, enum: [short, medium, long], default: medium } }, required: [content] } }注册表登记时我会额外记录这个技能的权限等级和平均耗时。权限等级用来控制哪些Agent可以用平均耗时用来做超时预算。5.3 第三步实现主流程编排主流程用一段伪代码说明整个编排逻辑def run_research_summary(query): # 步骤1串行调用搜索技能 search_results dispatch(web.search_web, {query: query, max_results: 5}) # 步骤2并行抓取正文只抓前3条控制成本 contents [] for item in search_results[:3]: content dispatch(web.extract_content, {url: item[url]}) contents.append({title: item[title], content: content}) # 步骤3逐个摘要再合并 summaries [] for c in contents: summary dispatch(text.summarize_text, { content: c[content], summary_length: short }) summaries.append({title: c[title], summary: summary}) # 步骤4汇总输出 return format_result(query, summaries)注意这里我用了一个dispatch函数来统一分发技能调用。dispatch的内部逻辑就是查注册表、加载执行函数、执行、返回结构化结果。整个主流程只依赖技能ID不关心具体实现这就是技能体系带来的解耦效果。5.4 第四步测试与效果调优复合技能上线前我至少会跑三类测试用例正常用例一篇清晰的科普文章验证基本流程通不通。边界用例搜索无结果、网页正文提取失败、内容超长验证兜底逻辑。对抗用例输入一个需要两步推理的问题观察模型能否正确处理子任务衔接。效果调优最大的变量在summarize_text的prompt上。摘要过短会丢关键信息过长又失去摘要意义。我的调法是在技能内部内置“长度自查”指令要求摘要结果控制在原文长度的五分之一到三分之一区间并在输出里强制分段。实测下来这个复合技能从设计到稳定运行大概花了一个下午加一个上午的调试时间。相比之前“硬编码一条龙”的写法维护成本和扩展性的提升是肉眼可见的。6. 常见问题与排查技巧实录最后这部分全部来自我实际调试Agent技能时遇到的真实问题。每一条都带着当时的排查思路和最终解决方案你可以对照自己的场景直接参考。6.1 模型死活不调用技能怎么办现象用户提出了一个明显应该走技能的问题但模型直接在文本里回复根本不触发tool_calls。排查路径有四个看技能描述是否写得太长被截断。整个Agent系统提示词是有长度预算的描述超长会被截掉模型根本看不到后面的技能。我测试过技能描述被截断的占比不小精简描述立竿见影。看技能名是否有歧义。模型如果分不清两个技能的区别有时会选择“不调用”来规避风险。用“模块.动作”的命名法能缓解。看示例里有没有引导模型调用的demo。在系统提示词里放一两组“用户问题→技能调用”的示例效果比单纯堆描述好得多。看温度参数是否过高。高温会让模型更随意可能跳过工具调用。技能类任务用偏低的温度比如0.1到0.3会更稳定。6.2 参数幻觉模型生成了不存在的参数现象模型调用技能时传了一个Schema里没有定义过的参数或者某个参数的值明显不合理。原因通常是模型在生成JSON时“自由发挥”。解决方案是在两个层面同时防御第一层是框架层强校验。在dispatch函数里按Schema严格校验参数多出来的键直接剔除枚举值不合法的直接报错并要求模型重新生成。不要相信模型生成的JSON必须过一层校验。第二层是描述层预防。在参数的description里写清楚值的格式比如“date格式为YYYY-MM-DD”能显著减少格式错误。我也试过给参数加few-shot示例效果可以但会增加token消耗只对关键参数使用。6.3 技能响应太慢拖垮整个对话现象Agent调用技能后用户要等很久才能看到响应体验很差。常规优化思路有三个给每个技能估算时间预算超过预算就异步化。耗时超过5秒的技能建议改成先返回“任务已开始”后台跑完再通知。能并行的子任务绝不串行。前面那个检索摘要案例抓取正文的循环改成并发整体耗时能降低一半以上。在技能返回结果前做瘦身。有些技能返回大量原始数据实际下游只用其中几个字段。在技能内部处理干净再返回能大幅减少上下文token占用间接提升后续模型响应速度。6.4 技能输出了不该输出的内容安全问题怎么兜底现象技能返回的结果里包含了敏感信息或者技能操作本身带有风险。我的安全底线有四条技能必须有权限等级敏感操作发消息、改配置、删数据一律高等级普通Agent无权调用。技能执行前的参数要做白名单过滤防止通过参数注入危险指令。技能输出的文本不能无脑拼接进系统上下文要做基本的内容安全检查。所有的技能调用都要留日志包括调用者、参数、返回结果。审计机制是技能治理的最后一道防线。有一次我在排查某Agent的异常行为时就是因为技能日志完整半小时内就锁定了问题出在“一个旧版本技能返回了意外的数据结构”。没有日志的话这种问题能查到人崩溃。6.5 技能版本更新后行为突然变了现象技能升级后模型调用的结果和以前不一样业务方反馈“变笨了”。排查后发现绝大多数是技能描述和实际行为不一致导致的。比如你更新了代码逻辑但description里还写着旧的行为模型根据旧描述调用自然出问题。我的习惯是版本升级时必须同步升级description和Schema并且在技能内部维护一段“变更记录”字段记录每个版本改了哪些行为。这样既能自查也能在出问题时快速回溯。最后分享一个心得体会回到开头那句话Agent开发里真正拉开差距的往往是工程能力。模型能力再强技能体系一团乱麻产出也是空中楼阁。我个人在实操中最深的一个体会是技能体系的建设不要等技能数量爆炸了再开始而要从第一个技能开始就按规范走。多写几行description的成本远低于事后返工排查的成本。刚开始可能觉得繁琐但一旦技能库滚到几十个规模你会非常庆幸当初定了这些规矩。另外有一个小技巧可以说一下每个技能上线前我会让它从“完全不使用的用户视角”跑一遍也就是想象一个从来没看过代码的人只凭description能不能正确用这个技能。如果能说明描述合格如果还要去翻代码才明白那这个技能描述就不及格需要重写。这个标准看着简单但在实际项目里非常管用。技能体系这件事说到底就是在给模型创造更确定的工作环境环境越确定产出越可靠。