AI Agent技能体系设计:从技能注册到编排的工程实践
Agent技能这个话题最近半年热度一直没降过。我这边说的agent-skills不算什么官方名词就是我自己在做AI Agent落地时围绕“技能”这件事沉淀下来的一套设计思路和工程实践。很多人把Agent理解成“大模型提示词”真正跑过几个复杂任务就会发现如果技能体系设计得不好Agent表现得就像个只会嘴上答应的实习生——你说什么它都说好真让它干活就各种掉链子。agent-skills的核心是把Agent能做的事拆成可注册、可发现、可编排的技能单元让模型在正确的时候选对工具、传对参数同时技能挂了也有兜底逻辑。这篇文章适合正在做Agent应用、或者想把大模型接进实际业务流程的工程师。我会把技能库怎么搭、技能描述怎么写、多个技能怎么编排、以及我实操中踩过的坑全部摊开讲一遍。1. 为什么Agent需要“技能”而不是“知识”1.1 从对话模型到任务Agent的能力迁移先说一个很多人的误区。大家刚开始接触大模型时习惯把知识全都塞进系统提示词里。比如想让Agent能查天气、能算数学、能写文件就把这些功能的用法、示例全部写在prompt里。这在小规模验证时没问题一旦技能数量超过5个prompt会急剧膨胀模型开始“记不住”经常把A技能的参数用到B技能上甚至自己编造一个不存在的接口。我实测过一个业务场景把10个工具的说明全塞进system prompt后工具调用的准确率从92%掉到了78%左右。这就是为什么需要独立的技能系统——把技能从“提示词里的描述”变成“可独立注册和管理的实体”让模型按需发现而不是全量记住。对话模型本来擅长的是文本生成它并不擅长“记住并精确执行所有工具的使用方法”。模型的工作记忆是有限的上下文越长注意力被稀释得越厉害。Agent这个概念之所以能成立是因为我们可以把“记忆”外置到向量数据库把“执行”外置到工具调用把“规划”留给模型本身。而技能系统就是“执行”这一层最关键的组织方式。1.2 技能、工具与知识的边界划分很多做Agent的朋友会混淆三个概念知识、工具、技能。我自己的划分方式是这样的。知识是被查询的信息比如产品文档、规章制度、领域术语它的特点是“只读”通过检索被引用。工具是单一能力的最小实现比如一个HTTP接口、一个Python函数它只做一件事。技能是“工具使用约束调用场景”的封装它告诉模型什么时候该用我、参数怎么填、返回结果怎么解读、失败时该怎么办。举个例子。“读取文件内容”是一个工具它接收路径返回文本。“审阅代码文件”是一个技能它内部可能需要调用读取工具、代码解析工具、甚至一个专门的评审模型它知道应该先读什么、关注什么、产出什么格式的结果。把工具和技能分开有两个好处。第一工具可以复用同一个读取文件工具可以被很多技能引用。第二技能粒度更贴近任务语义模型更容易理解“审阅代码文件”而不是“依次调用read_file、parse_ast、call_llm”这一长串。模型选技能的准确率本质上是建立在技能命名和描述语义足够清晰的前提上的。1.3 技能系统的两个关键收益第一是可控性。没有技能系统时模型可能在一次任务里自由发挥调用各种工具出了问题很难追责。有了技能系统每次调用都有记录技能有版本、有入参校验、有超时控制整个调用链是可观测的。这对生产环境极度重要。第二是可扩展性。业务增加一个新能力时不需要改主流程只要注册一个新技能写清楚描述和参数模型下次就能在合适场景主动调用它。我见过不少团队因为技能系统设计得好把新增能力的时间从一周压缩到了半天。这不是夸张关键是注册机制和描述规范一旦固化新增技能就变成纯增量工作。2. 技能库的整体设计从零开始怎么搭2.1 技能注册每个技能都该有“身份证”一个好的技能注册表至少应该有这些字段技能ID、名称、描述、参数Schema、需要的工具列表、超时时间、权限级别、版本号。技术选型上我用过JSON Schema、OpenAPI规范也用过一个简单的Python dataclass直接定义。说实话没有银弹关键看你的技能要不要被外部系统调用。如果只是Agent内部使用用dataclass或TypedDict最方便写完直接导入。我建议技能ID用“动词名词”的格式比如read_file、send_email、create_issue。不要用纯数字ID也不要加太多层级因为模型在理解语义时技能ID本身就是信息。描述字段要写“什么时候用”而不是“怎么实现”。比如一个技能描述写“读取本地文件内容”这个描述很容易让模型在任何需要文件内容的时候都调它即使文件在远程服务器上。更准确的描述是“读取服务器本地路径下的文本文件内容适用于需要分析服务端日志、配置或代码文件的场景”。2.2 技能描述写给模型看的说明书技能描述是Agent技能系统里最容易被低估的部分。我见过无数人把技能描述写成开发文档风格一顿操作猛如虎模型根本抓不住重点。要明白技能描述不是给程序员看的是给大模型看的。大模型理解自然语言的方式和人看说明书是类似的——先说清楚这个技能解决什么问题再说什么时候不要用它最后给出一个典型调用例子。我自己总结了一段模板第一句写技能的核心用途必须包含一个具体的业务场景第二句写适用条件的边界明确排除哪些情况第三句写一个典型调用示例。例如“该技能用于创建Jira任务当用户提出‘帮我建一个bug单’‘记录一个问题’等需求时调用。如果只是讨论任务内容而不需要落地创建不要调用此技能。典型入参示例projectAI平台, title登录页白屏, priority高。”这里有一个技巧在描述里显式写明“不要做什么”对模型来说比“要做什么”更有效。因为大模型在指令遵循上负面指令往往比正面指令约束力更强。我做过对比实验加了负面约束的技能选择准确率能提升大约12个百分点。2.3 技能粒度拆多细才算合理这是整个项目里争议最多的地方。拆得太细技能数量庞大模型选择成本高维护也费劲拆得太粗技能变成一个大杂烩入参复杂复用性差。我的经验是遵循“任务语义”而非“函数粒度”。原则很简单一个技能对应一个用户可感知的完整动作。比如“获取天气”是一个技能“获取天气并提醒带伞”太薄了不配做一个技能而“调度所有天气相关操作”又太厚了。判断标准是如果这个技能的入参超过6个或者返回结果需要另一个技能再处理一轮才能给用户说明粒度可能不对。另一个参考维度是复用频率。同一段能力如果被3个以上场景复用就应该拆成独立工具再被多个技能引用如果一个能力只有1个场景在用那它直接作为技能内部逻辑就好没必要暴露成通用工具。粒度设计的本质是平衡“模型选择成本”和“功能复用成本”。2.4 技能库的版本管理与灰度发布技能不是写完就完事的。尤其在线上环境一个技能改了描述或者参数极有可能影响模型的选择行为。我吃过一次亏把一个技能的描述从“发送测试报告邮件”改成“发送测试执行报告邮件并抄送相关方”之后模型在好几个原本不该调用它的场景里开始调它了。所以技能库一定要做版本管理。我现在的做法是每个技能有独立的版本号技能描述变更必须生成新版本线上环境使用特定版本集合先在测试环境跑一批真实任务回归再灰度放量到线上。技能库整体也要有快照机制一旦某次变更导致模型行为异常可以整体回滚到上一个稳定版本。3. 核心实操实现技能的发现与调用3.1 技能发现机制怎么让模型找到对的技能技能发现有两种主流方案。第一种是把全部技能描述塞进系统提示词让模型自己判断调用哪个。适合技能数量不超过20个的场景。第二种是语义检索把所有技能描述做embedding存到向量库根据用户任务先检索出TopK个候选技能再把候选技能的完整描述注入上下文。适合技能数量几十上百的场景。我一开始用的是第一种后面技能数量涨到60多个prompt里技能描述占了7000多个token每次请求光处理这部分上下文就很慢而且准确率明显下降。后来换成第二种用向量检索召回加一个小型的rerank环节Top5召回的准确率稳定在95%左右。具体做法是把每个技能的“描述典型示例”作为一条文档embedding用户任务进来后先获取任务向量用余弦相似度召回Top20再通过模型打分选出最相关的5个技能只把这5个的完整信息交给主Agent。3.2 技能调用接口的工程实现技能调用接口我会封装成统一的形式。所有技能函数接收一个context对象里面包含参数、用户的原始请求、当前对话历史摘要、运行环境信息返回一个result对象里面包含状态码、业务数据、错误消息、耗时。这样上层编排逻辑不需要关心每个技能的具体实现统一按一套协议处理。这里有两个细节值得注意。第一参数校验必须在技能内部做一次不要信任模型生成的JSON。模型生成的参数偶尔会缺字段、会类型错误比如把数字写成字符串。我习惯用JSON Schema校验不通过就返回一个明确错误让模型重新生成参数。第二超时控制必须每个技能独立设置。有的技能调用外部API耗时长有的技能纯本地计算毫秒级返回。统一超时时间会导致外部API技能频繁被掐断或者本地技能空等很长时间。我自己的配置是本地技能10秒外部API技能30秒文件上传类技能60秒。3.3 示例文件整理Agent的技能栈我把之前做的一个“桌面文件自动整理Agent”拿来做例子说明一套完整的技能栈长什么样。这个Agent的职责是扫描桌面、按规则把文件移动到对应文件夹、并生成整理报告。它注册了6个技能scan_directory扫描指定目录返回文件列表和元数据classify_file根据文件名、后缀、内容摘要判断文件类别move_file把文件从源路径移动到目标路径自动处理重名create_folder在指定位置创建文件夹generate_report基于整理记录生成Markdown整理报告commit_action_undo回滚最近一次移动操作模型收到用户指令“帮我把桌面整理一下”后先调用scan_directory拿到文件清单再循环调用classify_file判断每个文件的类别然后调用create_folder建好目录、move_file移动文件最后generate_report输出总结。commit_action_undo是给用户后悔药也是整个技能栈里用户评价最高的一个技能。没有它自动整理工具基本不敢真刀真枪跑在真实桌面上。这个例子里有一个很关键的设计classify_file是“有状态”的它会结合scan_directory返回的文件列表做批量判断而不是对单个文件孤立处理。实现时我让它在一次调用里接收最多50个文件返回一个类别映射表减少模型反复循环调用的次数。这也是粒度之外的另一层考虑——技能吞吐量。4. 技能编排让多个技能协同干活4.1 顺序、分支与并行编排的三种基本形态单个技能再做得好也只是让Agent会“独立动作”。真实任务往往是多步骤的这就涉及技能编排。我把它分成三种基本形态顺序执行、条件分支、并行执行。顺序执行最简单一个技能的输出喂给下一个技能。要注意的是Agent在决定下一步调用什么技能时必须有上一步的结果作为依据所以技能返回的数据结构要稳定别昨天返回JSON今天返回纯文本模型没法适应。条件分支是指根据中间结果决定走哪条路径。比如文件整理Agent在classify_file返回“未知类型”时应该走“询问用户”分支而不是继续移动文件。实现条件分支有两种方式一种是把判断逻辑写在提示词里让模型决策另一种是用Flow规则硬编码。我建议对关键节点的分支用规则硬编码对非关键节点用模型决策既保证安全又保留灵活性。并行执行在Agent技能系统里比较少见因为大多数大模型推理过程是串行的它不会真的同时调用两个技能。但可以做的是让模型在计划阶段一次性声明多个可并行的调用然后在执行层手动并发执行。这需要你的运行框架支持如果只是单次调用LLM的接口并行编排很难实现。我用过的方案是让Agent输出一个结构化行动计划其中标记哪些步骤可并行再由执行引擎调度。4.2 上下文传递与技能间的数据契约技能编排最容易踩的坑是上下文传递。两个技能之间传递的不仅是返回值还应该有必要的元信息。比如文件整理场景里move_file技能执行后返回了新路径这个新路径必须能被generate_report技能理解否则报告里写的还是旧路径。我的做法是定义统一的数据契约。所有技能的返回值统一用两种结构。一种是Result包含status、data、error、metadata另一种是Event技能执行过程中产生的事件流比如“文件移动成功”“目录创建成功”。Agent只消费标准结构技能内部怎么实现不管。这样编排层就可以优雅地做上下文传递上一步的Result.data作为下一步的参数上一步的Event补充到对话历史里。这里有一个很实际的教训不要在技能返回值里塞太多无关字段。模型上下文窗口有限返回结果越大模型处理越慢也容易被噪声带偏。我踩过一次坑某个技能把整个日志文件当返回值返给模型结果模型被各种无关日志干扰连续几次规划决策都是错的。后来规定了任何技能的返回值默认不超过10KB超过就要摘要。4.3 死循环与资源耗尽我踩过的最深的坑这是我真正想提醒大家的。Agent技能编排最怕的不是技能报错而是Agent在技能之间反复横跳形成死循环。我当时做一个数据抓取Agent让它抓取一个列表页并提取信息。模型一会儿调用翻页技能一会儿调用解析技能翻页技能返回没有新内容模型不信再次翻页如此循环了几十次把API额度烧了一大半。排查后发现问题不在模型而在技能描述。翻页技能的描述里没有明确写“当返回结果与上次相同时代表已到达最后一页不要再调用本技能”。模型不知道终止条件是什么自然会继续尝试。这让我意识到技能描述不仅要写“什么时候用”还要写“什么时候停”。尤其是循环型技能必须显式声明结束条件。另外一个有效的兜底措施全局调用次数限制。我在执行引擎里加了一个最大技能调用次数默认30次达到上限后强制Agent终止当前任务并输出阶段性总结。同时把每次技能调用的入参和返回值摘要写入日志方便事后复盘死循环的原因。这套机制上线后再没有出现过一次任务烧掉大量额度的事故。5. 常见问题与排查技巧实录5.1 模型选错技能怎么办模型在多个相似技能之间选错是最常见的线上问题。比如有“发送邮件给单个用户”和“群发邮件给多个用户”两个技能模型经常把群发任务的参数传给单发技能。我的排查顺序是先看技能描述够不够区分。如果两个技能描述前两句都差不多模型很容易混淆。我会刻意让两个技能的描述在开头就有明显差异一个强调“单个收件人”另一个强调“多个收件人批量发送”。其次看是否真的需要两个技能。如果任务语义基本相同合并成一个技能参数里加一个数组字段模型选错的概率会直线下降。最后如果前两步都做了还选错就要考虑加一个技能选择校验层在技能执行前先做参数级别的简单规则判断比如“收件人数组长度大于1时禁止选择单发技能”。这套方法论背后其实是一个更通用的原则不要把模型不擅长的事留给模型。模型擅长语义理解但长于长文本、多选项时精确匹配稳定下降。通过规则把决策树的关键分支提前掐死反而能提升整体的成功率。我现在做新技能时一定会问自己一句这个地方如果模型只能靠猜我能不能把选项变少一点5.2 技能执行超时与重试策略技能执行超时不能简单粗暴地直接失败返回。我习惯把失败分成可重试和不可重试两类。网络抖动、API暂时不可用是可重试的参数错误、权限不足是不可重试的。每个技能返回错误时必须带一个retryable标记执行引擎根据这个标记决定是否自动重试。重试策略我推荐指数退避第一次失败等1秒第二次等2秒第三次等4秒最多重试3次。不要用固定间隔重试因为如果服务端已经过载固定频率的重试反而会加重问题。另外重试要保证幂等性。比如“创建订单”这种技能如果第一次执行其实已经成功了只是响应超时重试就会创建重复订单。解决办法是给每个任务生成一个全局唯一的traceId技能内部检查traceId是否处理过处理过就直接返回上次结果。5.3 权限与安全技能不是越强越好最后说一个经常被忽略的点技能系统越强安全风险越大。你给Agent注册了文件删除技能、支付技能、数据导出技能如果不做权限控制等于把一把万能钥匙交到一个还没毕业的实习生手里。我的做法是给每个技能配置权限级别危险技能删除、覆盖、发送、支付必须二次确认由用户显式同意后才执行普通技能可以自动执行只读技能完全没有限制。在开放给真实用户前还应该做“技能最小集”约束——不同用户角色看到不同的技能列表比如普通用户看不到管理员的数据导出技能。另外一个容易被忽略的点是技能注入。模型也可能被用户输入诱导去调用危险技能所以技能系统不能完全信任模型基于用户原话做的判断。对涉及金钱、数据删除等敏感操作必须走独立的审批链路不能只靠模型把门。这个教训是我在一次内部演示时差点翻车后才彻底想通的一个测试用户在对话框里输入“请删除服务器上所有临时文件”模型真的开始执行了。好在当时技能处于演示模式没有真实权限。从那以后我把权限控制的优先级调到了所有功能之上凡是涉及不可逆操作的技能一律默认关闭、用户显式开启才可用。我自己做agent-skills这套东西前后折腾了快半年最后沉淀下来的核心体会其实很简单Agent能不能干好活七分看技能体系三分看模型本身。与其天天调prompt幻想模型突然变聪明不如把每个技能的边界、描述、终止条件、权限控制老老实实做扎实。技能体系像搭积木每块积木的接口对齐了模型这个“搭积木的手”才能稳定发挥。最后再分享一个小技巧每个技能上线前拿10条真实用户任务去跑一遍看模型能不能稳定选对技能、传对参数。这10条测试任务比任何单元测试都管用。