ARTICLE DETAIL

建站实战干货

来自一线的建站与推广经验沉淀,每一条都经过真实交付验证。

Agent技能体系实战:从提示词到稳定可复用的智能体技能库

2026/10/7 21:05:15 拓冰建站 浏览量
Agent技能体系实战:从提示词到稳定可复用的智能体技能库 我做了大半年Agent相关项目有个很深的体会绝大多数“看起来不够聪明”的智能体问题压根不在模型本身而在它没有一个像样的技能体系。模型明明能力不差上下文也给足了但行为就是飘忽不定——今天按A路径走明天偏要自己开发个B方案换个措辞结果完全变样。我一开始也怀疑是模型不行后来把几十段对话日志翻出来逐条看才确认问题出在哪它根本不知道“自己能做什么”所有操作都靠提示词现场发挥自然时灵时不灵。后来我开始认真搞agent-skills也就是给智能体搭一套可复用的技能库把底层的操作能力一个个定义清楚、注册好让模型像查工具手册一样去检索和调用。改完之后同样一批任务成功率和行为确定性都明显上了一个台阶。这篇文章我就把整个设计思路、实操步骤和踩过的坑完整记录下来适合正在做Agent应用、想解决智能体行为不稳定问题的开发者做参考。1. 为什么智能体需要“技能”体系而不是一份长长的提示词1.1 纯提示词方案的局限每一次都在“重新发明轮子”先看最常见的做法写一份几千字的系统提示词把各种规则、流程、工具用法都堆进去然后让模型自己“看着办”。这种做法我当时也用得很爽因为入手特别快——把约束写得尽量细模型基本就能照做。但问题随着任务复杂度上升会越来越明显。第一是不稳定模型本质上是概率模型每次请求都像重新开一局游戏同样的输入、同样的提示词它可能给你走出完全不同的路线。第二是不可控一旦中间步骤出错你很难判断是规则没写清还是模型理解偏了因为“整段提示词整段上下文”是搅在一起的黑盒。第三是成本我自己算过一笔账一个串了5个工具调用的任务如果全靠提示词让模型现场琢磨怎么用、参数怎么填每次都要携带大段工具说明和范例Token消耗比用技能体系高出一截慢倒是次要的关键是费钱。类比你就能想明白让一个新员工每次处理客户投诉都从头想一遍“先查订单、再翻售后规则、再决定退款还是换货”效率一定很低而且每个人想的路径不一样。可如果公司给他一本操作手册每个动作都明确“什么情况用、怎么用、参数是什么”他照着执行结果就稳定得多。技能体系对智能体来说就是那本操作手册。1.2 技能体系到底改变了什么所谓技能不是一个概念而是四个可落地的东西凑在一起一个能表达“什么时候用”的命名和描述一个定义“要什么参数”的输入Schema一段真正执行的函数或脚本以及成功后返回给模型的结果。模型在这个过程中只负责“调度”它判断当前任务该调用哪个技能然后把参数填好真正的执行由确定性的代码完成。这样做带来的第一个好处是行为可控。技能是预先定义好的动作模型的选择空间被收窄了它不能自由发挥只能从我给的清单里挑。第二个好处是可测试。每个技能可以单独跑可以写单元测试出错了能定位到具体环节。第三个好处是可复用同一个“查订单”技能客服Agent能用售后Agent也能用不用每处都重新写一遍提示词。第四个好处是可观测日志能清楚记录某一步调用了哪个技能、传了什么参数、返回了什么结果排查问题不再靠猜。我当时改完第一批技能后最直观的感受是以前让人头疼的“模型自作主张”变少了它更像一个知道分寸的执行者而不是一个漫无边际的写作机器。2. 技能库的设计任务拆解与技能粒度2.1 把业务目标拆成能被调度的原子动作搭建技能库的第一步永远不是写代码而是拆任务。我习惯把业务目标从粗到细拆成三层宏观目标、任务序列、原子技能。拿一个客服智能体举例。宏观目标是“处理用户退款请求”任务序列大概是先识别用户身份再查订单状态然后判断是否符合退款条件最后执行退款或转人工。到了原子技能这一层就会出现“查询订单信息”“查询退款策略”“提交退款申请”“转人工交接”这四个可以被调度的动作。一个技能只做一件事这件事要小到可以稳定执行、清晰验证。要是把一个“复杂报表分析”技能直接塞进去模型调度起来没问题但这个技能内部逻辑太黑盒一旦出错很难排查。我建议的粒度标准是能独立测试输入输出明确边界清晰。哪怕“生成Markdown表格”这种听起来很小的能力也值得做成一个独立技能因为它会被很多上游任务复用。2.2 技能命名与描述决定Agent能不能“看见”你的技能这里有个大多数人会忽略的关键点模型选技能主要不是靠函数名而是靠描述文本。你把技能定义好模型在推理时会读一遍技能列表根据描述判断“这个技能适不适合当前场景”。所以描述写得好不好直接影响技能被选中的概率和正确率。我总结的优质技能描述公式是这个技能做什么 适合在什么场景用 不适合在什么场景用。第三点特别重要很多人漏掉。举个实际例子我原来有个技能描述是这样写的原生描述根据订单号查询订单详情。这个描述够明确了吧结果测试的时候用户问“帮我查一下退款到哪一步了”模型居然也去调用了这个技能因为描述里没说明它只查订单基础信息、不包含退款进度。后来我改成优化后根据订单号查询订单基础详情包括商品、金额、下单时间。适合用户需要确认订单内容时使用。如果用户询问退款进度、物流状态请使用对应的查询技能不要调用本技能。改完之后错调用率明显下降。所以描述不是越短越好也不是越长越好而是要精准覆盖边界条件。2.3 一个可落地的技能清单样式当技能数量超过五六个之后靠脑子记就不现实了我习惯把它们整理成一个独立目录每个技能一个文件内容用结构化格式描述。skills/ ├── catalog.yaml ├── order/ │ ├── query_order.yaml │ └── query_refund_progress.yaml ├── logistics/ │ └── query_express_info.yaml └── notify/ └── create_ticket.yaml单个技能文件里我会保留这几项技能名、一句话描述、使用边界、参数定义、执行入口、返回说明。name: query_order namespace: order description: 根据订单号查询订单基础详情包括商品、金额、下单时间适合用户需要确认订单内容时使用。 when_not_to_use: 用户询问退款进度、物流状态时不要调用本技能。 parameters: order_id: type: string required: true description: 订单编号一般在用户消息中提取格式为ORD开头。 returns: - order_status - items - total_amount entry: functions/order_api.query_order这种做法最大的好处是技能库可以脱离Agent代码独立演进。新加一个技能就加一个文件改一个描述就改一个字段谁负责维护、做了什么改动都一目了然。3. 从零搭建agent-skills实操过程与核心步骤3.1 盘点现有能力整理技能候选正式动手前先把手上已有的API、函数、脚本全部盘一遍。我当时做的是一个内部工单助手所以我列出的技能候选包括查工单信息、查用户注册信息、查常见问题库、创建工单、给工单追加备注、发邮件通知——这就是第一批技能池。从盘点清单变成正式技能有个筛选标准这个动作是不是高频复用的输入输出是否稳定能不能被确定性代码执行。有些动作明明很小比如“从工单列表里筛选未处理项”但它会在很多场景里反复出现就值得封装。而有些动作比如“根据上下文判断用户情绪”这种带有主观判断的事现阶段不适合做成技能更适合让模型本身处理。3.2 编写技能Schema与参数约束接下来是给每个技能写参数定义。理论上这步很像写API文档但区别在于这个Schema的读者是模型不是人。模型不知道该不该用某个参数时会查描述填错类型时会看类型定义。我给一个实际用过的参数Schema做示例。{ name: query_refund_progress, description: 查询售后单的退款当前进度适合用户询问退款到哪一步时调用, parameters: { type: object, properties: { after_sale_id: { type: string, description: 售后单号格式为AS开头可多选以逗号分隔, minLength: 3, maxLength: 30 }, include_timeline: { type: boolean, description: 是否返回时间线明细默认false只在用户明确询问进度详情时设置true } }, required: [after_sale_id] } }这里我把所有可能用到的参数都放到properties里但required只保留一个。为什么因为模型会根据描述判断要不要传include_timeline而不是每个字段都非得给值。参数直接决定了后面代码能不能跑通。我第一次实践时吃过亏没写minLength和maxLength模型自行脑补了一个超长ID传进来接口直接报错返给模型一个异常结果它又一本正经地编了一个答案告诉用户处理失败了看着好笑其实很致命。3.3 注册技能并接入Agent循环技能定义好之后就要让模型能够在实际推理中调用到。现在很多框架都有封装好的工具注册机制我当时为了看得透彻直接用OpenAI风格的Function Calling写了一遍最小可执行循环。核心结构是这样先把技能声明成列表传给模型模型返回一个tool_call代码执行技能后把结果作为新消息回填给模型模型再继续推理。如果模型不需要调用技能就直接输出最终答案。import json import openai def query_order_details(order_id: str): # 实际对接订单服务的逻辑这里用模拟数据 return {order_id: order_id, status: delivered, items: [蓝牙耳机], total_amount: 299.0} skills [ { type: function, function: { name: query_order_details, description: 根据订单号查询订单基础详情适合确认订单内容时使用, parameters: { type: object, properties: { order_id: { type: string, description: 订单编号ORD开头 } }, required: [order_id] } } } ] def call_model(messages): return openai.ChatCompletion.create( modelgpt-4o, messagesmessages, toolsskills ) def run_agent(user_message): messages [{role: user, content: user_message}] while True: resp call_model(messages) msg resp.choices[0].message if msg.get(tool_calls): messages.append(msg) for tc in msg[tool_calls]: args json.loads(tc[function][arguments]) result query_order_details(args[order_id]) messages.append({ role: tool, tool_call_id: tc[id], content: json.dumps(result) }) else: return msg[content] print(run_agent(帮我查一下订单ORD20250118买了啥))这个循环就是所有技能调用的地基。模型不是真正执行了函数它只是告诉系统“我选了这个技能、我给出这些参数”真正干活的是query_order_details这个Python函数。所以技能的安全性、稳定性、权限控制都在这一层做而不是指望模型不乱来。建议刚开始做Agent的同学不要一上来就套重型框架先把这个最小循环跑明白后面再用框架心里就透亮得多。3.4 小规模验证技能触发效果技能接入后最怕的是自以为写得很完善实际模型根本不买账。我养成了一个习惯每次改完技能库先准备一组测试对话大概5到10条覆盖正向用例、边界用例和反面用例。比如测“查询订单”技能我会准备这些输入正向帮我看看这个订单ORD20250118发货了吗期望触发查询技能。边界查一下最近一单期望触发查最近订单而不是直接查订单因为没给具体单号。反面我的退款啥时候到账期望绝对不能触发查订单应该触发退款进度查询。跑完测试后逐个检查两个指标技能是否被调用参数是否填对。如果模型选错技能那大概率是描述有歧义如果参数填错大概率是Schema写得不够细。别急着骂模型先回头看自己的定义。这个验证过程每次改动都要做因为模型推理是松散耦合的改了一个描述可能连带影响其他技能的选中率。4. 技能编排进阶让多个技能串联成完整流程4.1 线性串联与依赖关系处理单个技能只能处理单点动作真实任务通常是一串技能按顺序协作。比如用户问“我这周五有空帮我约个会议室”至少需要两个技能查我日历确认空闲时间然后创建会议邀请。这里有个关键设计问题技能之间的依赖怎么表达。我推荐的做法是在技能描述里显式写明前置条件。比如创建会议邀请这个技能描述可以写成“根据参会人、开始时间和结束时间创建日历会议通常应先调用查询日历技能确认没有冲突再调用本技能”。模型看到这样的描述就会自然形成“先查后建”的调度顺序不需要额外在代码里写死。但要注意不要把流程编排全押在模型自觉上。对于那种步骤完全固定、顺序不能乱的任务宁可写一段确定性流程代码把技能依次调用模型只负责在每步填充参数。两者没有高下之分区别只在“需要灵活性”还是“需要确定性”。我现在的取舍标准是任务越线性、越容不得错就越倾向用代码固定流程任务越开放、越需要变通就越交给模型自行编排。4.2 中间结果保存与上下文管理多技能串联带来的另一个问题是中间结果怎么传递。最省事的做法是把上一步技能返回的结果拼回对话上下文让模型看到然后它继续调度下一步。这个做法在步骤少的时候没啥问题但一旦技能返回的是很长的大表单几千字符直接塞进上下文几次调用之后上下文就臃肿了模型越往后越抓不住重点。我后来学到的做法是大结果不要直接返回全文而是先把内容写入临时文件或者向量库技能只返回一个引用ID和摘要。上一个技能说“结果已保存在store_09ea62共120条记录”下一个技能需要具体字段时再用专门的读取技能去取。这就好比你在公司做交接不会把整份文档贴到消息里而是给一个文档链接谁需要谁再点进去看。这样上下文能被控制在稳定范围模型读到的都是精炼信息决策质量也更高。4.3 用日志判断编排质量技能编排是否合理不能靠感觉要看每一步的调用日志。我把每一步的推理结果都打出来格式大概是用户意图、模型选择了哪个技能、传了什么参数、技能返回了什么、模型基于返回结果又做了什么决策。不要嫌这一步麻烦它在排查问题的时候是救命稻草。之前有个案例让我印象很深模型在同一个问题上反复调用同一个查询技能一共查了5遍每次参数都一样。从外部看像是个循环Bug,但看日志发现是那个技能返回结果里缺少一个字段模型觉得信息不够就会再去查一遍形成死循环。后来我在技能返回结构里补了这个关键字段问题立刻消失。你只有把调用链路日志化才能捕捉到这些藏在细节里的坑。5. 常见问题与排查技巧实录5.1 模型总是选错技能这是遇到概率最高的问题。表面现象是用户问“退款到哪了”模型调了“查询订单详情”返回了订单状态然后一本正经告诉用户退款正在处理。根子基本都出在描述边界没有写明。解决套路其实很固定在when_not_to_use这种负面描述上下功夫。你可以在描述里直接列出不要调用的场景模型对这种显式指令的遵循率比泛泛的正面描述高不少。我还见过一个更极端的办法给相似技能分别加上“关键词提示”比如退款进度技能描述里写“常见问法包括商家退了吗、退款到哪一步了”实测效果也不错。这里有个小技巧你可以在每个技能描述的最后写一句“如果你不确定该用哪个技能请先询问用户而不要自己猜测”能有效减少模型的错配行为。5.2 参数“幻觉”模型生成了不存在的参数有时候模型会自己编参数比如给一个只接受order_id的技能额外带上order_status、user_name之类的字段。原因往往是Schema里字段名和业务的习惯叫法对不上或者模型看不懂字段的描述。我的排查清单是这样先确认参数名是不是业务术语再检查每个参数有没有说清格式和示例再看required是不是只留了真正必须的字段。另外有两个非常实用的约束给字符串参数加enum给数字参数加minimum和maximum。用enum限死取值范围可以极大降低模型编造非法值的概率。比如“退款原因”枚举成“不想要了”“商品质量问题”“物流太慢”三项模型就只能在里面挑。5.3 技能冲突与命名空间隔离技能库大了以后会出现一个真实存在却容易被忽略的问题两个技能描述上高度相似模型分不清。比如“查工单详情”和“查工单操作记录”听起来都有“查工单”但用途其实不同。如果描述里的区分信息不足模型就会随机蒙一个。我的经验是给技能做命名空间隔离不只靠名称更要在描述里刻意强调差异化场景。同时同类技能超过三四个就该引入“先分流、后细查”的机制先用一个分类技能判断用户属于哪一类需求再由模型决定调用子技能。这个做法有点像给书建目录一级目录内容少了二级目录才不会被看漏。5.4 上下文被拖垮技能定义过多的副作用最后一个问题很多人会忽略技能定义多了上下文窗口就被技能列表本身占满了。当技能数量超过10个且每个描述都在200字以上光技能列表就有好几千Token这会压缩模型真正用于思考的空间技能之间的相互干扰也会变严重。我的建议是分层管理第一层只给模型几个粗粒度技能分类比如“订单类”“物流类”“售后类”模型先选类别第二轮再把该类别下的具体技能定义发给它。这样每一轮模型需要阅读的技能数量都很少决策更准。如果你不想做得这么复杂至少要把描述压缩到一个合理长度我个人的上限是180字符只留最核心的边界信息。6. 技能库的版本管理与团队协作6.1 让每一次技能变更可追溯技能库本身是代码资产应该像代码一样管理。我团队的技能定义全部放在Git仓库里每个技能文件、每个描述变更都有提交记录。为什么强调这个因为技能描述是“模型行为的关键变量”有时候你改了一句描述可能连带影响其他技能的选中概率。如果变更没有记录出了问题就没有回滚依据。我们的流程是技能改动必须走合并请求至少一个人review描述和Schema再跑一遍第3.4节里的测试用例集全部通过才允许合入。版本号上所有Agent统一引一个固定版本的技能库不做成依赖最新main分支的裸引用。这样才能保证线上每个版本的行为可追溯不会出现“昨天还好好的今天加了个技能就坏了”这种玄学问题。6.2 技能维护的Owner机制技能库没人负责是维护不起来的。我建议按业务域划分Owner比如订单域一个Owner物流域一个Owner每块业务对应技能的新增和描述优化由对应的人负责。线上收集到的失败case统一整理到一个issue列表里每周挑出出现频次最高的几个优先优化对应技能描述或Schema。听起来好像有点“重”但Agent项目到了后期拼的其实就是这个技能库的维护质量。模型选型当天就能定下来一套持续迭代的技能体系却需要两周、一个月长期打磨。把技能库当成一个产品来运营是Agent走向稳定工程化的必经阶段。6.3 把失败case变成新技能技能库不是建完一次就固定了。实际上我经常在复盘线上对话时发现某类问题模型总能回答但答得特别费劲每次都要绕很多弯路。这种情况往往意味着那里应该出现一个技能但还没有被封出来。举个小例子一开始我们的Agent遇到用户投诉时会先查各种信息然后自己拼回复末尾的致歉语每次发挥都不一样。后来我单独做一个“生成标准致歉模板”技能把话术版本固化在里面模型只需在需要时调用结果不仅回复风格统一了上下文里还少了一大段临时思考的输出。这就是把失败case变成新技能的过程每次看到模型“不必要地努力”就想想这里是不是缺一个技能。技能库是活的它会随着业务理解加深持续生长而不是一次性交个差就完事。做技能库这件事我个人的体会很简单它的难度不在于写代码而在于对业务的理解和表达。能把这个技能清单写得让模型一看就懂让队友一读就明白是一件很见功力的活。如果你现在正在做Agent我建议别急着追新模型先花一天时间把手上的能力梳理成技能清单把描述反复打磨几遍整个系统的实际表现会给你惊喜。这个工作是慢功夫但回报会在后面每一天的稳定运营里慢慢体现出来。