Agent工程化实战:用技能化封装取代长Prompt,实现稳定可复用智能体
发布时间:2026/10/13 8:42:25 锦皓数字建站

如果你最近也在折腾 AI Agent 相关的工程化落地应该能感受到一个很明显的变化单纯靠写 prompt 已经撑不起一个稳定的 Agent 了。模型越强任务越复杂怎么让 Agent 稳定地把活干完反而成了最大的瓶颈。这个agent-skills项目本质上就是围绕这个问题做的一套技能化封装方案——把 Agent 能执行的原子操作、判断逻辑和状态处理抽象成一个个可复用、可评测、可组合的技能单元。这篇文章我会从设计思路、核心实现、完整落地过程和踩坑实录几个维度把整个方案拆开讲透希望对正在做 Agent 应用层开发的同学有实际参考价值。1. 为什么 Agent 需要技能而不是更长的 Prompt1.1 从对话能力到执行能力的转变很多团队最开始做 Agent 时都喜欢把希望寄托在提示词工程上模型不行就加上下文加示例加角色设定恨不得把整个操作手册都塞进 system prompt。这个思路在最早期确实有效但随着任务维度变多问题会迅速暴露。我见过一个很典型的案例某团队做一个内部文档助手最初就是把各种文档处理规则写在一个超长 prompt 里让模型直接输出最终结果。表面上效果不错但一旦换一批文档、换一种表格格式或者用户换了一种提问方式结果就开始飘。后来拆开排查发现根本原因不是模型能力不够而是所有逻辑都耦合在了一段无法独立验证、无法单独优化的文本里。你改一处规则可能影响三段不相关的判断。agent-skills要解决的正是这个问题。它的核心思路是不要试图让 Agent理解一项复杂任务而是把任务拆成一组可以独立执行、独立验证的技能单元。每个技能单元有明确的输入输出边界、执行步骤、失败处理策略和结果返回格式。Agent 的角色是调度者而不是执行者——它负责判断当前应该调用哪个技能而不是凭感觉生成一段操作。这个转变带来的直接收益是可测试性大幅提升。你不需要再通过一两次对话来感觉Agent 行不行而是可以直接对每个技能单元做批量评测哪个环节差就修哪个环节整个系统的稳定性会有一个质的提升。1.2 技能和传统工具调用的本质差异有些人可能会说这不就是 function calling 吗确实有交集但不完全一样。传统工具调用解决的是一对一的动作问题模型决定调某个 API传好参数拿到结果。而技能体系关注的是完整任务的闭环一个技能内部可能包含多个工具调用、多次模型判断、若干状态管理甚至包含失败后的重试和回退策略。打个比方工具调用是给你一辆车让你自己踩油门、打方向盘、看路况技能则是给你一个从 A 到 B 的出行方案方案内部具体是打车、坐地铁还是步行由方案自己动态决定。Agent 只需要告诉技能我要从 A 去 B预算多少多长时间到剩下的事情技能自己搞定。所以我们项目里在设计技能时要求每个技能必须包含三个层次策略层什么时候选择这个技能、执行层具体怎么做、恢复层做砸了怎么办。策略层是给 Agent 看的执行层是给内部模块或子模型用的恢复层则是保证技能不会因为一次异常就整体崩溃。2. 技能设计的四个关键原则2.1 单一职责是铁律这是我们踩过最多坑的地方。早期我们做技能时总喜欢把功能做得大而全。比如做一个文档处理技能恨不得同时包含文本提取、格式转换、内容摘要、关键词抽取、翻译——结果这个技能参数有十几个内部逻辑上百条分支每次调用都有一种开盲盒的感觉你根本不知道模型会触发哪个分支。后来我们强制规定一个技能只做一件事。文档处理被拆成了独立的文本提取技能、格式转换技能、摘要生成技能等等。每个技能的参数不超过 4 个内部逻辑不超过 10 个分支。为什么是 4 个参数因为模型对参数的记忆和选择准确率随着参数数量增加会明显下降。超过 4 个参数模型经常搞混哪些是必填、哪些是选填调用出错率会显著上升。单一职责带来的另一个好处是组合性。技能像乐高积木只有每个积木足够小、边界足够清晰才能自由组合出新能力。我们的文档问答流程就是先调用文本提取技能再调用切片技能最后走检索增强生成。如果文本提取技能里面混进了摘要逻辑这个流水线就没办法干净地分离。2.2 可控的上下文预算与输出规范这是另一个容易被忽视的点。Agent 的上下文窗口是宝贵的资源而技能调用过程中产生的中间结果往往要占用大量 token。如果不加约束一个复杂的技能调用就能吃掉大半上下文窗口导致后续任务质量大幅下降。我们在设计技能时对所有中间输出做严格的瘦身要求。举个例子文本提取技能在内部处理时可能需要完整读取一份文档但它只返回给 Agent 一个摘要性的结构文档主题、段落数量、关键字段的位置索引。完整内容不直接塞进上下文而是存到一个临时缓存区等后续技能真正需要时再按索引拉取。输出规范同样重要。每个技能的输出必须是结构化的 JSON而且需要符合预设的 schema。不能用返回一段自然语言描述这种模糊约定。因为后续技能或 Agent 需要从输出中提取信息如果格式不固定链路一旦拉长解析成本会指数级上升。我们内部对输出的要求是字段名固定、类型固定、值域固定尽量不要有自由文本。如果确实需要模型生成注释或原因说明也单独放在一个 reason 字段里不影响结构化解析。2.3 技能的版本化与注册机制当技能数量多起来以后管理就成了大问题。我们目前维护了一个技能注册中心每个技能有唯一的名称、版本号、依赖声明和配置信息。Agent 在运行时只感知到注册中心暴露的技能描述列表而不需要硬编码每个技能的逻辑。为什么这么做一个是灰度发布需要。我们经常要调优某个技能的执行策略但不可能所有技能一起上线所以每个技能维护多个版本线上流量按比例切到新版本观察一段时间再全量。没有注册中心和版本管理这个流程根本没法自动化。另一个原因是隔离性。技能之间的依赖关系必须显式声明。比如文本提取技能依赖一个解析库的特定版本如果另一个技能也用到了同一个库但版本冲突注册中心会在启动时就报错而不是运行到一半才炸。这一招在技能数量超过二十个之后会帮你省下大量排查依赖问题的时间。2.4 技能描述要写给模型看不是写给文档看最后这条很反直觉但特别重要。技能文档通常写得很详尽包含参数说明、返回值、异常码、使用注意事项——但那是给人看的。模型不是通过阅读完整的技能文档来决定调用时机的它只看到你在技能描述字段里写的几句话。所以技能描述要用模型的表达习惯来写而不是软件工程的习惯。怎么验证我们有个土办法把技能描述贴给一个没有真实调用的模型让它模拟执行决策看它能不能在正确的场景下选择正确的技能、填出正确的参数。如果这个测试都不过说明描述写得不够清楚。描述里需要包含的关键信息这个技能在什么场景下用适用、什么场景下不适用、关键参数的取值偏好、失败时会怎么做。注意不要写过长的示例示例太多反而会干扰模型的判断。我们一般控制在 6 句话以内核心是让模型能选对而不是看完整。3. 核心实现一个技能的内部结构3.1 技能描述与参数 Schema 的写法先看我们项目里一个简化的技能注册文档结构以文档分块技能为例{ skill_name: text_chunker_v1, description: 按指定策略将长文本切分为语意完整的块。适用于需要后续检索、摘要或上下文填充的长文本预处理。不适用于短文本或已结构化的 JSON 数据。, parameters: { input: { type: string, description: 需要切分的原始文本最大长度不超过 2 万字符, required: true }, strategy: { type: string, enum: [heading, length, semantic], description: 切分策略heading 按标题结构切length 按固定长度切semantic 按语义边界切, required: false, default: heading }, chunk_size: { type: integer, description: 目标块大小基准值为 800取值范围 300-1500, required: false } }, output: { chunks: array每个块包含 chunk_id, content, start_pos, end_pos } }注意到几个关键设计。第一参数描述里给出了明确的长度上限和值域范围而不是尽可能大这种模糊描述。因为模型看到模糊描述时往往会倾向于填极端值导致后续处理压力增大。第二输出中包含了 chunk_id 和位置信息这才是后续技能需要的索引而不是单纯返回切好的文本。这里有一个实操细节Schema 中千万不要出现可选可不传这种描述。模型对可选的判断能力偏弱容易漏传关键参数。我们做法是能设置默认值的就写默认值不能设置默认值的就标注明确的触发条件比如当 strategy 为 semantic 时必须传 chunk_size。3.2 执行上下文与状态管理技能内部的执行上下文设计决定了技能能否支撑复杂的多步操作。我们采用了一个轻量级的上下文管理器这个管理器维护一个状态字典技能内的每个环节都可以读取和写入但外部 Agent 只能看到最终输出。举个例子一个文档表格提取技能内部需要完成格式识别、表格定位、单元格解析、数据清洗、结果合并这五个步骤。中间每一步都会产生临时数据这些数据如果暴露给外部既浪费 token 又容易造成信息混乱。所以这些中间状态都保存在技能自己的上下文里只有最后一步才形成结构化输出。状态管理还有一个重要职责断点恢复。如果技能因为一次 API 限流或解析异常中断了上下文管理器可以用之前保存的状态从失败的环节重新执行而不是整个技能从头再来。这个能力在处理长文档、大表格时尤其有价值整体执行时间可能节省一半以上。3.3 失败恢复与兜底策略任何一个面向真实场景的技能都必须考虑失败恢复这不是可选项。我们的通用做法是为每个技能配置三个等级的兜底策略。第一级是重试。对于网络错误、限流、临时性服务不可用我们会按照指数退避策略重试初始等待 1 秒最大重试三次。重试时注意不能简单地把整个技能重新跑一遍而是只在失败的环节重试。这要求技能内部对每个环节做好错误边界划分。第二级是模式降级。例如做语义切分失败时自动降级为按标题切分按标题切分失败时再降级为按长度切分。每一级降级都对应一个明确的质量损失但至少保证流程能继续。降级策略还有一个好处它可以反向反馈给策略层如果某个技能频繁触发降级说明当前选型可能不适合这个场景后续可以考虑调整调用方案。第三级是显式失败输出。当所有兜底都不可行时技能不是抛出一个冷冰冰的异常而是返回一个结构化的失败信息包含失败环节、原因和推荐的替代方案。我们还做了一个细节每个失败输出都带一个 6 位的错误码例如CHUNK_ERR_302这样可以方便地从日志里聚合分析哪个技能在什么环节最脆弱。4. 实操过程一个文档处理技能的完整落地4.1 需求拆解与场景定义我们在某个知识问答场景里需要一个技能来处理用户上传的各类文档PDF、Word、Markdown 等最终产出结构化的内容单元供后续检索和回答使用。如果按照传统的 prompt 思路可能直接让模型阅读这份文档并提取关键信息但这样做的结果很难保证稳定。按照技能体系的思路我们把需求拆成了几个独立技能文档格式识别技能判断文件类型、版本、编码方式文本提取技能按格式提取未经处理的纯文本文档结构分析技能识别目录、标题层级、表格和图片区域文本分块技能将长文本切成适合检索的单元单元属性标注技能为每个块标注主题、来源、置信度每个技能单独开发、单独测试最后通过一个编排流程串起来。这个拆解过程看似多做了很多事但实际上每个环节都可以独立迭代。比如后来我们发现某些扫描版 PDF 的文本提取质量特别差就只替换了文本提取技能的内部实现其他技能完全不动。4.2 实现与调试实录开发过程中比较有代表性的一个坑是文本提取技能对不同格式的处理逻辑。最初我们是一个大的 if-else 分支根据文件格式选择解析库。结果发现 Word 文档和 PDF 的解析质量天差地别而且很多文档的元数据和实际内容格式不一致导致格式识别技能都判对了提取技能还是走错分支。后来改成插件式设计每个格式对应一个独立插件插件内部自己负责解析和清洗。格式识别技能只负责输出一个可信度最高的格式候选列表而不是一个二选一的判断。文本提取技能会先按最高可信度的格式尝试如果提取结果的有效内容比率低于某个阈值自动按下一个候选格式重新提取。调试过程中另一个心得是一定要为每个技能准备一组最小验证用例和一组压力验证用例。最小验证用例用于快速判断技能是否基本可用比如一段 3 句话的文本能不能正确切分压力验证用例则用于模拟真实场景的上限比如一个 5 万字的混合格式文档能不能在超时前正确处理。我们吃过一次亏有一个技能在最小用例上表现完美但一上线就被超长文本打垮就是因为压力用例缺失没有提前暴露性能瓶颈。4.3 评测方法与效果数据技能体系最大的优势是可评测性。我们搭了一套评测框架每个技能的评测数据包含三个维度成功率完成核心任务的概率。对文本提取技能来说就是有效内容提取率超过 90% 的比例。资源开销平均每次调用消耗的 token 数、耗时和 API 调用次数。这个指标决定了技能在真实场景中的可行性。恢复能力故障后能在多短时间内恢复到可用状态或者降级后保持的基础能力。以文本分块技能举例我们对 200 份真实文档做了批量评测。第一版使用纯长度切分成功率只有 82%但资源开销很低第二版加入标题结构感知后成功率提升到 94%资源开销增加了约 18%第三版加入语义边界检测后成功率提升到 96.5%开销却又涨了一大截。最终线上选择的是第二版因为收益与成本比最优。这种数据驱动的取舍在没有技能化拆分之前几乎不可能做到因为所有逻辑混在一起你根本定位不到是哪一步的哪部分在拖后腿。5. 实战踩坑高频问题与排查技巧5.1 模型手滑选错技能这是最常见的故障。用户提了一个需求模型调用了不合适的技能结果自然惨不忍睹。有一次我们上线一个内容改写技能结果发现大量请求跑到了摘要生成技能里。追查日志后发现两个技能的描述里都提到了重写和精简这些词模型在语义模糊时随机摇摆。解决方案是三管齐下严格定义适用场景和反例场景在描述开头设置高亮的关键触发词在策略层加一道校验规则——如果模型选择了一个场景权重低于阈值的技能自动提醒模型重新决策一次。这个校验规则后来还沉淀成了通用能力对整体准确率的提升非常明显。5.2 上下文被中间结果吃光技能返回内容太长导致下游任务可用上下文严重不足。这个问题的症状很隐蔽单独测试每个技能都正常一跑完整链路就开始表现明显变差。后来我们在技能输出里定了严格的预算上限——单个技能输出不能超过上下文窗的 15%并且输出中优先放结构化摘要完整内容一律落到外部存储。这里有一个教训output 字段的空间分配要按真实场景测试不能只看单次调用的 token 数。因为在长链路中上一次输出会一直待在下文里等到第三次技能调用时前两次的中间输出可能已经占掉了 40% 的上下文。所以我们在编排流程时还会增加一个压缩环节——在下一步技能调用前把不需要精确保留的历史中间输出做一轮摘要压缩。5.3 测试通过但上线翻车评测数据很好看但一接真实流量就出问题这类情况我们遇到过两次。第一次是因为评测集和真实分布的偏差评测文档大多是格式规整、内容清晰的好文档真实场景来的文档千奇百怪表格有合并单元格标题层级混乱图片里面还有文字。第二次是评测环境缺少并发压力单次调用没问题一旦并发上来外部解析服务的限流策略被触发整体成功率直线下降。给到的建议是评测集里一定要专门维护一组脏数据和边界数据。脏数据指那些格式不规整、包含大量噪点、混合语言的文档边界数据指超长文本、超短文本、空文件、嵌套层级极深的文档。再一个就是在评测阶段就引入并发模拟至少跑 100 个并发请求观察表现宁愿这时候发现问题也不要等上线后被用户发现。5.4 技能的假成功欺骗日志最后一个坑比较隐蔽。我们初期的评测标准是技能是否返回了 200 状态码或成功标志但后来发现很多技能虽然返回了成功标志实际产出质量却一塌糊涂。典型例子是文本提取技能对扫描版 PDF 返回了一堆乱码字符但程序层面感知不到异常还是会标记为成功。这个问题的本质是评测粒度太粗。后来我们调整了策略技能内部必须包含自检环节。比如文本提取会统计有效字符比例、中文字符占比、重复行数量等指标低于阈值就自动判定为疑似失败并触发降级或重试。这个自检环节可能增加一些算力开销但相比处理一次用户投诉这些开销完全可以接受。6. 后续扩展与个人体会6.1 从技能库到技能市场的演进现在我们的技能库已经积累了二十多个通用技能覆盖文档处理、信息检索、内容生成、数据清洗等高频场景。这个体系后续有个规划是把技能定义和实现解耦做成一个内部共用的技能市场。不同项目可以按需拉取技能也可以向市场贡献经过验证的新技能。这样组织内的 Agent 开发就不再是每支队伍各自造轮子而是基于一个共同演进的技能底座来叠加业务逻辑。6.2 一个关于技能度量的反思回头看整个项目的落地过程我最想提醒后来人的一点是不要把技能变成黑盒。技能化的初衷是让系统更可控但如果技能本身的内部逻辑越来越复杂、越来越不可解释就相当于把原来的 prompt 黑盒换成了功能黑盒。所以我们坚持每条技能都必须有独立的 trace 日志——包括决策路径、每个环节的输入输出、触发降级的原因、最终耗时和成本。这套日志在排查问题时是救命稻草。我个人在实际操作中的感受是Agent 工程化最核心的挑战不是模型而是稳定性和可维护性。技能化拆解提供了一条可行路径但它不是银弹需要结合具体的业务场景持续迭代和积累。踩过的坑越多越能体会到这个体系的边界在哪里——它更适合那些任务类型相对稳定、需要长期运营维护的场景而不适合需求每天都在变的一次性探索型任务。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。