做智能体这一年多我踩过最深的坑不是模型能力不够而是明明模型够聪明却总在一些具体任务上“有劲使不出”。你要它帮忙查一下库存数据它不知道去哪查要它整理一份周报它只能把对话里的零散信息拼起来。问题不是出在推理上而是出在“手”不够长。所以后来我把重心从“调模型”转移到了“搭技能”也就是做成一套可以不断累积的agent-skills体系。这篇文章就把我在这套体系上的设计思路、落地过程、踩坑复盘一次说清楚希望能给正在做智能体应用的朋友一点参考。1. 项目缘起智能体有了大脑但还缺一双手1.1 一次印象深刻的“翻车”现场最开始我做智能体的时候思路特别朴素模型负责理解意图、生成答案业务逻辑全写在提示词里。看起来没毛病但实际跑起来问题很大。有一个典型场景是让智能体帮用户查询订单物流信息。我在提示词里写了“当用户询问物流时请根据订单号调用接口查询”。第一版测试的时候确实能跑通但换个说法问“我的包裹到哪了”模型就懵了它不知道该用什么参数去查接口甚至会把客服系统的接口路径猜错。更麻烦的是一旦业务规则变化比如物流接口换了签名方式我得去改提示词改完还要担心其他区域有没有被连带影响。从那次之后我就意识到把业务能力硬塞进提示词里等于让一个项目经理兼职做搬运工什么都能干一点但什么都干不深。智能体需要一个更像插件的机制每个能力独立封装模型只用知道“什么时候该用哪个能力、怎么传参数”至于能力内部怎么实现完全不需要关心。这就是agent-skills想要解决的核心问题。1.2 技能的定义不只是“函数调用”很多人一听 agent skills第一反应是“这不就是 function calling 嘛”。确实有关系但两者不能直接画等号。Function calling 是一套接口协议它让模型可以按结构化的方式请求执行某个函数。而 skills 更接近一整套能力单元它包含接口描述、执行逻辑、触发条件、输入输出约定、依赖资源甚至还包括对应的调试和观测手段。打个比方function calling 像是一个电话交换机负责把来电接到正确的分机。而 skills 更像是每个分机背后的一整套服务有人接电话、有人处理问题、有人记录结果。只有交换机没服务电话打通了也没意义。所以我在设计技能时不单单把目光放在“怎么调API”上而是想清楚整个链路模型怎么发现技能、怎么决定使用技能、技能执行后结果怎么回流、失败时怎么降级。1.3 为什么自己搭一套而不是直接抄现成的市面上关于 agent 的开源项目、脚手架、商业平台已经很多了。我也试着用过一些比较有名的方案一开始确实省事但用着用着就发现不顺手。最大的问题是不透明框架层把很多东西都封装好了我很难知道某个技能到底是被谁触发的、为什么没触发、参数是怎么被模型填出来的。第二个问题是绑定太深凡是框架里没有的就得迁就框架去改造改动成本很高。所以我最后选择做一套相对轻量的、核心逻辑自己控制的技能框架。说白了就是一套规范加一个调度器规范用来定义每个技能长什么样调度器负责把模型输出的意图映射到具体的技能上。这样做的好处是我可以完全驾驭整个行为想加什么观测点就加什么观测点想改调度策略就改调度策略不会被框架绑住手脚。2. 技能体系的分层设计与核心思路2.1 四层技能从原子操作到决策辅助我最终把技能体系分为四层这四层不是随便分的而是按“复用频率”和“抽象程度”来切。第一层是基础技能也是最原子的一层。比如文本格式化、日期计算、编码转换、正则匹配。这类技能不依赖任何外部系统纯本地执行特征是稳定、通用、被调用的次数最多。很多智能体应用没做好就是基础技能太弱导致模型老在一些很简单的事上绕弯子。第二层是工具技能指需要对接外部系统的能力。查数据库、调业务接口、读写文件、发消息通知都算这一层。工具技能的一大特点是每个动作都可能产生副作用所以它在设计上必须考虑幂等、超时、权限边界这些问题。第三层是领域技能它是把多个基础技能和工具技能组织成一套完整业务流程的能力。举个例子“生成一份销售周报”这个技能内部要先查销售明细、再算环比增长、再调用模板渲染、最后推送邮件。如果让模型一步到位去完成这件事很容易中间断掉但如果做成领域技能模型只需要触发一次剩下的流程由技能内部编排完成稳定性和可控性都会好很多。第四层是元技能用来管理和调度其他技能。比如“技能推荐”“技能降级”“任务拆解”都属于元技能。元技能解决的是“我该用哪个技能”的问题而不是“某个技能内部怎么做”的问题。2.2 每个技能的“简历”五项必备信息我在设计技能规范时给每个技能定义了一套类似简历的结构。任何一个技能不管你底层实现多复杂对外都必须呈现五项信息。技能名称和描述是第一项。名称要短方便模型引用描述则要用自然语言写清楚“这个技能是做什么的、在什么场景下使用”。第二项是触发条件用来告诉调度器什么意图下该调用这个技能。第三项是输入参数定义我会规定每个参数的类型、取值范围、是否必填。第四项是输出结构包括成功时的返回格式、失败时的错误码。第五项是运行时元信息比如超时时间、并发限制、依赖哪些其他技能。这套规范最大的作用是让“技能”成为一个可以被模型理解和操作的对象。模型不需要看代码不需要知道内部用了什么算法只需要通过这份“简历”决定是否调用。这也是后来我能够给智能体叠加“技能推荐”功能的前提因为所有技能都以统一格式暴露给了模型它才能做全局比较。2.3 解耦与复用为什么分层能减少“牵一发动全身”我最初没有分层所有技能都平铺在一起。结果技能数量一多调度就乱了。比如日期计算逻辑我在周报技能里写了一份在日程提醒技能里又写了一份两边的边界条件处理得还不一样。模型碰到复杂任务时偶尔会串。后来统一抽成基础技能问题一下子就少了。分层的本质是解耦基础技能只关心自己的事领域技能只关心流程编排对外暴露的逻辑越少被误改的风险就越低。而且复用性也上来了谁需要日期计算就调用同一份基础技能逻辑保持一致。说句实在话agent 做大了以后复杂度主要不是来自模型推理而是来自这些细节的一致性。3. 从零开发一套技能实操全过程3.1 需求梳理先列业务场景再定边界做技能之前不要急着写代码先把自己要支持的场景列清楚。我当时是按“用户会提出什么样的请求、请求背后需要哪些系统能力”这个思路梳理的。每个场景都记录三件事输入是什么、期望输出是什么、中间要碰哪些系统。以“查订单物流”为例输入是订单号期望输出是物流轨迹和时间节点中间要碰订单系统和物流系统。好那这个技能至少需要处理订单号格式校验、物流接口参数拼装、轨迹信息排序、异常状态归一化四件事。把这些拆明白了再往下一步走就有方向了。3.2 技能注册与实现一个最小可用的例子我习惯用一个伪代码来描述技能结构因为不绑定语言团队里不同角色都能看懂。下面是一个“查询订单物流”技能的简化定义{ name: query_express_track, description: 根据订单号查询物流信息返回物流轨迹、当前状态、预计送达时间, trigger: 用户询问订单物流、包裹位置、快递进度, parameters: { order_id: {type: string, required: true, format: 订单号}, customer_hint: {type: string, required: false, description: 用户提供的额外线索如手机尾号} }, dependencies: [order_system_api, express_api], timeout_ms: 3000, output: { success: {status: string, track_list: array, eta: string}, error: {code: string, message: string} } }实现上这个技能内部做了四件事先校验订单号格式不合法就直接返回错误码然后通过订单系统接口确认订单归属避免张冠李戴接着调物流接口拿到原始轨迹最后做归一化把“运输中”“配送中”“派件中”这类状态统一映射成几个标准枚举值。映射这步很关键因为各物流公司的状态字段五花八门不归一化的话模型拿到乱七八糟的原始值输出质量就难保证。3.3 技能描述的编写技巧决定模型“会不会用”技能实现得再漂亮描述写不清楚也是白搭。我最早犯的错就是把描述写得太简单比如“查询物流信息”。结果模型在用户问“卖家发货了没”时拿不准要不要调这个技能犹豫半天还是去自由发挥了。后来我总结出一套写法描述里要包含能力范围、适用场景、排除场景。比如我可以写成“本技能根据订单号查询物流轨迹与包裹状态适用于用户询问快递进度、包裹位置、物流更新时。如果用户要求预估送达时间且无任何物流单号信息不建议使用本技能”。这段描述的效果立竿见影模型选择技能的准确率高了不少。另外我还发现一个细节参数说明里最好带一个示例值。模型对示例的敏感度很高比干巴巴的“string类型”要好理解得多。比如 order_id 的示例值写成“SO202409123456”模型遇到用户发来的订单号就能更快地匹配上。3.4 测试与联调除了正常路径更要测边界技能的测试不能只测“能跑通”就完事。我一般会把测试用例按四类准备正常请求、边界参数、依赖异常、调度歧义。正常请求就是标准输入验证返回结构和文案是否正常。边界参数要专门测空值、超长字符串、特殊字符、错误类型。依赖异常要模拟外部接口超时、返回格式变化、鉴权失败。调度歧义是很有价值的一类测试我会准备一些“有点相关但又不太相关”的请求看模型会不会误触发。举一个实际例子我测“查询物流”技能时故意用“我的订单出库了吗”去触发。根据描述这个请求和物流确实有关联但真正的答案应该在订单系统里查状态而不是调物流轨迹接口。这个问题靠模型本身很难判断最后是在描述里加了一句“本技能不负责订单状态查询订单是否出库请参考订单系统相关技能”歧义才解决。这种微调看着不起眼但积累多了整套技能的选择准确率会明显提升。4. 常见问题与排查技巧实录4.1 技能调用失败参数格式不匹配是头号元凶技能执行失败大部分情况不是代码bug而是模型传进来的参数不符合预期。比如模型把订单号传成了订单备注里的文本或者把日期传成了“后天下午三点”而不是标准格式。原因是模型不真正理解你的参数约束它只是根据描述去猜。我的解决思路是三层防护。第一层在技能描述里把参数规范写得非常具体包括示例值第二层在调度器里做一次参数预校验不合法就直接拦截不让模型生成的噪音参数进入业务逻辑第三层在技能内部做兜底归一化比如日期字段哪怕传的是自然语言也要尝试用模型解析成标准格式。这套组合下来参数错误导致的失败率降了非常多。4.2 技能冲突多个技能“抢任务”怎么破技能一多最头疼的问题就是冲突。比如用户问“帮我看看这个文件里有没有手机号”这时候可能两个技能都能干“文本内容提取”和“敏感信息识别”。如果没有机制去解决歧义模型就会随机选一个结果不稳定。我试过给技能增加优先级字段也试过让模型自己解释选择理由但这些方案都有点治标不治本。真正有效的做法是在元技能里加一个“技能消歧”环节当多个技能的触发分数都高于阈值时不急着执行而是让模型根据用户当前上下文做一次二次判断判断的维度包括技能精确匹配度、调用代价、历史成功率。相当于给技能调度加了一层“复核机制”准确率能提升不少。4.3 上下文膨胀技能返回太多内容导致模型“失焦”另一个隐蔽的问题是上下文膨胀。技能执行成功返回了一大段结构化的数据模型读了好几轮推理反而把用户的原始意图忘了。最典型的就是查库存一个技能返回了整张库存明细表几千个条目塞进上下文结果模型生成的回答又长又偏。我后来强制要求所有技能在返回前做一次“摘要化处理”优先只输出与用户问题直接相关的字段。另外在调度器层面对返回内容做裁剪超过一定体量的内容先压缩再交给模型。别小看这一步它直接影响了智能体在多轮对话中的稳定性。上下文一乱再强的模型也会“失智”。4.4 安全与权限边界技能越权比模型胡说更可怕技能一旦对接了真实系统安全问题就变得很严重。如果权限设计不到位模型被诱导去调用一个高权限技能就可能产生实质性的破坏。我的经验是技能权限不能只做“技能级别”的粗粒度控制还要做“参数级别”和“操作级别”的细粒度控制。比如“查询用户信息”这个技能不是所有调用者都能查全字段。我会在技能内部根据调用者身份、目的、数据范围去做过滤身份证号、手机号这类敏感字段未授权一律返回脱敏值。另外对高危操作类技能比如删除、修改、转账我会强制二次确认并记录完整审计日志。这部分的严谨程度决定了整个智能体能不能真正跑在生产环境里。5. 经验沉淀让技能体系持续进化5.1 技能也要做版本管理与回归技能不是写完就完事了。业务规则变了、接口协议变了、描述措辞要调整都会产生新版本。我参考软件工程的思路给每个技能都加了版本号和变更记录。升级技能时必须先用之前积累的测试集跑一遍回归确认没有影响其他技能再发布。听起来很重但实际操作起来成本并不高收益却很直接至少不会出现“修好一个技能弄坏另一个技能”的尴尬。5.2 把技能日志做成可观测的技能体系的调试难度远高于普通代码因为中间隔着模型出问题了很难定位是模型理解不对还是技能执行有问题。我要求每次技能调用都记录完整链路触发了什么意图、选择了哪个技能、传入了什么参数、返回了什么结果、耗时多少、有没有降级发生。这些日志汇聚之后我经常能发现一些很有意思的规律比如某个技能在下午被调用的频率特别高或者某个措辞在描述里出现多次后误触发率会上升。5.3 渐进式扩展先跑通一条链路再铺开做规模最后一条经验也是我撞了南墙才明白的技能体系一定要渐进式扩展不要一上来就规划一百个技能。先挑一个核心业务链路把从触发到执行到结果回流的全过程跑通再把经验和规范沉淀下来复制到第二个、第三个链路上。技能多并不代表能力强很多技能如果触发率很低只会增加调度开销和冲突概率。我在实际推进中每个阶段都会主动下线一批调用率低且价值不大的技能让整个体系保持轻量和准确。这大半年来我很深的一个体会是智能体的上限不只在模型推理能力上更在它身边围绕的那套技能系统是否顺手。一个技能接得稳、描述写得准、边界切得清模型可发挥的空间才会真正打开。我也还在持续迭代这套体系希望这些从实际项目里磨出来的经验能帮你在做 agent 应用时少走几段弯路。