接手这个“agent-skills”项目的时候我脑子里最先冒出来的问题是大模型明明已经能写诗、能解题、能聊得头头是道为什么真正想让它替我去处理一点“具体的工作”比如整理一下邮箱、批量改个文件名、跨系统同步一条数据它就立刻露馅了呢答案其实很扎心——因为模型本质上是个“嘴强王者”它没有手。光有思考能力没有可执行的技能Agent就只能停在“给你建议”的阶段永远到不了“帮你办完”的阶段。这篇文章我想把我在这类技能编排方向上的一点实战经验完整地拆开从一个项目标题出发说说我理解的“agent-skills”到底应该包含哪些东西以及当你真的动手去搭这样一套技能系统时最容易被哪些暗坑绊倒。先说明白这不是某个大厂开源框架的测评帖而是我自己做过一个内部模拟项目之后留下来的整套思路。项目中心只有一个让Agent像工具箱一样把“看一眼就会做”和“动手帮你做”这两件事彻底打通。我会从为什么Agent需要技能化讲起然后按技能定义、调度实现、工程化避坑、评测迭代这几个环节把我跑通流程时记录下来的问题和解法全部倒出来。适合的人群大概是两类一类是在做Agent应用但发现Prompt怎么调都不够用的朋友另一类是准备给自己的Bot接入API但又不知道怎么组织这些能力的技术人。1. 为什么Agent必须有一张“技能清单”从“会聊天”到“能办事”的距离先追一个问题当我们说一个Agent“会做某件事”时我们到底希望它做到什么程度最朴素的期望是你给它一个目标它能自主拆解步骤然后一步不落地把结果交回给你。但真实情况是模型生成的文字再漂亮它也无法在你电脑上真正创建一个文件夹无法真的调用某个第三方接口去改一条订单状态。这就是Agent和普通ChatBot的分水岭——前者必须被接上一双“手”而这双手就是我们所说的技能。1.1 纯靠Prompt硬撑的Agent为什么一进真实任务就崩我见过非常多的Demo演示的时候特别惊艳你输入“帮我搜一下最近三个月关于智能客服的招标信息”模型立刻给出了一段结构清晰的回复甚至列出了搜索关键词建议和筛选思路。但你如果追问“那你就顺手把筛选出来的链接汇总成一个Excel发我邮箱”它基本就哑火了。因为在这个过程里模型能输出的只是“做事的建议”而不是“做事的结果”。早期我尝试过一种偷懒方案——把所有工具逻辑全塞进System Prompt里告诉模型“你可以调用这些函数”然后用一个大而全的工具类去执行。这种做法在只有三五个工具的时候勉强能跑一旦技能数量超过十个问题就像雨后春笋一样冒出来模型开始混淆两个功能相似的技能该调A的时候调了B参数抽取不稳定同样一句话有时候能抽全有时候把必填项漏了执行过程中出现异常模型根本不知道发生了什么只会就着错误信息硬编一段“看起来没问题”的假成功返回。有一次测试让我印象特别深。我让一个纯Prompt方式的Agent“把这周收到的报销邮件按金额从大到小排序并生成摘要”。当时的Prompt写得很详细每个字段都注释了。结果那个Agent调用了邮件搜索技能拿到了邮件列表排序也对了。但到生成摘要这一步它没有真的把邮件正文重新读一遍而是根据邮件标题在“脑补”内容最后生成的摘要干脆把一封坏账的金额张冠李戴到另一封正常邮件上。那一刻我意识到Prompt再长也补不上“确定性执行”的缺失。1.2 技能化带来的三个本质变化后来我把这一整套思路推翻换成“技能优先”的重构方式才真正体会到什么叫从根上解决问题。所谓技能化本质上就是不再要求模型去“理解”每一个工具的内部实现而是只让它“看懂说明书然后做选择”。这把复杂问题拆分成了两个相对简单的子问题一个是意图理解一个是参数传递。技能化带来的第一个变化是复用。同一个“发送邮件”技能可以被“日报汇总”“周报提醒”“异常告警”多个Agent流程复用不需要每个流程单独去写一遍调用逻辑。第二个变化是隔离。某个技能出了问题你只需要修那一个技能模块不会牵连其他能力排查范围一下子缩小了。第三个变化是可观测。每个技能有确定的输入、输出和行为记录你可以清楚地知道Agent在哪个节点做了什么决定而不是对着聊天记录猜。打个比方。以前的Agent像一个大包间里随时随地喊人的管家所有事儿都靠“嘴对嘴”交代技能化之后管家手里多了一本白皮手册上面分门别类写着“换灯泡找电工”“疏通管道找水工”他要做的只是判断你现在需要谁然后按流程叫人。判断这件事依然靠模型但执行被彻底标准化了。1.3 和Function Calling的关系别搞混很多朋友一看“agent-skills”就想到OpenAI的Function Calling。这里我想泼盆冷水function calling是“通道”技能是“封装的业务单元”。一条通道可以传输任何函数调用但技能是“包含函数入口、参数校验、权限控制、异常处理、结果格式化”的一整套完整封装。真正在工程上起作用的是你在通道之上构建的那套业务规则。举个例子同一个函数“删除文件”放在“清理临时文件技能”里和放在“归档旧项目技能”里权限策略完全不同。前者可以无脑执行后者可能需要二次审批。如果你只把它理解成一个函数调用那这些控制逻辑你根本没地方挂载。所以整篇我会用“技能”这个词而不是“函数”就是为了强调它是在模型能力和真实动作之间的一层业务抽象。2. 技能描述就是给模型的一份“产品说明书”字段设计与反例如果说技能系统是Agent的手脚那技能描述就是这些手脚的“使用说明书”。模型只能靠你写出来的描述来决定“什么时候用这个技能”“怎么填参数”。换句话说技能能不能被正确触发七成靠描述写得好不好。这也是当初重构时我们投入时间最多的环节。2.1 一个合格技能该长什么样的字段结构我先把我最后沉淀下来的技能定义结构摆出来它不花哨但每个字段都有用途name技能唯一标识建议用动词开头比如send_email、create_calendar_event不要起email_util这种含糊名。description一段面向模型的自然语言说明内容包含触发条件、适用场景、使用边界和典型示例。parametersJSON Schema格式的参数定义声明每个字段的类型、是否必填、取值范围。returns执行成功后的返回结构明确告知模型“你将拿到什么”。permissions这个技能需要的权限级别包括数据读范围、写范围、是否需要用户审批。timeout超时配置防止一个技能卡死拖垮整个Agent循环。tags分类标签便于Agent在候选技能很多时做粗筛。这七个字段不是凑数的。description决定模型“选不选它”parameters决定模型“调得好不好”permissions决定“能不能调”returns决定“下一步怎么走”。任何一个环节缺失都会在长链路任务里变成隐患。2.2 description用模型能“读得懂”的方式写说明书我最想展开讲的就是description。很多人写它的时候习惯写成给人看的接口文档“发送邮件接口输入收件人、主题、正文。”这句话信息量太低了模型根本不知道什么场景下该调它。更好的写法是加入触发器、边界条件和典型场景。举个例子同样是邮件技能差描述和好描述效果天差地别维度差描述示例好描述示例触发识别“发送邮件”“当用户要求发送、回复或转发邮件时使用。也适用于批量给多人发送通知的场景。不适用于仅草拟不发送的情况。”参数说明“收件人、主题、正文”“收件人列表支持多个邮箱地址字符串数组格式正文建议使用纯文本如有附件请先调用upload_attachment获取文件ID。”边界提示无“如果收件人数超过100人需要先通过审批流程内部邮件和外部邮件的审批规则不同。”我后来总结了一个写description的套路先写“什么时候用”再写“什么时候不用”最后补“调用时要注意什么”。尤其是“什么时候不用”这一点绝大多数人都会忽略但它恰恰是解决技能混淆的利器。两个技能看起来很像的时候明确写出对方的边界能显著提高命中率。另外还有一个很实用的技巧在description里内置一个“输入示例”。不需要很长但要包含典型调用参数。这样模型在不确定的时候会照着示例去理解字段含义参数抽取错误率能降不少。实测中加了示例描述之后我们的参数残缺率从大约三成降到了一成以内。2.3 parameters克制比全面管用参数的度很难拿捏。早期我犯过一个典型错误做“创建工单”技能时我把什么字段都定义出来包括“客户等级”“工单来源”“紧急程度”“预期解决时间”“备注”“自定义字段”。字段确实全面但模型每次抽取的时候都像在做阅读理解而且经常漏掉必填项。模型一旦开始“猜”你就会看到他把“紧急程度”猜成P0把“备注”塞进“标题”里去。正确的方案是做减法只保留真正必需的参数能枚举的给枚举能设默认值的设默认值。参数少了模型抽对的概率自然高。我的经验是一个技能的参数控制在五个以内超过五个就要考虑拆技能或者改成服务端自动补全。比如“当前用户名”“当前时间”这类完全可以通过上下文拿到的东西不要让模型填直接在服务端注入。另外一个细节是参数校验必须做在服务端不能只靠模型“自觉”。因为模型输出的参数几乎不可能百分之百合法服务端用JSON Schema做一次严格校验该转类型的转类型该拦截的拦截。校验不通过时优先尝试程序化修正修正不了再通过Agent回滚语义重新抽取。这一层代码并不复杂但它能挡住绝大部分参数质量问题。3. 调度循环的四个翻车点参数幻觉、状态断裂、超时与越权技能架构搭好之后真正的硬骨头在调度层。Agent的核心循环本质上就是“感知—决策—执行—观察结果—再决策”的迭代过程。这个循环听起来简单但跑起来以后会出现很多反直觉的问题而且这些问题不跑到一定规模根本看不出来。我当时重构到第三周接连踩进四个大坑每一个都值得单独拿出来说。3.1 最小可用的调度循环长什么样先把基础框架摆出来。调度循环最核心的伪代码大概就是这个样子def agent_run(task): messages [{role: system, content: build_system_prompt()}] for step in range(MAX_STEPS): response llm.chat(messages) if response.action finish: return response.answer skill skill_registry.match(response.action) if not skill: messages.append(skill_not_found_feedback(response.action)) continue params validate_params(skill, response.params) if not params.is_valid: messages.append(param_error_feedback(params.errors)) continue result execute_skill_with_timeout(skill, params) messages.append(format_observation(result)) raise MaxStepsExceeded()这个循环的核心逻辑就是不断的“执行—反馈—再决策”。但如果你只是搭这么个骨架就上生产你会很惊喜地发现自己成功复刻了别人踩过的所有坑。接下来这四个坑我按踩坑顺序讲它们都是真实发生过、并且让我一度怀疑这个方向到底走不走得通的问题。3.2 坑一参数幻觉模型会给一个它“以为”存在的值这是所有调度问题里出现频率最高的。模型在生成工具调用参数时偶尔会无中生有地填一些合理但错误的值。典型场景是“删除过期缓存”模型会自己脑补一个目录路径看起来特别正规但那个路径根本不存在。还有一次更离谱模型调用“发送消息”技能时收件人列表里多了一个对话里从没出现过的邮箱仔细排查后确认是模型根据上下文“推测”出一个合理联系人。这种问题的根因在于模型是在做概率生成它的目标只是“生成的参数像真的”而不是“参数确实匹配用户意图”。对此我的解法是双管齐下第一服务端做严格参数校验能枚举的字段必须命中枚举否则直接判定无效第二在执行敏感技能前加一步“关键参数确认”把模型抽取到的参数用自然语言渲染给用户确认。虽然多一次交互但换来的是实打实的出错率下降。3.3 坑二状态断裂前一个技能的结果没喂给下一个技能第二个坑特别隐蔽发生在多技能串联的长任务里。举个我自查过的真实场景任务要求是“把会议纪要里的待办事项生成任务清单并给相关人发邮件”。前面两步都跑得好好的但模型在生成邮件正文时并没有把“任务清单”里的内容拿过来而是根据会议纪要的标题“重新组织”了一份完全不同的待办事项。问题出在哪我们的调度循环确实把技能结果作为文本拼到了下一轮消息里但模型上下文一长它会“选择性忘记”某个技能返回的结构化内容转而去依赖那些更早但更口语化的上下文。针对这个情况我把关键技能返回的结果做了“结构化回传”不仅是塞一段文本进去而是同时把格式化后的摘要和关键字段单独作为一个新消息类型传给模型并明确引导“后续引用待办清单时必须以这份结构化数据为准不要根据记忆重建”。加了这条约束之后状态断裂的问题才真正缓解下来。3.4 坑三超时与重试的策略不设防等于失控调度循环里如果某个技能执行特别慢比如外部接口响应了五秒还没返回整个Agent循环就会被卡住后面的决策全部排不上。更要命的是有些第三方接口会在超时临界点抖一下你不知道该重试还是该放弃。我的方案是给每个技能配三个档位的超时正常耗时、警戒耗时、最大容忍耗时。一旦超过最大容忍立即终止该技能调用把“执行失败”作为观察结果反馈给模型让它决定是换方案还是再次重试。另外重试必须带指数退避不能死心眼地连续打同一个已经挂掉的接口。还有一个细节对幂等技能可以放心自动重试对非幂等技能比如“创建支付订单”宁可标记失败让用户手工处理也不能盲目重跑造成重复扣款。3.5 坑四越权调用模型“好心办坏事”的边界问题这是四个坑里最需要谨慎对待的。模型在执行链路上经常会出现超出用户本意的操作。比如用户只是说“整理一下项目文档”模型调用了“列出文档”技能拿到文档清单后顺手又调用了“删除孤儿文档”技能因为它“判断”这些文档没有用了。从技术上说它是完全合法地调用了注册过的技能但从产品角度看这是一次越权操作可能会造成不可逆后果。要防住这类问题单靠Prompt约束是不够的因为模型在链路深处做出的自主判断很难用几句System Prompt管住。最终落地的方案是把技能按权限分级只读级、本地写入级、外部写操作级、高风险级。高风险技能必须显式获得用户授权才能执行。调度的代码里需要在执行前检查当前任务的权限集合而不是仅仅检查技能本身有没有注册。这个改动上线后恶意调用和误调用的数量立刻降了一个量级。4. 技能库长到三十个之后评测、回归与可观测性的工程解法技能数量少的时候靠人肉测试还扛得住。但一旦技能库长到二十个、三十个甚至更多你就会发现“刚才还好的功能怎么又不行了”开始高频出现。这阶段的工程重心已经从“搭架构”转向“建护栏”。4.1 评测维度不能只看“跑没跑通”我见过很多团队评测Agent只看一个指标——“最终任务完成率”。这个指标太粗了它掩盖了太多中间问题。我自己用了一套四分法每个技能和每条任务链路都分别记录意图识别准确率、参数抽取成功率、技能执行成功率、最终任务完成率。这四个维度分别对应模型选技能的能力、填参数的能力、系统执行的能力、整体任务闭环的能力。这四个指标不是强相关的。有时候最终任务完成率只有六成但你一拆开看发现技能执行成功率其实高达九成问题全出在参数抽取上。这时候你的优化重点就应该是description和参数定义而不是去调执行逻辑。反过来如果参数抽取完全没问题但执行成功率很低那你需要修的是技能内部实现或者外部接口稳定性。没有拆开的指标就只能“头疼医头”永远定位不到真正的病灶。4.2 维护一套金标回归集少走太多弯路技能系统的难点不在于新增技能而在于改动一个旧技能可能会悄悄破坏十几个既有任务链路。模型的行为有随机性你也许今天试了五次都正常明天换了模型版本同样的Prompt输出就截然不同。为了应对这种漂移我维护了一套“金标任务集”大概三十来条任务覆盖每一种技能类型以及主流的组合链路。每次改动任何技能定义、调度逻辑、底层模型或参数校验规则我都会先全量跑一遍金标集看四个维度的指标有没有回退。看起来是个笨办法但它带来的安全感无可替代。而且这套回归不用全自动可以做成半自动的脚本负责跑链路并记录结果人工负责看失败案例来判定是回归还是模型正常波动。把回归结果按周导出你还能清晰看到技能库的健康度趋势哪一块在退化一目了然。4.3 可观测性给每个决策留一份“黑匣子”最后想聊的是可观测性。Agent链路最大的问题在于不确定性你没法用传统接口测试那套“断言返回结果”的方法来排查问题。所以你需要的是一份完整的决策追踪每一步向模型发送了什么消息、模型生成了什么决策、参数是什么、匹配到哪个技能、技能返回了什么、超时花了多久、失败原因是什么。我一开始偷懒只是简单打印日志根本不够用。后来加了追踪ID贯穿整个任务链路并把每次调用的关键信息都结构化落库。排查一次失败案例只需要顺着追踪ID拉一遍时间线是哪一步出问题、模型当时看到了什么内容全都清清楚楚。这就像飞机上的黑匣子平时看起来没什么用一旦出了事故它就是唯一能找到真相的记录。强烈建议所有做Agent应用的人在功能上线第一周就把这套追踪结构搭好后面再补的代价远超你的想象。关于技能库的迭代我最后还想多讲两句。你可能已经发现我通篇都在强调“技能是工程问题不是咒语问题”。就算你用的模型能力再强没有一个结构良好、定义严谨、带权限控制、可观测可评测的技能层最终体验依然会在真实任务中崩盘。反过来只要这层技能底座够稳哪怕模型本事实力一般也照样能稳定完成大量高价值任务。我在这类项目里最大的一条体会就是别急着让模型变得更聪明先把你要交给它的“手和工具”打磨扎实它自然会表现得像个靠谱的助手。如果你也在做类似的技能编排建议从最小闭环开始挑三个高频任务定义好技能跑通调度循环先把“能跑”这件事做到极致再去追求技能数量。等第一次跑通“用户一句话、Agent多步执行、最终交付结果”的全过程那种正反馈会强烈到让你立刻想把这个体系铺到所有任务上。