news 2026/9/17 20:05:15

Agent技能体系设计与实践:从散装函数到可复用能力

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Agent技能体系设计与实践:从散装函数到可复用能力

“agent-skills”这个词,我第一次被它击中,是在一个开源的Agent项目文档里。当时项目方把几十个工具函数一股脑塞进一个工具文件夹,命名混乱到连作者本人都要翻半天才能找到某个功能,我就意识到,这件事不是“把函数换个目录”这么简单。真正把agent-skills当一回事去设计,是在我自己做一个多轮对话Agent产品、被一堆裸函数和硬编码逻辑折磨到崩溃之后。所谓技能体系,本质上是一套让AI Agent把“会做的事情”模块化、标准化、可复用、可评估的能力组织方式,它不绑定某个具体框架,而是一种实践方法,直接解决的问题是:Agent的能力边界在哪里、什么时候该调用什么能力、能力出错了如何快速定位。

这篇内容适合三类人:正在给Agent疯狂追加工具函数、但发现越来越难维护的开发者;在做企业级Agent产品、需要多人协作管理一大堆能力模块的团队;以及研究提示词工程和工具调用的算法同学。我会从为什么需要技能体系、到技能目录怎么设计、再到一个技能从零到能上线运行的完整路径,最后讲维护和排查的坑,尽量把这条链路讲透。

1. 为什么Agent需要一套“技能体系”而不是一堆工具函数

1.1 裸工具函数到底哪里让人崩溃

先还原一下我在项目里看到的典型“裸工具”状态。你定义了一个get_weather(city),另一个同事写了个query_weather_by_city(city_name),还有一个遗留系统里躺着fetchWeather(cityId)。三个函数做的事情几乎一样,但函数名、参数名、返回结构全不一样。大模型在规划时看到的是工具列表里的函数名和描述,它根本不知道这三个函数是同一个能力,更悲剧的是,它可能会随机选一个。

这带来的连锁反应很现实:工具调用日志里出现大量“无效规划”,模型今天选A明天选B,行为不可复现;出现bug时你得挨个排查三个函数的返回字段差异;想做一个统一的缓存策略或者权限控制,都没地方下手。说句不好听的,这种状态下的工具集根本谈不上“能力”,只是一堆散装函数。

类比一下:裸工具函数就像把一个五金工具箱里的螺丝刀全部混在一起,没有标注规格,也没有按用途分类。你需要一把十字螺丝刀的时候,得把整箱倒出来翻。而技能体系做的事情,就是给每个能力一个明确的“规格标签”,并且把相近能力做归一化处理。

1.2 技能的本质:工具是“能做什么”,技能是“承诺怎么做”

我理解中,工具函数只回答了“我能做什么”,但技能要回答一组完整的问题:这个能力在什么场景下该被使用、输入输出长什么样、失败时怎么处理、边界是什么。它更像一份“能力契约”,而不是一段孤立代码。

拿天气能力举例,一个工具函数版本的描述可能是:get(city) -> dict

一个技能版本的描述会变成:

  • 名称:current_weather_snapshot
  • 适用场景:用户询问当前天气、出行建议、穿衣建议等场景
  • 输入:城市名(中文或拼音均可),可选时间点
  • 输出:结构化天气数据 + 一句给用户看的自然语言摘要
  • 失败模式:城市不存在时返回明确错误码,不抛出未捕获异常

这带来的价值是:大模型在做工具选择时,不再是“猜”,而是根据技能的“适用场景”字段做一次精准匹配。调用方也不再需要关心技能内部怎么实现,只要按照契约传参和解析结果。技能内部是翻第三方API、查数据库、还是读本地缓存,对外部完全透明。

1.3 技能粒度怎么拆才合理

技能粒度是这套体系里最容易翻车的设计点。拆太粗,一个技能干太多事,比如“处理订单”这种技能,内部可能是分页、统计、修改状态、退款杂在一起,模型调用时不知道具体触发哪个分支,结果完全不可控;拆太细,技能数量爆炸,一个场景要串五六个技能,模型规划链路变长,Token开销和出错率一起涨。

我在实践中总结了一套“可再分性 + 复用频率”的衡量标准,体感比拍脑袋定规则靠谱得多:

粒度判定标准示例使用频率预期
原子技能单个业务行为,不可再拆分查库存、查订单状态、发验证码高频
组合技能固定顺序的多个原子技能串联下单前检查库存+锁定库存中频
工作流技能含有条件分支的完整业务流程售后退款全流程低频但复杂

