很多团队做智能体应用卡住的地方往往不在模型选型而在“怎么把能力交给模型”。我见过太多这类项目提示词写了一长串看起来什么都能聊一问到具体业务就东拉西扯或者API接了不少模型根本不知道什么时候该调哪个只能凭感觉乱撞。后来反复调下来的经验就一句话把能力拆成技能用一套统一的描述、注册、编排方式来管理智能体的手脚这个思路圈子里常被叫做agent-skills。这篇东西就是围绕这个思路展开的适合那些正在把大模型从“聊天Demo”推向真实业务的人不管是做客服助手、知识问答、内部办公自动化还是想把已有系统能力开放给模型都可以参考。先说清楚agent-skills 不是一个多么高深的理论框架它更像一种工程习惯把业务能力拆成可命名、可描述、可独立测试、可组合调用的“技能”然后让大模型像人一样照着技能清单去执行任务。下面我会从设计、定义、注册、编排、评估到排查把这套做法的完整链路拆开讲也会把实际项目中踩过的坑、调过的参数一起放进来。1. 智能体技能化的核心思路1.1 为什么要把能力拆成“技能”如果你把所有业务规则都塞进一个系统提示词等于让一个人在没有SOP、没有工具、也没有明确分工的环境下凭直觉干活。短期对话还行一旦涉及真实业务链路比如客服助手既要识别用户情绪又要查订单、算退款、写安抚语模型就会频繁出错——中间任何一步理解偏差后面全崩。技能化做的事情很简单把“查订单”变成一个独立技能它有明确的触发条件、入参、出参、失败返回。模型不需要自己脑补订单系统长什么样它只需要知道“用户问订单就去调用查订单技能”。这与后厨分岗是同一个逻辑大厨不需要亲自买菜、切菜、掌勺每个岗位有明确SOP哪个环节出了问题就直接定位到那个岗位。我在多个模拟项目里试过单一大提示词和技能化两种方案稳定性差距非常明显。技能化之后模型不再需要记住每一个业务的处理细节它只需要学会“选择合适的技能、填对参数、读取返回结果”这三件事。这大大降低了模型的认知负担也让业务逻辑从提示词里剥离出来变成可维护的工程资产。1.2 技能化解决的核心问题为了看得更清楚我把单体Promp和技能化这两种方式放在一起做对比这也是我给别人讲这套思路时最常用的表格对比维度单体提示词方案技能化方案稳定性改一处提示词全链路受影响技能独立修改互不干扰复用性换场景需要重写提示词技能一次封装多处复用可观测性模型思考过程不可控出错难定位技能调用有日志能定位到具体步骤可演进性业务变化只能靠调Prompt技能版本升级不影响全局边界控制所有能力都暴露给模型不可控通过技能白名单控制能力范围这个表做出来之后团队里讨论方向就清晰多了。技能化的本质不是限制模型而是给模型一个结构化的工作环境。它让模型从“全能答题选手”变成“会调用工具的调度员”后者在真实业务里远比前者可靠。1.3 技能化的适用范围和边界不过我也得泼一盆冷水技能化适合的是那些“流程明确、动作可拆、结果可验证”的业务能力。比如查数据、发通知、算价格、改状态这类动作非常适合做成技能。但如果是纯开放性任务比如“和用户闲聊”“写一篇营销文案”把它们做成技能的意义不大还不如直接用模型原生能力。技能化真正的价值区间在于业务动作可以被枚举、被标准化。如果一个能力连团队内部都说不清适用条件那就不该急着做成技能先梳理清楚业务逻辑再说。我见过不少团队一上来就建了几十个技能结果一半连触发条件都写不明白最后模型调用得乱七八糟这种“为了技能而技能”的做法是最常见的失败原因。2. 技能设计的三个关键环节拆解、命名与边界2.1 技能拆解的方法论拆技能是整套体系里最花时间、也最考验业务理解力的环节。我的经验是从用户请求的场景反推而不是从系统功能往前推。系统功能表往往很细很杂直接照着拆会把技能数量做得很大但从用户请求出发你会发现高频场景就那十几个。拆解的基本方法有三种实际项目中通常混着用按动作拆“查”“改”“算”“发”“删”每个动作配一个技能。按信息域拆客户域、订单域、商品域、库存域同一个域内的操作放一起管理。按职责拆接待、业务处理、风险控制不同职责对应不同的技能集合。不管是哪种拆法都要守两个原则。第一是单一职责一个技能只干一件事。如果一个技能需要传十几个参数里面塞满了if else说明拆得太大了模型很容易在参数填的时候犯迷糊。第二是最小可验证每个技能都能单独被调起来并验证输出不依赖其他技能先执行。比如“查订单”技能如果必须先知道用户ID才能查那这个拆法就有问题——你应该把“根据会话上下文解析用户ID”也做成独立技能让查订单技能只负责任务本身。2.2 技能命名让模型一眼看懂技能名的好坏事关重大因为模型是靠名字和描述来选择调用哪个技能的。我见过最经典的翻车案例是把一个功能命名为workspace_v3模型完全看不出来这是干嘛的自然也就不会调用。好的技能名应该是“动词名词”的直白结构比如search_user、create_ticket、check_order_status。命名的另一个细节是加别名。不同模型对中英文的敏感度不一样同一个技能最好在定义里把常见叫法都列出来比如查订单技能带上get_order、order_query、查询订单这类别名。这能显著减少模型“明明有技能却不用”的情况。技能描述文本也不能敷衍。要从使用者的角度写什么时候用、什么时候不要用、使用前需要知道什么。我习惯用这个模板触发场景当用户提到哪些关键词或意图时必须调用。不适用场景哪些情况千万别调用避免模型误用。参数要求每个参数从哪里获取用户没说全时如何从会话上下文补全。行为约束这个技能只能做什么不能顺手做什么。这个描述就像给模型的产品说明书写得越清楚模型调用得越准。很多团队把命名做得很规范但描述三行完事结果模型该调的时候不调、不该调的时候乱调源头就在这儿。2.3 技能边界与权限控制技能不是越多越好。技能越多模型的选择成本就越高误调概率也会上升。我建议把技能做成目录结构按业务域分组模型先在组内做选择而不是在几十个扁平技能里大海捞针。安全边界则要前置到技能定义里。对于高风险技能比如删除数据、发送消息、修改核心配置必须在描述里加上明确的前置条件。我见过一个团队把“发送营销短信”和“查询用户”放进同一个技能结果业务方想查一下客户信息技能顺手就给对方发了条营销短信用户投诉直接炸了。技能边界一定要做到“最小暴露”底层API也许能查所有数据但技能层只允许模型查当前登录用户自己的数据返回结果也要裁剪不能把整条数据库记录都丢给模型。模型上下文很金贵给一堆用不上的字段既浪费token又干扰判断。3. 从定义到注册让技能真正可被调用3.1 技能描述文件的核心字段技能定义最终要落成一个结构化文件我一般用YAML写核心字段是这些name: check_order_status description: | 根据订单号查询订单的当前状态、物流信息和商品摘要。 当用户询问“我的订单到哪了”“发货没有”“订单状态”时使用。 不要用这个技能修改订单内容不要用这个技能查询其他用户的订单。 params: - name: order_id type: string required: true description: 用户提供的订单号如果用户只给了一部分尝试从会话上下文补全 - name: user_id type: string required: false description: 用户编号用于权限校验可从会话上下文中获取 returns: - field: status type: string enum: [pending, shipped, delivered, cancelled] - field: tracking type: array description: 物流轨迹数组可能为空 - field: summary type: string description: 商品摘要一句话描述方便模型转述给用户 version: 1.2.0 examples: - input: {order_id: SO202407001, user_id: U123} output: {status: shipped, tracking: [{time: 2024-07-01 10:00, location: 转运中心}], summary: 手机一部}这里最容易被忽略的是examples字段。它等于给模型“开卷考试”的答案样式模型会模仿示例中的返回结构来组织自己的回答。没有示例的技能描述是抽象的模型拿到返回结果后不知道该怎么转述经常会漏信息或者自创格式。我给每个技能至少配两个示例一个是常规情况一个是边界情况比如空结果。3.2 技能注册与发现机制技能定义好之后要有一个“注册中心”让模型在运行时能发现它们。最简单的做法是把所有技能描述放进系统提示词但这条路走不远——每个技能描述大约占200到500个token五十个技能全塞进去上下文就快挤爆了主对话的质量会明显下降。更实用的机制是分组加载。先有一个意图路由层把用户请求分到对应的技能组比如订单域、用户域、售后域然后只把该组内的技能描述加载到上下文中。这就像逛超市你不需要把整个超市的商品摆在面前只需要知道哪个分区卖什么走到对应区域再挑就行。注册中心还应该承担“健康检查”的职责。技能文件提交上来时要校验参数格式是否完整、描述里有没有歧义、示例输出是否符合返回结构定义。我在一个项目里就吃过亏一个技能描述里的示例输出和实际代码返回结构不一致模型照着示例格式去读结果读不到字段就自己编数据那个功能的准确率一度惨不忍睹最后查了三天才发现是示例和实现脱节。3.3 技能背后的实现形态技能描述只是“说明书”真正干活的是技能背后的可执行程序。常见形态有三种HTTP API、内部函数、外部插件。最省事的落地方式是把团队已有的系统接口包一层技能描述不用重写业务逻辑。但有一个原则必须守住不要把所有底层接口原样暴露给模型。底层接口往往权限颗粒度很粗直接暴露相当于把整个系统后门交给模型。技能层要做一次收敛包括权限校验、参数白名单、返回值裁剪缺一不可。我给一个内部函数形态的示意def check_order_status(params, context): user_id context.get(user_id) order_id params[order_id] # 权限校验只能查自己的订单 if not can_access(user_id, order_id): return {error: PERMISSION_DENIED} result order_api.query(order_idorder_id) # 返回值裁剪只给模型需要的信息 return { status: result.status, tracking: result.tracking_summary, summary: f{result.product_name} x {result.quantity}, }这段代码里有几个细节是反复调整之后才确定的。权限校验必须放在最前面不能等结果出来再判断否则等于把数据读出来又扔掉浪费时间和资源。返回值绝不能直接返回整个数据库记录只挑模型需要转述的字段。裁剪后的返回结构要和技能描述里的returns字段完全对应否则模型会迷失在字段的海洋里。4. 技能编排把单个技能串成完整闭环4.1 为什么需要编排层单个技能是零件完整解决方案是流水线。一个“退款助手”背后通常是这样一串动作查订单 → 校验是否可退 → 计算退款金额 → 执行退款 → 发送结果通知。如果只把五个技能扔给模型让它自己排顺序每次结果都可能不同这在实际业务里是不可接受的。所以需要编排层。实现方式有两种一种是在系统提示词里写明“请依次执行以下步骤”另一种是用流程编排器把步骤固化成工作流。我个人的判断标准是低频、变化多、需要灵活理解的场景用提示词编排让模型自主安排高频、固定步骤的业务场景必须用工作流编排把顺序固定下来。编排层的价值在于可回滚、可监控、可人为干预。模型自主安排步骤一旦出错你很难复现当时它的思路但工作流编排每一步都有日志每一步都可以单独暂停、重试、人工修正这在真实业务里是底线要求。4.2 两种编排方式的取舍提示词编排和工作流编排不是互斥的它们可以同时存在于一个系统里。我用一个表格梳理两者的适用边界对比维度提示词编排工作流编排灵活性高模型可以随机应变低严格按既定顺序执行稳定性不稳定同一场景可能有不同路径高每次执行路径一致调试难度难无法精确复现每次决策易每步有日志可查适用场景开放任务、异常处理、多分支决策核心业务链路、固定流程、高合规场景工作流编排的示意可以是这样的steps: - skill: check_order_status - skill: check_refundable condition: order_status shipped - skill: calc_refund_amount - skill: confirm_refund require_user_confirmation: true - skill: notify_refund_result固定流程可以减少模型幻觉但也会降低灵活应变的能力。平衡点在哪我的做法是把“必须严格”的步骤固化成工作流把“可以自由发挥”的步骤交给模型比如给用户解释退款原因的部分用提示词让模型基于结果自然生成不固定模板。4.3 编排中的异常处理这一步是最容易被忽略的但恰恰是真实线上最值钱的部分。我总结了三个高频异常场景和对应的处理方案。第一个是技能返回空结果。模型在拿到空结果时常常会为了“完成任务”而编造信息。解决办法是让技能出参包含明确的状态字段比如status为NOT_FOUND或EMPTY模型拿到这个状态只能走“未找到”话术分支不允许自由发挥。第二个是多个技能同时满足触发条件。如果不做干预模型会随机乱选。解决办法是在技能描述中加入优先级说明比如明确写“当用户同时需要退换货和维修时优先使用售后处理技能再根据用户反馈决定是否调用维修技能”或者在技能定义里加一个priority字段。第三个是编排中某个技能超时。解决方案是给每次技能调用设置超时时间同时在技能描述里注明“如果该技能返回超时请告知用户系统繁忙不要自动重试超过一次”。没有这句约束模型会在技能超时后反复自我重试把整个链路的耗时拉到不可接受。5. 技能评估与迭代数据驱动的技能运营5.1 评估指标怎么定技能建好只是开始后续运营才是决定效果的关键。基础指标包括调用成功率、参数填充完整率、平均调用次数、任务完成率。但更关键的指标是“模型是否在正确场景调用正确技能”也就是技能选择的准确率与误调率。我习惯用混淆矩阵的思路来看该调的时候调了这是真正能完成任务的基础。不该调的时候也调了这是最需要警惕的误用往往说明技能描述有歧义或者技能边界与其他技能重叠。该调的时候没调说明模型根本没发现这个技能的存在大概率是描述里的触发场景没覆盖到实际用户用语。在美国大片里常说的“可观测性”落实到技能体系里就是日志。每次技能调用都要记录用户问题原文、模型选择的技能、参数填充情况、返回结果摘要、耗时。没有这些日志后面所有优化都是盲人摸象。5.2 建立技能评测集评测集是技能迭代的守护神。做法是从线上请求里抽一批真实对话人工标注出“应该调用的技能序列”形成回归用例集。每次有任何技能定义改动都跑一遍这个集合看技能调用序列是否仍然正确。我常用一个比较轻量的校验方式用脚本统计“技能序列准确匹配率”不要求模型的回答逐字一致只要技能调用的顺序和选择对得上就算通过。指标达到95%以上再上线达不到就回滚改动。这个阈值是我在实际项目里反复验证过的太低会让线上问题频发太高又会让迭代速度慢到无法接受。三个典型评测用例分享第一个是明显应该调用查订单技能的问题第二个是包含多个条件需要组合调用多个技能的问题第三个是模糊表述看模型是会主动追问澄清还是乱猜技能。第三种最能暴露技能描述的质量问题。5.3 技能迭代与灰度发布技能描述文件一定要纳入版本管理和代码一样走分支、评审、合并。改技能时要注意参数兼容性新增参数要设默认值删除参数要确认没有其他技能或工作流在依赖它否则会出现“旧工作流调用新技能时参数对不上”的诡异问题。线上技能的灰度发布也很有讲究。我建议先让新版本技能覆盖5%的请求对比旧版本的成功率和用户反馈观察一两天如果没有异常再逐步放开到30%、50%、100%。有一次我图省事直接全量上线结果新技能的参数校验逻辑写得太严一批线上请求直接失败排查了一个多小时才定位到问题。从那以后灰度发布就成了铁律。还有一条血泪经验不要在一个版本里同时改参数格式和触发条件。一旦出现问题你根本不知道是哪一个改动引起的。一次只改一个变量这是技能迭代最基本的原则。6. 常见问题与排查技巧实录6.1 模型总是调用错技能这是技能化落地后咨询率最高的问题。排查思路按顺序来先看技能描述是否足够精准再看技能边界是否和其他技能重叠。一个非常典型的场景是“查订单”和“查物流”经常混淆因为用户问题里“我的快递到哪了”既可能触发订单查询也可能触发物流查询。解决办法有两种要么把两个技能合并成一个让一个技能同时返回订单状态和物流信息要么在描述里写清楚边界——“查订单”只回答订单整体状态“查物流”才返回物流轨迹再在“查订单”的描述里明确加一句“当用户询问物流轨迹时不要使用本技能”。6.2 技能返回正确但模型回答错误这类问题最隐蔽因为日志里技能调用是成功的但用户侧看到的回答一塌糊涂。我排查过几次之后发现根因通常不是模型理解能力差而是返回结果的结构模型读不懂。比如技能返回一个嵌套很深的JSON模型根本不知道该取哪个字段来回答用户。解决办法有两个一是在技能描述里补充每个关键字段的含义尤其要说明哪些字段可以直接转述给用户二是在技能内部做一个“结果摘要函数”把复杂结果直接拼成一句大白话比如“您的订单已发货预计今天到达”让模型拿来就能用。6.3 上下文太长导致乱调用这个问题的典型特征是对话开场还好聊到第三四轮之后模型开始答非所问甚至调用完全无关的技能。大概率是技能描述被全量塞进了上下文占用了大量空间主对话信息被挤出去了。解决办法很简单技能描述做精简examples控制在一到两个同时严格落地分组加载机制不相关的技能组不加载。还有一个容易被忽视的细节——技能描述里的版本号、更新时间这类元信息不要放进去这些对模型没有任何意义纯粹浪费token。6.4 技能调试的最小化原则遇到技能问题我的第一步永远是把模型完全踢开直接调用技能本身输入固定参数检查它的输出是否符合预期。如果技能本身输出不稳定上层什么努力都是白费。先确认技能输出稳定再检查调用参数是否被正确传递最后才看模型对返回结果的理解对不对。这个顺序不能乱否则你会在模型幻觉的迷宫里绕很久。我最后分享一个小工具思路写一个“技能调用日报”脚本每天统计每个技能的调用次数、成功率、平均耗时、返回空结果的比例。这个脚本不需要多复杂但价值极大。它能直观暴露两类问题——有些技能天天被调用但成功率很低说明技能实现有bug有些技能从上线的第一天起就没被调用过说明它根本没有被模型发现或者已经和现有业务脱节。前者触发技能修复后者触发技能下线或描述重写。agent-skills这套思路实践下来我最大的体会有两点。第一它不是一个“银弹”真正困难的地方不是写技能描述文件而是把业务流程梳理清楚。如果业务团队自己都说不清“什么情况下该做什么”那就别指望模型能替你搞清楚。第二别追求一步到位的全自动务实一点先让技能辅助人的决策把单个技能的稳定性做扎实再考虑让技能串成完整的自动化链路。技能化不是给模型套上的枷锁而是给模型铺好的轨道——轨道铺得越清晰模型才能跑得越稳。