news 2026/10/14 9:25:56

Agent技能体系搭建实战:从函数调用到稳定可靠的agent-skills

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent技能体系搭建实战:从函数调用到稳定可靠的agent-skills

做 Agent 应用以来,我最大的体会是:模型本身决定不了一个 Agent 能干什么,真正决定它能不能干活、干活干得稳不稳的,是你给它配的那套技能体系——“agent-skills”。我见过太多团队把提示词调得天花乱坠,结果 Agent 一执行具体任务就露馅:要么调用错工具,要么参数传得乱七八糟,要么一个简单操作要走五六轮对话。问题不在模型,在于技能层没设计好。这篇文章想把我在实际项目中把“agent-skills”从无到有搭起来、踩坑之后又反复打磨的过程完整写出来,包括技能拆解的思路、注册与调用的完整流程、真实场景里的失败案例和排查方法,适合正在做 Agent 应用开发、想把工具调用从“能跑”做到“好用”的工程师参考。

1. 先想清楚:技能库和“一堆函数”到底差在哪

1.1 模型负责“想”,技能负责“做”

很多人第一次接触 Agent 开发时,会下意识地把它理解成“给模型挂几个函数,让它按需调用”。这个方向没错,但太粗糙。模型是一个“意图理解器”,它能把用户的话翻译成“应该调用什么能力”,但它本身不会真的替你去查询数据、操作文件、调用外部服务。真正执行这些动作的,是你提供的技能。

那技能和普通函数有什么区别?普通函数是给人写的,调用方是人类程序员,只要函数签名清晰,人就能看懂怎么用。技能是给模型写的,调用方是语言模型,它不像人那样能自动理解“这个参数到底该填什么”“这个返回结果的格式意味着什么”。技能必须额外携带一层“让模型正确理解和使用”的上下文:什么时候该选我、参数怎么填、返回结果怎么解读、出错时怎么办。

这个区别听着小,实际影响巨大。我最初照搬以前写 SDK 的习惯,把十几个功能直接导出成函数列表丢给模型,结果模型在真实对话里频繁选错、漏参、幻觉出根本不存在的参数。后来我才意识到,我缺的不是函数,而是一套“技能说明书”。

1.2 技能、工具、插件的边界

在团队协作时,大家经常把技能(Skill)、工具(Tool)和插件(Plugin)混着说,其实在 agent-skills 的体系里,它们各有侧重。

工具最底层,单个可执行能力,比如“发送HTTP请求”“读写本地文件”“调用某个内部API”。它做的是原子操作。插件偏应用层,指多个能力的打包分发形式,通常由第三方提供,比如“一个数据处理插件”可能包含格式转换、字段抽取、去重等多个底层能力。技能则介于两者之间,强调“面向任务的完整能力单元”,它不仅包含调用某个工具或函数,还包含触发条件、参数说明、后处理逻辑和错误恢复策略。

可以这样理解:一个技能可以内部调用多个工具,也可以组合多个技能完成更复杂的任务。技能是模型和底层执行能力之间的一层标准化接口。设计技能库时,如果只盯着“函数”维度,很容易做成一堆碎片;如果盯着“插件”维度,又容易粒度太大,模型不好组合。而“技能”是这中间最合适的抽象层。

1.3 一份技能描述就是一份“使用说明书”

我在项目里反复强调一句话:技能描述不是写给开发文档的,是写给模型看的说明书。你要想象使用这个技能的是一个个性和理解偏好各不相同的模型,而不是一个训练有素的工程师。说明书写得好不好,直接决定它会不会在正确时机被选中。

写技能描述有几个常见坏习惯:一是只写“这个技能做什么”,不写“什么场景下应该用”“什么场景下不应该用”;二是参数说明含糊,模型不知道从哪里取值;三是描述里塞了过多业务细节,反而淹没了最关键的功能声明。

我自己在迭代 agent-skills 时,把技能描述逐渐收敛成四个固定部分:技能的目标与适用边界、触发该技能的自然语言场景示例、参数说明与取值规则、返回结果的格式与含义。每个技能的描述稳定在 100 到 200 词之间,太短说不清,太长会把模型的选择注意力带偏。

2. 技能拆解实操:把业务能力翻译成模型能理解的东西

2.1 从用户需求反推技能清单

技能库最忌讳“先堆功能,再想场景”。正确做法是从真实用户请求出发,先收集一批代表性的用户输入,再倒推出需要哪些技能。

