ARTICLE DETAIL

资讯详情

深耕郑州网站建设与运营推广的一线实战洞察。

Agent技能系统从零搭建:工具调用、注册与故障排查全指南

Agent技能系统从零搭建:工具调用、注册与故障排查全指南 做了三年多Agent相关的东西工具调用、函数声明、插件系统这套我自认为已经玩得很溜了。直到最近一次复盘发现一个扎心的事实我的Agent大部分时间都在会说话真正会做事的技能少得可怜。有一次让它去整理一个Git仓库的历史提交它折腾了半天最后告诉我暂时无法完成这个操作——因为我就没给它配任何跟Git相关的技能。这让我彻底想明白了一件事大模型的推理能力再强也只是个空壳引擎。真正决定Agent能做成什么事、做成什么样的是它手里握着多少把趁手的工具。而怎么设计、怎么组织、怎么调教这些工具就是所谓的agent-skills也就是Agent技能系统。这篇文章不打算讲那些虚的架构。我想用最直接的方式把我从空模型开始到真正搭建出一套能干活、能扩展、能稳定的Agent技能系统的全过程写下来。从一个技能的解剖到注册协议的选型再到技能库的组织和管理最后是那些必须避开的大坑和完整的排查思路。这套东西我在几个项目里反复打磨过踩过不少坑也总结了一些很实用的经验分享出来应该能让你少走很多弯路。1. 先搞清楚Agent技能到底是什么别把它和工具调用混为一谈我见过太多人一上来就研究各种框架结果基础概念是糊的。在动手写代码之前有几层概念必须先理清楚否则后面每一步都是在给自己埋雷。1.1 技能、工具、API这三层关系决定了你的Agent上限简单的理解一个Agent技能就是一段可以被大模型主动触发、按既定逻辑执行特定任务的能力单元。但注意技能跟工具、API是三个层面的事。API是最底层的原始接口它描述的是系统有什么能力工具Tool是把API包装成一种可供调用的形式通常包含名称、描述、参数定义这些元数据而技能Skill则是在工具的基础上再叠加了调用逻辑、前置条件、后置处理和上下文意识它描述的是在什么场景下、为了什么目标、应该怎么用这个工具。打比方来说API是引擎工具是方向盘技能则是完整的驾驶行为——不只是转动方向盘的机械操作还包括判断转弯时机、观察路况、控制车速这些决策过程。一个只有工具没有技能的Agent就像一个握着方向盘却不知道怎么开上路的驾驶员而一个技能完整的Agent才真正具备完成整条任务链路的可能性。我把这套理解用一张简表说明方便你跟我后面讲的对应起来能力层级定义举例需要解决的问题API已有的原始功能接口Git的list_commits接口、数据库的query接口怎么把接口暴露出来Tool对API的规范化封装git_list_commits(name, date_range)怎么让模型看懂并调用Skill工具调用策略结果处理整理最近7天提交记录并生成周报怎么在正确场景下触发和执行1.2 为什么OpenAI和Anthropic都在卷技能这个概念如果你关注最近的模型能力变化会发现各家都在强化技能的标签。OpenAI在ChatGPT里推的scheduled actions、Custom ActionsGoogle在Gemini里推的ExtensionsAnthropic在Claude里推的Agent Skills——本质上都在做同一件事让Agent更容易获得任务级的执行能力而不是停留在回复一次的对话级交互上。这不是单纯的概念包装。一个Agent要完成复杂任务比如帮我分析竞品并输出一份调研报告它需要的不是单一工具而是一整套技能的编排信息搜集技能 数据结构化技能 文档生成技能。如果都要靠模型自己临场发挥结果往往不可控如果提前把每个环节都做成技能包模型就能像搭积木一样按需要组合调用过程可控产出稳定。从我实际项目的经验看技能化的核心收益有三点一是提高成功率因为每个技能内部都是确定性逻辑模型只需要学会什么时候用不需要从头推理怎么做到二是便于维护单个技能升级、故障定位都被隔离在模块内部三是天然支持复用同一个技能可以在不同的Agent角色里装配比如一个web_search技能既能给客服Agent用也能给市场分析Agent用。所以接下来我要讲的所有设计心里锚定的都是这个目标——把Agent从聊天机器人升级成能干活的工作伙伴。2. 技能注册机制与工具调用的协议选型搞明白了技能的本质接下来就要面对一个很实际的问题一个技能怎么注册进去让模型看见它并且知道该在什么时候调用它这块是整个技能系统能不能工作的地基也是最容易被人忽视却影响最大的地方。2.1 技能描述怎么写直接决定了模型会不会用注册一个技能时模型能看见的其实只有三类信息技能名称、技能描述、参数Schema。也就是JSON格式声明的要传什么参数。这里我要重点说一个容易被忽略的真相对大模型来说技能描述就是它的使用说明书说明书写得好不好直接决定工具是不是被正确使用。同一个工具你描述成搜索网络信息跟描述成当被问到实时资讯、最新新闻、或者需要验证某个过时知识时使用可使用query参数输入关键字进行搜索。注意该工具返回的是摘要而非全文如需完整内容请结合知识库或原始网站模型的使用效果天差地别。我自己最常用的套路是描述里包含四要素触发场景、使用约束、参数说明、返回说明。另外有一个经验描述中写明什么时候不要用比只写什么时候用更有价值。比如我做过一个翻译技能如果不在描述里加一句当用户明确表示需要人工翻译时不要使用本工具模型经常自作主张去调用反而让流程卡住。参数Schema的精确度也至关重要。OpenAI的function calling模式里参数定义用的是JSON Schema规范字段名、类型、枚举值、是否必填、默认值每一个都要尽量收紧。我在项目里见过最典型的问题参数名取得太抽象比如data、input模型根本不知道里面应该放什么结果传了一堆模棱两可的字符串。最终报错、重试浪费大量时间和tokens。2.2 常见注册方式对比function calling、手动描述、上下文注入我实际用下来当前给Agent装技能有三种常见方式各有优劣我梳理一张对比表你可以直接参考注册方式实现机制模型感知方式适合场景明显短板原生function calling结构化声明后随请求发送模型按需发起调用模型主动决策调用绝大多数标准技能需要模型供应商支持自定义逻辑受限手动描述注入把工具说明拼接进system prompt模型看见文本后自行决定轻量试用、不依赖SDK无结构化返回容易和上下文混淆上下文感知注入根据对话内容动态挑选技能说明再注入动态感知、按需可见技能数量多避免信息过载需要额外做技能检索增加复杂度原生function calling是我在所有生产项目里的首选。原因很简单它把调用决策和参数生成都交给模型结构化程度最高解析靠谱出错率最低。OpenAI和Anthropic的实现略有差异但大方向一致——模型在需要时会返回一个函数调用指令我这边负责解析指令、执行对应的本地函数、把结果回传成一条tool result消息模型再基于结果继续生成回复。手动描述注入看起来最省事把技能说明写在system prompt里就行但有一个致命问题随着技能增加prompt会越来越长而且模型拿到的是文字描述而不是结构化工具缺少参数校验和返回类型约束出错了也更难排查。它适合拿来快速验证一个想法不适合放进生产。上下文感知注入是我在技能数量超过40个以后采取的方案提前给每个技能做向量索引每次请求前先算一下用户意图和技能的相关性只把最相关的几个技能的说明注入到模型可感知的范围内。这样既保住了模型对技能的覆盖率也避免了长上下文带来的浪费和注意力分散。效果很稳就是前期得花时间搭检索。2.3 我使用的技能注册数据结构可以直接抄作业光说理论不行我直接把我项目中用的技能注册数据结构简化版贴出来。每个技能就是一个Python字典或者JSON对象设计了好几个版本之后这个格式我用得最顺手skill_registry { skill_name: analyze_sales_data, description: ( 当用户需要分析销售数据、查看趋势、对比环比/同比时使用。 输入应为标准化后的数据表路径。 注意本工具只做数据分析不做数据采集 若用户要求从外部系统拉取数据请先调用fetch_data工具。 ), parameters: { type: object, properties: { data_path: { type: string, description: 要分析的数据文件路径parquet/csv格式 }, metrics: { type: array, items: {type: string}, description: 需要计算的指标列表如[revenue,orders] }, start_date: { type: string, description: 起始日期格式YYYY-MM-DD默认最近30天 } }, required: [data_path, metrics] }, handler: analyze_sales_data_function }这段结构里handler就是实际执行的本地函数也就是把之前说的技能工具逻辑落到代码层面的地方。description和parameters是给模型看的handler是给执行引擎用的两者泾渭分明。我踩过一个很深的坑早期为了图省事把参数校验逻辑写在handler里而参数定义写得极其宽松什么都是optional string结果模型多次猜错参数格式每次都能走到handler里才发现错然后报错、再试浪费大量重试次数。后来我把参数定义收紧每个参数都写清楚类型、必填和默认值同时在调用handler之前加一道独立的参数校验层错误率一下子降了很多。在技能注册难看但参数严格好过注册好看但执行崩溃——模型很擅长在宽松规则下自我发挥而自我发挥往往是失控的开始。3. 核心原理拆解技能选择、技能调用与技能返回在这一节我专门讲技能系统的三个核心机制。这三个机制但凡有一个设计不合理你的Agent跑起来就会要么不干活要么干傻活要么干一半就断。3.1 技能选择模型是怎么决定用哪个技能的技能注册好之后当用户输入一句自然语言请求模型内部要经历一次技能选择的推理。你可以把它理解成一个推荐系统用户需求是query技能库是候选集模型要在其中挑出一个或几个最匹配的技能来执行。这里我强调一个容易误解的点模型不是靠搜索来选技能而是靠理解。在function calling模式下所有技能声明是一起发给模型的模型根据当前对话语境和技能描述之间的语义匹配度做判断。所以技能描述写得好不好直接影响选择准确率。在我项目中技能选择环节最常犯的错误是技能边界重叠。比如我同时注册了analyze_sales_data和generate_sales_report两个技能前者偏数据分析后者偏报告生成。如果不把边界写清楚模型经常会用错想生成报告却调了数据分析工具拿到一堆数字表格然后傻眼。解决办法是两条一是在描述里互相引用边界明确如果你需要生成报告请使用generate_sales_report而非analyze_sales_data二是尽量合并同类技能宁可一个技能里多做几个步骤也不要拆成多个让模型来挑。实际还能进一步用技能标签来辅助选择。每个技能注册时打上domain标签如finance、hr、dev然后在系统提示里固化场景映射。比如只要对话里出现报销工资考勤强制走hr域的技能组。这种硬路由软选择结合的方式是我试过稳定性最高的方案。3.2 技能调用参数填充和执行链路的工程细节模型决定调用某个技能后会按参数Schema生成一份结构化参数比如上面的例子模型会返回{data_path: s3://bucket/report.parquet, metrics: [revenue, orders], start_date: 2024-05-01}。拿到这份参数后引擎要做的事情可不止是执行函数几个工程细节特别关键第一参数校验必须前置。检查必填项是否齐全、类型是否匹配、枚举值是否合法。不要等到handler内部才报错那样既浪费了一次完整调用链也让模型收到的错误信息不够清晰。我在Handler前面套了一个统一的validate_args函数专门跑JSON Schema校验校验失败就直接返回一个标准化的错误结构。第二超时控制必须有边界。Agent的技能执行往往比普通API调用耗时更长比如数据分析技能可能要跑几分钟。如果不设超时一个技能卡住就会拖垮整个Agent会话。我的实践是给每个技能注册额外配置timeout字段默认120秒数据密集型的可以放宽到300秒但绝对不能无限等。第三并发调用要显式支持。有些场景下模型会同时调用两个独立的技能比如对比我们和竞品的销售数据模型可能同时选该品牌和竞品的两个取数技能。执行引擎如果不支持并行而是串行排队整个响应时间会翻倍。我建议执行引擎天然支持并发用asyncio.gather这类机制来跑但也要小心技能间如果有依赖关系必须等待前序技能返回后才能触发后续。3.3 技能返回把结果翻译回模型能读懂的语言技能执行完拿到的是一个JSON原始结果或者一个布尔值比如{success: true, data: {...}}。这一步看似简单其实有个非常重要的设计点大模型看到的结果不是原始返回值而是要被它再接再厉继续生成回复的。也就是说handler返回的东西不能是机器格式而应该是既能给机器解析又能让模型顺畅衔接的形式。我通常把handler的返回值统一包装成{ success: True, message: 成功获取2024年5月的销售数据共1200条记录。, data: { ... } }这里message是写给模型看的summarydata是给后续逻辑用的原始数据。别小看这个summary它的作用极其大模型在生成下一轮回答时会优先参考summary来判断这个技能干成了什么而不是去逐条解析庞大的data字段。返回环节还有一个很重要的设计技能执行失败时的返回信息也要规范化。千万不要只返回一个空对象或者抛一个异常让引擎崩溃。我会让失败的返回带上明确的错误类型和可给模型看的建议比如{ success: False, error_type: timeout, message: 数据查询超时300s建议缩小日期范围或检查数据源状态。 }这样模型收到之后要么自己调整策略再调用要么能直接向用户解释为什么失败而不是陷入死循环或者给用户一句敷衍的出错了。这一套可选参数用默认、可恢复错误用提示、不可恢复错误用明确终止的原则磨合了几轮之后Agent的稳定性显著提升。4. 技能库工程化落地从脚本到可维护Agent系统的演进前面讲的都是单点机制现在讨论一个现实问题当技能数量从几个涨到几十个、几百个怎么管我最早是把所有技能都写在同一个skills.py文件里一个文件三四千行后来每次加技能都要找半天位置改一个公共逻辑要牵一发动全身。后来痛定思痛按下面三条拆才算真正工程化。4.1 技能目录结构与加载机制我的项目里技能按以下目录结构组织skills/ __init__.py # 注册入口 common/ logger.py # 统一日志 error_utils.py # 错误规范化 data_tools/ fetch_data.py analyze_sales.py transform.py content_gen/ generate_report.py summarize.py dev_tools/ git_ops.py code_search.py run_tests.py每个技能文件里只做一件事定义handler并声明该技能的注册元数据名称、描述、参数Schema。然后一个统一的注册器脚本去扫描目录把所有技能合并进一个大注册表。这样做的好处很明显新增技能不用改动既有代码只要在原目录加文件、写注册信息技能间的依赖通过公共模块复用不互相耦合出问题时能快速定位到具体技能文件不用翻山越岭。加载机制上我建议做成懒加载。不是进程启动就把所有技能handler都import而是维护一份技能元数据索引直接导入真正执行某个技能时才动态import对应的handler模块。这样做的好处是启动快而且不会因为某个技能的依赖库没装好比如有人新加了个技能依赖了requests而另一台机器没装导致整个服务启动失败。这事我真实遇到过深有体会。4.2 上下文压缩和技能选择检索技能数量多了以后如果每次请求把所有技能声明都塞给模型tokens消耗大不说模型的选择准确率还会下降。这就是我前面提到的上下文感知注入的用武之地。具体做法也不复杂先把每个技能的description用embedding模型转成向量存进一个向量库用轻量的chromadb或faiss就够了每次收到用户请求时先把用户query也转成向量然后检索topK最相关的技能K根据业务复杂度取5~10再把这些技能的完整声明拼接到system prompt里。这样模型每次只能看到少量的、高相关的技能选择准确率会明显提高。这里有一个细节值得提检索的相似度计算可能让描述短但匹配的技能落选也可能让描述长但相关性低的技能入选。我实际调优时会给高优先级的核心技能比如支付、用户身份这类必须随时可调用的额外加一个always_include白名单强制进入上下文其余技能走检索。这个混合策略是我觉得最稳的。4.3 技能测试与回归别让新技能破坏老技能工程化做得再好没有测试这道防线迟早会翻车。我吃过的亏是某次给Agent加了一个温度换算的技能本来很简单结果不知道什么原因影响了既有天气查询技能的参数Schema当天线上Agent的天气查询全挂了。从那次以后我为每个技能配置了一套自动化测试和验证机制每新增或修改技能必须跑一遍该技能的单元测试构造假handler传标准化参数验证返回结构跑一遍技能整体的schema一致性测试确保所有技能参数Schema合在一起不冲突尤其注意参数名不要重复、不要有覆盖跑一遍回归对话集就是预先准备100条典型用户query每轮改造后检查Agent的技能选择结果和最终回复质量有没有明显退步。这最后一项我强烈建议做虽然一开始写测试样例比较费时间但对技能的每一次改动都相当于上了保险。别偷懒我见过太多项目死在加了一个新技能之后老流程莫名变傻这种无声的回归上。5. 实战中的坑与排查链路一次完整的Agent技能故障复盘技术方案讲完分享一个真实的踩坑过程。这个案例很有意思也很典型涵盖了技能系统里三个最容易出的问题技能冲突、错误误导、执行卡死。5.1 故障现象Agent突然不会用分析类技能了某个周五同事跑来说线上Agent出问题了用户输入帮我把这周的分区销售数据分析一下Agent不再调用analyze_sales_data而是直接回复我可以帮您分析但需要您提供数据表格。用户莫名其妙——数据明明之前已经上传过了。我看了一下技能选择记录确实模型在技能选择环节把analyze_sales_data给跳过了。没有报错没有异常就是看不见这个技能。这是最阴险的一类问题。5.2 排查过程从症状反推到根因排查技能问题时我的建议是严格按链路逐层验证不要上来就怀疑模型或者某一行代码。那次排查我按下面四步走的你可以直接复用这套思路第一步确认技能是否注册成功。查看服务启动日志确认analyze_sales_data注册记录存在元数据完整。这一步没有异常。第二步检查该技能的description和参数Schema是否被正确注入。我直接打印了当时发给模型的system prompt和tools声明片段发现了一个问题因为技能数量增加analyze_sales_data被上下文压缩机制排除在了topK之外模型的上下文里根本没有这个技能的完整声明。第三步验证为什么被排除。我检查了这个技能的 embedding 向量和当天新增的几个技能的向量发现新增技能generate_weekly_report的描述里包含了大量销售数据分析这类词汇导致语义上把用户query分析销售数据给吸走了模型选择了技能B生成周报而技能B其实是依赖技能A结果的。第四步也不要把锅全甩给向量检索。我还做了一次手动对比不注入任何背景只把用户query和技能列表做一个简单的语义匹配正确答案也不只一个。这就说明其实不是技能消失而是两个技能的语义边界重叠太严重模型有了看似合理实则错误的选择空间。5.3 根因修复与验证根因清楚了问题核心是技能边界模糊导致的错误路由再加上上下文压缩策略放大了这个错误。修复方案做了两条都不是灵丹妙药但对症下药第一把generate_weekly_report的description改得更加收口明确写本技能依赖analyze_sales_data的分析结果其自身不执行数据聚合和分析。若用户尚未获得分析结果请先调用analyze_sales_data。同时在analyze_sales_data的描述里加一句当用户意图是分析数据而不是生成报告请优先使用本技能。这相当于给两个技能立了护栏。第二把analyze_sales_data加进always_include白名单。因为是核心技能每次请求无论如何都要出现在模型可见范围内避免被上下文压缩误伤。修复后我又重新跑了那100条回归对话集把原先出错的那一档全部修正回来了准确率从78%提到94%。这个94%不是终点但它让我对整个技能系统的路由稳定性有了信心。现在回想这类隐形故障最坑的地方在于它不爆错。系统看起来一切正常模型照样回答只是选错了工具、走错了流程用户只会觉得这个AI变傻了却很难说清哪里坏了。所以排查技能类问题的核心思路就八个字从注册到调用逐层确认可见性。6. 进阶扩展与我的经验体会到这里一个能跑的Agent技能系统已经成型了注册机制、调用链路、技能库管理、排查方法论都有了一套落地打法。最后一节聊三件我最近在做、也确实效果很好的延伸方向。6.1 技能编排让Agent学会多技能协作单个技能说得再多也覆盖不了复杂任务。真实业务场景里用户的需求往往是查数据 → 分析 → 出报告 → 发邮件四个步骤四个技能。怎么让Agent把这四个技能串起来而不是只调一个就停我的做法是引入一个beyblade模式——定义一个任务分解技能这个技能本身不干具体活只负责把用户复杂请求拆成有序子任务然后逐个触发对应的执行技能。比如收到把上周各区销售数据做出对比分析并发给团队后拆成fetch_data(region, date_range)→analyze_sales(data_path, metrics)→generate_report(analysis)→send_email(report, recipients)。每个子任务完成后将结果作为下一个技能的输入参数继续执行。实现上我直接用代码编排而非让模型自由发挥多步调用。为什么模型在一次回复里自作主张连续调用五六个技能每步都要我不停确认上下文这种长程依赖组合的出错率太高了。固定流程编排代码写死每个技能前后依赖模型只需要在单个节点上做选择和参数填充稳定性能到99%。除非你的任务真的要求模型实时动态决定每一步否则大原则是能编排就编排能不自由发挥就不自由发挥。6.2 技能学习与动态更新技能系统跟代码库一样是需要持续维护的基础设施第二件事跟让技能越来越聪明有关。我采用的是技能缓存预加载思路不是让模型在运行时学习新技能而是把经过验证的高频技能提前定义好再通过技能里的adaptive update逻辑动态调整参数默认值。比如fetch_data技能第一次跑时默认时间范围是30天但如果发现业务上一周内90%的查询都是7天我会在技能配置里把默认值改掉不需要改代码。这种机制我做了很多轮效果很顺滑。这块最大的感触是不要指望Agent自己长出新技能。要让技能库真正成长靠的依然是人——一位具备业务判断力的开发者把业务场景拆成可复用的技能单元逐个沉淀。模型只是让这些技能用起来更自然、更会用。6.3 最后想对你说的几句话如果让我把做Agent技能系统这段时间的经验浓缩成第一批的话大概是这三句技能的设计是门槛描述的功夫是深水区。多花时间打磨每个技能的description和Schema比任何花哨的框架都管用。永远不要信任未经验证的技能路由。上线任何新技能前跑一遍回归确认老流程没有被带偏。Agent会做很多事不等于Agent做对了很多事一个稳定调用、边界清晰的技能库才是Agent真正称得上能用的前提。我的项目还在继续迭代比如下一步准备把技能的失败恢复策略做成可配置的某个技能连续失败后自动走替代方案以及给技能加上成本估算让每次调用的token消耗可预测。这条路挺长的但走对方向之后每一次加技能、每一次改描述都能感觉到Agent真的在变靠谱。希望这篇分享能帮你少走我走过的弯路。有什么更好的思路或者更巧妙的技能设计欢迎交流。
返回列表