判断一个技能是否该独立出来,我建议问三个问题:这个行为会在多少个场景里被复用?这个行为是否只有一个清晰的业务意图?它的输入输出是否可以独立于其他技能定义?三个问题答案都是“是”,就值得拆成一个独立技能;有一个“否”,再往上一层考虑。

2. 技能目录怎么设计:从命名、结构到文档规范

2.1 一个能跑起来的技能目录长什么样

先给一个我目前比较满意的目录结构,这个结构经历了三轮迭代,现在基本稳定:

skills/ current_weather/ SKILL.md metadata.yaml main.py tests/ test_main.py test_snapshots.py examples/ case_01.json query_order/ SKILL.md metadata.yaml main.py tests/ test_main.py examples/ case_01.json

每个技能一个独立目录,目录名就是技能名,里面必须有SKILL.mdmetadata.yaml和主实现文件。SKILL.md是给大模型看的“操作手册”,metadata.yaml是给框架看的“注册信息”,主实现文件是给运行环境看的“执行代码”。这样三个角色各自看各自该看的东西,互不干扰。

有人会问,为什么不直接用一个大tools.py把所有技能塞进去?我的体会是,当技能数量超过30个后,单文件模式的协作冲突会急剧上升,两个人同时改同一个文件,合并代码都能耗掉半天。独立目录带来的隔离性,短期看是多了一些文件,长期看在多人协作时收益非常明显。

2.2 SKILL.md 里的核心字段,少一个都容易翻车

SKILL.md是整个技能体系里最容易被低估的文件,我见过太多人把它当成一个“简介”来写,结果模型根本不知道怎么用这个技能。一个合格的SKILL.md至少要包含以下字段:

字段作用填写建议
name技能唯一标识与目录名一致,禁止重名
description一句话说明能力写明“做什么”,不要写“怎么做”
when_to_use触发场景描述列出明确的业务场景,越具体越好
when_not_to_use负向排除防止模型在错误场景误调用
input_schema输入参数定义字段类型、是否必填、枚举值范围
output_schema返回结构定义顶层字段、嵌套结构、空值策略
examples调用示例2-3个不同场景下的输入输出样例
error_handling错误处理说明每种错误码的含义、调用方该怎么做

descriptionwhen_to_use是最影响模型决策的两个字段。我见过一个写得极差的描述:“此技能用于查询订单相关信息。”这个描述几乎没有任何区分度,因为订单相关的技能可能有十个。后来改成:“此技能用于查询订单当前状态、物流信息、支付状态,适用于用户咨询‘我的订单到哪了’‘发货了没’‘什么时候到货’等场景。”效果立竿见影,误调用率下降了一半以上。

2.3 技能命名与版本管理,这两件小事别忽略

技能命名看似小事,实际影响着模型的理解和团队协作效率。我推荐的命名规则是“动词 + 名词”结构,比如query_ordersend_emailconvert_pdf。名字里不要带版本号,不要带环境名,不要用拼音缩写。convert_pdf_v2_final这种名字在第一版上线那天就注定是灾难。

版本管理上,每个技能目录内部维护自己的版本号,用语义化版本规则(主版本号.次版本号.修订号)。主版本号在输入输出结构不兼容时升级,次版本号在功能扩展但保持兼容时升级,修订号只改内部实现和bug修复。metadata.yaml里记录版本号、作者、最后修改时间、依赖项,这些信息在定位问题时能省大量时间。

3. 从零写一个可复用的Agent技能:以“仓库库存快照”为例

3.1 场景设定与输入输出定义

下面我用一个具体的技能“仓库库存快照”完整走一遍从设计到落地的过程,这是我在一个电商客服Agent里实际做过的技能。

业务背景是:客服Agent被问到“这个商品还有货吗”“现在下单什么时候能发”,需要在回复前先查库存。原先是直接读数据库,后来发现库存表在高峰期有大量读写,而客服Agent查询频率高,直接查库经常超时,另外返回一整张表的几十个字段,模型反而不知道该怎么组织回复。

于是我决定做成一个“快照式”技能:底层维护一个定期刷新的库存快照缓存,对外只暴露精简结果。输入参数定为商品SKU列表,输出为每个SKU的库存状态、剩余数量、预估发货时长。定义输入输出时,我优先考虑三个原则:参数尽量少、类型尽量明确、输出带一个面向用户的话术摘要。