我在做一个企业信息整合助手时,先整理了用户经常提的问题,包括“查一下这家公司的基本情况”“对比一下两个产品的公开评价”“整理一份某个行业的近期动态”,然后把每个问题拆解成可执行的子能力。比如“查公司基本情况”需要搜索引擎检索、网页正文提取、关键信息抽取及结构化;而“对比两个产品评价”则需要批量采集评论、情感判断、对比维度归纳。这样拆下来,技能清单就非常自然地诞生了。

一个常见误区是拆得太细。比如把“网页正文提取”和“网页标题提取”分别设为两个技能就没有必要,它们完全可以合并为“网页信息采集”。拆得太细会增加模型的选择负担,也让技能库膨胀难以维护。我会以“用户可感知的一个完整任务结果”为粒度边界,一个技能交付一个相对完整的结果,而不是一个中间步骤。

2.2 三要素:名称、描述、参数

agent-skills 里每个技能被我定义成三条核心元信息,这三条信息是模型决策的关键输入。

第一个是技能名称。名称必须简短且有区分度。项目里出现过“信息检索”和“资讯搜索”这种含义接近的两个名字,模型经常选错。后来我把它们改成“精确关键词检索”和“实时资讯搜索”,模型才能准确区分。名称里最好带上使用场景或方式限定词,不要用过于通用的动词。

第二个是技能描述。在模型通过打分或语义匹配选择技能时,描述是影响最大的字段。描述要说明三个问题:这个技能解决什么问题、适合哪些输入、不适合哪些输入。我习惯在描述结尾写一个“当用户提到XXXX时使用,但如果用户只是需要YYYY请不要用本技能”这类排除式句子,实践证明对降低误调用非常有效。

第三个是参数定义。参数要明确类型、取值范围、默认值以及缺失时的处理策略。尤其要注意的是,模型在抽取参数时不一定准确,所以每个参数都要写清楚“从哪里获取”。比如“时间范围”参数,我会注明“当用户未明确说明时,默认取最近30天,不要自行假设开始时间”。

2.3 粒度选择:太粗听不懂,太细选不对

技能粒度是个需要反复打样才能找到感觉的参数。我最初把“做一份完整行业报告”当成一个技能,结果发现它内部牵扯太多子任务,模型常常在半路就执行不下去了。后来我把“行业报告生成”拆成“行业信息采集”“信息结构化整理”“报告内容生成”三个层层衔接的技能,整体成功率反而提升了不少。

反过来,如果拆得太细,比如把“从网页中提取标题”也做成技能,那么模型面对几十个候选技能时,准确率会明显恶化。我做过一个对比测试:技能数量从 8 个增长到 25 个时,模型对技能选择的准确率大约从 94% 下降到 82%;而当技能数量超过 40 个时,准确率进一步跌破 70%。这说明技能库规模必须控制,宁可合并同类项,也不要把细分能力全部摊开。

粒度调整没有一劳永逸的公式,我一般遵循“单技能内部步骤不超过三个、涉及外部调用不超过两次”的经验值,超过就考虑拆,少于则该想想是不是要合并。

3. 落地一个最小技能库:agent-skills 的完整搭建流程

3.1 目录结构与注册方式

我在 agent-skills 的实战中采用了一个相对固定的目录结构,便于团队协作和后续扩充。每个技能独立一个目录,包含技能定义文件和实现代码:

agent-skills/ ├── registry.json ├── skills/ │ ├── web_search/ │ │ ├── SKILL.md │ │ └── run.py │ ├── content_extract/ │ │ ├── SKILL.md │ │ └── run.py │ └── info_compare/ │ ├── SKILL.md │ └── run.py └── runtime/ └── executor.py

技能注册表 registry.json 记录了技能名称、入口函数、描述文件的路径,以及是否启用。运行时加载所有启用的技能,把它们转成模型能读的结构。这里有一个很容易踩的坑:注册表里千万不要直接用 Python 对象的名字当作技能名,技能名应该和自然语言衔接,比如“web_search”背后对应“网页关键词检索”,模型调用时使用的是技能名,太技术化的名字会把模型带偏。

关于入口函数,我要求所有技能实现统一签名:接收一个参数对象,返回一个包含状态码、数据或错误信息的标准化结果。这样后续做降级、路由、日志都会方便很多。

3.2 一个技能从函数到注册的完整代码

下面我以一个“信息对比”技能为例,演示 agent-skills 里一个技能的最小落地过程。这个技能做的事情是:输入两个待对比的主体(比如两个产品),调用底层检索能力采集信息,然后输出结构化的对比结果。

