从零搭建Agent技能体系:让大模型应用可控、可测、可迭代
发布时间:2026/10/8 11:31:38 锦皓数字建站

今年年初我接手了一个内部效率项目目标很朴素让有业务经验但不会写代码的同事也能用自然语言驱动系统完成一系列固定流程。项目代号就叫“agent-skills”。折腾了三个多月我最大的感触是——Agent能不能落地模型能力只占一半另一半全压在“技能体系”设计上。很多人把精力都花在调prompt、换更强的大模型上却忽视了一个核心问题你的Agent到底会做哪些事、做事靠不靠谱、边界清不清晰。这三个问题恰恰是“技能”要解决的。所谓agent-skills通俗讲就是把Agent能执行的原子能力抽象成一份份“技能说明书”。每个技能包含名字、功能描述、输入输出协议、调用前提和失败兜底策略。模型在对话中根据用户意图动态选择合适的技能来执行。它解决的痛点是没有技能的Agent只能靠模型“自由发挥”容易幻觉、失控、不可复用有了技能Agent的行为就变成“意图识别技能调度结果验证”每一步都能追踪、都能回退团队也能像维护代码库一样维护Agent能力。这篇文章适合正在做Agent应用、想从demo走向生产的团队也适合一个人想搭个靠谱AI助手的技术博主。我会从技能设计原则、落地实现、编排调度、问题排查四个层面完整拆解我们这套体系是怎么从0到1跑起来的。1. Agent技能设计的底层逻辑1.1 能力边界取决于技能库而不是模型参数量我见过不少团队把Agent能力不稳定的原因甩给模型其实大多数情况是技能设计出了问题。技能本质上是在给模型的自由发挥套上一道“可执行的护栏”。模型负责理解意图、拆解任务但真正动手干活的必须落到技能上。这也是为什么同一款基座模型有人做出生产级Agent有人只能做聊天玩具。技能设计的核心是“可测”、“可追踪”、“可回退”。可测指每个技能独立成档可以单独运行、单测覆盖率可追踪指Agent每一次调用技能都有完整记录包括触发原因、输入参数、输出结果可回退指技能执行失败时有明确的补偿路径不影响整条业务链路。拿我们做的“订单状态查询”技能举例。一开始没做技能化让模型直接从数据库捞数据结果惨不忍睹——模型生成了错的SQL还一本正经地“解释”错误数据。后来抽象成技能模型只负责把用户问题解析成结构化的订单ID和时间范围真正查数据库的动作由技能固定完成准确率立刻从六成提到九成以上。这个案例说明了Agent的靠谱程度不在于模型多聪明而在于你把多少种情况提前想清楚固化成不会犯错的技能。1.2 技能库的三种进化路径技能库不是一步到位的我们的经验是分三个阶段滚动建设。第一阶段叫做“显性技能”就是把现有系统的接口、API直接包装成技能。团队里谁最了解某个接口谁负责对应技能的维护相当于把API文档翻译成模型能理解的描述。第二阶段叫做“组合技能”就是把多个配置文件、接口串成一个新技能。比如“拉取xx看板数据并生成日报”这个技能内部会依次调用鉴权、查询、格式化、发送四个环节整个过程封装成一次调用。第三阶段叫做“学习技能”就是允许Agent从历史成功案例中自我归纳新技能模板。这个很前沿我们目前也只尝试了很小的范围但方向大家都可以留意后续技术成熟了技能库会具备自生长的能力。2. 定义Agent技能六要素2.1 名称、描述、参数、返回值、触发条件、兜底策略我们内部把技能模板固定成六个字段缺一个都不完整。名称要短一看就懂。描述要长而具体一定要写清楚“这个技能适合在什么场景、不适合在什么场景下使用”。参数声明最关键每项参数都要给类型、取值范围、必填还是选填。返回值同样要声明结构方便模型在后续对话中继续引用。触发条件要写明什么情况下模型应该优先选择这个技能什么情况下坚决不能用。兜底策略则要明确执行失败时怎么办比如返回固定提示、调替代接口、还是转人工。其中描述这一项最容易被人忽略。很多团队写技能描述时图省事一句话糊弄过去结果模型在意图识别阶段频繁点错技能。后来我们干脆立了个规矩描述里必须写清楚“如果用户问了什么你应该怎么做如果没有满足什么条件禁止调用”。加上这几句技能召回的准确率直接提升了一大截。参数设计上也踩过坑。早期我们为了图方便把所有参数都声明成字符串模型传参时经常丢三落四。后来强制要求每个参数填写示例值模型在解析参数时有参考样本传参的完整性明显改善。比如时间范围参数示例值写“2025-01-01至2025-01-31”比只写“时间字符串”要稳得多。返回值一定要结构化不要返回非结构化文本。模型拿到结构化JSON返回后续进行总结、追问、对比这些操作时可靠性和效率都会高很多。如果返回是自由文本模型在二次加工时容易自说自话数据准确性就不好保证了。2.2 技能表述的三大纪律八项注意三大纪律禁用含糊词禁用前后矛盾禁用多重含义。描述里的每个动词都必须是可检查的比如“获取”、“计算”、“保存”、“推送”。像“处理”、“分析”、“优化”这种模糊动词尽量少用因为模型分不清边界。八项注意这里挑三个最容易踩坑的讲。一是技能名称和描述要互相呼应。名称是“查询天气”描述里就不能写“获取归属地信息”模型会混淆。二是参数默认值宁可写空也不要写一个“看似合理”的默认值。我们遇到过技能默认语言设成“中文”结果一个英语用户被强行返回了中文内容。默认值带来的隐性错误比显性错误更难排查。三是负面清单必须写。一个技能要明说“什么情况下不承担此职责”。比如订单查询技能要写清楚“本技能只查已支付订单退款中订单请调用退款状态技能”。不写负面清单模型碰到边界情况就会自作主张去猜测。3. 从零搭建Agent技能执行引擎3.1 技能注册中心技能不能散落在业务代码里一个集中的注册中心很有必要。我们的实现方式很简单一个技能等于一个目录目录下必须有meta.json技能元数据、entry.py技能入口、tests/单元测试。agent-skills/ ├── order_query/ │ ├── meta.json │ ├── entry.py │ └── tests/ │ └── test_order_query.py ├── data_export/ │ ├── meta.json │ ├── entry.py │ └── tests/ │ └── test_data_export.py └── skill_loader.pymeta.json是整个技能注册的核心文件负责描述技能的所有信息以查询订单为例{ name: order_query, description: 根据用户输入查询订单状态和物流信息适用于以下场景用户询问订单发货没、到哪了、什么时候送到。当查询条件缺少订单ID时先反问用户获取订单ID再调用本技能。本技能不处理退换货申请。, parameters: { type: object, properties: { order_id: { type: string, description: 订单唯一标识如SO-20250101-001, required: true }, query_type: { type: string, enum: [status, logistics], description: 查询类型status为仅查订单状态logistics为查物流轨迹, default: status } } }, returns: { type: object, properties: { order_status: {type: string}, logistics_track: {type: array} } }, trigger_conditions: [ 用户明确提到查询订单状态或物流信息, 用户提供或上下文能推断出订单ID ], fallback: 当查询不到订单时统一返回未查询到该订单请核对订单号并引导用户再次输入 }entry.py相对直白主要做参数校验调业务接口把结果打包成标准response结构。这里有一点建议入口函数里不要写太复杂的业务逻辑就做三件事解析参数、调用下游、格式化输出。复杂逻辑放技能内部独立模块管理这样单独调试技能时会很舒服。3.2 技能加载器与意图路由技能加载器负责把注册中心的所有技能在服务启动时加载到内存并转成模型友好的格式拼进系统Prompt中。这一步很关键你不可能把几十个技能的完整描述全都塞给模型那会占用大量上下文窗口所以加载器还要做“精度筛选”。我们采用的方案是两层筛选第一层根据模型初次解析出来的用户意图标签做粗筛比如用户意图是“查物流”那么所有涉及订单、物流、售后类的技能进入候选池其余技能不加载。第二层在候选池里做细排根据用户提的具体实体和上下文语义把最匹配的三到五个技能完整描述给模型让它做最终选择。两层组合起来用既保证了召回率也不会撑爆上下文。意图路由这块有一个参数需要特别注意temperature。我们刚开始把所有技能调用环节的temperature都设成0.7结果模型在参数解析时经常“创意发挥”把订单号编错了。后来改成技能选择阶段用0.1对话生成阶段用0.7问题直接消失。技能调用和参数解析是强逻辑任务不应该有太多随机性只有最终面向用户的回复文本生成才需要多一点创造空间。3.3 技能执行中间件一个好的技能体系还需要一个统一的中间件层负责三件事写审计日志、做执行期超时控制、加统一的错误处理。我们写过一个简单的中间件示例import time import traceback from functools import wraps def skill_middleware(logger): def decorator(func): wraps(func) def wrapper(*args, **kwargs): start time.time() skill_name func.__name__ try: result func(*args, **kwargs) latency round((time.time() - start) * 1000, 2) if latency 5000: logger.warning(f[SLOW_SKILL] {skill_name} 耗时超过5秒: {latency}ms) return {success: True, skill: skill_name, data: result, latency_ms: latency} except Exception as e: logger.error(f[SKILL_ERROR] {skill_name} 异常: {str(e)}\n{traceback.format_exc()}) return {success: False, skill: skill_name, error: str(e), latency_ms: -1} return wrapper return decorator应用这个中间件每个技能的入口函数只要保持参数纯净不用各自处理日志和异常代码会轻量很多。4. 多技能协同编排策略4.1 工作流式编排Agent很少只用一个技能更多场景是把多个技能串成工作流。工作流编排有两种路线一种是我们先把流程写死即固定调用顺序中间异常时直接终止或跳到兜底技能另一种是流程由模型动态决定即模型根据用户反馈自行判断下一步该调用哪个技能。以“用户要一份上周的销售报表并发送到邮箱”为例固定工作流的顺序就是获取时间参数-拉取销售数据-格式化报表-发送邮件-反馈结果。五个环节里任何一个失败就只能终止整个流程不能半途跳过。这种方式的优点是稳定、可控、审批容易通过缺点是僵硬用户中途不按剧本走Agent就可能卡住。动态编排则让模型在每轮工具调用后自己决定下一个动作。写起来灵活但线上稳定性稍弱需要给模型设定很强的约束。我们现在的做法是混合式主链路用固定工作流但主链路的某一环内部允许模型根据分支结果选择不同子技能。比如邮件发送环节用户可以选摘要邮件还是附件邮件这个分支决策交给模型主链路还是定死的。4.2 优先级与冲突消解当多个技能都匹配用户意图时技能之间的优先级设计必须有规则。我们的规则很简单更具体的技能优先于通用的技能。比如用户说“帮我查一下昨天的订单有没有发货”那么订单查询技能会优先于“客户全信息查询”技能因为它跟用户的关键词出现场景更贴近。还有一个常见冲突是用户意图同时命中两个领域的技能这时我们的剪枝策略是看上下文连续对话的最近焦点。最近三轮中出现过的技能领域会在冲突消解时加分。相当于给Agent维护了一个“话题热度表”有效避免用户一个简单问题被横跳到不相关技能上。跟技能编排配套的是JSON Schema校验。模型回传的参数在正式调用技能前必须经过预先定义的Schema做严格校验。类型不对、缺参、枚举值非法全部打回让模型重新选参数。校验不通过时我们要给模型明确提示“你传入的参数无法通过校验请补充以下字段XXX”第一次提示就该说清楚避免来回拉扯浪费一次模型调用。5. 技能质量验证三板斧5.1 单技能单测与回归测试技能是代码凡是代码就该有测试。我们的做法是给每个技能维护一张测试用例表里面包含可能输入、期望输出、备注说明。这一组用例既用于开发期的调试也用于每次技能修改后的回归验证。单技能测试主要看三件事参数解析是否准确边界输入有没有防住异常分支是否走了兜底逻辑。边界输入是最容易漏的比如订单ID为空、时间范围倒置、数组为空。我们曾经漏了一个时间倒置用例结果用户传“2025-01-31至2025-01-01”系统没报错返回空数据看起来像“用户没有订单”实际是时间条件写反了。回归测试跑起来也不复杂就是每次改完技能把固定输入样本全部过一遍对比输出是否和基线一致。我强烈建议技能数量超过十个以后把回归测试接进CI流程不然手工跑一次要十几分钟起步人很容易麻木。5.2 全链路评估样本库单技能测试过了不代表Agent端到端就好用了。端到端评估我们需要一套“用户真实问题集”每条记录几件事用户问题原文、期望调用的技能序列、期望的最终回答关键要素。然后每一条用Agent完整跑一遍看技能序列是否匹配、关键要素是否齐全。真实项目经验证明端到端评测比单技能评测更容易发现两类问题一类是意图路由错乱即模型在多个相近技能之间跳来跳去另一类是返回结果丢信息比如技能返回里明明有价格字段但模型给用户的总结里漏写了。这类问题单看技能返回是无法发现的只能靠全链路评测样本库日积月累地去捞问题。6. 常见问题与排查技巧实录6.1 Agent不调用技能只顾着“嘴炮”这是新手阶段出现频率最高的问题。排查思路先看用户输入是不是带有明确意图比如“帮我查”“给我算”这些指令词再看技能描述是否足够具体。绝大多数情况是技能描述写得太抽象模型根本不知道“这个技能什么时候该用”。另一个高发原因是系统Prompt里没有强调技能优先原则模型觉得凭现有知识也能回答就绕过了技能。我们的Prompt里专门加了一句“遇到与以下技能列表相关的任何请求必须调用技能完成不允许凭记忆直接作答。”加上这句话之后技能覆盖率有明显改善。6.2 技能执行成功率高但用户感知很差典型表现是技能返回了正确的结构化数据但最终回复又长又干没有温度用户根本不想看第二遍。这里的问题不是技能而是回复生成环节缺少“口语化重写”。我们的方案是增加一个专门的summary技能输入是前一个技能返回的结构化数据输出是一段面向用户、口语化、带关键数据亮点的总结。它和业务技能分开维护业务技能保持机器风格summary技能负责转译成人话两端稳定性和体验都能兼顾。6.3 加了新技能旧技能召回率反而下降我们加技能加到15个左右时发现原有查询技能的召回率突然掉了一截。排查后发现原因是新技能描述和旧技能描述里存在重复关键词模型在相似描述前发生混淆。解决办法有两个方向一是给容易混淆的技能之间写“技能区分说明”明确交代“本技能与XX技能的区别在于某方面当用户XX场景时使用那个技能”二是在技能描述开头加上一句话的摘要让核心功能一眼可见不要在长描述里淹没重点。两个优化都做掉后召回率基本恢复到原来的水平。7. 几点实操心得最后按我的个人经验分享几个真正能提升Agent技能体系工程质量的小建议。技能描述的每一句话都要想着“让一个小学刚毕业的人也能看懂”。描述越具体模型的调用准确率越高。参数描述里写死示例值这件事看起来不起眼但对调用成功率贡献很大建议作为技能入库评审的强制项。在开发阶段给每个技能单独做一个“试玩界面”只保留技能调用和参数输入不做花哨的对话包装。快速暴露技能自身的问题比在完整Agent里排查要高效得多。日志一定要在第一时间就埋全。技能名、耗时、参数摘要、返回码、错误类型每一样都记下来。还没遇到线上问题不觉得等到技能达到二三十个的时候再造日志框架会很痛苦。每次修改技能描述后对文本的改动本身也要追踪。我们这个领域有个老坑某一天你发现Agent突然开始频繁调用错误技能翻遍代码没发现异常最后查到原因是两天前有人默默改了一个参数的描述。技能配置文件应该走版本管理变更历史一目了然排查效率会快很多。Agent技能体系的建设本质上是把不可控的大模型能力逐渐收敛成一套可控、可测量、可迭代的工程系统。我们这套project-skeleton不算惊天动地但跑了一段时间在线业务后稳定性说得过去团队协作的默契度也上来了。如果你手头也在做Agent相关项目我的建议是从技能设计入手先别迷信模型参数把每个技能做扎实Agent的整体表现自然不会差。
锦
锦皓数字建站
深耕本土企业品牌数字化升级,专注原创端正雅致商务官网,从视觉设计到稳定运维全程保驾护航。