资讯详情

Agent技能系统设计实战:从Function Calling到可插拔技能体系

📅 2026/9/25 22:13:45 | 华诺云谱 👁 阅读
Agent技能系统设计实战:从Function Calling到可插拔技能体系
一个 Agent 做不了的事从来不是“模型不够聪明”而是“手不够长”。我在实际项目里最深的体会是模型能说出正确的步骤但不一定能执行正确的动作。它知道你该去查数据库、调接口、发邮件但它不知道怎么查、怎么调、怎么发——这中间的鸿沟就是技能系统要填的。最近把 agent-skills 这个项目重新梳理了一遍把我踩过的坑、沉淀下来的设计思路完整记录下来。这套东西适合正在做 Agent 落地、被工具调用不稳定折磨、或者想从零搭一套可扩展技能体系的团队参考。1. 先搞清楚agent-skills 到底在解决什么问题1.1 Agent 的“知识”与“动作”之间的断层大语言模型经过预训练和指令微调之后确实掌握了海量知识推理能力也越来越强。但在真实业务场景里光有知识是不够的。让模型帮你查一下这个月的订单量它得先知道数据在哪个库、表结构是什么、用 SQL 还是 API 查、返回结果要做不做聚合。这一串动作里每一步都不是模型“天生”会做的而是需要外部系统提供能力支持。我见过很多团队在集成 Agent 时的典型误区把所有能力塞进一个超大 function calling 定义里一个函数描述写两百字参数十几个结果模型经常选错工具、传错参数、或者干脆编一个不存在的函数名。原因很简单——模型对工具的选择本质是一次语义匹配工具定义越多、越复杂匹配的精确度就越差。agent-skills 的核心思路就是把“工具”升级为“技能”每个技能带着完整的描述、参数协议、执行逻辑和反馈机制让模型的每一次调用都是一次可解释、可回退、可审计的动作。1.2 技能体系的价值定位连接模型与世界的“标准化接口层”如果只做一两个工具调用根本不需要技能系统直接在系统提示词里写清楚就行。但真实业务一上来就是几十个动作查订单、算优惠、调库存、发通知、解析文件、写报表……这时候就必须要有一套工程化的管理机制。技能体系本质上是在模型和执行环境之间加一层标准化接口层。它管三件事技能的定义与注册、技能的选择与调用、技能执行后的反馈与状态管理。有了这层模型的每一次外部动作都被结构化不会出现“模型自由发挥调用方式”的情况。这也为后续加权限控制、加审计日志、加灰度发布打好了基础。1.3 技能 ≠ 工具函数差在哪很多人觉得技能就是函数换个名字而已。其实差别很大函数是为代码调用设计的入参出参清晰、调用方是程序技能是为模型决策设计的描述信息直接影响模型能不能选对、参数能不能填对。函数只负责执行技能还带着“什么时候用它”“什么情况下别用它”的边界信息。函数的出参是程序可解析的技能的出参既要程序可解析还要模型可理解——因为模型可能要基于上一次执行结果决定下一步动作。函数没有生命周期概念技能需要版本管理、下线和灰度。我打个比方函数是“一颗螺丝”技能是“一个带说明书的标准件”。螺丝只需要规格匹配而标准件除了能装上还得让装配工人知道它用在哪、怎么用、什么情况下不能硬装。Agent 技能系统的设计目标就是让模型这个“装配工人”能够快速准确地用好每一颗标准件。2. 技能的最小组成单元声明、Schema、执行器与回退2.1 四个要素缺一不可我在 agent-skills 里把一个技能拆成四部分静态声明metadata、参数协议input schema、执行器executor和回退策略fallback。静态声明包含技能名称、用途描述、适用场景、调用边界。这部分是给模型读的所以写法非常讲究后面专门说。参数协议定义模型应该传什么参数、每个参数的格式要求、哪些必填哪些可选。执行器是真正干活的部分可以是函数、API 调用、命令行脚本甚至另一个 Agent。回退策略定义执行失败或参数校验不过时怎么办。这四部分里最容易忽略的是回退策略。实测下来一个没有任何失败兜底的技能系统在生产环境里基本撑不过两周——不是技能本身有 bug而是模型的参数生成天然带随机性总有那么 5% 的调用会传一些离谱参数进来。2.2 参数 Schema 的反直觉设计少即是多设计参数协议时我有个反直觉的经验参数能少就少越少模型填写越准。一个技能超过 6 个参数模型填错的概率会显著上升。这不是我瞎说是我自己项目里统计过的——参数超过 8 个的技能调用失败率是 4 个参数以内技能的 3 倍以上。如果确实需要很多参数我建议做个中间层把经常一起出现的参数组合成对象。比如“发送通知”这个技能收件人、标题、正文、优先级、定时时间、附件路径拆成 6 个扁平参数的调用成功率明显低于拆成 recipient 对象 content 对象 schedule 对象这三个嵌套参数的方案。模型更容易按层级结构理解参数之间的归属关系。参数类型也不要搞太花哨。数组、对象、字符串、数字这几种够用了。尽量不要设计复杂的联合类型或嵌套多层的对象模型在生成严格符合 schema 的参数时本身就有一定概率出错schema 越复杂出错概率越大。2.3 描述文本是隐形的第五要素写过 function calling 的人都知道工具描述怎么写直接影响模型的选择准确率。但很多人把描述写成了接口文档全是技术术语。模型的语义匹配靠的是自然语言理解不是关键词匹配。我的写法是“场景驱动式”描述先说明什么场景下用再说明这个技能做什么最后说明不要用它做什么。比如技能名称: query_order 用途: 在用户询问订单状态、物流进度、售后进度时查询订单详情。 适用场景: 用户提供了订单号或手机号需要获取订单的当前状态、商品清单、金额信息。 不要使用: 当用户只是询问“能不能退货”而没有订单上下文时不要主动调用当需要修改订单信息时不要使用此技能。这种描述方式有一个好处它帮助模型做排除法。模型其实很擅长“这个不该我管”的判断前提是你给足边界信息。我甚至在部分技能上做过 A/B 测试加了“不要使用”部分的技能描述误调率能降低 40% 左右。2.4 一个完整的技能定义示例以 Python 项目为例我在 agent-skills 里实现技能的标准结构如下agent_skill( nameget_weather, description根据城市名查询实时天气。当用户询问今天天气冷不冷适合出行吗时使用。, keywords[天气, 温度, 降雨, 空气质量], exceptions不要根据常识猜测天气必须查询后回答。, input_schema{ type: object, properties: { city: {type: string, description: 城市名如北京、上海}, date: {type: string, description: 日期格式 YYYY-MM-DD默认今天} }, required: [city] } ) def get_weather(city: str, date: str None): # 执行真实查询 ...这里有个细节keywords 字段不是给模型做关键词匹配用的而是给技能路由用的。当技能数量超过 20 个时让模型在全部技能里做选择会变慢而且容易出错。我的做法是先做一个轻量级检索用 keywords 和 query 的相关性筛出 top 5 候选再让模型从这 5 个里做最终选择。这招在技能数量多时非常管用后面第 3 节细说。3. 注册表与路由让模型在几十个技能里选对那一个3.1 技能注册表把技能变成可插拔的标准化组件技能系统里必须有一个注册中心否则技能一多就乱套。注册表解决三个问题全局唯一的技能标识、技能元数据的统一管理、技能版本与依赖关系的追踪。我实现了一个轻量级注册表支持在项目启动时自动扫描装饰器标记的技能函数并自动生成技能清单。每个技能在注册时会被分配一个稳定的 skill_id这个 ID 一旦发布不能变否则历史日志和调用链就对不上了。class SkillRegistry: def __init__(self): self._skills {} def register(self, skill: SkillDefinition): if skill.skill_id in self._skills: raise DuplicateSkillError(skill.skill_id) self._skills[skill.skill_id] skill def list_skills(self): return list(self._skills.values()) def get_skill(self, skill_id: str): return self._skills.get(skill_id)注册表的另一个职责是做冲突检测。比如两个技能名称相似、描述雷同注册时就要给出警告。我有一次把“创建订单”和“确认订单”两个技能同时挂上去模型频繁把二者搞混后来加了一致性检查在注册阶段就拦截了描述相似度过高的技能。3.2 两级路由先粗筛再精选技能数量少10 个以内时直接让模型从全量技能里选就行简单粗暴效果好。技能数量 20 个以上必须上路由策略否则模型的选择准确率会明显下降——这是我多次踩坑后的结论。我的方案是两级路由第一级基于召回从全量技能中筛出候选集。可用文本检索或向量检索关键词匹配就行不用太重。第二级让模型从候选集控制在 5 个左右里做精准选择。这个方案大大缓解了模型的选择压力。打个比方让一个人从 100 个商品里挑出你要的品类他容易看花眼但如果先按标签筛到 5 个再让他挑准确率自然高得多。3.3 路由失败的兜底别让模型硬编一个技能名路由环节最容易翻车的一种情况候选集里没有正确技能但模型硬要选一个。这时候模型的“幻觉式调用”就出现了——它会把查询天气这个技能编造成能查航班的样子然后传一堆不存在的参数。我的处理方式有两个。第一个在候选集里永远放一个 reject 选项明确告诉模型“以上都不合适时选这个”。这个设计非常有效等于给了模型一条体面的退路让它不用硬凑。第二个在路由输出阶段做 schema 强校验凡是选了 reject 或参数校验不过的情况一律不调用外部执行器而是回到模型层要求重新解释用户需求。这样至少能保证错误在路由层被拦截不会污染真实业务数据。3.4 路由层还要管的事时效性与权限有些技能不是所有用户都能调用的。比如“发送营销短信”和“删除用户账号”这种敏感操作必须做权限控制。我的做法是在技能定义里增加 access_level 字段路由层根据当前会话的用户身份动态决定哪些技能进入候选集。这样权限管理前置到路由层敏感技能根本不会暴露给无权限的模型调用链。另一个值得注意的点是时效性。有些技能有明确的生效时间段比如促销活动接口只在活动期间开放。这类技能注册时我会带上 effective_period路由层在生成候选集时自动过滤过期或未生效的技能。这避免了模型选了一个已经被下线但注册表里还留着的技能然后执行时报错的情况。4. 技能编排从单技能到工作流的组合模式4.1 顺序、并行与条件分支单技能调用只解决“一步到位的动作”但现实业务大多是“多步动作的组合”。比如用户说“帮我查一下这个订单能不能退能退的话顺便算下退款金额”这个流程至少涉及三个技能查询订单状态、查询售后规则、计算退款金额。技能编排就是把这些技能按业务逻辑组合成一个可执行流程。我的编排引擎支持三种基础模式顺序执行A 完成后执行 BB 的入参依赖 A 的出参。并行执行多个独立技能同时执行等所有结果集齐再走下一步。适合那种查多个维度数据的场景。条件分支根据某个技能的执行结果决定走哪条分支。比如退货规则查询结果是“不可退”就不再执行退款计算直接进入拒绝话术流程。编排引擎里每个节点都对应一个技能调用记录包含输入输出快照方便回溯。“为什么要做快照”因为模型 Agent 的调用链一旦出错没有快照就只能看到最终错误中间哪一步产生了错误值完全不知道。有了快照我能精确到某一个技能的某一次输入参数和返回结果排查效率高一个量级。4.2 技能之间的状态传递别把数据倒来倒去编排过程中最容易出 bug 的是状态传递。技能 A 返回的结果里有个字段叫 order_amount技能 B 需要的参数叫 amount。名字对不上就得写转换逻辑。技能一多这种转换逻辑能把编排层变得臃肿不堪。我的做法是引入“上下文总线”的概念每个技能执行完成后把输出结构化写入一个共享上下文。后续技能在定义时显式声明自己依赖哪个历史输出字段编排引擎自动完成字段映射。如果依赖的字段不存在或类型不匹配编排引擎直接报依赖错误而不是把 undefined 传下去让模型瞎猜。这个设计还有额外好处——可以省掉很多参数。因为技能如果依赖上下文里已经有的数据就不需要让模型再传一遍。模型需要填写的参数越少出错就越少。所以上下文总线不仅是工程组件还是提升模型调用成功率的有效手段。4.3 编排失败时的自我修复策略技能编排一定会遇到失败关键是怎么失败得漂亮。我定了一条原则凡是被编排引擎识别的异常都不能直接抛给用户必须经过修复策略处理。最基本的修复是重试。对于网络抖动、服务临时不可用这类瞬时故障第一次执行失败后间隔几秒自动重试一次成功率能提升很多。这里有个细节重试只适用于幂等技能。“发送邮件”这种技能重试很可能导致用户收到两封一模一样的邮件所以我在技能定义里加了 idempotent 标记只有标记为幂等的技能才会被编排引擎自动重试。更高级一点的修复是替代路径。比如主技能“查询物流轨迹”失败时备选技能“查询快递单号状态”可能也能完成任务。我在技能定义里预设 fallback_skills编排引擎在主路径失败后自动切换到备选技能。这跟程序里的降级是同一个思路只是把它做成了技能系统的标准能力。5. 调试与评测技能系统的两个隐形杀手5.1 参数幻觉的排查链路技能系统上线后遇到最多的坑不是代码 bug而是参数幻觉——模型传了一个明显不存在的参数值但它的表述还非常自信。比如城市的枚举列表里只有北京、上海模型硬给你传个“北方某市”。排查参数幻觉时我的链路是这样的先调执行器日志看模型实际传入的原始参数再检查 schema 定义确认是否有枚举值限定最后回溯会话上下文看模型是从哪句话里推断出这个参数的。实测下来“从哪句话推断”这一步最关键绝大多数参数幻觉都源于模型对上下文信息的过度解读。针对这个根因我在技能 schema 里增加了 source 字段要求声明每个参数应该来自哪里用户直接提供、从上下文推断、还是调用其他技能的返回值。如果模型要填一个用户没提供过的参数并且技能定义里没有标注“可以从上下文推断”就会强制通过反问确认流程向用户提问。这一招我管它叫“不确定性拦截”比让模型自由发挥靠谱得多。5.2 构建技能系统的回归评测集Agent 技能系统最容易出现的问题是“改一个技能崩三个会话”。今天优化了查询技能的描述明天模型的意图识别可能就歪了。要控制这种回归风险必须有一套评测集。我的评测集不是简单的 QA 对而是场景调用追踪。每条测试用例包含用户原始输入、期望调用的技能链、期望的参数值、期望的结果形态。评测时用固定模型跑一遍全流程比对模型实际选择的技能链与期望技能链是否一致参数值是否匹配。这个评测集里的用例来源是真实日志。我会定期把线上调用日志里成功率低的会话抽出来人工标注重构为评测用例。每次修改技能定义、路由逻辑或编排策略后都跑一遍评测集看成功率是否下滑。这套机制运行时间越长评测覆盖越全面系统越稳定。关于评测频率我的经验是小修改只改文案描述也要跑回归因为描述直接影响模型的选择行为。我自己吃过一次大亏——只优化了某个技能的中文表述结果另一个相似技能的被选率从 60% 跌到 20%连带整个流程的成功率降了一截。没有评测集的话这种问题可能要过好几天才被用户反馈出来。5.3 技能版本的灰度发布技能系统上线后一定会持续迭代。但技能的变更不像普通代码——改了路由逻辑影响的不是一次函数调用而是所有依赖这个技能的会话流程。所以技能的变更必须走灰度发布。我的做法是给技能打版本号路由层支持按会话比例或者按特定用户名单分流。比如新版本的查询技能先让 10% 的流量使用观察调用成功率和参数幻觉率和旧版本对比。如果新版本的指标没有显著下降再逐步扩大流量到 50%、100%。灰度发布同时要求新旧版本技能都保持在注册表里并且支持一键回滚。我遇到过一次情况新版本技能为了提升参数召回率把某些必填参数改成了可选结果模型在 30% 的调用里直接不传这个参数导致下游业务报错。回滚到旧版本后指标立刻恢复这就是灰度发布存在的意义。最后再分享一个我个人的体会技能系统的复杂度不需要一步到位。如果你的 Agent 目前只有几个工具调用先用最简单的 function calling 就够了别急着上注册表、路由、编排这些重型设计。当技能数量开始突破 20 个、调用链条开始多于 3 步时再按这套思路逐步引入机制每一步都能看到明确的收益。技能系统的本质不是技术炫技而是让 Agent 的动作层像代码一样可控、可靠、可观测——做到这一点Agent 离真正可用也就不远了。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