先写 SKILL.md,这是模型真正会读的技能描述:

# 技能:主体信息对比(info_compare) ## 功能 对两个业务主体(产品、公司、方案等)进行多维度信息采集与对比,最终以结构化列表返回。 ## 触发场景 用户提到“对比一下A和B”“A和B有什么区别”“哪个更好”等意图,且需要基于外部信息进行比较时。 ## 不适用场景 用户只是询问单一主体信息;用户已经提供静态对比资料,不需要额外采集时。 ## 参数 - target_a:第一个主体名称,必填,字符串,应从对话中直接抽取。 - target_b:第二个主体名称,必填,字符串,应从对话中直接抽取。 - dimensions:对比维度列表,可选,默认["基本信息","用户评价","优劣势"]。 ## 返回 返回一个JSON对象,包含 state(success/error)和 data(对比表格数组),数组每个元素包含 dimension、target_a_value、target_b_value、summary。

然后是核心执行文件 run.py。这里的关键设计是:执行逻辑只关心参数对象,不关心模型怎么来的参数,也不关心输出怎么渲染。它把内部采集和对比封装成独立函数,方便单测:

# run.py from typing import Any def execute(params: dict[str, Any]) -> dict[str, Any]: target_a = params.get("target_a") target_b = params.get("target_b") dimensions = params.get("dimensions", ["基本信息", "用户评价", "优劣势"]) if not target_a or not target_b: return {"state": "error", "data": {"message": "缺少对比主体参数"}} material_a = _collect_materials(target_a) material_b = _collect_materials(target_b) result_rows = [] for dimension in dimensions: result_rows.append({ "dimension": dimension, "target_a_value": _pick_value(material_a, dimension), "target_b_value": _pick_value(material_b, dimension), "summary": _make_summary(dimension, material_a, material_b), }) return {"state": "success", "data": {"comparison": result_rows}} def _collect_materials(target: str) -> dict[str, Any]: # 实际项目中,这里会调用检索技能和内容提取技能 pass def _pick_value(material: dict[str, Any], dimension: str) -> str: # 从采集结果中按维度抽取关键信息 pass def _make_summary(dimension: str, material_a: dict[str, Any], material_b: dict[str, Any]) -> str: pass

最后在 registry.json 里注册:

{ "info_compare": { "name": "主体信息对比", "description_file": "skills/info_compare/SKILL.md", "entry": "skills.info_compare.run:execute", "enabled": true } }

这套结构的好处是,每个技能都是自包含的,可以单独测试、单独替换、单独下线。团队里来了新人,只要照着 SKILL.md 的格式就能新增技能,不需要动运行时代码。

3.3 请求-调用-返回的闭环设计

技能注册好了,还需要一个运行时的闭环,让模型可以从用户输入走到技能执行结果。我总结的流程分五步:意图识别、技能选择、参数抽取、执行、结果处理。

意图识别阶段,模型判断用户这句话是否真的需要调用技能。如果不需要,就不应该进入技能调用链路。Agent 最容易犯的错就是“什么都想调用技能”,即使只是闲聊也去触发检索,白白增加延迟。

技能选择阶段,把用户输入和所有候选技能的描述做匹配。可以直接让模型输出技能编号,也可以计算描述与输入的语义相似度,考虑到准确率,我通常直接让模型在候选技能中选一个并简要说明理由。在技能数量小于 20 个时,这个策略足够稳定。

参数抽取阶段,把用户的自然语言输入映射成技能定义中的参数。这一步最容易出现幻觉,所以我在体系里做了较强的校验:必填参数缺失时不允许直接进入执行,而是向用户追问。宁可多一轮对话,也不要带着残缺参数执行出一个错误结果。

执行阶段,运行技能的入口函数,捕获所有异常并统一处理。真实调用外部服务时,还要考虑超时、重试和限流,不能把所有问题都暴露给模型。

返回处理阶段,把技能结果转换成模型能继续对话的格式。如果结果是结构化 JSON,我会把它压缩成紧凑摘要,再交回模型。这直接关系到后续对话的质量和上下文开支,后面会详细展开。

4. 真实场景中的五类翻车现场与排查链路

4.1 参数幻觉:模型补了不存在的时间

第一次明显感觉到问题,是用户问“查一下最近的行业政策”,技能返回了一堆三个月前的旧闻。排查后发现,模型在参数抽取时给“时间范围”填了“最近7天”,但用户根本没提时间,而政策发布本身有滞后性,7 天内确实几乎没有内容。

