ARTICLE DETAIL

资讯详情

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

Agent技能体系实战:解决工具调用混乱与参数误填的完整方案

Agent技能体系实战:解决工具调用混乱与参数误填的完整方案 2. 项目整体设计与思路拆解1. 这个项目到底在解决什么问题我是在一次实际交付中意识到“技能”这件事必须要独立成体系的。当时团队做了一个带工具调用的智能体功能越堆越多工具列表从十几个涨到六十多个结果发现智能体越来越“不会干活”——用户问一句“这周的销售数据怎么样”它可能翻遍所有工具也拉不对报表用户说“帮我盯着这个页面价格变了提醒我”它完全不知道该调哪个能力。排查下来问题很清晰工具是一个个孤立的功能点它们没有上下文没有触发条件说明没有参数约束更没有“什么场景下该用我”的边界感。把几十个工具像一串钥匙那样挂在腰上智能体确实能摸到每一把但它不知道哪把能开当前这扇门。后来我在维护内部一个自动化助手时把工具重新组织成了“技能包”也就是给每个能力配一份完整的说明——它解决什么问题、什么时候该被调用、参数怎么填、边界在哪、失败时怎么处理。这套东西跑了大半年我再回头看这次改造核心收获就三条智能体不再“什么都想试一下”调用成功率明显上去了新增能力时也不用把老逻辑反复推倒重来。我把它沉淀成了一个叫做 agent-skills 的项目。简单说它就是一套“给智能体定义、组织、使用技能”的规范加实现框架。谁需要它凡是你在做智能体应用、自动化脚本、带工具调用的AI助手或者说团队里多个人在往同一个Agent里塞新能力你大概率会踩到和我一样的坑那这套东西能帮你把“能力管理”这件事从一团乱麻理成一张清晰清单。1.1 为什么不能只靠 Function Calling不少刚接触这个方向的人会问我现在大模型厂商都支持 function calling我直接把函数列表传给模型让它自己选不就行了理论上可以但你在生产环境跑一段时间就会发现问题。函数列表本质是“平铺的”。每个函数只有名字和参数 schema模型面对几十个函数时靠的是参数名和描述去猜意图。同一个功能你可能写了三个变体模型分不清它们的使用边界某个函数只在特定业务场景下有效模型却可能在无关对话里强行调用它。这不是模型能力不行而是你给它的信息密度不够它根本无从判断。技能系统的差别在于它把“什么时候可以用”作为一等公民来定义。每个技能不仅描述“我能干什么”更强调“什么场景下你该想到我”“什么时候你千万别用我”。模型不是在几十个平铺选项里碰运气而是先通过意图层做一次筛选再决定要不要进入某个技能。这一步筛掉大量误调用。注意我不是说 function calling 没用而是说它更接近“机制”技能体系更接近“方法”。生产环境里真正稳定的是“方法”这一层。1.2 技能、工具和插件的边界很多人会混淆几个概念我在这里一次性说清楚。工具Tool是最小可执行单元通常是一个函数或一个 API 调用负责做一件具体的事比如“获取指定地区的天气”。插件Plugin是工具的集合通常围绕某个平台或产品打包比如“飞书插件”里包含发消息、拉会议、查日历一堆能力。技能Skill则是面向“任务完成”的能力封装。它可以内部调用多个工具也可以自己包含完整的处理逻辑。核心区别在于技能带“判断层”它会根据当前对话的上下文、用户意图、前置条件决定自己是否应该被触发以及具体怎么执行。用一个通俗的类比工具是一把螺丝刀插件是一个装满各种螺丝刀的工具箱技能则是“知道什么螺丝该用哪种刀头、正确姿势是什么、拧完怎么检验”的老师傅经验。你想要的是一个经验丰富的老师傅而不是一箱子好工具。agent-skills 把这三层都包含进来最底层是工具封装中间是技能组织上层是调度逻辑。我先定义好每个技能的“身份信息”再让 Agent 在身份信息的基础上做选择和执行。后面几节我会具体展开。3. 核心细节解析与实操要点2.1 技能包的文件结构怎么搭一个技能包就是一个目录我把它叫做 a-skill 包。每个技能包内部结构刻意保持精简不搞花活就五个部分skills/ date-info/ SKILL.md code/ main.py requirements.txt tests/ test_basic.py assets/ examples.jsonSKILL.md 是这个技能包的大脑没有它技能代码就是普通脚本。它里面写清楚这个技能是谁、什么时候用、参数怎么填、边界在哪、失败怎么办。code/ 目录放实际执行逻辑Python、Node、Shell 都行只要能被调度器拉起。tests/ 目录放回归用例这是后续技能改动的安全网非常重要。assets/ 放示例、模板可选。我强烈建议每个技能包都配 requirements.txt。哪怕你只是用 Python 标准库也写上哪怕就一行requests。因为技能包一旦多起来依赖管理会变成灾难。我现在维护三十多个技能包如果当初没从一开始做隔离后面每次新增依赖都是一次全量环境体检想想都头大。2.2 SKILL.md 怎么写才是关键这是整个 agent-skills 体系里最值得花时间的地方。我把 SKILL.md 的核心字段列一下每个都来自实际踩坑后的迭代不是拍脑袋想出来的。name技能名要短要唯一要一眼知道它是干什么的。比如date-info比date_and_week_info_query_service好太多。长名字在日志里占地方在模型上下文中也占 token。description一句话说明功能。我强调“一句话”如果你发现要用三句话才能说明白一个技能是干嘛的大概率是技能边界没划好应该拆。when_to_use什么场景触发这个是重中之重。写触发条件时一定要具体不要写“用户想了解日期信息”要写“用户询问今天的日期、星期、农历、节假日或者给定日期换算星期、计算相隔天数”。越具体意图匹配越准。when_not_to_use什么场景下千万别用。这个字段是我后来加的效果出奇的好。比如日期技能里写上“如果用户只是提到时间但实际要求的是定时任务的调度不要使用本技能”误触发率立刻降一截。parameters参数列表。每个参数都要写类型、是否必填、默认值、取值范围。参数设计要克制能少则少能用默认值兜底就绝不搞成必填。examples典型调用链示例。给两三个真实对话示例让模型知道“用户这么说 → 技能这样响应”的映射长什么样。这一步对模型理解技能触发边界的效果非常直接。failure_modes这个技能常见的失败情况以及处理方式。比如网络请求失败、参数解析不到、目标页面结构变了分别该怎么兜底。我当时每写完一个 SKILL.md都会读给旁边的同事听如果他听完能立刻说出“这个技能什么时候会被触发”说明描述合格。如果他一脸茫然或者追问细节说明还不够具体继续改。2.3 技能描述的字数也有讲究初始版本我恨不得把每个技能写成一篇文章结果发现模型根本不会逐字读。后来做了压缩实验结论是when_to_use 保持在50到150字之间效果最好description 控制在30字以内examples 给3个左右就够。为什么是这个量级因为模型在做工具/技能选择时本质上是在一个受限上下文里做注意力分配。你给的信息太多关键触发条件反而被淹没。你给的信息太少模型只能靠猜。50到150字是实测下来“既能覆盖关键边界又不会稀释注意力”的甜点区间。描述里的动词也有讲究。我建议统一用“询问”“查询”“获取”“计算”这类动作开头让模型一眼识别出这是一次“主动执行型任务”而不是闲聊。描述里如果出现“帮助用户”“为用户提供”这类措辞模型有概率把技能当作一段知识而不是一个操作入口调用率会掉下来。2.4 参数设计要收敛、要容错参数是技能包和模型之间最容易扯皮的地方。模型侧的天性是“你给我几个字段我尽力填满”所以参数越少模型填错的概率越小。我这个项目里真实例子日期技能初始版本有五个参数year、month、day、format、locale后来砍到两个query、date_string效果反而更好。为什么因为模型根本不需要精确拆出年、月、日它只需把用户原话丢给技能包由技能包内部用日期解析库自己去拆。这个思路是“把判断放回技能内部”不要让模型去做它不擅长的高精度字段拆分。参数容错也很重要。参数解析失败不要直接抛异常先尝试智能修正。比如date_string传过来“明天”“下周一”“12月25号上午”技能内部要能处理这些自然语言表达而不是只认标准格式。这部分的实现我会在下一节详细讲。4. 实操过程与核心环节实现3.1 从零实现一个技能包以“日期信息查询”为例我拿这个项目里的一个真实技能包来完整走一遍流程。它不算复杂但能覆盖从定义、实现到注册的全部路径。先写 SKILL.md--- name: date-info description: 查询日期、星期、农历、节假日信息支持日期换算 when_to_use: 用户询问今天是几号、星期几、农历日期、节假日安排或者要求计算两个日期相隔天数、给定日期推算星期 when_not_to_use: 用户要求设置提醒、定时任务、日程安排时不要使用本技能应交给日程管理技能 parameters: query: string, 必填, 用户关于日期的完整表述例如今天、下周一、2025年的春节是哪天 locale: string, 可选, 默认zh-CN, 支持的语言区域 examples: - user: 今天星期几 result: 调用date-infoquery今天 - user: 2025年的国庆节距离现在还有多少天 result: 调用date-infoquery2025年的国庆节 failure_modes: - 日期解析失败返回支持的自然语言格式提示让用户换一种表述 - 农历数据缺失明确说明只支持公历不让模型自己编 ---为什么 example 里只给两条前面说了给太多反而稀释注意力。两条已经能覆盖“今天是几号”和“指定日期推算”两个最重要的场景路径。真遇到更偏门的需求模型会查 when_to_use 字段来兜底。然后是实现代码。这里我故意没有让模型去拆解结构化的日期参数而是把整个 query 原文传给技能包由技能包内部做解析。这样模型只需做好一件事把用户原话抄进 query 字段。这是最容易做对的也是大部分同类项目参数出错最大的来源。# code/main.py import re import datetime from dateutil import parser def parse_query(query: str) - dict: 将自然语言日期描述转换为结构化日期对象 today datetime.date.today() if query in (今天, 今日, now): return {date: today} if query.startswith(下周): # 下周一 → 下周一日期 weekday_map {一: 0, 二: 1, 三: 2, 四: 3, 五: 4, 六: 5, 日: 6, 天: 6} for char, weekday in weekday_map.items(): if query.endswith(f周{char}) or query.endswith(f星期{char}): days_ahead weekday - today.weekday() if days_ahead 0: days_ahead 7 result today datetime.timedelta(daysdays_ahead) return {date: result} # 兜底交给 dateutil 做宽松解析 parsed parser.parse(query, fuzzyTrue) return {date: parsed.date()} def run(query: str, locale: str zh-CN): result parse_query(query) # 这里继续根据 result 里的日期对象计算星期、农历、节假日信息 # 返回结构化结果后续由上层按模板格式化为文本 return {date: str(result[date])}这个技能包的核心策略是“模型只负责传话、技能自己负责理解”。实现里把常见的时间表达今天、下周一先做硬规则映射再用 dateutil 做宽松解析兜底。硬规则保证准确率宽松解析保证覆盖率。3.2 技能注册与调度链路技能包有了之后怎么让 Agent 真正用起来在 agent-skills 里我做了三层注册机制。第一层技能注册表。这是所有技能包的索引记录技能名、路径、描述、版本。每次启动时扫描 skills/ 目录自动拉取最新的 SKILL.md 索引不需要手工维护列表。我把这个扫描逻辑放在一个registry.py里本质上就是遍历目录、读元信息、组装成一张 skill table。第二层意图匹配。当用户输入进来先做意图识别判断当前请求需要进入哪个技能域。这一步可以调用大模型做语义匹配也可以先用轻量规则过滤掉明显不相关的技能。我的实际策略是把 SKILL.md 里的 when_to_use 和 when_not_to_use 组装成一小段文本让模型在运行时做匹配。实测下来把 when_not_to_use 放进匹配上下文里误匹配率能降一半以上。第三层参数填充与执行。确定技能后把用户原话和技能参数 schema 一起交给模型让模型按 schema 填参数。但参数填充这一步我还是建议保留一层校验必填参数缺失时可以尝试从原话里做一次关键词抽取兜底解析不到就让模型向用户追问不要默认填一个错误值。这三层链路看起来多一层跳转好像比直接 function calling 多了一道手续但换来的是每次调用之前都有人先看一遍“这个技能合不合适”。我在这套体系里跑了上千次调用后对比过直接 function calling 的调用准确率大概 75% 左右加了技能注册与意图匹配后能到 90% 以上。代价只是多了一小段模型上下文换来的收益相当值。3.3 技能之间的调用与冲突处理技能不是孤岛。你在生产环境里一定会遇到一个任务横跨多个技能的情况比如用户说“帮我查一下这周的销售数据然后生成一份周报发到邮箱”这就涉及数据查询、报告生成、邮件发送三个技能。我处理跨技能调用的方式是技能内部不直接调用另一个技能而是把任务拆解后交给上层调度器。调度器维护一个依赖关系图知道“数据查询技能产出的结果可以作为周报生成技能的输入”。拆得足够细以后技能之间不要互相依赖而是各自做一个纯步骤让调度器像流水线一样把它们串起来。冲突问题更隐蔽。假设你有两个技能都能处理“查天气”类的需求一个调用第三方天气 API一个走内部自建数据源这时候模型很容易随机挑选或轮询用户每次拿到的结果可能都不一致。我的经验是同一类型的技能只保留一个默认项把其它设置为备用项并在 when_to_use 里写明“默认不选择本技能除非默认技能连续失败两次”。这是最简单也最有效的冲突消解方式比在调度器里写复杂路由规则更可靠。5. 常见问题与排查技巧实录4.1 技能“躺在注册表里但从不被调用”这是我收到最多的求助。现象是技能包在 registry 里SKILL.md 写得也没问题但 Agent 就是不碰它。排查路径我一般固定走三步。第一步检查 when_to_use 是否太窄。很多人在这个字段里写的是内部实现细节比如“当用户咨询中以 python 字符串格式提供 UTC 时间戳时”这种描述在模型眼里根本抓不到“用户真实诉求”的入口。改成“用户询问某个时间点对应哪个时区的当地时间”之后调用率立刻上来了。第二步检查 examples 和真实用户说话方式是否对得上。比如你 examples 写的是“请问今天天气”但真实用户说“今天出门要不要穿外套”模型对后者匹配不到你的示例。这时候要去翻线上日志把真实对话样本补充进 examples模型才能建立映射。第三步也是最容易忽略的检查是不是有别的技能在“截胡”。如果你的两个技能描述区域高度重叠模型每次都选了另一个。这时候你要么给当前技能重写 when_not_to_use明确把它排除在外要么调整另一个技能的优先级。我先排查优先级再考虑改描述因为优先级只需要改配置回滚也快。注意改完 SKILL.md 以后一定要跑一遍该技能的回归测试确保新增的描述没有破坏已有触发路径。这一点我是吃过亏的本来想提升一个技能被选中的概率结果描述改动后连原本能命中的场景都丢掉了。4.2 技能被调用了但参数填得驴唇不对马嘴这个问题的根子往往不在模型在参数设计。我见过太多技能包的参数列表里有一堆可选字段模型热情地全填了其中一半是错的。我的排查顺序是先看参数是不是过多能不能通过“传原文让技能内部解析”来替代模型解析。日期技能就是这么改的删了四个参数只剩 query问题直接消失。再看必填参数是否真的必填。有时候某个参数有很好的默认值你却把它设置成必填模型在没拿到值的情况下只能硬编一个这必然出错。最后看参数说明写得好不好。说明越具体模型填对的概率越高。不要写“日期用户提到的日期”要写“日期用户原话中出现的日期表述保留原样传入不要转换格式比如2025年1月1日直接传2025年1月1日”。4.3 技能执行成功了但 Agent 不会用结果这是最容易被忽视的一类问题。技能包返回了结构化结果模型也成功拿到了但它不能把这个结果组织成好回答的答案。我见过的情况是技能返回 JSON模型把 JSON 原样抛给用户也不解释含义。问题不在模型表达能力而在于技能返回结果时没有附上“该怎么解读”的说明。我在技能包的返回结构里加了一个human_readable字段让技能内部直接把 JSON 结果格式化成一段用户友好的文本并且附带“结果解读建议”。调度器把这段文本连同原始数据一起交给模型模型在生成最终回复时有据可依就不会把 JSON 甩给用户了。这个细节我强烈建议每个技能包都做到。你可以在技能包内做一个format_result函数它就是纯粹的文本模板渲染把结构化数据填进去输出一段自然语言。成本很低收益是每次调用体验都会稳定提升。4.4 技能升级后老场景开始出问题技能包更新是常态但每次改动都有回归风险。我早期几乎每个月都遇到“上一个版本好好的这版改完反而不会用了”。现在我在 agent-skills 里强制推行一个规矩每个技能包的 tests/ 目录至少要覆盖该技能全部 when_to_use 场景的 80%。测试用例就是用 SKILL.md 里的 examples 做原型构造 user 输入跑一遍技能链路校验输出里包含期望的关键信息。每次改动技能包先跑测试绿了再上线。这个习惯养成了技能包迭代就会相当舒服。比如我改日期技能的农历显示逻辑只改底层函数只要测试覆盖到位我根本不担心它会破坏已有的“今天是周几”之类的老路径。这也是整个项目里我最建议大家早期就上的一项机制。6. 兼容性与扩展从单一Agent到技能市场5.1 技能包如何跨Agent复用我最初整理这套技能包的时候只服务一个 Agent后来公司内部好几个项目都想用这些能力。如果每个项目各写一套技能实现重复开发和维护成本会不断积累。于是我把技能包抽象成与 Agent 无关的纯功能包技能包内部不感知上层的 Agent 类型不直接和某个特定 Agent 的会话上下文耦合只接收参数、执行逻辑、返回结果。这样同一套技能包可以被不同的 Agent 加载只要各自的调度器能解析 SKILL.md 即可。为了让这个复用更顺滑我还给每个技能包加了一个 manifests 字段标记它适用的运行环境比如“适用于对话助手”“适用于批处理任务”。不同 Agent 在启动扫描技能目录时按自己的需要做过滤只加载自己适用的那一批。5.2 技能包版本管理与发布技能包多了之后社区化协作是必然方向。我把每个技能包视为一个独立的小项目有自己的版本号。版本号遵循语义化主版本表示行为不兼容的改动次版本表示新增能力补丁表示修复。当技能包被多个 Agent 引用时依赖关系会变得复杂。我的策略是Agent 配置里锁定技能包版本区间比如date-info1.0.0,2.0.0当技能包有重大改动时先在一个预览区放新版本跑一轮所有下游 Agent 的回归测试确认全部通过以后再提升到稳定版本。这个流程和普通软件发布的思路完全一致只是对象从代码库变成了能力包。5.3 基于技能包构建内部技能市场当技能包做到三十个以上时我发现新的问题大家不知道同事已经做过什么技能于是反复做重复的轮子。我决定搭一个内部的技能市场页面每个技能包就是一个条目展示 SKILL.md 摘要、评分、调用次数、最近更新时间。这个市场的核心价值不是展示而是“复用前的筛选”。同事想加新技能时第一件事去市场搜一圈确认没有类似技能才新建。如果找到类似技能但缺某个能力先看看能不能给现有技能包提需求或做增强而不是另起炉灶。半年跑下来重复建设的数量降了一大截。如果你一个人维护技能包市场可能用不上但至少可以维护一份技能清单文档定期回顾哪些技能可以合并、哪些场景没有被覆盖。做 Agent 能力建设这件事本质和打理一个产品库没有区别需要的是持续迭代而非一次性堆量。7. 一些实操后的体会整套技能体系搭完我自己最大的感受是把“让模型更聪明”这件事转换成了“让模型的装备更整齐”。你在模型层面解决不了工具乱调、参数乱填的问题你只能在技能组织层面把这些坑提前填平。最开始我觉得这是在做一件很麻烦的事毕竟多了一堆 SKILL.md 要写、测试要跑、版本要发。但跑顺了之后新增技能的边际成本其实很低——因为模板已经定好测试路径已经铺好调度逻辑已经稳定新技能只是往这套流水线上再加一个工位。如果你正在做 Agent 相关的项目我的建议是不要等到工具列表混乱了才开始整理技能体系从第一个能力开始就按技能包的规范来写。前期多花一个下午把 SKILL.md 写清楚后面能省下无数次调 prompt、查日志、修误调用的时间。这套规范本身不复杂但它真正要求你把每个能力想清楚它是给谁用的什么时候该出现什么时候该退场。想清楚这些Agent 的表现自然会更可控。
返回列表