资讯动态

提示词膨胀终结者:Agent技能系统设计与实现

发布时间:2026/10/28 4:44:04 来源:尧图企业网站定制
我最早做agent项目的时候把所有业务逻辑都塞在系统提示词里。今天加一个算税的功能明天加一个查库存的指令提示词越来越长模型行为越来越不可控。后来才意识到真正该做的是把能力的边界从模型本身挪出去让agent像乐高拼图一样按需组合能力。这就是我做agent-skills这个项目的原因给智能体配一套可注册、可编排、可观测的技能系统。本文就是这套系统的设计和实现记录适合正在自建agent后端、或者想把工具调用从prompt里抽离出来的开发者参考。1. agent技能系统要解决的三个核心问题1.1 提示词膨胀后模型行为失控的困境先说说我在没有技能系统之前遇到的实际场景。做一个客服agent一开始只处理售后咨询提示词大概一千多字效果还可以。后来业务方要求加上订单查询、物流跟踪、退换货申请、优惠券核销每一块都要在提示词里写清楚触发条件、调用接口、参数格式、返回处理规则。提示词膨胀到五千字左右之后模型开始出现幻觉式的伪调用——它会在没有调用接口的情况下直接编造一个订单状态给用户。你问它为什么这么做它认为自己只是在模拟帮助用户但这在生产环境是不可接受的。技能系统的第一个目标就是把每个能力封装成有明确输入输出声明的单元由模型根据用户意图动态选择调用哪个单元而不是让所有能力都堆在提示词里互相干扰。这就像把一个大杂烩仓库改造成带货架和标签的超市模型只需要看图上的分类指示牌走到对应的货架取货不必知道仓库里每一件货品放在哪里。1.2 技能与普通工具调用的本质区别很多朋友会问这跟OpenAI的function calling、跟LangChain的tool不是一回事吗区别在哪我一开始也这么想过但做完之后发现差异还挺明显。普通的工具调用通常是一层扁平列表模型根据用户输入在可选工具列表里挑一个执行。技能的抽象层级更高具备三个额外特征第一技能能自己声明适用场景而不是靠名字推断第二技能可以组合出新的技能形成层级关系第三技能内部可以有自己的状态和多步骤逻辑不只是单次请求响应。换句话说tool是一次函数调用skill是一个完整的完成任务能力。以我的agent-skills项目为例一个处理退款技能内部的执行流程包括调用用户身份核验、调取订单状态、判断退款政策是否允许、计算退款金额、执行退款操作、给用户生成回执。对模型暴露的只是一个高层的退款技能但内部实际跑了六七个底层工具。这样模型的决策负担就小了很多它不需要在一长串工具列表里精确匹配到第六个计算退款金额接口。1.3 技能复用带来的边际成本递减效应技能系统的第三个好处是复用性。我做客服方案这套技能后来另一个项目做在线教育助教的时候需要订单查询、退款处理、课程咨询我发现可以直接复用之前的订单和退款技能只需要新增一个课程匹配技能。新项目的agent开发周期从三周缩短到五天因为大部分能力已经具备现成的技能封装只需要修改配置里的技能描述让它们贴合新业务的说法。复用的边际成本递减效应是我坚持用技能体系的核心理由。没有技能系统之前每个项目都从零写一遍工具调用、参数校验、错误处理每个项目的行为逻辑还不太一致。有了技能系统之后新业务只做增量配置不推翻重来这是一个质的改变。2. 技能注册中心的schema设计与动态加载机制2.1 技能声明的核心字段与JSON示例agent-skills里最基础的数据结构是技能声明SkillSpec。这个schema我迭代过好几版最终保留的字段如下展示name技能的唯一标识供agent内部编排使用建议用kebab-case风格例如order-refund。description面向模型的自然语言描述说明何时使用该技能要写得像任务简报而不是API文档。parametersJSON Schema格式的参数定义用于模型产出结构化调用参数。execute实际执行逻辑的入口可以是本地Python函数、远程HTTP调用或命令行脚本。capabilities技能依赖的底层能力集合用于依赖解析和冲突检测。state_schema可选技能在运行过程中持久化状态的格式声明支持多轮会话内保持上下文。一个典型技能声明的JSON大致如下{ name: order-refund, description: 处理用户的退款请求。当用户明确要求退款、取消订单、追回已支付金额时使用。需要先核实用户身份再检查订单状态和退款政策。, parameters: { type: object, properties: { order_id: { type: string, description: 用户提供的订单号 }, reason: { type: string, description: 用户反馈的退款原因 } }, required: [order_id, reason] }, capabilities: [user-verification, order-lookup, refund-calculator, payment-operation], execute: { kind: http, endpoint: http://internal-agent-svc/refund/run, method: POST } }2.2 技能描述怎么写才能让模型准确选中这个部分可能是我整个项目中投入产出比最高的一个点技能描述的描述质量直接决定模型选技能的准确率。我一开始写得简短比如退款技能结果用户问我的钱怎么还回来模型完全不触发技能。后来把描述改成当用户表达想要退回已支付的费用、取消订单或申请经济补偿时使用召回率立刻提升了三成。描述要避免三个常见错误一是用名词堆砌而不是场景描述二是描述过于泛化导致多个技能都匹配三是只描述功能不写明触发条件。我建议每个技能的作者都在描述里回答两个问题用户在什么时候会需要这个技能用户通常会怎么说把这两个答案写清楚模型的选择准确率会有肉眼可见的提升。另外我给技能加了负向提示在描述末尾注明当用户仅咨询退款政策但不执行退款时请勿调用本技能。这个小技巧极大减少了误触发——咨询和办理是两回事模型之前经常混淆。2.3 依赖解析与技能冲突检测技能不是孤立的。refund技能依赖user-verification、order-lookup这些底层能力。agent在启动时要建一张依赖图边是技能之间的依赖关系。注册新技能时我会先跑一遍冲突检测need顺序技能是否依赖当前尚未注册的能力路径冲突两个技能是否暴露出相同的名称但实现不同参数冲突两个技能是否对同名参数有不同格式要求这套检测逻辑不复杂本质就是一个DAG拓扑排序加上命名空间检查。但少了它后期维护的时候随手改某个技能描述很可能会悄悄破坏另一个技能的调用链路。依赖检测在项目刚上线时可能用不上等技能数量超过15个以后它就是救命稻草。我踩过的具体坑是技能A和技能B都依赖一个payment-operation能力后来技能B的开发者升级了这个能力把回调地址换了技能A的执行链路第二天就全部超时。因为是隐式依赖日志里根本看不出是能力变化导致的失败排查了很久。所以依赖关系一定要显式声明并且每次注册或更新技能时都跑一遍依赖检查把受影响技能列表打出来。2.4 动态加载开发环境热更新与生产环境锁版本技能注册中心在agent-skills里还承担了动态加载的角色。开发环境下我通过监听技能目录的变动来动态注册和卸载技能也就是改完技能描述和逻辑后不需要重启agent进程就能生效。这个热更新能力对调试体验的提升非常明显尤其是在一遍遍微调技能描述来改善模型选择准确率的时候改一行描述等五分钟重启是让人崩溃的。生产环境则反过来所有技能版本进入注册中心后自动生成快照发布时锁定版本号不允许热更新导致线上行为漂移。这里我设计了一个双轨机制开发环境使用watcher模式技能目录变更后毫秒级重新加载生产环境使用registry模式从不可变配置读取已发布的技能版本。有一次我在生产环境误开了动态加载导致运营同学改了个错别字一样的技能描述全局agent决策行为立刻变化用户端出现了好几天的异常回复。从那以后生产锁版本成了铁律。3. 技能编排器从单技能调用到多技能协同3.1 为什么模型不能直接调用一串技能理想情况下一个用户请求可以对应一次技能调用但实际业务往往是一条长链路。用户说我上周买的手机壳太大了想退掉然后重新买一个小一号的这个请求拆解后需要执行退款技能、商品查询技能、重新下单技能还是连续依赖的顺序执行。如果让模型直接输出这三个技能的一次性调用列表就存在一个关键问题第二个技能的输入依赖第一个技能的输出。手机壳的新尺码不是用户直接给的而是从退款订单详情里推断出来的。所以编排器需要具备逐步计算能力让模型先在当前上下文中决定调用哪个技能把结果放入上下文再根据结果决定下一个动作。这就是agent-skills编排器的核心思路单步决策循环执行。模型每轮只给一个决策系统执行完技能后把结果追加到上下文再让模型决定下一步。这和把整个流程塞给模型相比决策准确率高了很多因为每一步都基于真实返回结果而不是模型臆想的中间状态。3.2 顺序、条件分支与并行执行的实现编排器内部支持三种执行模式顺序执行sequence上个技能输出作为下个技能输入用于强依赖链路。条件分支condition模型根据当前上下文判断是否继续执行/选择哪个分支。并行执行parallel多个技能之间无依赖可以同时发起缩短总耗时。顺序执行是最常见的。我在实现时维护了一个task_context对象它是执行期间所有技能的共享内存。每个技能执行完成后不仅可以返回给用户的消息还可以往task_context里写入结构化数据供后续技能读取。比如退款技能执行完会把order_item_spec写入上下文后续的商品查询技能直接读取这个值不需要用户重新输入。并行执行的收益在批量查询场景中很突出。用户问我的订单什么时候到退款到账了没有优惠券什么时候过期这三个技能互不依赖并行发起HTTP请求总响应时间从三秒压到一秒。但并行执行也引入了复杂度需要处理超时、部分失败、结果合并。我的策略是只要有一个技能失败就整体路由到兜底对话流程不让模型在部分信息缺失的情况下继续瞎猜。3.3 上下文窗口管理与中间结果截断策略多技能协同执行虽然效果好但会快速消耗上下文窗口。一个技能的结构化返回结果可能是一大段JSON如果三个技能的返回都原样塞进上下文再叠加系统提示词和历史记录很容易撑爆窗口。现实中的token成本也会随之上升。我采用的策略是三级截断第一级技能返回的结构化数据已经转换成摘要化的人类可读文本丢掉了内部调试字段第二级对超过长度限制的结果做截断并附上字段列表字段类型摘要这样模型虽然没有看到完整的值但知道存在哪些字段第三级历史记录的高阶层压缩把多轮之前的详细内容压成摘要只保留关键实体和决策结论。这里要特别注意截断不能无差别截。用户刚提到的订单号、地点、金额这类关键实体必须保留而那些用户自己已经看过并且不再依赖的纯展示内容比如详细的商品说明可以大胆压掉。我靠这个策略把一次长流程调用的平均token消耗压低了约35%用户感知不受影响。3.4 失败回退与重试策略的设计取舍技能执行的失败处理是编排器设计里最考验细节的部分。一开始我的策略是失败就让模型解释抱歉暂时无法完成您的请求。但这在业务上完全不能接受。后来我做了三层回退第一层技能执行器自动重试幂等操作比如订单查询、物流状态查询网络抖动造成的失败会通过重试恢复重试间隔用指数退避最多三次。第二层重试仍失败时编排器把错误信息传入模型上下文让模型判断是否可以换个技能完成任务。比如订单查询技能挂了但用户可能只需要知道物流进度物流查询技能还活着模型应该路由过去。第三层模型判断无法替代时才向用户输出降级话术并且降级话术要包含用户可执行的替代动作比如您可以稍后在我的订单页查看物流更新。这套策略上线后技能调用失败导致用户收到错误消息的比例从12%降到1.5%。但也要注意幂等判断做不好会导致重复扣款这种严重问题。我的原则是涉及资金、积分、库存变更的操作一律不做自动重试失败直接交由人工或用户决策。4. 实测踩坑记录技能环路、描述二义性与状态污染4.1 技能互相调用形成的死循环这个坑让我印象很深。我设计了一个order-refund技能它内部依赖user-verification来核验身份而user-verification技能内部又依赖一个查询用户是否存在的底层call。看起来没什么问题。但后来我加了一个兜底技能fallback-human-agent它被设计成在无法解决用户问题的时候触发。问题来了fallback-human-agent的描述里写着当用户需要人工客服处理问题时使用而order-refund技能里的一段描述写着当退款流程无法自动完成时建议用户联系人工客服。模型在一次复杂退款请求中先触发了order-refund发现参数不足接着触发fallback-human-agent而这个技能又想调回order-refund来处理退款诉求。两个技能互相呼唤了六轮上下文被灌满无意义的中间结果最后token耗尽才停下。排查这场事故花了大半天。最后我做了两个防循环机制一是全局执行深度限制单个任务最多执行8次技能调用达到上限强制走安全兜底二是技能调用的去重表同一个技能在连续三轮内不允许重复调用超过一次除非用户明确补充了新的信息。这两个限制牺牲了极少部分合法场景的灵活性但彻底杜绝了技能环路导致的失控。4.2 技能描述歧义导致模型选错技能描述二义性是另一个高频问题。我有两个技能calendar-create用来创建日程事件reminder-set用来设置提醒。第一次上线后实测用户说帮我记一下明天下午三点开会两个技能的使用次数几乎五五开。用户看到的结果差异是创建日程会真正写一个日历事件而设置提醒只是弹一个通知。体验差别非常明显。我的解决办法是对描述做场景化改写。calendar-create的描述改为当用户明确提到会议、日程、约会、事件安排并且需要把该安排同步到日历时使用。一般会包含时间和参与人信息。reminder-set的描述改为当用户想在未来某个时间点收到通知提醒而不需要该时间点作为一个日历事件记录时使用。例如设置吃药提醒、还款提醒。改写之后测试集上的选择准确率从55%提到了91%。还有一个补充技巧我在注册中心加了一个相似技能检测工具每次注册新技能时和已有技能描述两两计算embedding相似度超过阈值就提示该技能与已有技能描述相似请确认是否存在语义重叠。这个静态检查能帮人提前发现二义性问题而不是等到线上才开始修。4.3 技能内部状态在多轮会话间的偷偷污染技能状态管理是个容易忽视的重灾区。我有一次做了个shopping-cart技能用户往购物车里加商品然后切走聊了别的过了很久回来问我现在购物车多少钱结果金额算出来完全不对。排查后发现shopping-cart技能会在会话内保持一个状态变量记录用户加的每件商品。但另一个discount-check技能在计算优惠时会把优惠价覆盖到同一个状态字段上污染了原始价格数据。技能之间的状态隔离是必须做的。我的方案是给每个技能执行分配一个独立的作用域scope只允许技能访问自己声明过的状态字段跨技能共享必须通过task_context的显式共享区而且共享区只放纯数据不能放可变引用。这个改动让状态类bug几乎绝迹也让我对多技能协同的调试重新有了掌控感。4.4 token有限下的长技能输出优化长技能输出是最后提到的一个坑。有一些技能返回的结果非常膨胀比如inventory-check会把仓库里几百个SKU的库存全部返回。模型根本不需要这么多数据但返回结果全塞进上下文既浪费token又干扰模型判断种子项。我调整了技能输出协议的约定技能返回的结构化数据必须附带summary摘要字段编排器优先使用摘要字段而不是全文。现在我在技能开发规范里强制要求所有技能返回必须包含一个不超过200字的中文摘要用于模型决策完整数据走旁路存储只有用户明确要求时才追加加载。这是一个性价比极高的优化既延长了上下文的有效使用长度又降低了模型被海量低质信息干扰的概率。5. 技能可观测性与迭代闭环5.1 每一次调用的trace应该记录哪些字段无观测不优化。技能系统上线两周后我发现最痛苦的不是写技能而是不知道线上技能到底表现怎么样。于是我给编排器加了一层trace埋点每条技能调用记录以下字段request_id和session_id关联同一次任务的完整链路。user_intent经过归因抽取后的用户原始意图摘要。selected_skill模型本次选中的技能名。skill_confidence如果是置信度阈值触发的技能选择记录置信度。execution_time技能从发起到返回的总耗时。parameters模型生成的技能参数。result_summary技能返回的摘要字段。error_reason出错时的错误分类如超时、参数错误、引用不存在。context_usage执行前后上下文token占用变化。这些数据落成JSON日志进ClickHouse最后在Grafana上做看板。有了trace之后很多优化就有的放矢了。比如你发现某个技能的选择率极低但描述却不差那就很可能是因为技能描述里的触发场景和实际用户话术不匹配需要重新改写描述。5.2 基于真实会话数据修正技能描述的迭代方法我想分享一个实操技巧不要靠感觉改技能描述要拿历史会话数据做回归测试。我的做法是每天从真实日志里抽取覆盖各技能类型的100条代表性session人工标注正确技能选择形成一份技能选择基准集。每次修改技能描述或新增技能后在基准集上跑一遍模型决策模拟看选择准确率是否下降。第一次这么跑结果让我很意外。我以为优化得很好的描述在基准集上准确率居然只有74%。问题在于我优化时只关注了典型话术没有覆盖到那些含糊的、口语化的、带情绪的表达。比如用户说这也太贵了吧理想情况应该触发discount-check来向用户解释促销方案但因为描述里没写贵、价格、优惠这类触发词模型没有选中。后来我专门把描述写成包含常见表达变体的形式准确率从74%提升到86%。这个基准集回归测试的闭环已经把技能描述当作一块正经的代码来对待了。每次改动都得过测试这听起来繁琐但长期下来省下的排查时间远大于写测试的时间。5.3 新技能灰度上线与自动回滚机制技能上线也需要稳妥的发布流程。我是按比例灰度做的新技能先只在5%的会话里开放观察48小时的trace数据确认选择率、执行成功率、用户负面反馈率都达标后再逐步提升到30%、70%、100%。回滚机制做得比较轻量每个技能在注册中心都有一个版本号配置里指定当前生效版本。如果灰度期间发现某个版本触发异常行为只需把配置里的版本号回退滚动生效。因为技能描述是文本性的回滚成本极低不需要代码发布流程。自动回滚的触发条件我设了三个硬指标指标阈值技能执行成功率 85%技能选择后用户5分钟内负面反馈率 20%单次技能调用的平均期望输出token偏差 60%任何一个指标触发编排器就会自动把该技能的流量切回前一个稳定版本同时告警到开发者群。这套机制上线后我再也没有被凌晨的电话吵醒过技能版本的回滚基本在用户感知之前就完成了。5.4 trace日志驱动的技能依赖关系图谱化最后一个环节是把trace聚合起来生成技能依赖关系图谱。因为生产环境的技能调用链往往比开发时设计的更复杂有些技能执行时额外触发了编排器没有显式声明的内部依赖这是通过模型的基于上下文的动态决策触发的。通过聚合trace中的selected_skill和next_skill关系我能在线上数据里看到真实热度图和调用链路发现哪些技能经常被连续调用哪些技能是某些用户在特定任务中的必经节点。这些信息对技能架构演进很有价值。比如我发现order-refund之后经常跟着product-recommend——用户退完款马上被推荐了替代品这个高频路径在开发时根本没想到。由于聚合明显我后来直接把两个技能组合成一个高阶技能refund-with-recommendation既减少了模型单步决策的次数又缩短了用户完成全流程的路径。这是数据驱动技能设计的一个范本案例也是这套可观测体系最大的回报。我个人在实际操作中的体会是技能系统的价值不在于代码多花哨而在于把agent的边界从模型本身挪到了一个可以结构化演进的位置。每次业务要加能力我不用改提示词不用碰模型参数只需让一个领域专家按规范写出一份技能声明就能上线。这套模式在20个技能以内时看不出太大优势但技能数量开始膨胀之后它的架构红利就完全释放出来了。技能的拆分粒度、描述风格、状态边界都是在一次次线上事故和trace回看中逐步迭代成形的没有任何一行设计是拍脑袋想出来的。如果你的agent项目也开始遇到提示词膨胀、工具管理混乱、行为不可控的问题不妨试试按这个思路把能力拆成技能让模型只做决策让技能负责执行。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