根因是参数定义里没有说明默认取值。模型面对一个可选参数,倾向于自己“补一个合理的值”,而它认为的合理往往不符合业务逻辑。修复很简单:在参数描述里明确“用户未指定时默认取最近180天,并不要手动修改”。这之后类似问题基本消失。

排查参数类问题,我建议在技能执行链路里加一层“参数审计”,把所有模型抽取出来的参数和执行结果一并记录到日志里。否则光靠肉眼复盘对话,很难定位是哪一步引入了错误参数。

4.2 选择漂移:技能多了反而找不准

技能库扩展到 15 个左右时,我又遇到一类问题:有些请求明显该用 A 技能,模型却选了语义相近的 B 技能。比如用户说“看看这两个方案有什么不同”,技能库里既有“主体信息对比”也有“文档要点提取”,模型经常在两者之间犹豫。

我用一个笨办法解决了大部分问题:把所有技能名称和描述打印出来,让一个完全不了解系统的人来选,找出那些他也会犯迷糊的地方。这一类技能通常就是描述区分度不够。我调整了“文档要点提取”的描述,改成“专门针对用户上传的文档进行要点抽取,不涉及外部检索,也不做主体间对比”,选择准确率明显回升。

还有一个小技巧:当两个技能确实有重叠时,在彼此的描述里互相加引用说明,明确边界。模型对显式的“这个技能不做什么”比“这个技能做什么”更敏感。

4.3 返回结构不定:解析结果比调用还难

这个坑是在技能写好一阵子后才暴露的。当时有一个“信息汇总”技能,内部逻辑会根据不同输入返回不同结构的字典:有时候是 list,有时候是 dict,字段名也不一致。模型拿到这种不确定性极高的返回后,后续追问经常答非所问。

后来我给所有技能定了一条铁律:返回结构必须严格遵循 SKILL.md 里声明的 JSON Schema,哪怕某项数据为空,也必须输出对应字段的空值,不能省略字段。这样模型每次拿到的结构都一样,后续处理就容易得多。凡是涉及到数组结果的,我都要求统一用“items”作为键名,避免模型去猜。

4.4 中途失败没有兜底:一次小异常毁掉整轮对话

最让人头大的是技能内部静默失败。我们的“内容提取”技能偶尔因为目标网站结构变化而解析不到正文,页面级异常全部被统一捕获时因为处理得比较草率,返回了一个空对象。模型拿到空对象后,没有意识到“技能失败了”,反而一本正经地告诉用户“该页面没有相关内容”。

修复思路是给技能执行加“状态语义”:任何非预期情况都必须返回 error 状态,并附上错误类别。runtime 里再对 error 做统一处理,比如触发重试、切换备用技能,或者明确告诉模型“这个技能没能完成任务,需要向用户如实说明”。同时暗含一个强调点:错误信息也要结构化,不要用一个空数组糊弄过去。

4.5 上下文膨胀:技能说明吃掉了窗口

技能数量增加后,我发现模型的有效上下文被占得厉害。每个技能的描述有几百字,20 个技能相当于塞进一长篇文档,加上参数定义和返回结构说明,一次性把所有技能全部塞给模型的做法越来越不经济。

解法是分层加载:先加载技能摘要列表,让模型只根据摘要做初筛;确定候选技能后再加载候选技能的完整描述。这样窗口占用大约降了六成,技能选择准确率没有明显下降。对于更长远的扩展来说,这一步几乎是必做的架构调整。

5. 技能体系进阶:从“能跑”到“跑得稳”

5.1 技能路由与降级策略

技能多了以后,我增加了简单的路由层。路由层不负责执行技能,只负责决定“这一轮请求应该进入哪个技能组”。比如用户需求偏“信息获取”时,就只加载检索、提取、对比这一组技能;偏“内容创作”时,就只加载资料整理和生成类技能。这样一个技能组内的候选数量始终保持在 5 到 8 个,模型选择压力小,准确率也稳定。

降级策略是针对执行失败设计的。以“信息对比”为例,如果外部信息采集失败,我不会直接让整个技能失败,而是先判断采集失败的原因。如果是单次网络超时,重试两次;如果是连续失败,则切换为“仅对比已有素材方案”,调用另一个更轻量的技能先顶上。这样用户感受到的是流程顺畅,而不是“又出错了”。

5.2 技能质量度量的三个核心指标

技能体系做得多了,没有度量就等于闭眼开车。我给自己定了三个核心指标:选择准确率、执行成功率、用户修正率。