3.2 实现代码:从数据模型到主函数

pydantic定义输入输出结构,是我目前体感最稳的方案,类型校验、错误提示、序列化都省心:

from typing import List, Optional from pydantic import BaseModel, Field class InventoryInput(BaseModel): sku_list: List[str] = Field( description="需要查询库存的SKU列表,最多支持20个", min_length=1, max_length=20 ) check_time: Optional[str] = Field( default=None, description="查询时间点,格式YYYY-MM-DD HH:MM:SS,默认当前时间" ) class SKUInventory(BaseModel): sku: str stock_status: str = Field(description="in_stock/low_stock/out_of_stock") remaining: int = Field(description="剩余可售数量") estimated_delivery_days: int = Field(description="预估发货天数") class InventoryOutput(BaseModel): items: List[SKUInventory] summary: str = Field(description="给用户看的一句库存摘要") generated_at: str = Field(description="快照生成时间")

主函数的核心逻辑是先从缓存读快照,再标记已过期或缺失的SKU,异步回源数据库刷新一次:

def run(sku_list: List[str]) -> InventoryOutput: cache_hits = [] miss_list = [] for sku in sku_list: item = snapshot_cache.get(sku) if item and not item.is_stale(): cache_hits.append(item) else: miss_list.append(sku) if miss_list: fresh_items = query_and_rebuild(miss_list) cache_hits.extend(fresh_items) items = [SKUInventory( sku=x.sku, stock_status=classify_status(x.remaining), remaining=x.remaining, estimated_delivery_days=estimate_delivery(x.remaining) ) for x in cache_hits] summary = build_summary(items) return InventoryOutput(items=items, summary=summary, generated_at=now())

这里有几个在真实业务里踩过的细节:classify_status不能只看“有没有货”,要结合安全库存水位判断;estimate_delivery要考虑仓配区域;build_summary要生成自然语言摘要而不是只给结构化数据,否则模型每次都要自己“翻译”一遍数据,既费Token又容易出错。

3.3 技能注册与Agent调用链路

实现完主函数后,需要把技能注册到框架里。我用一个简单的注册表来管理,避免各技能之间互相感知:

from typing import Dict, Callable skill_registry: Dict[str, Dict] = {} def register_skill(name: str, description: str, when_to_use: str, handler: Callable, input_model, output_model): skill_registry[name] = { "name": name, "description": description, "when_to_use": when_to_use, "handler": handler, "input_model": input_model, "output_model": output_model } register_skill( name="inventory_snapshot", description="查询商品库存状态、剩余数量和预估发货时长", when_to_use="用户询问是否有货、是否能发货、预计发货时间等场景", handler=run, input_model=InventoryInput, output_model=InventoryOutput )

在Agent运行时,框架根据LLM生成的“技能名+参数”去注册表里找handler,执行后再把结构化结果拼回对话上下文。这里有个关键点:技能的执行结果要“翻译”成适合放回上下文的格式,我的做法是把summary字段直接拼进去,同时附上结构化items供模型按需取用。

4. 技能的测试、评估与迭代

4.1 单元测试是底线,不是可选项

技能一旦被模型调用,它的输出质量直接影响用户体验,因此单元测试必须覆盖核心逻辑。对每个技能,我要求至少写三组测试:正常输入、边界输入、异常输入。

正常输入用典型业务场景,比如库存技能查询5个SKU;边界输入要覆盖空列表、单个SKU、超过20个SKU;异常输入要覆盖不存在的SKU、数据库超时、缓存为空等。断言不只验证返回结构,还要验证summary里的关键信息是否与结构化数据一致,比如remaining=0时摘要里不能出现“现货”字样。

测试框架用pytest,再配合快照测试保护输出结构。快照测试的作用是:当某次改动无意中改了返回字段名或类型时,测试会直接失败,逼着你审视这次改动是否合理。

4.2 场景回放,比十组手写测试更能暴露问题

单元测试能保护“代码逻辑”,但保护不了“模型调用技能的决策”。我后来建立了一套场景回放机制,思路是把真实对话历史里“Agent成功调用技能”的样本收集起来,形成回归集,每次技能改动后自动跑一遍。

具体做法:从生产日志里抽200条包含技能调用的完整对话,把用户原始提问、模型规划时的工具列表、最终选择的技能、传入参数、技能返回结果、用户反馈都存成JSON。回归脚本会重放这200条记录,输出“技能选择一致性”和“参数生成有效性”两个指标。如果某次改动导致80%的用例选择了不同的技能,那八成是SKILL.md里的描述被改坏了。

