
agent-skills这个话题最近在圈子里被问到的频率明显高了起来。不少团队做完第一个Agent Demo后发现最头疼的不是模型能力不够而是技能管理太混乱几十个工具函数堆在一个文件里模型调用全靠猜加一个新能力要小心翼翼生怕影响其他功能。我花了两周时间把一个零散的tool collection重构成了带注册、编排、评估机制的技能体系。这篇稿子就把整个设计思路、踩坑过程和可复现的方案完整记录下来。1. 项目概述Agent技能体系到底在解决什么问题1.1 从工具调用到技能体系的演进逻辑先厘清一个概念。很多人把Agent调大模型接口当作工具调用但实际项目中工具Tool和技能Skill是有本质区别的。工具是最原子化的能力比如计算两个数的和、查询某条数据库记录、调用某API获取天气。而技能是一组围绕某个目标组织起来的行为序列比如完成一次电商订单履约这件事可能包含库存查询、地址校验、支付接口调用、短信通知四个步骤。这四个步骤作为四个独立工具暴露给模型时模型经常犯两个错误要么调用顺序颠倒了要么漏掉某个中间环节。agent-skills这个项目的核心目标就是把散落的工具能力按照业务场景重新组织成可发现、可调用、可编排、可评估的技能单元。可发现意味着模型能通过合适的检索方式找到当前任务最匹配的技能可调用意味着技能有清晰的输入输出协议不会出现传参歧义可编排意味着多个技能能按一定规则组合成一个完整工作流可评估意味着每一个技能的质量是可测的而不是试试看。1.2 三个典型的痛点场景我做这个项目前手里已经有三个真实的痛点场景在逼着我重构。第一个场景是内部数据问答机器人。原本团队把SQL查询、Excel读取、API拉取全部做成工具结果模型在用户问上个月华东区销售趋势如何时会随机选择一种工具。有时候选对了有时候选错了就报错用户根本不知道Agent是怎么思考的。第二个场景是多轮对话中的状态管理。用户说了记一下周一要开会然后又问帮我看看周一天气怎么样。Agent需要先调用日历技能创建一个日程事件再调用天气查询技能。如果这两个技能没有编排关系模型独立去调就会产生两次毫无关联的调用日历里多了一条无用的日程。第三个场景是技能升级。原来的工具体系里每次改一个函数都可能牵连其他功能没有隔离机制。团队里两个开发者同时加了两个权限校验逻辑直接互相覆盖线上出了一次问题。这三个痛点总结起来就是能力膨胀导致选择困难、技能之间缺乏协作协议、变更管理没有隔离边界。agent-skills这个项目就是针对这三个问题设计的。2. 技术方案选型skill layer应该如何设计2.1 三条技术路线对比实现技能层有几种常见路线我逐个用测试项目验证过一遍。路线一直接用框架内置的Agent工具机制。很多主流Agent开发框架本身就支持工具注册给函数加一个装饰器大模型就能自动发现。优点是上手极快一个文件就能跑通。缺点是技能粒度太粗暴基本就是一函数一工具没有所谓的技能编排层。试下来几十个函数还好突破一百个函数后模型选择准确率明显下降而且函数之间互相看不到调用关系想实现查询库存后再下单这种组合逻辑要写很复杂的提示词去引导。路线二基于知识库式技能检索。把所有技能描述整理成自然语言文档每次Agent要调用前先用向量检索召回Top3技能描述再让模型做选择。这种方式解决了技能太多时选择困难的问题但是动态召回会引入随机性同一个问题两次运行可能召回不同的技能集合调用的稳定性不好。还有一个坑是技能描述写得太详细向量检索会被细节字段干扰反而匹配不到核心用途。路线三自研一个轻量技能注册中心配合固定路由规则和可选的模型路由。技能按领域分组每个技能有独立的描述文档、输入schema、执行函数和测试用例。框架层靠先规则后模型的策略做路由规则命中了就直接走规则规则没命中才让模型在所有技能里选择。实际验证下来这是三个方案里最可靠的。我最终选择了路线三。原因很简单做Agent技能层第一优先级不是智能而是可控。模型路由虽然灵活但确定性不如规则路由。技能层是在大模型外面包的一层工程设施工程设施的核心价值是稳定。2.2 技能注册中心的核心构成整个技能注册中心由四个部分组成缺一不可。第一部分是技能目录。每个技能进来的时候都注册到这个目录里注册信息包含技能ID、技能名称、所属领域、描述、版本号、输入输出协议。目录本身就是一个数据库表用起来像一本技能字典。第二部分是技能元数据索引。除了基础信息每个技能还会维护一组触发关键词、使用场景标签、依赖关系统计。这些元数据不参与运行时调用主要为了支持技能检索和编排推荐。第三部分是执行引擎。技能注册进去不是让你手动调用的执行引擎负责统一调度。它做的事情包括校验输入、按序调用技能函数、捕获异常、记录调用日志。所有技能都被包裹在一个统一的执行上下文里这样能统一埋点和追踪。第四部分是评估模块。每个技能注册后必须附带三个测试用例执行引擎跑完会对结果做断言检查。这个模块集成在CI流水线里每次技能变更后自动跑一遍回归测试。2.3 为什么每个技能要强调版本与隔离版本和隔离是技能体系里最容易忽略但是最致命的部分。按我的经验技能一旦超过五十个版本管理混乱是迟早的事。两个开发者分别修改了同一个技能的不同部分如果没有隔离和版本控制最后合并代码时冲突一堆。技能隔离有一个具体的实现技巧每个技能跑在独立的执行沙箱里哪怕是需要共享数据也走上下文对象传参不直接共享全局状态。这样子技能A改了自己的数据不会意外影响技能B。沙箱方案可以用容器做也可以用轻量的执行环境隔离比如Python的subprocess或者Node的worker_threads。在agent-skills里我还给每个技能加了一个互斥锁的配置项。个别技能涉及写同一个文件或者占同一个端口必须声明互斥关系执行引擎在编排时看到两个技能互斥会用顺序等待的方式处理不会并发执行。这个设计救过我一次口径查询和订单写入这两个技能同时跑把库存表锁了加上互斥声明后彻底避免。3. 核心实操从零搭建一套Agent技能库3.1 技能定义与数据模型设计我先把技能的数据结构写出来这是整个项目的基座。一个技能由一个配置头和一个实现体组成。配置头是给系统和大模型看的实现体是真正执行任务的代码。# skill_definition.py # 技能定义的数据结构示例 from typing import Dict, Any, Optional from pydantic import BaseModel, Field class SkillParameter(BaseModel): name: str type: str # string / integer / boolean / array / object description: str required: bool True enum: Optional[list] None class SkillDefinition(BaseModel): skill_id: str # 全局唯一 name: str # 技能短名词 domain: str # 所属领域数据查询/流程编排/内容生成... summary: str # 一句话描述技能用途给模型看的 description: str # 详细说明什么时候该用、什么时候不该用 parameters: list[SkillParameter] # 输入参数声明 output_schema: Dict[str, Any] # 输出结构声明 version: str 1.0.0 tags: list[str] [] # 触发标签帮助检索 dependencies: list[str] [] # 依赖的技能ID is_sandbox: bool True # 是否沙箱运行 timeout_ms: int 5000配置头里最重要这两个字段summary和description。summary写给大模型看必须一句话讲清楚这个技能是干嘛的、什么时候用。description写得更详细要说明边界比如本技能仅处理已格式化的地址数据无法自动识别原始文本中的地址。实现体是一个普通的函数但需要遵守统一签名。输入一个上下文对象返回一个结果对象。执行引擎会负责把SkillDefinition里面的参数校验逻辑跑完再调用实现体。# skill_impl.py # 技能实现体的统一签名示例 def execute_skill(context) - dict: # context.pre_parsed_params 是经过schema校验后的参数 param_a context.get_param(param_a) # 执行自己的逻辑 result do_actual_work(param_a) return { status: success, data: result, error_code: None }统一签名的好处是执行引擎可以无差别调度所有技能。无论是Python写的数据处理能力还是Java写的业务API封装只要是这个签名就能接进来。3.2 技能加载与动态发现机制技能目录不是写死在一个数组里的而是按照配置化注册的思路动态加载。每个技能目录下有一个manifest.json文件里面描述了技能ID、入口文件、参数schema等信息。启动时系统扫描所有已注册的技能目录解析manifest构建技能索引。这样做有几个直接收益。第一新增技能不用改主程序代码只要加一个目录重启或热加载就能发现。第二技能可以从仓库里按标签拉取团队A维护的技能不打到主包里也能被调用。第三故障排查方便一个技能出了问题直接看那个目录下的日志。{ skill_id: order_fulfillment, name: 订单履约处理, domain: 交易流程, entry: ./implementations/main.py, handler: execute_skill, version: 2.3.1, dependencies: [inventory_query, user_address_verify, payment_charge], timeout_ms: 15000 }动态发现机制还有一个关键点技能冲突检测。两个技能注册的skill_id重复了系统启动的时候直接报错并拒绝启动避免运行期才发现“调错了函数”。3.3 技能编排单技能、链式编排与条件路由技能编排是agent-skills里最有含金量的部分。单纯把技能暴露给模型每次只让模型选一个执行这当然可以跑但应对不了复杂场景。我在这里加入了三种编排方式。单技能执行是最简单的方式适合那些一步到位的技能比如汇率转换、天气查询。调用时模型或规则路由选定一个技能执行引擎同步等待结果返回。链式编排适合固定顺序的任务。比如订单履约永远是校验地址 - 查询库存 - 扣库存 - 发起支付 - 通知结果这五步顺序不能乱。在技能配置里这类技能会声明一个pipeline由执行引擎按照pipeline的顺序逐步执行每步执行的结果会喂给下一步作为输入。这个方式可以绕开模型达到完全确定性。# pipeline_example.py # 链式编排的执行逻辑示例 PIPELINE_ORDER_FULFILLMENT [ user_address_verify, inventory_query, inventory_deduct, payment_charge, notification_send ] def run_pipeline(skill_id, context): interim_data {} for step in PIPELINE_ORDER_FULFILLMENT: # 执行引擎局部变量互相传递 interim_data[step] executor.invoke(step, interim_data) return interim_data条件路由适合同一个意图有多个技能候选的场景。举个例子用户问今天上海的天气怎么样可能命中城市天气预报技能、生活指数技能、出行建议技能三个候选。条件路由先看数据能不能满足如果不能就去查依赖如果还不行再让模型做决策。这种先确定性后不确定性的策略就避免了模型瞎猜。3.4 技能评估与回归测试机制技能体系一定要配评估机制不然升级一个技能就是在赌运气。我在agent-skills里坚持给每个技能配一套测试用例保证改动不影响历史行为。测试分两个维度。第一个维度是输入输出断言测试给定输入检查输出是否符合预期结构、数值范围、关键字段是否存在。第二个维度是语义正确性测试这一步靠大模型自动打分。拿订单履约技能举例输入一个真实用户地址执行技能然后把执行结果和用户原始需求一起交给裁判模型让裁判模型判断是否解决了用户的诉求有没有做多余操作。裁判模型的评分标准可以按场景定制不是通用的像不像人话而是是否达成目标、是否有副作用。# evaluation.py # 技能评估脚本的简化框架 TEST_CASES [ {task: 查询2024年第一季度华东区销售总额, expected_result_type: number}, {task: 把用户周五14:00的会议提醒添加进日历, expected_result_type: calendar_event}, ] def evaluate_skill(skill_runner, test_case): # 1. 用模型生成一个Agent执行计划 plan skill_runner.generate_plan(test_case[task]) # 2. 执行计划并得到技能调用结果 result skill_runner.execute_plan(plan) # 3. 调用裁判模型打分 score judge_model.score( original_tasktest_case[task], executed_resultresult, expected_typetest_case[expected_result_type] ) return score 0.8这套评估机制跑在CI流水线上之后我们团队改技能的最短验证周期从一天降到了十分钟。改了技能自动跑完全部测试用例没问题才能合并代码。4. 技能检索与模型路由的细节打磨4.1 技能描述怎么写模型才不选错技能描述是模型选择技能的主要依据这块的投入产出比非常高。我见过很多人的技能描述写得敷衍就一句话完成订单操作模型根本不知道这个技能接受什么参数、适合什么场景。实际写的时候我建议按这个模板来首先写一句话的summary包含能力类型 主要操作对象 典型使用场景。其次写3到5条使用场景明确列出该技能适用和不适用的情况。接着是参数说明每个参数标明类型、是否必填、取值范围。最后写注意事项放一些非标准的约束条件比如该技能只能处理人民币订单其他币种请调用货币转换技能后再调用。给一个简洁示例summary: 查询指定日期范围内的销售订单汇总数据支持按区域、渠道维度聚合 description: - 使用场景用户询问销量、销售额、订单数等聚合指标时调用 - 不使用场景用户要求查看具体某一条订单明细时请调用订单明细查询技能 - 注意事项查询范围超过90天时性能较差建议分批查询 parameters: start_date: string, 必填, 格式 YYYY-MM-DD end_date: string, 必填, 格式 YYYY-MM-DD region: string, 可选, 区域名称不填则默认为全部区域为什么这个描述结构好用因为它把决策信息和执行信息分开了。summary和场景描述帮助模型做决策参数说明指导模型填参数注意事项避免模型做出超出能力的操作。4.2 先规则后模型路由器的两层设计技能路由是技能层稳定性的关键。我设计的路由器分两层底层是规则匹配顶层才是大模型决策。规则匹配层做三件事。第一关键词映射根据用户问题中的关键词与技能标签做匹配比如天气直接映射到天气技能。第二正则模式有些问句格式固定比如帮我定个X点到Y点的闹钟可以先用正则提取关键值直接走定时技能。第三会话状态如果用户当前处于某个业务会话中如订单创建流程那么优先使用当前流程绑定的技能组不让模型跳到其他领域的技能。只有这三层都没命中才把候选技能列表连同用户问题一起发给大模型做选择。这种设计的计算成本低响应时间稳定而且把决定性路线的技能绑定死了不会出现用户说要创建订单模型却调用了天气查询的荒谬结果。4.3 长上下文里的技能检索优化随着技能数量增长把所有技能描述一股脑塞给模型显然不可行。一个大模型的上下文窗口有限塞了100个技能描述后面跟用户的对话历史就被挤掉了得不偿失。我的解决方案是两级检索。第一级基于向量召回把技能描述向量化存进向量库每次根据用户问题向量召回最相关的20个技能候选。第二级基于关键词精排从20个候选里用规则筛选出5个最终候选再把这5个技能的完整描述交给模型。这样既保证了候选覆盖面又控制了模型的输入长度。注意一个细节向量召回用的技能描述跟给模型选技能时展示的描述可以不完全一样。向量召回阶段用的是偏泛化的描述强调技能与各种问法的语义关联决策阶段用的是偏精确的描述强调边界和参数。这样的两套描述体系能有效提升召回率。5. 常见问题与排查经验实录5.1 技能明明存在模型就是选不到这个是最容易遇到的坑。排查的思路是先看路由日志确定走到了哪一层。如果关键词规则没命中、正则也没命中模型决策时还没选到这个技能问题大概率出在技能描述与用户问法的语义距离上。举例用户问的是这周还剩多少天可以用年假而技能里写的是请假剩余余额查询。如果技能描述里没有任何年假、剩余、可用这些词向量召回可能根本召不回这个技能。解决办法有二一是给技能加别名标签二是让描述中的summary尽量口语化模拟真实用户的问法。5.2 技能执行成功但答案是错的这种问题最迷惑人。日志显示执行正常返回值格式也正确但用户拿到的答案就是跟事实对不上。排查关键在于查看技能内部逻辑是否做了结果校验很多技能只检查了有没有数据返回没检查数据是否合理。我遇到过一个案例订单汇总技能返回了一个空列表Agent顺着流程告诉用户没有订单。实际上问题是查询参数里日期字段传错格式静默被数据库忽略导致查不到数据。从那之后我在技能实现里加了一条规定任何查询技能如果结果为空必须显式抛出数据缺失异常而不是返回空值继续往下走。这个细节值得写在技能编写规范里。5.3 多个技能同时竞争同一个操作用户说帮我新建一个项目项目创建技能和项目模板技能可能同时被召回。如果模型没区分清楚就可能先调用了项目模板技能生成了模板却漏掉了项目创建技能导致项目根本没建出来。这个问题的根源是技能边界重叠。从操作层面看解决方法是两个一是弱化模板技能把它变成创建技能的参数选项而不是独立技能二是如果的确是独立技能必须在描述里写上此技能仅创建模板文档不创建实际项目这类边界约束。5.4 技能升级导致旧任务失败一次技能升级参数schema从city改成了city_code结果旧日志里所有任务全部报参数缺失。这也是我在实际中踩过的坑。排查后发现升级时没有做参数兼容性处理。现在的做法是所有技能的参数命名和格式变更必须经过兼容层。旧参数名称即使废弃也会保留在schema里标记为deprecated由执行引擎做自动转换。这样旧任务不会因为字段改名就崩溃最多被记录一个警告日志。6. 技能体系的扩展与未来方向6.1 从单机技能库到分布式技能市场技能目录做到后面自然会产生跨团队共享的需求。一个团队的库存查询技能另一个团队可能也需要。与其复制代码不如把技能做成一等公民通过内部包管理机制分发。我在后续扩展里定义了技能包格式包含描述、实现、测试用例三件套。任何团队要接入只要拉取技能包并注册进自己的技能中心即可。这个扩展的关键在于包版本语义化和安全校验。技能包会携带数字签名注册中心拉取后自动校验完整性避免中间过程被篡改。同时每个技能包声明自己依赖的基础运行时版本不满足就不让注册防止在一个环境能跑、在另一个环境跑不起来的破事。6.2 技能评估从回归测试走向持续优化回归测试保证了下限但技能层的上限提升还得靠持续优化。现在我在尝试把线上日志里模型选错技能的case自动收集起来定期聚类找出最频繁的错误模式反向改进技能描述和规则命中逻辑。最近已经改进了两个技能的summary写法选对准确率提升了大概七个点。这个优化闭环的雏形是采集日志 - 提取失败案例 - 聚类模式 - 人工修订 - 回归验证。做起来工作量不小但回报也很可观。技能体系的投入不是一次性建设而是一条不断滚动的优化过程。每次修订都在沉淀团队对如何描述能力和如何约束行为的理解这些经验不依赖某个特定模型换个模型底座依然有效。写在最后的实践体会如果你准备在自己的项目里引入技能管理我建议不要一上来就想着做大而全。先盘点现有工具分成三类稳定的原子操作、有固定顺序的流程片段、需要模型决策的复杂行为。只把前两类封装成技能第三类暂时保留为模型自由调用等观察清楚了再固化。我做agent-skills的时候前两周都在做分类和描述规范真正写调度代码反而只花了三天。另外一个值得投资的点是技能测试用例库的建设。一开始嫌麻烦觉得写用例的时间比写技能本身还多。但项目跑到第四个月一次技能升级引发线上问题之后我再也不敢省略测试用例。用测例库兜底技能变更才能放心大胆做。技能体系不是什么高深的技术它就是一套工程纪律靠的是持续的规范约束和案例沉淀。这个项目的价值不是某一个酷炫的函数而是让团队能用工程方法管理Agent的行为边界让Agent从偶尔好用变成持续可用。如果你手上也有一个技能满天飞但总出问题的Agent项目按这套思路重构一次会看到明显的稳定性提升。