从能聊天到能干活:Agent技能体系设计与落地复盘
发布时间:2026/10/12 4:32:49 锦皓数字建站

先说结论把大语言模型接入真实业务之后你会发现它的思考能力和动手能力之间存在一条巨大的鸿沟。模型能写出漂亮的计划但真要它去查数据库、调接口、算指标它往往手足无措。我做的这套内部代号为 agent-skills 的东西就是把模型不擅长的确定性执行拆成一个个可复用、可测试、可观测的技能单元让 Agent 真正从一个会说话的聊天框变成一个能稳定交付任务的执行体。这篇文章不是概念科普是我在搭建技能体系过程中踩坑、推翻、重来之后的一次完整复盘。内容包括技能为什么需要抽象、接口契约怎么定、路由怎么选型、实测中遇到的五类翻车现场以及最后怎么用评测指标把技能质量从感觉还行变成有据可依。如果你们团队正准备让 Agent 做实事或者已经在做了但总感觉不稳定这篇应该能帮你避开不少弯路。1. 从能聊天到能干活Agent 到底缺了什么1.1 一个让 LLM 翻车的真实场景先说个我实际遇到的例子。当时我们接了一个内部数据问答的需求用户会问上个月华东区的回款金额是多少这类问题。最初方案很直接把用户问题、数据结构说明、SQL 生成规则全部塞进提示词让模型直接输出 SQL然后拿去执行。听起来挺合理对吧实测下来简单问句还好一旦涉及对比上季度排除退货订单按周聚合这些条件组合模型生成的 SQL 就开始出幺蛾子字段名凭空捏造数据库里根本没有这个列时间过滤条件写错把上个月理解成自然月而非财务月多表关联漏掉关键 JOIN结果集直接翻倍更麻烦的是同样的问法今天生成的 SQL 能跑通明天生成的就报错。我当时的判断是问题不在模型的推理能力而在我们把太多不该让模型自由发挥的东西交给了它。模型天生适合做开放域理解不适合做严格语法生成。SQL 语法容错率极低一个字段名写错就全部白费而模型恰恰会在细节上不稳定地出错。1.2 Skills 的本质把非确定性推理变成确定性执行这个经历让我意识到一个核心问题Agent 的能力体系应该分成两层。上层是模型的决策层负责理解用户意图、制定执行计划下层是执行层负责把计划变成具体操作。执行层不再依赖模型逐字生成而是由预先编写、预先验证过的代码模块来承担。这就是 agent-skills 的核心思路——把每一个可复用的执行动作查数据、发通知、调接口、算指标、处理文件封装成独立的技能。模型只需要做两件事判断用户想要什么效果然后从技能库里选出匹配的技能并填好参数。至于技能内部怎么实现模型完全不需要关心。这套做法的直接好处有三个正确率大幅提升。技能代码是经过测试的不会像模型生成的 SQL 那样时好时坏。模型只要路由正确、参数填对结果就是稳定的。可观测性变强。每个技能执行都有日志、有耗时、有成功失败标记出了问题能精准定位到是哪一环。可迭代性变好。技能之间互相独立改一个技能不影响其他技能新增能力就是往技能库里多塞一个模块。一句话总结技能就是给大模型装的可信任的双手。模型负责思考技能负责动手。思考可以天马行空动手则必须稳定可靠。2. 技能体系的分层抽象从工具函数到领域技能包2.1 技能的接口契约描述、入参、出参、副作用如果你只是给模型暴露一堆散装函数很快就会陷入新的混乱。模型根本不知道该用哪个入参格式也五花八门。所以在 agent-skills 里我规定每个技能必须满足一套标准接口契约总共四部分语义描述、输入参数、输出结果、副作用声明。语义描述是整个契约里最重要的字段它决定了模型能不能在关键时刻认得出这个技能。描述必须包含三要素这个技能做什么、适合什么场景、不适合什么场景。举个例子一个查客户信息的技能描述就不能只写查询客户基本信息更好的写法是根据客户名称或 ID 查询客户的工商注册信息包括统一信用代码、注册地址、法人代表。适用于用户询问客户资质、注册情况时。不适用于查询客户交易流水、订单记录。注意最后那句不适用于它看起来不起眼但在模型路由时能有效拦截误用。很多误路由都源于语义边界不清技能描述把边界写明白了模型的出错率能降一半。输入参数用 JSON Schema 定义。这里的原则是尽量展开成扁平结构少用嵌套对象。原因很简单模型在填参数时扁平的键值对远比嵌套结构填得准。比如查客户技能输入就三个字段customer_name、customer_id、include_registration_details别人一眼就能看懂模型也不容易漏填。输出结果我要强调一点技能的返回值必须是高度结构化的最好直接是 JSON而不是一段拼好的文本。为什么因为 Agent 拿到技能结果之后往往还要做二次推理比如判断这个客户的注册资金是否满足投标门槛。如果技能只给我一段话Agent 就得重新做文本解析极容易出错。结构化 JSON 直接拿来算、拿来比较干净利落。副作用声明是很多人会忽略的。所谓副作用就是技能执行对外部世界产生的不可逆影响。比如发送邮件是副作用操作查询库存不是。副作用信息必须显式声明因为它在 Agent 规划阶段直接决定了执行顺序。传统思维是先查数据再决定要不要发通知但如果有副作用先后约束Agent 就得调整计划顺序。把副作用写清楚相当于给 Agent 的决策多提供了一条关键规则。2.2 领域技能包按业务场景聚合技能技能多了之后会出现一个管理问题一百个技能平铺在一个列表里每次路由都要把全部描述塞给模型既浪费 token又容易互相干扰。比如生成周报技能和发送日报技能放在一起模型偶尔会把两者搞混。我的做法是引入技能包的概念。每个技能包对应一条业务线或一类场景内部包含若干技能。Agent 先做第一层路由——判断用户请求属于哪个技能包再做第二层路由——在技能包内部选具体技能。这就像图书馆先有分类索引再有书目编号查找效率完全不一样。拿我们的实际场景举例一共有四个技能包客户分析包查询工商信息、查交易流水、算客户贡献度、风险评分营销运营包生成人群包、计算触达名单、发送优惠券、统计活动效果报表生成包拉取销售数据、生成周报、发送订阅邮件系统管理包查询任务状态、清理缓存、重跑失败任务。这样分组之后每次模型需要关注的技能数量从一百多降到了二十以内路由准确率肉眼可见地提升。更重要的是每个技能包可以独立迭代和发布不会因为一个技能升级影响了别的场景。2.3 技能清单的生成与维护技能清单不是一次性写好就完事的。我在实际维护中发现一个问题技能的语义描述和路由效果之间存在强耦合随着 Agent 交互量增长你会发现某些描述会被模型反复误解。我现在的流程是每隔两周把新出现的误路由 case 汇总一遍逐条分析原因。如果是技能描述写得有歧义就改描述如果是两个技能功能确实重叠就合并或拆分。这个维护动作看起来很简单但贵在坚持它是技能体系保持健康的关键所在。另一个重要的维护项是技能的健康状态。每个技能都带上版本号、负责人、依赖的数据源和最近一次验证时间。技能依赖的数据表如果改了字段技能没有同步更新那就是定时炸弹。我在项目里强制要求技能涉及的数据源变更时必须有配套的回归测试通过后才能升级版本否则不允许上线。3. 技能路由的选型与调参让 Agent 找对手3.1 文本匹配的局限性与语义路由的必要性技能路由说白了就是让模型解决一个问题给定用户的一句话从技能库里挑出最匹配的技能并填好参数。听起来这也算模型的强项但实际跑起来有两个陷阱第一个陷阱是关键词匹配式的浅层理解。比如用户说看看这个客户靠不靠谱如果你用的是严格关键词路由那得先出现风控或信用才算触发这种问法很容易空转。但语义路由能理解靠不靠谱背后的意图——其实是想查风险分或逾期记录。第二个陷阱是参数提取的隐蔽难度。用户说查一下三家供应商的注册资本技能可能只支持单个查询那就不能一次调用而是要做循环调用。这种情况模型经常漏掉三家这个数量词只查了第一家就返回。我尝试过的路由方案有四种简单做个对比路由方式优点缺点适用场景纯关键词匹配响应快、零成本召回率低、易误命中技能数量少且用语固定嵌入向量相似度能处理同义改写、无需训练依赖向量质量、阈值调优难技能数量中等LLM 结构化路由理解力强、可处理复杂映射延迟高、token 成本高技能包内层精细选择规则模型级联兼顾效率和准确率流程复杂我最终采用的方案建议直接照抄我的级联方案第一层用规则极少数明确的关键词问题直接命中大多数问题走向量相似度粗筛召回 top 8 的技能最后把 top 8 候选的完整描述交给 LLM 做精排。这样既能控制 token 消耗又能靠模型的语义理解能力兜底。3.2 路由中最重要的技能描述优化我花了很多时间调技能描述这里其实有规律可循。一开始我写得像技术文档规矩是规矩但模型就是选不对。后来我把所有高正确率的技能描述拿出来对比总结出三个共同点第一用用户视角的语言写描述。不要写该模块提供 POST /api/customer/info 接口的封装要写根据客户名称或客户编号获取基础档案信息。模型是拿用户的原话去对照描述的描述越贴近日常用语匹配越准。第二给描述加触发词和反触发词。触发词就是回报率、流水、交易明细、下单记录这类明显指向该技能的关键词写进描述里相当于给模型提供了路标。反触发词就是不适用于、请勿用于句式把容易混淆的边界划清楚。第三控制描述长度。太短了信息量不够太长了模型容易迷失重点。我实测下来150 字左右最合适超过 250 字之后准确率反而下降。原因可能是过长的描述稀释了核心语义让模型抓不住主特征。3.3 兜底策略与拒绝执行路由调得再好也免不了碰到模型拿不准的时候。一个有经验的工程师一定会给 Agent 设计好拒答路径而不是让它硬着头皮从技能库里随便选一个。我在 agent-skills 里设置了两种兜底第一是低置信度拒答。模型精排阶段如果觉得所有候选技能的匹配度都不够就返回一个专门的标志告诉用户这个问题我暂时处理不了已知会人工处理。第二是参数缺失追问。技能有了但用户提供的参数不足比如技能需要客户 ID用户只说查一下那个大客户那就触发追问流程请用户补充信息。这两种兜底虽然牺牲了一点看起来智能的感觉但换来的是可靠性真实业务里可靠性永远比表面智能值钱。4. 实测翻车记录五个差点让我放弃的坑4.1 技能描述模糊导致误路由第一次大规模实跑时出现了一个很尴尬的场景用户问帮我看看哪些订单还没发货系统的路由结果五花八门有时候走查询订单有时候走查询物流甚至有几次走到查询客户。排查过程很有意思。我把完整的用户提问和模型精排时给出的候选排序记录拿出来逐条对比发现所有误判都集中在技能描述里缺少了状态这个维度。查订单技能描述写的是获取订单基本信息包括下单时间、金额、商品明细它根本没有承诺返回发货状态但模型误以为订单技能也能查物流状态于是硬选了它。根因找到之后修改方案非常直接把查订单改成查订单基本信息不包含物流状态单独加一个查订单物流状态技能写清楚适用场景是物流跟踪和发货进度。改完之后这类误路由直接清零。这个坑给我的教训是技能的边界描述不是用来给人类看的是拿来给模型划红线的写描述时多想想模型会怎么误解。4.2 上下文碎片化技能执行后丢掉了关键信息第二个坑出现在 Agent 做多步骤任务时。典型场景是对比 A 和 B 两个客户的贡献度然后给贡献度高的那个发一张优惠券。这个任务要调三次技能查客户 A、查客户 B、发优惠券发给谁。问题是第三步骤需要作为决策依据的数据但我们的技能设计里上一个技能的结果没有传给下一个技能使用上下文的上下文就断掉了。其实在对话场景里模型本来可以记住前面聊过的内容但技能调用一旦发生内部执行的中间结果如果不显式写回上下文模型就看不见了。我的解决方式是引入一个工作记忆区每个技能的输出会追加到工作记忆区里供后续步骤读取。从实现上看就是在技能结果返回后由调度模块负责把关键结论提取成简短的文本注入到下一轮的上下文中。这个方案听起来简单但工作记忆也不是越大越好塞太多结果会让模型被无关信息干扰。我在工作记忆里设置了一套轻量级的优先级当前步骤依赖的数据排最前历史步骤的中间结果排在后面超过一定量就滚动丢弃。4.3 重复执行的幂等问题第三个坑跟重试机制有关。有一次某个技能因为网络超时没有返回Agent 自动重试了一次。结果两边同时执行成功给同一批用户发了两遍通知直接导致客诉。这个问题的本质是技能不满足幂等性。所谓幂等就是同一个请求执行一次和执行一百次产生的最终效果相同。对于查询类技能幂等天然成立但对于发通知、扣库存、发优惠券这类有副作用的操作重试就是灾难。我后来对所有副作用型技能加了两个强制要求一是每一次调用必须携带唯一的请求 ID服务端根据请求 ID 去重重复请求直接返回第一次的结果二是涉及资金、优惠、库存类的关键操作在技能描述中显式标注绝对禁止自动重试如果失败必须走人工确认流程。这是我在整个项目里踩得最重的一个坑也让我彻底明白了副作用声明不只是写给人看的规范更是系统的生存底线。4.4 并行技能调用的顺序依赖第四个坑是关于并发的。一开始为了追求速度我在多技能任务里做了并行调度多个技能同时执行。结果有个场景出了问题先检查库存再决定是否下单因为两个技能是并行的检查库存的结果还没来得及影响下单决策下单动作就先出去了。这类问题上直觉的解决方案是串行化所有有依赖关系的技能但这样性能太差。我后来的做法是给每个技能增加一个依赖标签声明这个技能在执行前必须拿到哪些标签的数据结果。调度器先跑无依赖的技能等结果返回后再启动依赖它们的下游技能。这相当于一个有向无环图的拓扑排序逻辑清晰性能也没有浪费。一开始我并没有直接采用图调度是踩了并行坑之后才补上的。但这个改动值得推荐因为 Agent 未来处理的真实任务只会越来越复杂尽早把依赖关系显式化后面扩展起来会轻松很多。4.5 失败重试机制导致的死循环最后一个坑也是最隐蔽的一个Agent 失败以后反复重试陷入死循环。当时某个报表生成技能因为底层数据源临时故障抛了异常Agent 捕获异常后以为是自己步骤出了问题于是换了一种方式重试连续重试五次每次耗时好几秒用户那边看到的反馈就是一直转圈没有任何结果。排查之后发现根因是异常信息没有区分永久失败和临时失败。数据源故障属于环境问题重试没意义参数错误属于代码问题重试也不会改变结果。只有网络超时这类情况才值得有限次数的重试。我给技能执行框架加了一个错误分类协议异常统一打上错误类型标签包括参数错误、状态冲突、依赖服务不可用、未知错误等。调度器根据标签决定策略参数错误直接返回给用户修改状态冲突进入人工复核依赖服务不可用则标记为不重试并返回明确原因。与此同时将全局重试次数硬性限制在两次以内任何技能都不允许无限重试。5. 技能的评估与持续迭代不能只靠感觉5.1 技能级评测集的构建技能体系做出来之后一个绕不开的问题是我怎么知道它现在好不好哪里需要改进靠人工试用和凭感觉收效太慢也不可复用。我花了很大力气做了一套技能级评测集核心思路是每个技能配一组典型测试用例每个用例包含四部分用户提问样例、预期选中的技能、预期提取的参数、预期返回结果的特征。比如查客户工商信息这个技能评测集里会有这样的用例提问帮我看看北京晨光科技有限公司的注册资金是多少预期技能查询工商信息预期参数company_name 为北京晨光科技有限公司预期返回注册资金字段存在且为整数我把平时真实交互中积累的 case 整理进去一些是容易误解的、容易路由失败的专门留着防回归。这个评测集不需要太多但覆盖要全。我现在维护了大概两百条用例就能覆盖所有技能的核心路径和主要边界情况。5.2 三个关键指标准确率、覆盖率、回退率评测集建立之后我定义了三个核心指标每次改动技能或调整路由策略后都会跑一遍用数据说话。技能命中准确率在所有评测用例里路由到正确技能的比例。这个指标反映的是技能描述和路由策略的整体质量。参数覆盖率在所有正确命中的用例中参数提取完全正确的比例。参数提取是很容易被忽视但影响很大的环节用户说查一下最近三个月的流水模型可能只填了最近三个月漏了账户 ID。回退率评测集里的用例触发低置信度拒答的比例。回退率太低说明模型可能过度自信太高说明模型过于保守需要寻找一个平衡点。我每次改动技能描述后最怕的就是准确率上去的同时回退率也飙升。那说明模型开始不敢接了虽然答错少了但能答的问题也少了用户的体验反而会差。所以我一般会同时盯准确率和回退率两个指标确保它们一起向好的方向移动。5.3 迭代节奏与灰度发布技能迭代这块我一开始是改了就直接上结果出过几次问题比如新技能描述在评测集里表现完美一上线就误伤了别的技能。因为新技能的描述和旧技能在语义空间里产生了重叠导致原来该走旧技能的请求走去了新技能。现在的流程是所有技能变更统一走灰度发布。先把新版本技能部署到一个单独的环境里让它只处理 5% 的线上流量和旧版本的命中结果做对照。等到两个版本在评测集上的指标没有显著差异之后再逐步放量到全部流量。整个灰度周期一般是一到三天时间不长但已经足够暴露大多数问题。这样做还有一个额外好处上线一个技能时不小心影响到的其他技能也能通过灰度环境里的对照数据被及时发现。6. 技能的可测试性与模拟环境6.1 依赖隔离每个技能都能被独立验证技能一旦多了互相调用、共享数据源的情况就会变得很复杂。为了能独立验证每一个技能我在 agent-skills 里做了依赖隔离设计。每个技能的运行环境是独立的容器它访问的数据源、调用外部接口的通道都在环境配置里显式声明不允许隐式依赖。也就是俗话说的墙上的水龙头——有什么、走哪里清清楚楚,通路上不可能有意外。这样做最大的价值是测试方便。当一个技能出了问题我可以把它的运行环境完整地复制出一份来调试不需要依赖整个系统跑起来才能复现问题。对日常开发效率的提升非常明显。6.2 录制回放用真实流量做回归测试考虑到评测集用例毕竟是人工构造的真实流量的复杂程度远高于人工想象。我引入了录制回放机制把线上真实交互中经过清洗和脱敏的请求记录下来等技能有变更时把这些真实请求重放到新版本上对比新旧版本的路由结论和执行结果。这种回归方式能捕捉到评测集覆盖不到的场景。尤其是一些很偏的问法、或者用户带着很大噪声的表达人工构造用例很难想到真实流量里却经常出现。录制回放实质上是用真实世界长期验证每一处改动成本不高收益却很可观。6.3 技能的可观测性给每个技能配日志和看板最后一点也是整个体系稳定运行的基础可观测性。我在每个技能里统一埋点记录四类基本数据调用时长、传入参数、返回结果摘要、错误信息。这些数据汇聚到看板上按技能维度展示调用量、成功率、平均耗时和错误分布。看板能看出来很多非预期的问题。比如有些技能深夜调用量异常增多排查后发现了服务于别的小时任务也调用了同一个技能导致高峰期争抢资源。没有日志就没有洞察力很多系统问题本质上都是黑盒运行造成的,一旦看不见故障全靠猜后续自然没法优化。7. 技能参数提取的细节打磨7.1 结构化 Schema 对参数提取的帮助参数提取是 Agent 正确执行技能的临门一脚但很多人把它想得太简单以为填个参数表就完事了。实际上模型填参数时经常丢三落四、张冠李戴优化空间非常大。我强烈推荐用 JSON Schema 定义技能入参类型、必填项、枚举值都写清楚。模型按 Schema 生成参数 JSON出错率比自由格式低得多。尤其是枚举值比如订单状态如果只允许待支付、已支付、已发货、已完成Schema 里约束好之后模型就不会生成支付完成这种不在枚举里但语义相等的值。还有一个细节是参数映射。用户表达习惯和技能参数名经常对不上比如用户说客户的公司全称而技能参数名是 registered_company_name模型要能把这层映射关系厘清楚。做法是在技能描述的入参说明里明确列出用户可能用哪些说法来指代这个参数可选值、同义词都写上模型就很容易对齐。7.2 模糊表达的补全策略用户说话信息不全但你又不能老是追问追问体验太差。我总结了一个三级补全策略按信息缺失的严重程度递进处理效率高又不惹人烦可推断补全只要不影响最终结果就取默认值或从上下文推断。比如查一下华东区的订单地理区域可以从用户属性推断无需追问。单轮追问缺失的是关键参数就用一个问题向用户确认。比如查客户信息没说客户名称就问一句请提供客户名称或客户 ID。多轮澄清只有模糊程度很高、必须确认偏好的场景才用多轮澄清比如生成一份报表用户没说时间范围得先问清楚按周报、月报还是季度报。这套策略到位之后用户能明显感觉到 Agent 变得更聪明了——该问的问不该问的傻子才问。7.3 参数校验的前置与后置参数提取完成之后执行之前必须做一轮参数校验这是很多人忽视的防线。我在技能执行框架里内置了校验规则必填项、类型、范围、枚举值任何一个不过直接返回参数校验失败不进入真实执行逻辑。你以为有了前置校验就够了吗不够。后置校验同样重要即技能执行完成后检查输出结果是否满足预期基本结构对不对、结果集不为空、关键字段有没有缺失。前置校验防伸手后置校验防伸错手还硬拿两者结合技能执行的结果质量才会稳。8. 跨场景迁移同样一套技能系统如何复用8.1 从数据问答到自动化操作技能体系最初是为数据问答设计的后来发现它能复用的边界远比想象中宽。只要把技能包的分类方式换一下技能核心语义描述换一换同一套框架就能从查数场景迁到操作场景。举个例子我们在客户运营场景里新增了发送优惠券和生成短链这类操作型技能在数据问答场景里新增了趋势分析和异常告警这类分析型技能。它们共用同一套路由、记忆、重试、评估机制只是技能包的内容不同。对我个人来说最大的感受是写技能的工程方法论是通用的把接口契约定义好把描述写清楚技能放在哪个场景都能跑得稳。8.2 技能编排流程化单技能最多解决单点问题但真实任务几乎都是多步骤的。为此我封装了一个极简的技能编排引擎支持顺序执行、条件分支、循环执行和并行执行四种基本流程模式。拿月末销售复盘来举例整个流程可以编排为先拉取本月销售数据然后与上月数据做对比再生成摘要文本最后按抄送列表发送邮件。四步走清晰无比Agent 只需要按编排执行每步调用对应技能不需要自己临场即兴构思流程系统的稳定性和可预测性会好很多。编排引擎还有一个好处流程可以被复用和收藏。同样的复盘任务不同人来做得到的结果在结构上是一致的不会今天一种格式明天另一种。8.3 技能生态的建设建议最后聊聊团队协作层面的事。技能体系做大了之后就不再是一个人能维护的了。我的建议是每个技能必须有明确的负责人技能的定义变更必须走评审技能的数量控制在合理区间优先合并重复技能避免无意义的过度细分。我在 team 内部立了一个规矩新增技能之前先问三个问题这个技能是否足够通用、会不会在多个场景被复用它和现有技能的功能边界是否清晰它能不能被稳定地测试和评估三个问题都能回答是才允许进技能库。这套准入标准帮我们挡掉了很多只面向单次需求的临时 hack也间接保证了整个技能库的质量始终在线。实际维护下来的体会是技能库像一座花园不是种完了就撒手不管。你需要经常修剪、浇水、除虫才能让它的生命力持续下去。把每一项能力都打磨成可复用、可测试、可观测的模块Agent 这套系统的能力上限才会节节抬升。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。