Agent技能化架构实战:从长Prompt到可复用技能库

📅 发布时间:2026/10/12 6:17:26
Agent技能化架构实战:从长Prompt到可复用技能库
前阵子我接手了一个客服场景的Agent项目一开始的写法很朴素把所有业务规则、角色人设、工具调用说明、话术模板全部塞进一段超长System Prompt里。前几次效果还行但业务一变整段Prompt就得重新擦写而且每次改动都提心吊胆因为根本说不清是改了哪句话才让效果变好或者变坏。后来我把这套系统推倒重来换成了以“技能skills”为核心的架构这就是agent-skills这个项目的由来。如果你也在做Agent大概率遇到过同样的问题模型不是不聪明而是我们把所有东西都混成一锅粥导致它不知道该在什么时机做什么。agent-skills的思路很简单——把Agent能执行的每一个动作、每一项知识调用都封装成一个独立的、可描述的、可测试的“技能”让模型像“挑工具”一样去选择合适的技能来完成目标。这篇文章我就从为什么需要技能化、技能体系怎么设计、如何从零搭建、怎么调试避坑、怎么扩展复用这五条线展开都是实际跑过之后的经验总结适合正在搭建业务型Agent的开发者、甲方项目经理以及想系统化理解Agent工作方式的产品同学。1. 为什么Agent需要“技能”而不是“指令”1.1 一段让我推翻重来的经历最早那个客服Agent我给它写了一个“全能提示词”你是一个客服助手你要热情、专业遇到退款问题要查看订单、遇到物流延迟要安抚用户、遇到库存不足要推荐替代品……每个规则我都写得很清楚但是模型的表现始终忽上忽下。有一次业务方要求把退货窗口从7天改成15天我改了四个关联段落结果用户问“能不能退”时模型又开始背诵旧的退货政策。问题出在哪不是因为模型笨而是因为“指令”本质上是一次性的、模糊的、上下文耦合的。模型没有一套可以独立选择、独立调用、独立验证的动作单元它只能靠对整段文本的理解力去猜该做什么。猜对了是运气猜不对才是常态。1.2 技能与指令的核心区别“技能”和“指令”听起来差不多但设计逻辑差别很大。我用一张表格来说明维度传统指令技能化存在形式散落在提示词中的文字描述独立的代码模块/配置单元触发方式模型根据上下文“领悟”模型通过技能描述主动选择调用可测试性几乎无法单独测试可为每个技能写独立用例可复用性换个场景基本作废可跨项目打包复用可扩展性新增规则需重写大段提示词新增技能不影响既有能力技能化的本质是把“大模型即时推理”和“确定性执行”分开。模型负责理解用户意图、做判断、选技能技能负责把决策落到可执行的动作上。这样既保留了模型的灵活度又获得了确定性系统的稳定性。1.3 技能化的直接收益技能化之后我在这个客服项目里立刻看到了三个变化。第一是可测试每个技能都能单独喂测试数据验证比如“查询订单状态”这个技能我可以准备20条不同表述的用户问题看模型能否正确选择并解析出参数。第二是可组合一个“处理退货”技能可以内部依次调用“查询订单”“校验退货资格”“生成退货单”三个子技能逻辑清晰。第三是可追溯每次调用技能都有日志模型为什么选了这个技能、参数是什么、结果对不对一目了然出了事故排障也要快得多。一句话总结技能化就是给Agent建一本“菜单”模型不用背诵整个菜谱只需要根据现状选菜下单。2. 一套技能体系应该包含的核心模块技能化不是简单地把函数列出名字真正能跑起来、能维护的技能体系至少要包含四个核心模块技能描述、技能实现、技能注册与发现、技能评估与沙箱。少了任何一个后面都会踩大坑。2.1 技能描述Agent怎么知道该调用你技能描述Manifest是技能体系里最容易做糊弄的部分但它恰恰是最关键的。模型是靠描述做“工具选择”的一句话描述或者一份结构化的Schema直接决定了模型在正确场景下能不能想起这个技能。一个合格描述要包含技能名称、一句话说明、适用场景列举、输入参数定义、输出结构约束。我通常用一个统一的YAML结构来定义技能描述name: check_return_eligibility description: 校验订单是否满足退货条件适用于用户咨询退货资格、退货期限、订单是否在可退范围内。 params: order_id: type: string required: true description: 用户订单号一般由用户在对话中提供 apply_date: type: string required: false description: 申请日期默认取当天 returns: eligible: type: boolean description: 是否可退 reason: type: string description: 不可退时的原因说明这份描述里的“适用场景列举”非常重要它相当于给模型划定了这个技能的使用边界。很多团队只写一句话“校验退货资格”模型在遇到退货期限、退货费用、特殊商品等衍生问题时就会犹豫或者干脆瞎选别的技能。2.2 技能实现层真正执行动作的代码技能描述是“说明书”技能实现层才是“执行的手”。实现层就是普通的Python函数遵循一个约定输入是参数对象输出是结构化JSON。函数内部可以做任何事——查数据库、调第三方接口、算折扣、组装话术但对上层只暴露沙箱化的入参和出参。def check_return_eligibility(order_id: str, apply_date: str) - dict: order get_order(order_id) if not order: return {eligible: False, reason: 订单不存在} days (parse_date(apply_date) - order.paid_at).days if days order.max_return_days: return {eligible: False, reason: f已超过退货期限{order.max_return_days}天} return {eligible: True, reason: }这里有个容易被忽略的原则函数签名就是技能边界。不要在技能函数里做超出描述范围的事情比如“校验退货资格”的技能就不要顺手把退款金额算了那应该让另一个技能去做。边界越干净模型越好选择测试也越好写。2.3 技能注册与发现机制技能不是写了一个个函数就完事了还需要一套注册机制让系统知道“我有哪些技能可用”。注册表负责技能描述的统一维护、版本管理、参数校验规则登记和启用/停用开关。运行时发现机制则负责在每一轮对话中把当前可用的技能描述列表打包喂给模型供其选择。我习惯的做法是写一个轻量加载器启动时扫描技能目录建立名称到实现函数的映射def load_skills(skills_dir: str): skills {} for manifest_file in Path(skills_dir).glob(**/skill.yaml): manifest yaml.safe_load(manifest_file.read_text()) module_path manifest.pop(module) func import_module(module_path).execute skills[manifest[name]] Skill(manifestmanifest, executorfunc) return skills注册表是技能的“户口簿”让每个技能都有归属、有版本、有状态。没有注册表的技能库跑不了多久就会变成一屋子找不到东西的仓库。2.4 技能评估与沙箱最后一个核心模块是评估与沙箱这是很多项目草草略过的部分。每个技能除了实现代码还要配一份测试用例文件覆盖典型场景、边界场景和异常场景。比如“校验退货资格”的测试用例至少要有订单不存在、已过退货期、特殊商品不可退、正常可退等。测试的重点不仅是功能正确性也包括“模型是否能在正确场景选择这个技能”的选择正确率。沙箱的概念则更严格技能运行时必须有独立的执行环境不能直接改生产数据不能访问无关系统。落地做法有两种轻量级的是在函数层做权限拦截和参数校验重量级的是把技能部署到独立容器。我一般建议先做函数层沙箱等技能数量多了再升级到容器隔离。3. 从零搭建一套Agent技能库的实操步骤有了整体认知下面讲讲怎么从一个业务需求出发亲手搭出一套技能库。别急着写代码先做场景拆解再做技能定义最后才到编码和注册。3.1 先定义你自己的技能边界拿到一个业务场景第一件事不是敲键盘而是把所有高频动作列出来。我以一个典型的“订单售后”场景为例先列出潜在动作业务动作是否拆成技能理由查看用户订单信息是查询类频率极高参数清晰校验退货资格是规则独立且经常变更生成退货单是写操作需要严格校验计算退款金额是涉及多种计价规则修改收货地址是独立动作可单独复用发送安抚话术否属于生成式回复让模型直接写转人工客服是有明确条件的分流动作拆分的粒度判断标准很简单这个动作是否可以被单独描述清楚是否需要独立的参数和返回结构是否可能在多个流程中被复用如果答案都是“是”就值得拆成技能。反之如果它只是另一个技能里的一小步那先不拆等它被复用两次以上再拆。3.2 写好一份让模型“不犹豫”的技能描述技能描述写得好不好直接看两个指标在正确场景下模型能否稳定选中在相似场景下模型能否准确排除。我见过最典型的问题是把描述写得太泛例如“该技能处理所有与订单相关的问题”这种描述等于没有描述因为它把本来就该由其他技能分担的场景全部模糊化了。好的写法是“正向条件 负向条件”结合。举个退款相关的描述对比差查询订单信息。适用所有需要了解订单的时候。好查询订单基础状态信息。适用于用户询问订单物流、金额、商品清单、下单时间。如果用户询问退货资格、退款金额请改用check_return_eligibility、calc_refund_amount技能。加了负向条件之后模型的选择准确率会显著提升。原因很好理解LLM做工具选择本质上是一个文本匹配任务你给它更多的“不要调用”信号它就不会在相似场景里纠结。3.3 技能注册表与自动发现有了技能定义接下来需要一套注册与发现机制让Agent运行时知道该轮对话可以调用哪些技能。我倾向于用一份集中式注册表再配合自动扫描目录。集中式注册表的好处是业务方可以直观看到当前有哪些技能、每个技能启没启用、版本是多少。我的注册表长这样skills: - name: check_return_eligibility enabled: true version: 1.2.0 module: order_skills.check_return - name: calc_refund_amount enabled: true version: 1.0.3 module: order_skills.calc_refund运行时的发现机制则是每一轮对话系统根据当前业务上下文从注册表里选出候选技能集合再把候选技能的描述合并进用户的本次请求等待模型选择调用。注意不要每次都把全部技能喂给模型——技能数量超过20个时模型的选择准确率会明显下降所以需要一层“粗筛”先按业务场景缩小候选范围。3.4 冷启动时的人工模拟测试技能库搭好初期大概率还没有真实业务流量这时候不能干等可以用“模拟器客户端”把技能调用链完整跑一遍。模拟器的套路是准备一批模拟对话记录每一轮都让模型走完整的“意图识别→选技能→传参→执行→生成回复”链路然后将技能选择结果与人工标注结果比对。我自己常用的做法是写一个简单的回放脚本喂入用户消息和当时的候选技能列表记录模型选中的技能名称再跟标注结果比对自动产出选择准确率。这一步能帮你在上线前发现大量“技能边界不清晰”“描述太相似导致混淆”的问题成本远低于上线后修事故。4. 让技能真正可靠调试策略与避坑经验4.1 最常见的坑模型选了一个“看起来对”但语义不符的技能技能化之后最常遇到的故障不是代码报错而是“技能选择错误”——模型调用了一个名称语义沾边、但实际不该用的技能。比如用户问“能退多少钱”模型去调“查询订单信息”虽然拿到了订单却没有算退款金额导致最后答非所问。我排查这类问题的第一步是把用户原话和当时的技能候选列表一起打出来看模型是怎么“误解”的。多数情况下问题出在技能描述里缺少“可替换选项的对比信号”。解决手段就是在那份“好描述”里加负向条件如果用户询问退款金额请调用calc_refund_amount不要调用本技能。另一个太容易犯的错误是两个技能描述高度相似比如“查询订单金额”和“计算退款金额”表面看都跟钱有关但一个是读数据一个是按规则计算。我会专门做一张“技能冲突矩阵”把语义相近的技能两两列出写明区别点再把这些区别点各写进各自的描述中。这方法笨但非常有效。4.2 参数校验与安全边界模型传参不可信这是一个铁律。模型可能会把“去年”解析成奇怪的年月日也可能会漏传订单号甚至会把用户的一句牢骚当参数传进技能。因此每个技能入口都需要做严格的参数校验不能直接把模型返回的参数透传给数据库或接口。我用JSON Schema做统一校验这比手写一堆if判断要规范得多from jsonschema import validate schema { type: object, properties: { order_id: {type: string, minLength: 8}, apply_date: {type: string, format: date} }, required: [order_id] } validate(instanceparams, schemaschema)校验不通过时技能系统应该返回一个明确错误提示模型“参数不完整需要补充订单号”让模型有机会进行澄清追问。这比直接把异常抛出去要好因为对话场景里一次合格的追问就能化解掉参数缺失问题。4.3 技能的可观测性每一次调用都要留下痕迹技能体系的迭代完全依赖数据没有调用日志就没有优化依据。我要求每个技能调用都必须记录技能名称、入参、出参、执行时长、调用的模型名称和版本、token消耗、用户原始消息、模型选择该技能时的推理理由如果可获取。这些数据沉淀下来是后续做评估、做回归测试、做描述优化的原料库。我的日志采用统一结构log_entry { skill: check_return_eligibility, params: params, result: result, latency_ms: 218, model: cloud-llm-v2, user_msg: 我上月买的东西还能退吗, }没有这套日志当你发现某个技能选择率下降时会完全无从下手。有了日志你可以定位是描述歧义、是参数质量问题、还是模型更新导致的选择偏好变化。4.4 技能回归测试与版本管理技能改了一版描述或者改了一段逻辑有没有可能影响其他技能答案是可能而且经常是悄无声息的影响。所以我给技能库配了一套轻量回归测试每次改动后跑一遍历史标注集看技能选择准确率是否下降、执行成功率是否变化。这个回归集不用很大几百条覆盖主要场景的记录就足够发现退化。版本管理上我遵循一条原则技能描述和技能代码一起打版本不允许“代码升级了、描述没更新”或反过来。因为技能描述是模型选择它的依据如果描述与实现不一致模型就会照着旧说明书用新工具出问题只是时间问题。5. 技能复用的实战扩展5.1 从一个项目到多个项目技能打包与分发技能化的另一个隐藏红利是跨项目复用。我在客服项目里沉淀的“订单查询”“资格校验”等技能到了另一个售前咨询项目里直接打包成独立技能包接入使用省去了大量重复开发。打包的思路是技能目录是一个独立Python包内含技能描述文件、实现模块、测试用例、README说明。分发则通过私有制品库如内部pip源或镜像仓库进行各项目按需安装运行时通过注册表注册即可。关键是“技能包版本”和“依赖的Agent框架版本”要绑定避免接口不一致。5.2 把技能组合成新的能力技能除了单个调用还可以编排成更复杂的工作流。比如一个“退货全流程处理”高级技能内部依次调用“查订单”“校验资格”“算退款金额”“生成退货单”“发通知”五个子技能。这种组合编排可以在代码层实现用简单管线即可def process_return(order_id: str, apply_date: str) - dict: order check_return_eligibility(order_id, apply_date) if not order[eligible]: return {status: rejected, reason: order[reason]} refund calc_refund_amount(order_id) ticket create_return_order(order_id, refund[amount]) notify_customer(order_id, ticket[no]) return {status: done, refund: refund[amount]}组合技能的收益是让Agent平台的能力从“能执行单一动作”升级为“能完成完整业务需求”同时仍然保留每个子技能的独立复用性。模型只需要决定调用“退货全流程处理”这一高层技能细节由编排层接管既降低了误召风险也减少了token开销。5.3 给技能加“自我改进”钩子把每次技能调用的反馈数据收集起来还能形成一条自我改进链路。最实用的做法是给每个技能定义“成功标准”查询类技能看“模型选择正确率”写操作类技能看“业务执行成功率和用户后续反馈”。每天用这些指标生成报表发现哪个技能突然变差就把它策略性地从候选列表里摘除修复后再放回而不是让它在线上持续带病运行。更进一步的“自动改进”方案是在历史反馈数据上定期重新评估技能描述——用新的描述版本在旧数据集上做离线选择测试指标提升才允许上线。这个流程目前我还没有做到全自动但手工版本已经能带来明显的稳定效果逻辑上更清晰兜底策略更可靠。写在最后我最直观的感受是模型永远会更新但技能库是你在模型之上沉淀下来的最有价值的稳定资产。把业务的确定性部分用技能固定住把开放式的判断交给模型这种分工已经成了我做Agent项目的基本盘。最后分享一个小技巧每写一个新技能先写一份它“在什么场景下不该被调用”的说明再写正常描述先划清禁区再扩大领地这个技能在复杂场景里才能扛得住考验。