选择准确率衡量的是“用户的真实意图和技能系统的选择是否一致”,可以在测试集上人工标注后离线计算。执行成功率看的是技能入参合法且内部流程完整跑通的概率,这个指标最能反映底层工程质量。用户修正率则比较微妙,它统计的是用户对 Agent 技能执行结果提出纠正的频率。

我把这三个指标做成每周看一次的小报表。哪个技能的选择准确率低了,就去优化描述;哪个技能执行成功率低了,就去排查内部调用;哪个技能用户修正率高,就要考虑是不是能力边界本身设计错了。指标不是摆设,它能帮你从“感觉哪里不对”变成“知道哪里不对”。

5.3 把技能与记忆结合,让 Agent 越用越准

最后还想聊一个和 agent-skills 深度相关的进阶点:技能执行结果应该沉淀成记忆数据,反过来优化后续的技能选择。

用户第一次问“对比一下 A 和 B”,技能执行后会留下结果记录。下一次用户再问“上次那两个方案现在怎么样了”,系统可以从记忆中恢复上次对比的主体名称,并直接复用相应技能的参数,而不是让模型重新猜一次。这能极大减少重复采集,也给用户一种“这个助手有记忆”的体验。

实现上,我在技能执行器的返回结果里增加了一个可选的记忆字段,由上层决定是否写入记忆存储。写入时会对原始结果做脱敏和摘要,控制体积。这个机制跑通了以后,Agent 的体验会有一个质的提升,相当于每个技能都从“一次性使用”变成了“可积累资产”。

做了这么久的 agent-skills,我最大的感受是:技能体系的本质不是写代码,而是“让模型和真实世界之间拥有一套可靠的翻译层”。很多时候我们以为模型不够聪明,翻车之后才发现是技能描述含糊、参数定义不清、错误处理缺失。我现在给项目定技能时,会先逼着自己回答一个问题:如果一个完全不知道业务细节的陌生人,只看技能描述能不能正确使用这个技能?如果答案不确定,那这个技能就不该上线。先专注打磨三五个技能,把它们做到近乎完美,再考虑扩张,这条路看起来慢,走起来其实是最快的。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/10/14 9:25:40

混合型AI智能体管理平台架构设计与实操指南

1. 混合型 AI 智能体管理平台的核心命题 1.1 为什么单一模型架构正在被淘汰 过去一年我经手过四个不同规模的智能体项目,从客服工单自动分类到多步骤数据分析流水线,一个非常明显的感受是: 只靠一个模型打天下的时代已经过去了 。早期大家…

作者头像 李华
网站建设 2026/10/14 9:25:06

基于DCT的数字水印:原理、Python实现与鲁棒性优化

简介:基于DCT的数字水印技术MATLAB实现资源包,涵盖从图像变换、系数处理到水印嵌入与提取的完整代码方案,适合图像处理、信息安全及相关方向的学习者用于课程设计或科研验证。压缩包内共3个文件,包含可直接运行的.m脚本、原始图像…

作者头像 李华
网站建设 2026/10/14 9:24:32

MATLAB整合NASA电池数据集实战指南

1. MATLAB电池数据集整合实战指南作为一名长期处理实验数据的工程师,我经常遇到需要整合多个来源电池数据的情况。NASA电池数据集是业内广泛使用的基准数据,但原始数据分散在多个.mat文件中,直接分析非常不便。今天我就分享一套经过实战检验的…

作者头像 李华
网站建设 2026/10/14 9:24:16

Agent技能库实战:从散装工具到标准化技能封装体系

“agent-skills”这个标题乍一看很简短,但真正动手维护过 Agent 项目的人会明白,它背后其实是一整套关于“如何让大模型稳定地调用外部能力”的工程问题。我在过去几个月的实践中,把一个小仓库从最初只有几个 prompt 脚本,逐步迭代…

作者头像 李华
网站建设 2026/10/14 9:23:47

接口测试之基本介绍

接口测试之基本介绍1、接口测试 接口测试也叫api测试, 定义:测试系统和系统之间的数据交换,模块与模块之间的数据交互,程序与程序之间的数据交互; 如: http://cms.duoceshi.cn/manage/loginJump.do登录接口…

作者头像 李华
网站建设 2026/10/14 9:23:32

深入理解C++ 容器 std::basic_string

std::basic_string 是 C 标准库中所有“字符串类”的底层模板。我们平时用的 std::string、std::wstring、std::u16string、std::u32string 等&#xff0c;本质上都是 std::basic_string 针对不同字符类型的别名。它定义在头文件 <string> 中。1. 模板定义与常见别名std:…

作者头像 李华