ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Agent技能编排实战:让大模型真正“动手办事”的工程指南

Agent技能编排实战:让大模型真正“动手办事”的工程指南

接手这个“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:一段面向模型的自然语言说明,内容包含触发条件、适用场景、使用边界和典型示例。
  • parameters:JSON 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多步执行、最终交付结果”的全过程,那种正反馈会强烈到让你立刻想把这个体系铺到所有任务上。

RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进