资讯详情

Agent技能体系建设:从提示词到可复用、可评估的稳定架构

📅 2026/10/14 9:14:32 | 华诺云谱 👁 阅读
Agent技能体系建设:从提示词到可复用、可评估的稳定架构
我手头这个Agent项目前前后后调了好几周最让人崩溃的不是模型不够聪明而是它“什么工具都会却总不好好干活”。让它查个订单它能自己编一个订单号让它处理退货它能绕到物流接口去瞎猜包裹状态稍微复杂点的多步任务它能把同一步重复执行四五次。最开始我以为换个大模型就能解决后来发现不是又以为是提示词写得不够狠改了十几版还是不稳定。最后把整个思路翻了个底朝天才意识到问题出在“能力”的组织方式上——所有本领都堆在提示词里没有抽成结构化的技能体系。这篇东西我就想围绕 agent-skills 这个主题把“怎么把模型手里的能力变成一组可复用、可评估、可维护的技能”这件事掰开揉碎讲一遍。这篇文章不是给你讲某个现成框架的API怎么调用而是讲一套方法论从业务任务里拆出技能、把技能定义成接口契约、搭一个简单的技能库、再给技能做测试和迭代。适合正在做Agent落地、被模型乱调工具折磨过的人也适合刚接触Agent、想从第一天就把架构搭对的开发者。哪怕你手头只是一个几千行的小项目这套思路一样能用。1. 先想清楚再动手Agent技能、工具和提示词到底差在哪1.1 技能不是工具也不是提示词而是一层“稳定套路”很多人在做Agent的时候把“给模型接上工具”当成核心工作。工具确实是基础但工具只是一组能力接口比如一个订单查询API、一个物流轨迹接口、一个优惠券计算函数。模型知道这些接口存在不代表它知道“在什么场景下、按什么顺序、用什么参数、怎么处理异常”去调用它们。这就是技能要解决的问题。我习惯把这三个层次分开看工具是汽车本身提示词是路况提示牌技能则是驾驶技术。汽车停在路边路况提示牌告诉你前方转弯但你得先掌握起步、换挡、刹车这一整套动作才能安全开到目的地。模型虽然能从提示词里知道“可以用订单接口”但真要处理一个“用户没说订单号、还想知道包裹到哪了”的场景它就会开始瞎发挥。技能就是把这套“动作序列”固化下来先提取订单号提取不到就追问查到物流信息后只把可透传的状态展示给用户遇到异常状态先解释原因而不是把报错日志直接甩出来。所以技能凭什么比一段提示词靠谱因为提示词是给模型看的一次性说明模型每次执行都会重新“即兴发挥”技能则是一个可以被反复调用、单独测试、独立版本管理的封装单元。你在技能里写清楚“做什么、不做什么、怎么兜底”模型的选择空间就从“自由发挥”收窄到“按套路执行”。这一步收敛正是Agent稳定性提升最大的来源。1.2 为什么要把技能拆成独立体系复用、演进和兜底我在项目里最开始也偷懒过把工具调用逻辑直接写在一个巨大的system prompt里所有业务规则全靠几段长文本描述。结果遇到了三个绕不开的问题第一个是复用。客服场景里有“查订单”“查物流”“算优惠”这些动作在退货退款处理里要用在售后咨询里也要用但每次写prompt都是一遍新解释稍不留神两个地方的描述还打架。第二个是演进。业务规则一变我得在prompt里找到对应段落改改完还得担心会不会影响其他部分。第三个是兜底。只要模型自由选择工具就一定会出现参数幻觉、重复调用、错误工具等问题而提示词对这件事几乎无能为力。把技能独立成体系之后三个问题一起解决。“查订单”做成了技能所有下游流程都引它复用“优惠计算”规则变了只改这个技能跑一遍测试集就行每个技能内部都写了异常处理和参数校验模型调用时就算表现不好框架层也能把它拽回正轨。我们在项目里实测下来单纯重建技能库这一件事就让同一个任务的平均工具调用次数降了将近一半无效调用明显减少。这个收益不是模型升级带来的是结构本身带来的。2. 四步法拆解任务把业务流程变成一组可调用的技能2.1 盘点任务日志从高频、易错、强规则三个维度挑环节动手拆技能之前你要先搞清楚“哪些环节值得被做成技能”。不是所有逻辑都得技能化否则你会得到一个比prompt还难维护的技能库里。我的做法是从任务日志开始把所有用户对话中触发过工具调用的样本捞出来按业务目标分组然后从三个维度打分——是不是高频、是不是易错、是不是强规则。高频很好理解一天出现几百次的动作即使再简单也值得做成技能。易错主要看特征参数难提取的场景比如用户说话时没给订单号、需要多轮确认的场景、多步骤操作中一步错步步错的场景。强规则指的是业务逻辑层层叠叠的场景比如退货退款要判断购买时间、商品状态、物流是否签收这种判断放在提示词里就要写很长且容易漏放在技能里可以用显式逻辑实现。低频、一次性的动作就不要做技能了留在普通流程里让模型自由处理反而简单。当时我们客服Agent的任务盘点表大概是这样的业务动作频次易错点是否做技能理由查询订单状态高订单号缺失、格式错误是参数校验复杂复用率高查询物流轨迹高经常与订单查询混淆是边界需要锁死退货退款处理中多步骤依赖、状态流转多是流程长必须编排优惠券计算中规则经常变是方便单独迭代转接人工客服低几乎无否太简单直接写流程里这个盘点做完你就有了一条清晰的边界哪些走技能哪些不走走。别凭感觉拍脑袋拿日志说话最靠谱。2.2 划分技能粒度原子技能负责执行组合技能负责编排技能粒度是一个经典的分寸问题。太粗一个技能里面塞了“查询订单、判断退货资格、生成退货单、通知用户”四条逻辑复用度低太细光一个退货流程就拆出八个技能模型选择成本巨高还容易选错。我这里采用的是两级结构原子技能和组合技能。原子技能是一个不可再拆的最小可复用动作。比如“查订单”“查物流”“校验用户身份”它们各自独立完成一件事输入输出清晰没有“然后”“再”这种流程描述。组合技能是把多个原子技能编排成一个完整业务流程的动作。比如“退货退款处理”它的内部流程是“校验身份 → 查订单 → 判断退货资格 → 生成退货单 → 通知用户”每个步骤都指向一个原子技能或者简单的工具调用。判断粒度是否合适有一个简单的检验方法读技能的描述文本如果你发现里面出现了超过两次的“然后”“再”“同时”那它八成不是原子技能应该拆开。反过来如果两个技能永远一起出现从来没有单独被调用的场景那它们就该合并成一个组合技能。技能数量的总控制也很重要我当时给自己定的线是技能总数尽量控制在几十个以内超过这个数你就要开始考虑更复杂的检索和路由机制复杂度会明显上升。2.3 用接口契约定义技能描述、Schema、异常一个都不能少一个技能要被模型和框架正确使用需要一份清晰的“接口契约”。我习惯用YAML来定义每一项字段都有它必须存在的理由。一份技能定义大致长这样name: fetch_order version: 1.2.0 description: 根据订单号和用户ID获取订单的基础信息与当前状态。 当用户询问“我的订单怎么样了、订单到哪一步了、帮我查下订单”时使用。 when_not_to_use: 当用户只询问物流轨迹、没有给出订单号时不要使用 当用户明确在问退款进度时应使用 process_refund 而不是本技能。 input_schema: 订单号: type: string required: true source: user_query validation: ^[A-Za-z0-9]{12,20}$ 用户ID: type: string required: true source: session_context output_schema: order_id: string status: string items: array created_at: string tool_sequence: - call: query_order_api params_from: [订单号, 用户ID] retry: true max_retries: 3 error_handling: on_missing_order_id: ask_user_for_order_id on_order_not_found: return_order_not_found_message on_timeout: return_temporary_unavailable timeout: 3s这里面最容易被忽略的是 when_not_to_use 字段。只写“什么时候用”模型会在边界场景里反复横跳多写“什么时候千万别用”误触发概率会明显下降。这就像给模型划了一条清晰的禁区线它能更容易理解技能的边界。input_schema 里面的 source 字段也很关键它告诉执行器参数是从用户当前消息里抽还是从会话上下文里拿还是上游技能传过来的。没有这个约束模型经常会去上一轮对话里乱抓一个订单号填进来这种错误我见过太多次了。2.4 画出技能依赖关系避免循环调用和版本混乱有了多个技能之后它们之间不是相互孤立的。组合技能会依赖若干原子技能它们之间的依赖关系必须被显式管理。我习惯在技能定义里加一个 depends_on 字段name: process_refund version: 2.0.0 depends_on: - fetch_order: 1.2.0 - verify_identity: 1.0.0这个字段有两层价值。第一层是循环检测。构建技能库的时候遍历依赖关系如果发现技能A依赖B、B又依赖A直接报错不能等上线跑起来才发现死循环。第二层是版本兼容。组合技能绑定的原子技能版本范围一旦被打破行为可能突变。有一次我们升级了 fetch_order把订单状态的枚举值改了结果 process_refund 里针对“待收货”状态的判断逻辑全部失效。因为版本绑定做得好回归测试立刻暴露了问题没有让错误跑上线。画依赖关系的时候不用画得很复杂在注册表里维护一张简单的表就够了技能名、依赖技能、依赖版本、当前版本。组合技能之间的依赖也要检查避免出现一个组合流程递归调用自己的情况。这一步做扎实后面迭代升级才有底气。3. 技能库落地实操从目录结构到执行器一步步搭起来3.1 技能仓库的目录结构、命名规范和测试用例怎么放理论讲完开始落地。第一步是把技能仓库的目录结构搭起来。我采用的是一套非常朴素但够用的约定skills/ atomic/ order/ fetch_order/ SKILL.yaml handler.py test_cases.json logistics/ query_logistics/ SKILL.yaml handler.py test_cases.json identity/ verify_identity/ SKILL.yaml handler.py test_cases.json composite/ refund/ process_refund/ SKILL.yaml flow.py test_cases.json coupon/ calc_coupon/ SKILL.yaml handler.py test_cases.json每个技能一个独立目录SKILL.yaml 保存元数据和接口契约handler.py 或 flow.py 保存实际执行代码test_cases.json 保存这个技能的测试用例。把测试用例和技能代码放同一个目录是我特别坚持的一点。技能迭代时第一件事就是跑这个目录里的测试而不是去找一个集中的测试仓库——分散维护的好处是每个人改技能的时候注意力能集中在当前技能测试用例也会和技能一起演进。命名规范我用的规则是“动词_对象”全部小写加下划线。比如 fetch_order、query_logistics、verify_identity。不要用“订单查询”“OrderCheck”这种五花八门的风格否则后面做技能发现和检索的时候文字匹配会出各种幺蛾子。命名一旦定下来就写进规范文档新技能入库时必须遵守。3.2 注册表与技能发现让Agent在几十个技能里找到最合适那一个技能建好了还得让模型能“看到”它们、能选对。这一步我称之为技能发现。最简单直接的方式是维护一份技能注册表把每个技能的元数据汇总成一个数组Agent启动时加载到上下文里。技能数量少的时候直接把这几个技能的精简描述放进system prompt就够了。技能数量超过十五到二十个就要考虑检索式发现先用用户查询去检索技能描述把最相关的Top-K个技能注入上下文而不是把所有技能都堆给模型。注册表里的描述字段是决定检索和模型选择质量的关键。这个描述要写得像一个“搜索索引”把用户可能说的话映射到技能上。当时我们有一个教训“获取订单信息”和“查询物流状态”这两个描述写得太接近模型经常把物流查询请求路由到订单查询技能。后来我把注册表里的描述重新梳理了一遍明确写上“查物流轨迹请用 query_logistics此技能返回包裹运输节点fetch_order 只返回订单基础状态和商品列表”误触发率立刻降下来了。注册表的形式可以很简单一份JSON或YAML都可以skills: - name: fetch_order description: 查询订单基础信息与当前状态用于用户问“订单怎么样了” version: 1.2.0 status: active - name: query_logistics description: 查询包裹物流轨迹与运输节点用于用户问“包裹到哪了” version: 1.1.0 status: active这里的关键是注册表不只是给人看的更重要是给模型看的。你的注册表描述必须与用户语言习惯对齐不能只写技术名词。用户不会说“我要调用订单查询服务”他会说“我东西咋还没到”。描述里最好把这类口语化的触发点也捎上。3.3 执行器设计一条技能调用的完整链路和容错细节技能发现是选择问题执行器是执行问题。一条技能调用的完整链路大概是预处理用户输入提取上下文把用户查询和技能描述做匹配选中一个技能从输入中抽取技能所需参数校验参数合法性按 tool_sequence 逐个调用工具把结果按 output_schema 整理返回遇到错误按 error_handling 兜底。其中最容易出问题的环节是参数校验。模型抽取出来的参数有时候字段名跟schema不一致有时候多抽了信息有时候把订单号里的字母O当成了数字0。一个靠谱的执行器要做几件事类型转换、正则校验、枚举匹配、缺失字段提醒。我当时的思路就是用schema生成一个校验函数凡是不满足校验的参数直接返回“参数不完整需要追问用户”而不是默不作声地把错误参数传给下游工具。伪代码大致长这样def run_skill(skill, session, user_input): params extract_params(skill.input_schema, user_input, session) validated validate_params(params, skill.input_schema) if not validated.ok: return {status: need_user_input, message: validated.message} result {} for step in skill.tool_sequence: resp call_tool(step[tool], paramsbuild_call_params(step, result)) if step.get(retry) and resp.is_error: resp retry_with_backoff(step, params) if resp.is_error: return handle_error(skill, step, resp) result.update(resp.data) return build_output(skill.output_schema, result)执行器还必须处理两类特殊场景。第一类是超时兜底。一个技能整体执行超过阈值比如3秒就停止后续调度返回“当前服务繁忙请稍后再试”不能把超时错误堆给用户。第二类是写操作的幂等设计。像“生成退货单”这种操作如果第一次调用超时了重试很可能会生成重复的退货单。解决方法是给每次调用生成一个 client_request_id下游接口靠它做去重重试时带上同一个ID这样最多只会有一个退货单被创建。3.4 提示词侧的小技巧把技能清单给模型看但别把工具代码丢给它执行器搭好之后还有一道工序在提示词层面让模型更稳定地选择技能。这里有个经验给模型看的技能清单应该只包含“技能名一句话能力描述关键输入要求”而不是把工具函数的完整代码丢给模型。模型要的是高层语义不是实现细节。实现细节太多了反而会干扰它的判断。我当时的system prompt里是这样写技能清单的可用技能 1. fetch_order(订单号, 用户ID)获取订单基础信息与当前状态 2. query_logistics(订单号)查询包裹物流轨迹没有订单号时不可调用 3. process_refund(订单号, 原因, 金额)执行退货退款完整流程需要客服二次确认 规则 - 用户未提供订单号时先向用户索要订单号不要调用任何技能。 - 用户只问包裹到哪了用 query_logistics不要用 fetch_order。 - 所有参数必须从用户当前消息中提取不要使用历史对话中的过期数据。 - 所有技能均无法处理时明确告知用户转人工不要强行调用技能。最后一条规则特别重要。很多Agent出问题不是因为选错技能而是因为“没得选还要硬选”。给模型一个“无技能可用就转人工”的出口能让它在该认怂的时候认怂而不是为了完成任务去编参数、硬调接口。这个出口不仅在提示词里要写执行器里也要有对应的路由逻辑。4. 技能不是写完就完了评估、监控与迭代三板斧4.1 离线测试集给每个技能建立体检标准和回归基线技能库建完只能说明它能跑不能说明它跑得好。我在把技能上线之前会做一件事给每个技能建立一套离线测试集。这个测试集从真实对话中选取样本每个技能至少准备几十条覆盖三类场景——正常场景、边界场景、异常场景。正常场景就是参数齐全、流程走通边界场景是订单号缺失、订单号格式错误、用户中途改主意异常场景是内部工具返回报错、上游接口超时。有了测试集就可以给技能定量化指标。我的体检指标主要是这几个指标含义我的建议值触发准确率该触发时触发、不该触发时不误触发95%以上参数抽取准确率参数值与标注完全一致90%以上任务完成率走完整个技能流程且结果正确90%以上平均工具调用次数完成一次任务实际调用工具次数越少越好这里要特别注意参数抽取准确率我按严格标准算多一个空格都算错。因为真实下游接口往往对参数格式敏感模型抽取出来的参数带一个多余换行符可能整条调用就失败。每次修改技能之后跑一遍测试集做回归。我吃过不少亏有一次只是改了一个技能描述比如把“订单状态”改成了“订单当前状态”结果模型对相关测试样本的行为整体变了。没有回归测试这种问题当天根本发现不了。4.2 线上日志监控哪些数据必须捞怎么捞离线测试再全面也覆盖不了线上复杂的对话变化。所以监控日志是必不可少的。我给每条技能调用记录了这些字段request_id、技能名、技能版本、输入摘要、输出摘要、工具调用序列、总耗时、成功或失败状态、错误类型、用户反馈。这些日志不光用来排查故障更要用于持续改进技能库。每周我会抽一批失败日志做人工复盘。具体方法是从失败日志里随机抽一百条人工标注失败原因——是技能选错了、参数抽错了、工具本身报错了还是模型压根不按提示词来。把带有共性问题的样本补充进测试集让下一轮技能迭代有靶子可以打。这个“线上失败日志 → 人工复盘 → 补充测试集 → 技能迭代 → 回归上线”的闭环是技能库质量能持续上升的根本原因。还有一个容易被忽略的指标成本与延迟。如果一次普通咨询触发了四五个技能、每个技能又串行调用好几个工具用户会明显感到慢token消耗也会高。所以要监控每次任务的平均技能调用个数一旦发现上涨趋势就要考虑是不是组合技能编排得太啰嗦了。我在线上观察过组合技能里有些“校验用户身份”的步骤其实会话开头已经做过了盲目加校验就是浪费。4.3 高频翻车问题排查表触发失败、选错、参数错、超时、重复请求把容易踩的坑整理成一张排查表可以省去大量重复定位时间。症状可能原因排查与解决技能完全不触发技能描述里的触发条件写得太窄扩大 when_to_use 描述加入口语化触发词技能选错多个技能描述边界不清检查注册表描述明确写清 when_not_to_use参数抽取多字少字schema缺少校验规则模型从历史对话取数增加正则校验和枚举限定提示词里声明“只能从当前消息取参数”工具调用超时上游接口慢或技能串行调用太多加超时、重试、部分步骤并行化重试产生重复请求写操作未做幂等给写操作加 client_request_id下游去重排查第一条“技能完全不触发”时可以先看日志里模型输出的是哪个技能名。如果模型输出了“没有可用技能”说明它在注册表里检索不到匹配项大概率是描述问题。如果模型输出的是另一个技能名比如用户问“包裹到哪了”却触发了 fetch_order那就按“技能选错”处理回到注册表描述上找原因。这类问题有一个通用规律跟模型较劲不如跟描述较劲描述改了行为通常就改了。4.4 版本管理与灰度上线让技能库可以持续演进不出乱子技能库上线之后一定会遇到业务规则变化技能版本管理就得跟上。我采用语义化版本号MAJOR.MINOR.PATCH。兼容性扩展比如新增一个可选输出字段升MINOR破坏性变更比如输入参数变了升MAJOR小修小补升PATCH。组合技能引用原子技能时必须带上版本范围防止原子技能升级后把组合流程搞坏。灰度上线是我坚持的一步。新版本技能先切一小部分流量用线上的真实对话验证效果同时和旧版本并行运行。对比指标没有异常再逐步放量。一旦发现问题直接回滚到旧版本。因为技能是独立目录、独立版本号的回滚操作很简单替换一下handler和注册表条目就行。这套机制看起来多了一堆流程但它是技能库能不能长期维护的关键。没有版本管理和灰度机制技能库大概率会在第三个版本之后变成一团乱麻。5. 一点个人体会技能体系建设这件事做到最后你会发现自己做的不只是技术架构而且是在给模型“立规矩”。最开始我沉迷于调prompt觉得只要描述写得好模型就一定能稳定执行后来被现实教育了一通才想明白prompt是给模型看的路况牌技能才是真正让车能跑起来的那套驾驶动作。把能力从提示词里抽出来变成结构化的、可测试的、有版本号的东西Agent的稳定性才能从一个不可控的“模型状态”变成一个可以管理的“工程状态”。最后再分享一个小技巧写技能描述的时候多花点精力在“什么时候不要用”上面比只写“什么时候用”更能减少误触发。这个反直觉的写法是我在几十次线上事故之后总结出来的现在每一份技能定义里when_not_to_use 都是我必填的字段。希望这篇关于 agent-skills 的实战总结能帮你少走几步弯路。
📝

华诺云谱内容团队

资深建站顾问 · 行业研究员

10年+企业数字化服务经验,专注智能建站、SEO优化与品牌营销,持续输出建站技巧、行业洞察与营销干货,已帮助5000+企业实现数字化增长。

你可能需要的服务

订阅华诺云谱资讯周报

每周一封,精选建站技巧、SEO与营销干货,直达邮箱。已有 8,000+ 企业主订阅,助你少走弯路。

↑