资讯动态

Agent技能层实战:从技能定义到稳定工具调用的完整方案

发布时间:2026/10/14 9:05:17 来源:尧图企业网站定制
搞智能体Agent这件事团队里最容易踩的坑就是把精力全砸在模型选型和提示词调优上结果一接入真实业务Agent就变成一个“会聊天但干不了活”的摆设。我去年带队重构某跨平台系统时把Agent的交互能力、工具调用和业务逻辑彻底拆开沉淀了一套内部分层方案项目代号就叫 agent-skills。这篇文章不聊理论就把我们怎么定义技能、怎么搭技能库、怎么让模型稳定调用工具的完整思路和踩坑记录摊开讲希望能给正在做Agent落地的朋友省几个星期的摸索时间。1. 为什么Agent需要一套“技能层”1.1 先想清楚Agent到底卡在哪大部分Agent demo阶段跑得挺顺一上生产就拉胯根因往往不在模型聪明不聪明而在于模型不知道“有哪些工具可以用、什么场景该用哪个、参数该怎么填”。你把一堆函数塞进系统提示词里模型要么漏看要么看错要么调用了但参数格式不对整体成功率可能只有一半。我们当时的场景是某跨平台系统的智能助手背后要接订单查询、物流追踪、商品库存、优惠计算、售后答疑等十几个不同域的服务。如果每个域都在提示词里写一堆接口说明提示词直接膨胀到爆炸而且不同接口的参数风格还不统一——有的是订单号有的是用户ID加上时间范围模型很容易混。所以核心矛盾不是“模型能力不够”而是缺少一个标准化的技能抽象层把“模型能理解的动作”和“系统能执行的动作”对齐。1.2 技能层到底封装了什么我们给“技能”下的定义很简单一个技能 一段清晰的能力描述 一份参数约束声明 一个可执行的函数入口。模型不需要知道你的数据库在哪、接口走什么协议、鉴权怎么做它只需要看到三样东西技能名字如query_order_status自然语言描述说明这个技能适合处理什么类型的用户请求参数清单每个参数的名称、类型、是否必填、取值范围这三样被包装成一个结构化的 JSON 文档由系统注入到模型的上下文中。用户发来一句话模型做“意图匹配 → 技能选择 → 参数抽取 → 发起调用”整个过程不再靠模型自由发挥而是靠一份明确的协议。这个设计的核心思路是把决策权和执行权解耦。模型只负责“决定做什么、抽什么参数”执行层负责“把参数变成真实调用”两边通过标准接口通信互相不越界。1.3 相比裸写函数调用技能层优势在哪团队里之前有人觉得“直接让模型输出一段 JSON 调函数不就行了搞什么技能层”确实可以但只是应付 demo。真实业务里裸写函数调用有三个致命问题第一接口描述和业务语义混在一起。你告诉模型“GET /api/order?order_idxxx”模型记住了但换个业务域、换套接口风格它又得重新学。技能层把“查订单”这件事本身的语义固化下来接口变了只改执行端模型侧感知不到。第二参数校验没有标准。模型输出的参数经常缺胳膊少腿没有技能层做统一 Schema 校验你每个接口都得自己写防御代码工作量爆炸。第三无法做权限和审计。技能层天然是一道屏障哪些角色能用哪些技能、谁调用了什么技能都能在这层统一管控这在企业级系统里是硬需求。2. 技能库设计的核心参数与细节2.1 技能描述怎么写模型才听得懂技能描述是整个技能层里最考功力的部分。写得笼统模型不知道该在什么时机用写得太长上下文塞不下反而干扰其他技能。我们总结了一条可复用的模板技能描述 触发场景 典型用户表述 能力边界 输出说明举个例子我们有个技能是计算优惠后的订单金额。第一版描述是“计算优惠金额”结果模型经常在用户只问“这个订单多少钱”的时候就调用传参也乱七八糟。后来改成{ skill_name: calculate_order_discount, description: 当用户询问某个订单在应用优惠券、会员折扣、满减活动之后的实际应付金额时使用。典型问法这个订单最后多少钱用了券之后还要付多少。此技能负责所有优惠叠加计算不负责退款和发票也不处理售前咨询。, parameters: { order_id: { type: string, required: true, description: 订单号 } } }改完之后调用准确率肉眼可见地上来了。核心原因是描述里给了正例典型问法和负例不负责什么模型对边界的感知清晰了很多。2.2 参数Schema是命门一定要往死里抠参数定义直接影响两个事模型能不能抽对参数以及后续校验能不能拦得住脏数据。我们的参数 Schema 直接采用 JSON Schema 的子集重点约束四个字段typestring、number、boolean、object、arrayrequired哪些参数必须提供enum枚举值比如订单类型只允许 online/offlinedescription每个参数都要解释含义尤其是易混淆的比如“下单时间”和“支付时间”这里有一个我们趟过的坑参数里不要出现模型无法从用户对话中直接推断的字段。比如某个技能需要内部租户 ID但用户压根不会说“我的租户是xx”这种参数必须由系统在调用时自动填充而不能指望模型猜。否则模型每次都会编一个出来校验直接炸。2.3 技能数量膨胀了怎么办技能少的时候还好一旦超过 20 个、30 个把所有技能描述全部注入上下文Token 开销巨大模型注意力也会被稀释。我们最终的选择是两层技能路由先用一个轻量的意图分类模块把用户请求粗分到某几个候选技能再把这些候选技能的完整描述注入上下文。这个方案实测下来技能选择准确率提升了约 11 个百分点上下文占用反而降了 40%。代价是多了一次内部调用但对整体时延影响很小。如果你团队没有单独的意图模型也可以用 Prompt 让主模型先输出候选技能列表再走第二步精排。两种方式都可行关键是别把所有技能一股脑塞给模型。3. 实操从零搭一套最小可用的技能调用链路3.1 步骤一定义技能清单文件先建一个技能清单skills.json所有技能定义统一放这里方便审计和版本管理。每项技能的结构保持绝对一致绝不允许执行端各写各的。[ { skill_name: query_stock, description: 当用户询问某商品是否有货、库存数量时使用。典型问法这个还有货吗库存多少。仅接受标准商品ID。, parameters: { product_id: {type: string, required: true, description: 标准商品ID}, warehouse_code: {type: string, required: false, description: 仓库编码不传则默认全部仓库} } }, { skill_name: estimate_delivery, description: 当用户询问某个订单何时送达、物流进度时使用。典型问法什么时候到物流走到哪了。, parameters: { order_id: {type: string, required: true, description: 订单号} } } ]这个文件就是技能的“字典”模型看到的、执行端加载的、人去 review 的全是这一份。3.2 步骤二写一个极简的技能执行框架核心是做一个通用的SkillExecutor负责把模型的技能选择结果翻译成真实函数调用。代码结构大致如下# 简化演示实际项目里会再加配置中心、限流等 class SkillExecutor: def __init__(self): self.registry {} # skill_name - callable def register(self, skill_name, func): self.registry[skill_name] func def execute(self, skill_name, params, context): if skill_name not in self.registry: return {error: unknown skill, skill: skill_name} try: result self.registry[skill_name](**params, contextcontext) return {ok: True, result: result} except Exception as exc: # 关键把错误吃进去返回给模型让它自己修正 return {ok: False, error: str(exc), retryable: True}注意retryable这个字段。执行失败时系统会把错误信息拼装成一段反馈文本回传给模型模型根据反馈可以修正参数或者换一个技能而不是直接给用户报错。这比让模型一次成功的容错率高很多。3.3 步骤三跑通一次完整调用链路并记录日志我们随便模拟一个用户请求“这个手机壳还有货吗什么时候能送到”链路走一遍意图路由层识别出候选技能query_stock、estimate_delivery系统把这两个技能的完整定义注入上下文模型判定需要先调query_stock参数为{product_id: P10023}执行端调用库存服务返回“华东仓有 58 件”系统把执行结果格式化成模型可读的反馈库存查询成功华东仓 58 件模型继续推理认为还需要调estimate_delivery来回答“什么时候送到”但此时缺少order_id于是模型回复追问“请提供订单号”用户补上订单号模型再调estimate_delivery最终组合成一段自然语言回答整条链路里最重要的不是每一步多高效而是每一步的输入输出都被结构化记录。我们在该环节同步落了一份完整的 JSON 日志用户原始输入、候选技能排序、最终技能选择、抽取参数、执行结果、模型追问内容全部有迹可循。后来排查线上问题这份日志救了大命。没有它模型偶尔犯浑时你根本不知道它在哪一步开始跑偏。4. 技能编排让多个技能协作而不是各自为战4.1 单独技能好做组合起来才见功力真实场景里用户很少只触发一个技能。顺着上一个例子往下走用户问“手机壳有没有货、什么时候能到”实际上涉及“库存”和“物流预测”两个技能而且有先后关系——如果商品根本没货物流预测就没有意义。我们刚开始直接让模型“自由发挥”组合技能结果发现它经常把两个技能并行调用或者在不该调用的顺序上调。后来引入了一个轻量级的编排声明在技能清单里增加一个workflow字段描述技能之间的依赖关系。{ workflow_name: stock_and_delivery, steps: [ { skill: query_stock, then: check_stock_result }, { condition: stock_result.available true, then: estimate_delivery } ] }这里不需要上复杂的流程引擎一个简单的条件判断就够用。核心是让执行端对执行顺序有把控而不是完全交给模型临场发挥。模型仍然负责“识别意图、抽取参数”但执行顺序由编排层兜底这样即使模型偶然抽风也不会出现“没货还在预估配送时间”的离谱回答。4.2 编排层要考虑回退策略编排不是只能顺序执行。我们压测时遇到过一种情况主技能调用失败模型尝试修正参数但修正两三次还失败这时不能让 Agent 无限重试。我们在编排层加了两条兜底策略第一单技能最大重试次数默认 2 次超过后走人工转接或统一话术兜底。第二技能降级。比如查库存的主数据源超时可以降级到备用的缓存数据源再不行才返回失败。这个机制在促销大促场景特别有用高并发下主接口大概率会抖。通过这些策略技能编排从“看起来能跑”变成了“压力下能扛”。顺便说一句线上大促那天这套兜底拦截了约 76% 的技能调用失败几乎没有让用户感知到异常。4.3 上下文管理是编排的隐藏关卡多技能串联之后模型上下文里会堆入中间过程的执行结果。如果执行结果是一张实时库存大表几千字直接塞回去第一是 Token 开销爆炸第二是模型读完大表可能反而忘了主线。我们的做法是对每次技能执行结果做摘要格式化只保留模型回答用户所需的精简信息。比如库存接口返回 20 行明细摘要成“华东仓有货58件华南仓缺货华北仓有货12件”物流预测返回一大段 JSON摘要成“预计 3 天内送达”。这样模型拿到的是可以直接用的信息推理质量会更稳定上下文长度也一直受控。5. 常见问题与排查技巧实录5.1 技能调用一直不准的排查顺序如果模型频繁不调用技能、或者选错技能别急着调 Prompt。按这个顺序排查大概率能找到根源先查技能描述是否包含负例边界。很多情况下模型选错是因为两个技能描述的场景高度重叠负例能帮它区分。再查参数是否都能从对话中直接提取如果参数依赖系统侧信息而你没有做自动填充模型就只能瞎编。最后查候选技能数量超过 10 个候选模型的选择准确率会明显下滑优先考虑加路由层。我们实际线上问题的分布大概是约一半是参数问题三成是描述不清两成是路由不合理。顺着这个比例去排查效率会高很多。5.2 模型自己修正时越修越错怎么办技能执行失败后我们会把错误信息回传给模型让它可以重试。但有些错误信息太长太专业比如“连接池超时、SQLSTATE 08006”模型根本看不懂反而会对着错误信息瞎猜参数越修越离谱。解法是把错误信息翻译成模型能理解的语言。执行端抛错时不要直接回传原始异常而是丢一个结构化的小标签参数缺失、参数格式错误、依赖服务不可用、业务校验拒绝。每种标签配一句修复建议比如参数缺失就提示“请向用户说明需要哪些信息并等待用户补充”。这套机制上线后模型自动修正的成功率从 62% 提到 81%用户被追问补参数的体验也比直接报错好得多。5.3 上下文被塞爆的补救手段多技能组合场景下就算做了摘要长时间对话里上下文还是会慢慢累积。我们设置的防线是滑动窗口摘要对话超过一定轮数后系统把早期的用户提问和技能调用记录压缩成一段几百字的历史摘要只保留用户意图和时间线丢掉详细参数和执行结果。实测这样处理后长会话的技能选择准确率只掉了约 3%Token 开销却省了一半以上。如果你的 Agent 主打一次性问答可以忽略这一步但只要涉及多轮对话上下文管理迟早要做越早越好。5.4 常见问题速查表现象排查方向推荐解法模型完全不调用技能技能描述太抽象或没有触发场景补典型问法和负例边界技能调对了但参数缺参数必须全部能由用户语句推断系统侧字段自动填充不让模型猜选了错误的技能技能间描述相似度过高强化负例区分或拆分技能粒度多技能串联错序执行顺序完全交给模型引入 workflow 编排声明上下文越滚越大缺少中间结果摘要执行结果摘要化 滑动窗口历史压缩失败重试几次仍失败错误信息模型无法理解结构化错误标签 修复建议6. 一点实操经验与扩展建议整个 agent-skills 项目从立项到上线我个人最大的体会是技能层的核心不在技术而在边界感。什么时候让模型决策什么时候执行层兜底这套边界的划分决定了系统是稳定可用还是偶尔抽风。初期我们会把所有重担都压在模型身上结果就是整天救火后来把边界划清模型的脆弱性被限制在很小范围内整体可用性一下就上来了。如果再给我一次机会我会把技能评测体系更早做起来。我们现在有一套回归用例集里面记录了近千条历史用户语句和期望的技能调用链路每次技能定义改动后自动跑一遍回归这一步极大降低了改 A 技能弄坏 B 技能的概率。建议你在搭技能层时同步把这份测评集建起来越往后收益越大。后续我们正在做的一件事是把技能层的方法论平移到更多的业务团队让每个团队可以用一套配置界面自己维护技能定义不需要每次改技能都走一次研发发版。等跑顺了我再把配置化运营的经验整理出来分享。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