场景回放还有个额外好处:它帮你积累了一批高质量的“调用示例”,这些示例可以反过来补充到SKILL.mdexamples字段里,形成正向循环。

4.3 技能调优:从“能跑”到“好用”的三板斧

技能做完能跑只是第一步,真正好用的技能要经过几轮调优。我的经验集中在三个方向。

第一,调描述。如果日志里模型经常在“该用技能A时偏偏选了技能B”,先检查两个技能的when_to_use是否重叠过多,再把高频场景词写进描述里。我见过最有效的一次调整,是往描述里加了三个用户原话的例子,误选率直接从30%降到8%。

第二,容错输入。模型生成的参数经常不按套路来,日期可能是“明天”而不是具体日期,城市名可能是“帝都”而不是“北京”。技能内部做一层宽松表达式解析,把常见口语别名映射到标准值,能极大减少“技能会写但不会用”的尴尬。

第三,控Token。SKILL.md会随系统提示一起发给模型,它越长,每次调用消耗的Token就越多。一个技能文档超过800字时,我开始警觉,优先精简描述性废话,把关键触发词前置到开头200字内。实测下来,在不影响调用准确率的前提下,能省掉约20%的Token开销。

5. 多人协作维护一套技能库的坑

5.1 接口评审是维护技能库的“安全带”

当技能数量多起来,尤其是团队超过三个人之后,接口评审就变得非常重要。一个常见事故是:客服系统的人改库存技能时,为了让管理后台也能用,给InventoryOutput增加了一个warehouse_detail字段,结果客服Agent的上下文被塞进一堆用不到的详情数据,Token消耗飙升,模型开始犯糊涂。

我后来规定:凡涉及输入输出结构变动、新增技能、删除技能、修改触发场景,必须走一次接口评审。评审不看实现代码,只看三样东西:SKILL.mdwhen_to_use、输入输出schema、旧版本兼容方案。这个过程其实很快,但能挡掉90%的隐性事故。

5.2 文件管理、私有包、中心平台怎么选

多人协作还牵涉到一个工具选型问题:技能库到底存在哪、怎么分发。

最简单的是“git仓库 + 文件目录 + PR评审”,适合10人以内的小团队,每个技能一个目录,串行评审合并。缺点是技能达到上百个之后,检索和依赖管理会比较痛苦。中等规模团队我推荐“私有包管理”,比如用内网的pip源或npm源发布每个技能为独立包,调用方按版本安装,可以在服务间复用技能,但打包发布流程会增加一定维护成本。大型组织会倾向于建设中心化的技能管理平台,统一存储、统一权限、可视化调试,但搭建成本高,团队规模不够大时容易变成负担。

我的建议是:团队人数在5人以下,先别急着造平台,git仓库 + 目录约定完全够用;等技能量级上来了再迁移,不要一上来就追求“平台化”。

5.3 几个真实踩过的协作坑

说几个我在实际协作中踩过的坑,都是血泪教训。

第一个坑是“技能目录名改动引发连环失败”。有个技能原来叫query_stock,后来我觉得不够语义化,改成了inventory_snapshot,结果没同步更新历史会话里的技能调用记录,导致场景回放时大量用例失败。现在所有技能改名都必须走评审,并且要批量同步更新历史样本里的名字。

第二个坑是“共享数据结构被悄悄修改”。两个技能都用OrderInfo结构,有人觉得加个字段很安全,结果另一个技能的测试全部挂了。后来我把公共schema抽出来单独管理,并规定:任何人改公共结构必须全量跑一遍所有技能的测试集。

第三个坑是“文档和实现脱节”。有同事改了技能内部逻辑,但忘了更新SKILL.md,模型按照旧描述传参数,接口不兼容直接报错。现在我在CI里加了一个检查:如果SKILL.mdmetadata.yaml的最后修改时间戳相差超过某段时间,就触发提醒,逼着作者同步更新文档。

6. 常见故障与排查技巧

6.1 技能在Agent运行时总是不被调用

这是最让人头疼的问题之一:技能明明存在,测试也能跑通,但模型就是不用它。排查思路首先看描述和触发场景,把技能名和when_to_use打印在日志里,对比模型实际生成了什么,容易发现问题。

