
做AI Agent半年多折腾过不少框架最后发现真正决定上限的往往不是模型本身而是围绕agent-skills搭建的那层技能库。这词听起来像概念其实就是把Agent能重复调用的工具流程、认知策略和校验逻辑固化下来做成可组合的单元。这样做的直接收益是同一套能力可以在不同场景复用Agent在复杂任务里不再每次都从零推理规则能兜底模型的随机性团队协作时也能像维护普通代码一样维护“智能”。这篇文章适合正在做Agent落地的工程师、用过LangChain或自己凑Function Calling的开发者以及想搞清楚“Agent到底怎么标准化”的产品负责人。我会从技能层的设计思路、数据结构、实操案例、运行编排到问题排查完整讲一遍我在agent-skills项目里的经验。1. agent-skills到底是解决什么问题的1.1 没有技能层时Agent为什么总“飘”刚接触Agent时大部分人会把所有能力塞进系统提示词让模型自己决定调用哪个工具。这种方案的痛感很直接提示词一长模型就开始忽略细节工具一多选错工具的概率成倍上升一旦换了底模型整个Prompt咒语仿佛重新写过。另一个问题是工具函数本质上是“无状态的操作”但真实业务里操作是有依赖的——比如先查订单号再查物流再判断是否超时最后决定是否生成安抚话术。如果这些逻辑全部写在模型的一次推理里既不可控也没法复用。agent-skills要解决的不是“模型不会调用工具”而是“如何让工具的调用过程变成可维护、可测试、可组合的资产”。打个比方把Agent想象成一个新入职的员工工具函数是办公桌上的各类设备但没有操作规程、没有SOP、没有异常处理预案他只能靠感觉办事。技能层就是把这些操作经验固化成一张张卡片他拿到卡片就知道什么时候该用、先做什么后做什么、失败了怎么办。1.2 一个技能到底长什么样我在项目里把技能定位成“介于提示词和工具函数之间的独立单元”。它不完全是一个函数因为函数不会描述“什么时候用”也不完全是一段提示词因为提示词不能稳定执行多步操作。一个典型的技能由五个部分组成触发条件、参数契约、执行逻辑、异常预案、观察结果。触发条件告诉路由层“什么情况下应该选中这个技能”参数契约要求模型必须提供哪些字段格式如何执行逻辑可以是调用外部API、写一段Python代码、或者让LLM做一次内部推理异常预案定义了API超时、字段缺失、类型不对时怎么办观察结果则是技能执行后返回给主Agent的标准摘要。这五个部分缺一不可。早期我偷懒只写“技能名 实现代码”结果模型经常在错误场景调出错误技能比如把“天气查询”用在“体感温度穿衣建议”上虽然底层都涉及天气但处理逻辑完全不同。加了触发条件和观察结果之后整个系统的准确率提升非常明显。2. 技能定义的接口设计把经验变成可执行契约2.1 数据结构先定契约再写实现在agent-skills里每个技能本质是一份元信息 一段可执行逻辑。元信息是给路由和模型看的执行逻辑才是真正跑的东西。下面是我比较稳定的字段设计字段类型说明namestring技能唯一标识如logistics_trackingdescriptionstring给模型看的触发场景说明必须包含典型用户表达parametersJSON Schema声明所需参数及类型模型必须按此生成executorstringcode或llm或apientrypointstring执行入口函数名timeout_secint超时上限fallbackobject失败时的降级策略output_schemaJSON Schema对返回结果做校验防止污染上下文tagslist技能分类标签方便路由过滤versionstring技能版本做兼容管理这里最需要注意的是 description 的写法。我见过太多人写“查询物流信息”然后模型经常在不同场景误调用。正确写法是“当用户询问订单运输状态、快递到达时间、包裹是否签收时使用本技能。用户没有提供订单号时先调用获取订单列表技能。”description 里要写清楚“什么时候用”和“什么时候不用”比写“这个技能能做什么”重要得多。参数也同理要定义成模型熟悉的自然语言字段而不是后端字段。order_id如果直接暴露给模型它可能不知道这是“用户订单号”还是“支付单号”需要在字段描述里补充。2.2 为什么用 JSON Schema 管参数模型生成参数时自由度很高如果用弱类型定义Agent 生成的参数会五花八门。比如查询时间范围有些模型生成startDate: 2025-01-01要么生成start_time: 2025/01/01 00:00:00。技能层如果不对参数做严格校验下游接手的代码迟早被搞崩溃。JSON Schema 在这里的价值有两个一是结构清晰能让模型知道有哪些字段、类型和枚举二是校验严格可以在参数进入执行逻辑之前就拦截错误。我在实践中还加入了两层兼容处理允许模型传别名比如user_phone和phone都映射到同一字段对于常见类型不一致例如字符串123传给整型order_count会做一次温和转换而不是直接报错。一个真实教训线上有一版技能没有对温度字段做类型校验天气预报 API 返回temperature: 26字符串下游的穿衣建议技能用数值逻辑判断结果所有26度都被当成异常。后来我加了output_schema用type: number严格校验并在执行器里做float()转换问题才真正解决。3. 从0到1搭建技能库一个真实需求的全流程3.1 需求拆解与技能边界划分我用一个核心案例梳理完整过程做一个“客服助手”Agent用户询问“我的快递卡在转运中心两天了什么时候能送到能不能帮我催一下”。这个需求看似单一实际涉及三个技能订单查询、物流追踪、催派单申请。刚开始做时我只写了一个“查询并催件”的大技能把订单查询、物流查询、催件申请全部揉在一起。结果就是只要用户不想催件这个技能也会多走一步创建工单既浪费额度又影响响应速度。后来我改成三个独立技能再通过编排层决定是否组合。这个改动的核心原则是一个技能只做一件完整且有明确输出的事技能间通过参数传递协作。再拆一下“催派单申请”它需要接收order_no和reason内部调用后端工单API返回ticket_id。而“订单查询”技能会返回order_no作为前置结果。这样一来“物流追踪”就不必重复在技能内部再次查询订单表只需要接收上游传过来的参数。3.2 实现一个技能从低代码脚本到注册以一个“订单查询”技能为例我采用Python实现的常见模式。首先定义一个入参 schemaquery_order_schema { type: object, properties: { user_query: {type: string, description: 用户的原始提问用于情感分析和意图确认}, phone: {type: string, description: 用户手机号如果上下文里没有则必须询问}, order_id: {type: string, description: 用户提供的订单号能从上下文中提取就传}, }, required: [phone] }注意user_query的设计很多人不理解。它是在技能内部做意图判断时需要的原始输入尤其是“是否催件”这类需要情感倾向的场景原始query比结构化参数更可靠。然后实现执行逻辑def execute_order_query(params: dict): phone params[phone] order_id params.get(order_id, ) # 调用内部订单查询服务 order_info query_order_service(phone, order_id) return { order_no: order_info[order_no], status: order_info[status], delivery_company: order_info[delivery_company], summary: f订单{order_info[order_no]}当前状态{order_info[status]} }这个技能的返回值不是裸数据而是经过裁剪的 summary。这样设计是因为主 Agent 的上下文窗口有限如果把整个订单对象都甩回去几轮对话后上下文就爆了。所以我规定技能返回时必须包含一个summary字段控制在50字以内同时保留少量关键字段供编排层使用。注册技能时我会维护一份技能目录把元信息、入口函数和版本号绑定。路由层最终靠这份目录决定当前调用哪个技能。可以理解成给Agent做了一份“服务菜单”菜单上不能全是复杂流程也不能有重复菜名。3.3 技能组合用有向图决定执行顺序单技能好写组合才是难点。agent-skills的核心能力之一是让技能可以按需串联或并联。以客服案例为例执行顺序是订单查询 - 物流追踪 - 催单申请如果需要。最开始我用“提示词让模型自己决策”结果经常出现先催单、后查物流的情况。后来我引入一个轻量的人工配置工作流技术实现上就是一个有向无环图节点是技能边是依赖关系。模型只负责在两处做判断入口处选择第一个技能执行完每个节点后根据结果决定是否走分支边。这套设计的最大好处是“把路由的复杂度和技能内部逻辑解耦”。模型不用知道技能内部怎么调用API只需要在关键节点上做选择题。同时程序层可以严格保证前置依赖比如催单申请绝不允许在订单查询之前执行。实际项目里我把图的定义放在配置中心用Python dict描述{ skills: [order_query, logistics_tracking, dispatch_ticket], edges: [ [order_query, logistics_tracking, result.order_no is not None], [logistics_tracking, dispatch_ticket, result.is_delayed is True] ] }这个配置短小但效果非常稳。执行过程中每个技能返回的summary会作为后续技能的上下文且支持根据条件中断流程。例如物流没有超时就不会进入催单节点。4. 运行时的编排与调度技能不是堆在一起就行4.1 路由选择让模型做减法规则做加法技能少的时候靠模型直接选择即可技能一旦超过二三十个光靠模型选置信度会明显下降。我见过一个项目把五十个技能全部塞给 Function Calling结果选错率接近20%。我的做法是把路由拆成两层。第一层用标签过滤比如判断用户场景是“售后”还是“售前”动态缩小候选集第二层才把缩小后的技能列表交给模型。这个策略在agent-skills中体现为tag字段比如logistics、order、aftersale、refund。路由层的代码会用简单的关键词初筛再让模型精排。为什么不用纯规则因为用户表达太灵活规则只能覆盖高频表达模型擅长做模糊匹配。但为什么也不能纯模型因为实时延迟和成本不允许每次都对五十个技能做完整推理。两层配合后准确率提升非常明显实测误调率从12%降到4%左右。利润很重要的一点是路由结果需要记录日志。每次点击技能、所需时间、模型决策的候选集都记录下来。后续优化技能库时这些日志是最重要的依据。4.2 上下文与结果压缩防止状态被垃圾填满技能在面向主Agent返回信息时我必须控制上下文膨胀。长对话中每次技能返回原始JSON几百轮下来很容易超过上下文长度。这里我引入两级机制第一级是摘要生成。每个技能基 Read 返回的 summary 字段必须足够精简。比如“订单号SH20250116状态运输中承运商顺丰”而不是把售后列表全部带出来。第二级是运行时缓存。同一订单的物流状态在一个小时内重复查询会直接命中缓存不重新调用技能。还有一个小技巧在技能内部写一个is_sensitive标记当技能返回内容涉及个人隐私时主 Agent 会做脱敏再写入上下文。这既保护用户数据也减少无意义tokens。4.3 限流、超时与成本控制Agent技能库与普通API不一样技能执行可能包含多个内部调用一个技能可能导致三次外部服务请求。因此我在调度层加了单独的并发控制核心原则是“一个主线程最多执行一个技能组的限定并发数”。如果用户同时发起多个请求排队合并相同的技能调用减少后端压力。超时设置也得看场景。简单查询技能一般设 5s涉及多步编排的技能最多给 15s。我给每个技能配置超时时会配套一个partial_result策略如果超时但至少拿到了部分数据就把部分数据作为结果返回并标记warning。宁可给模型“残缺但真实”的信息也不要让模型陷入漫长的等待后一无所获。成本方面技能内部凡是可以用代码确定的逻辑就不要让模型再次推理。比如判断“是否在预期时间段内”这是一行代码就能算出来的千万别让模型做数学运算。模型只负责需要语义理解的判断其他全部下沉到代码层。这种分配直接影响账单。5. 踩坑记录那些文档里不会写的细节5.1 技能不调用或调错八成是描述和上下文的锅遇到Agent“卡住不调用技能”我第一反应不是查模型而是翻技能描述。常见的问题是描述写得太笼统模型读了几遍都不知道这个技能适用什么场景。比如“查询天气”和“穿衣建议”两个技能描述如果不加上典型例句模型很容易把“穿什么衣服”的问题派给天气技能最后返回一堆温度和降水就是不给穿搭建议。另一个高频原因是历史上下文污染。多轮对话后Agent 可能把用户早期提到的“地址”误当成“订单号”导致技能参数不完整。解决方法是在调用技能之前写一个上下文提取器把最新意图对应的关键实体重置到参数槽内。这个提取器可以是一个小模型也可以用规则正则关键是不让旧实体串进来。5.2 参数校验失败到底该怎么返回当模型生成参数不符合 schema 时不要直接抛错给用户。正确做法是向模型返回“缺失参数清单”并附带一个可行的补全问题。比如技能要求phone但模型没传应该给出如下提示参数缺失: phone(用户手机号)。请查看对话历史若没有则直接向用户询问“方便提供下手机号吗”这种反馈的最大价值是告诉模型如何自我修复而不是让对话陷入死循环。在日志里我把这类修复过程单独打点统计每个技能的“首答参数完整率”。完整率低于70%的技能说明 description 和 schema 需要重写而不是模型笨。同时要注意required字段里的参数必须谨慎非必要不设置。如果技能能根据上下文推断出字段值就做成可选模型不会因为感觉缺信息就不敢调用。5.3 版本更替的兼容性管理技能库从二十个扩展到上百个之后一定会面临版本变更。我早期没有版本意识直接在原技能函数里改逻辑结果线上Agent表现时好时坏回滚很困难。现在每个技能都带version和变更记录灰度策略用的是“同一技能双版本并存”先让新版本只在10%的流量里生效采集与传统版的效果对比后再切换。版本切换还有一个必须测试的点技能返回的summary格式是否有变。下游工作流往往依赖summary里的字段比如提取order_no如果新版本改成了orderNumber那所有下游都会断。所以我在发布前会跑一遍离线回归测试把历史对话数据回放对比新旧版本的关键字段覆盖率和任务完成率。5.4 速查表常见问题快速定位现象首要排查位置常见解决模型完全不调用技能技能 description 是否包含典型触发表达重写描述加入“不要用”的场景调用了错误的技能候选技能列表是否太大增加标签过滤缩小候选集参数总是缺或错JSON Schema 是否过于严格减小 required 字段补充别名映射技能执行报错fallback 是否配置补超时重试及部分结果返回上下文快速膨胀summary 是否太长限制 summary 60字以内加缓存新版本一发布就出问题summary 格式是否兼容做字段回放测试双版本灰度以上是我在 agent-skills 运营中总结下来最意外的细节。没有哪个知识点需要高端算法但每一条都在真实用户访问量下被证明是关键。做这半年技能库我个人最大的体会是Agent的聪明程度由模型决定但可靠程度由技能库决定。别把技能当作一次性函数写要当作长期维护的接口来设计。最后分享一个小技巧给每个技能配一个最小复现用例一个用户query一个期望输出。这不仅是调试利器也是未来训练评估集的最佳起点。