更多时候问题出在“技能描述与其他技能太相似”,模型在两个相似技能之间纠结,最终选了错误的那一个。解决办法是在描述里增加负向排除,比如when_not_to_use: 本技能不适用于售后改单场景,改单请使用order_modify_skill。另一个常见原因是技能列表太长,模型上下文窗口被塞满,有些技能压根没被“看到”。这时需要做技能分片或摘要机制,而不是一味堆技能。

6.2 参数解析报错与模型“乱传参”

模型传参不按input_schema来,是技能上线初期的高频问题。常见表现是:日期传了“后天”而不是具体日期;枚举值传了“快点发货”而不是express/normal;数组类型传了逗号分隔字符串。

我的应对策略不是让模型变得严谨,而是让技能变得宽容:在runner层统一加一层参数清洗,这层根据字段注释做类型转换和别名映射。比如日期字段支持natural_language_date()解析,把“后天”转成具体日期;枚举字段做模糊匹配,容忍同义词。经过这层清洗后,参数解析报错率下降非常明显。

6.3 技能返回结果太复杂,Agent反而看不懂

有些技能返回的数据结构设计得极其“完备”,嵌套四五层、字段几十个,模型拿到后一脸懵,不知道该用哪个字段组织回答,最后给用户一段含糊其辞的话。

解决办法是设计“双层输出”:顶层是给模型直接使用的摘要和核心判断,底层是给需要深度处理场景的详细数据。比如库存技能的summary是一句“该商品上海仓现货充足,预计明天可发货”,底层是各仓库存明细。模型大多数场景只用顶层摘要,复杂度高的场景再取底层数据。这个设计从根本上减少了模型“读不懂输出”的问题。

排查这类问题时,我建议开起技能调用日志的“输出长度统计”,如果某个技能的平均输出Token显著高于其他技能,就优先考虑精简它的返回结构。


技能体系不是一次搭建就一劳永逸的东西,它更像是一个需要持续打理的花园。每次看到模型准确命中一个技能、顺利生成用户满意的回复时,你会觉得前面那些设计、评审、测试的功夫都值了。如果你正在被一堆散装工具函数折磨,不妨先从一个小技能开始尝试,把SKILL.md写详细,把输入输出结构钉死,再慢慢铺开,这套实践的收益会随着技能数量的增长越来越明显。

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

OpenMontage:面向视频理解的Agentic架构实践指南

1. OpenMontage 是什么:一个被严重误读的开源视频智能体项目OpenMontage 这个名字最近在技术社区里频繁闪现,但绝大多数人点开 GitHub 仓库后都愣住了——页面干净得像刚初始化,README 只有一行“Montage for the open era”,连个…

作者头像 李华
网站建设 2026/9/17 20:03:23

版权登记去哪儿办?这篇讲透

版权保护,到底在保护什么 很多企业主第一次接触版权,往往是从一张设计图、一段宣传片或一套软件代码开始的。版权保护的核心,是让原创成果在产生纠纷时能拿出确凿的权利证明。我国唯一的软件著作权登记、著作权质权登记机构,就是中…

作者头像 李华
网站建设 2026/9/17 20:03:01

Abaqus焊接仿真:热-力耦合建模与dflux子程序实战

简介:本资源是一份面向结构仿真工程师与焊接工艺研究人员的Abaqus热力耦合建模实战指南,聚焦于使用Dflux子程序实现双椭球热源焊接温度场模拟的核心技术路径。内容以平板焊接为典型算例,系统拆解建模、材料定义、装配、分析步设置、边界条件施…

作者头像 李华
网站建设 2026/9/17 20:01:25

Security-101 第一课:深入理解 CIA 三元组与网络安全核心概念

Security-101 第一课:深入理解 CIA 三元组与网络安全核心概念 【免费下载链接】Security-101 8 Lessons, Kick-start Your Cybersecurity Learning. 项目地址: https://gitcode.com/GitHub_Trending/se/Security-101 本文基于 Security-101 课程的第 1.1 课&a…

作者头像 李华
网站建设 2026/9/17 20:00:09

如何构建MathModelAgent桌面版?Electron打包macOS与Windows完整指南

如何构建MathModelAgent桌面版?Electron打包macOS与Windows完整指南 【免费下载链接】MathModelAgent 🤖📐专为数学建模设计的 Agent & skills ,自动完成数学建模,生成一份完整的可以直接提交的论文。 An Agent Designed for …

作者头像 李华