拓冰建站拓冰建站
首页 / 资讯中心 / 正文

AI代理技能系统:从提示词失控到技能库的工程实践

过去半年里我在两个方向完全不同的项目里反复撞上同一堵墙大模型本身的能力看起来已经够了但一旦放进真实任务里掉链子的频率比想象中高得多。要么漏步骤要么把上一步的中间结果错误地带到下一步再要么上下文越滚越长最后整个代理像喝多了一样开始自说自话。后来我花了大约三周时间把所有能力点从“提示词里的一段描述”重构为独立维护的“技能包”把项目的核心代码从零散的工具函数改成了一套带元信息、注册表、调度策略的技能库。这个动作带来的效果非常直接任务完成率从不到六成提升到八成以上排错时间至少缩短了一半。今天想把这套“agent-skills”方案从设计思路到落地细节完整拆一遍给正在被“能聊但干不了活”困扰的朋友一个可以参照的实践路径。这套东西适合谁适合正在做AI代理、自动化工作流、或者是想把大模型接进业务系统的人。你不需要有很深的研究背景但需要动手写过一些业务代码并且对“提示词为什么不可控”有过切身感受。1. 为什么“会聊天”不等于“会干活”技能是代理能力的基础单元1.1 我观察到的两种失败模式先说我踩出来的经验。绝大多数AI代理项目做不起来不是模型选得不好也不是算力不够而是能力组织方式出了问题。常见的失败模式就两种。第一种可以叫“一把梭模式”。把所有操作步骤全写进一个巨大的系统提示词里让模型自己看着办。开头几百次调用可能还行但随着业务规则增多提示词膨胀到三四千字之后模型开始抓不住重点。你今天加了“优先用A接口”明天又加了“如果B接口返回空值就重试两次”模型就会在某个奇怪的地方突然反复走同一条死路怎么也出不来。第二种叫“工具爆炸模式”。每增加一个功能就挂一个函数进去工具列表越拖越长。模型每次决定调哪个工具都要在几十个函数签名之间做选择。根据我自己的统计工具超过15个之后模型的选型准确率会肉眼可见地下降。它会把“查天气”的工具当成“查询订单物流”来用因为两个函数的描述里都出现了“查询”这个词。这两种模式本质上是同一个问题模型被要求在一次推理里同时处理“怎么干”和“干什么”。当决策空间太大模型就一定会出错。agent-skills的核心思路就是把决策空间拆开——每个技能只负责一件具体的事代理只负责判断“现在该用哪个技能”不用关心那个技能内部是怎么实现的。1.2 技能、工具、提示词三者到底有什么区别很多文章把这三个概念混着用但它们的职责边界其实非常清晰我用自己的话重新定义一下。**提示词Prompt**是给模型的一段话目的是引导一次对话的走向。它没有状态、没有输入输出约定、无法被程序审计。你今天把提示词改动了三个字不影响代码运行但可能影响整个输出的质量而且这种影响很难被测试用例捕获。**工具Tool**对应的是一个可执行的函数比如“调用天气API”“写入数据库”。工具是程序的一部分但它和模型的交互只有一个入口模型生成一个包含函数名的JSON代码执行后把结果返回给模型。工具好用的前提是功能足够原子化一旦工具内部开始有分支判断、有条件重试问题就变得复杂了。**技能Skill**在两者之间加了一层技能是一个带明确目标的、自包含的能力单元。它包含一段“何时使用”的描述、一组允许调用的工具或脚本、一个输入输出约定甚至还可以带上它自己的小段提示词。代理在运行到某个节点时先扫描技能注册表找到匹配项然后才加载这个技能内部的任何内容。可以理解为提示词是随便写在便签上的话工具是锤子螺丝刀这类散装零件技能则是一个贴好标签的工具箱——上面写着“修水管用这个箱”里面装好了扳手、生料带和一句“先关总阀再动手”的提醒。代理不需要知道生料带怎么用它只需要知道现在这个场景应该搬哪个箱子过来。2. 技能包的最小完整结构一个可以直接照抄的骨架2.1 一条技能记录到底包含什么我在项目里给每个技能定义的元信息结构经历了三轮简化之后稳定成了下面这套字段。它不是标准但如果你还没想好怎么做直接照这个起步不会走偏。字段含义示例值name技能的唯一标识全局不可重复extract_invoice_amountdescription给模型看的“何时用我”的说明要写触发条件不要写实现细节当用户上传了发票图片或PDF且需要提取金额时使用input_schema输入参数的JSON Schema约束参数名和类型{ file_path: string, ocr_lang: optional string }instruction一段可选的技能内提示词控制执行风格或规则金额字段取第一个出现的数字忽略合计entrypoint实际执行入口可以是函数名、脚本路径、API端点scripts/run.pyversion技能版本号用于变更追踪和回滚1.2.0dependencies运行依赖清单可选[pypdf, paddleocr]这里最容易被忽略的是description字段。很多人的写法是“一个用于提取发票金额的功能”这种描述对模型来说毫无信息量。模型判断“要不要调用这个技能”依赖的不是你的函数怎么实现的而是场景描述。好的description应该回答一个问题什么情况下这段文字被用户提出来之后我应该想到这个技能。举个例子。如果我要注册一个“查询服务器状态”的技能弱描述是“获取服务器运行状态”强描述是“当用户提到服务器宕机、CPU过高、内存不足、接口超时或需要查看机器健康度时使用。如果用户只是问‘你今天怎么样’不要调用它。”第二句里的“如果……不要调用”是我在反复踩误触发坑之后加进去的。这种负向提示比正向提示更能压制模型的错误冲动后面第三节会展开讲。2.2 注册与发现技能不是写在某个文件里的常量技能要能被代理使用必须有一个注册和发现的过程。我的实现里维护一个skills.json作为注册索引每次启动时加载到内存扫描所有可用技能的基本信息。注意这里只加载元信息和description不加载技能内部的Instruction和脚本内容。这样做的好处是启动快、上下文干净代理在做最初决策时只需要读轻量级的“目录”而不是把每本书的正文都摆在面前。一个简化版的注册索引长这样{ skills: [ { name: extract_invoice_amount, description: 当用户上传发票图片或PDF且需要提取金额时使用, input_schema: { type: object, properties: { file_path: { type: string } }, required: [file_path] }, version: 1.2.0, entrypoint: scripts/run.py } ] }在代码里我会在代理每次运行任务开始前先调用一次“技能选择器”。它把用户输入和所有技能的description一起发给模型让模型输出一个最匹配的技能名列表。这一步我用的是独立的小模型或同一个主模型的轻量级调用避免占用主对话上下文。因为description和用户原话之间做的是“语义匹配”而不是“关键词匹配”所以这个环节对模型的指令理解能力有一定要求。实测下来使用同一套模型做选择器时把temperature调低到0.2左右比默认值稳定很多。选择器是一次性的、上下文很短的调用不需要像正式对话那样保留记忆。这一步看起来很不起眼但它决定了后面所有技能加载是否精准。3. 代理如何“选中”正确技能决策回路设计3.1 先看目录再看正文两阶段加载策略技能库搭好之后下一个关键问题就是代理在具体一轮任务里怎么决定用哪个技能。我最初踩过的坑是把所有技能的内部指令全部塞到系统提示词里。那个方案代码上最简单但很快出问题——五个技能各带两段内部指令系统提示词直接突破六千字模型开始敷衍只挑前后部分看。后来我改成了两阶段加载才真正把技能系统的优势发挥出来。第一阶段叫“目录扫描”。代理拿到用户请求后只向模型展示所有技能的description列表让模型输出候选技能名。模型不需要理解技能内部怎么操作只需要判断“哪个描述和当前用户意图最匹配”。这个阶段保持轻量响应速度很快token消耗也很低。第二阶段叫“正文注入”。代理根据候选技能名去加载对应技能包内的instruction、脚本和输入Schema把完整定义注入到当前对话上下文里然后才让主模型继续执行任务。这里要特别强调当一个技能被选中并执行完后它的内部指令就该从上下文里“退场”了。否则对后续无关的对话来说这些指令就是纯噪声。我在实现里给每个技能定义了一个“生命周期作用域”技能执行结束后自动把对应的instruction从消息列表里移除。这样做的收益是代理始终只用当下必要的信息做判断上下文消耗维持在低水平。3.2 调度策略规则优先模型兜底很多人以为技能选择应该完全交给模型判断但我做了几十次实验之后的结论是纯靠模型选技能在技能库规模变大之后仍然会出错。更稳的做法是“规则优先、模型兜底”的混合调度策略。规则优先的意思是先用程序判断一些“硬条件”。比如技能A的输入要求必须包含file_path如果用户当前没有提供任何文件路径那这个技能就不该被候选。技能B的description里声明了只处理PDF如果用户上传的是Excel直接排除。这些判断不需要模型参与写死在调度器里就行正确率100%。数学模型算不过来的部分才交给模型用户说“帮我把合同里甲乙双方的地址都抠出来”这句话没有明确的文件名、没有格式关键词但语义上指向了“合同信息抽取”技能。这时规则层没有直接命中条件就会退到模型选择器让模型在所有未排除的技能里做相似度匹配。这个混合调度的优势非常明显硬规则挡住了低级错误模型只做它擅长的高层语义判断。两者配合后我的项目里技能选择准确率稳定在了95%左右剩下5%的误差主要来自description写得不够好下一节会重点讲。4. 实战翻车记录误触发、上下文污染、技能退化4.1 误触发排查为什么技能总在不该用的时候被调用技能系统上线第一周我收到最多的反馈是“我没让它查天气它非要查天气”。我一度以为是模型脑子不对后来仔细查日志才发现问题出在我自己写的description上。当时get_weather技能的description写的是“用于获取某个城市当天及未来几天的天气信息”看起来没毛病。但用户在某次闲聊里说了一句“今天心情像外面的天一样阴沉”模型就触发了天气技能。原因很清楚description里有“天气”这个词模型在语义匹配时把“阴沉”和“天气”关联起来了。修正方式有两个要点。第一在description里强化触发条件的前置场景比如写明“仅当用户明确询问气温、降雨、风力等气象数据或明确提到城市名天气时才调用”。第二加入负向条件比如“如果用户只是在表达心情或文学修辞不要调用”。这类负向条件一开始我写得很不好意思觉得像是侮辱模型的智商。但实际跑下来负向条件对误触发的抑制效果出奇地好。“不要调用”三个字比任何强行推理都有效。仔细想想也合理模型在生成函数调用时本质上是在做一个多选一的选择题如果你不主动排除某些选项它总会倾向于选一个最像的而不是选一个“不选”。4.2 上下文污染技能执行完不等于可以卸载另一个高频问题更难排查我用了一整天定位才找到根因。场景是这样的技能A从PDF里提取了表格数据技能B负责对表格做可视化。用户先调用了A再调用了B。B执行的时候总是带出A技能里的一段“如果数据格式不合法请尝试二次清洗”的内部指令导致模型在B的图表里加入了根本不是用户要求的数据清洗逻辑。原因就是我在实现时把A技能的内部指令追加到了系统提示词里但A执行完后没有移除。这就导致B在决策时看到了A的指令以为那也是当前任务的一部分。这个问题在日志里极难发现因为错误不是报错而是“多干了一些多余的活”。后来我把每个技能的instruction放到独立的message对象里并在技能执行结束时标记过期调度器在下一轮模型调用前自动过滤掉过期的message才算彻底解决。这里我想给一条普适经验任何技能的内部内容都必须是“按需加载、用完即退”的临时上下文而不是常驻的系统提示词。常驻上下文每增加一百个字模型的注意力就会被稀释一分。你希望模型专注在当前任务上那就不要让无关信息占据它的工作记忆。4.3 技能退化改了一个技能影响了另一个技能的判断技能库还有一个隐性问题我称之为“技能退化”。表现是某个技能单独测的时候一切正常但放到完整技能列表里它被选中的概率越来越低或者调用参数经常出错。典型的诱因是技能description里用了和另一个技能相似的词汇。比如有两个技能一个叫parse_sales_report一个叫analyze_sales_trend。第一个的描述里有“读取销售报表文件中的数据”第二个的描述里有“基于销售报表历史数据产出趋势分析”。模型面对“帮我看看上个月的销售报表有什么问题”这句话时大概率会犹豫不决甚至可能选错。解决方式有两个方向。一是重构description强制让每个技能的描述和其他所有技能的同义词天然区分开。我在实践里要求每个技能描述里必须包含至少一个“独占触发词”比如parse_sales_report只写“读取并提取表内字段不做任何趋势判断”把“趋势”这个词完全让给另一个技能。二是给每个技能增加aliases数组明确列出可能混淆的词语并标注“这些词不属于本技能”。后者的效果很好但维护成本高只能用在核心高频技能上。低频技能之间偶尔混淆一次问题也不严重不值得为它付出额外的description维护精力。5. 技能库做大的组织方式命名空间、版本管理与评估闭环5.1 命名空间与分级别把所有技能平铺在一个目录里当技能数量超过30个之后平铺结构就开始让人头疼了。技能名前缀变得越来越长description也开始互相打架。我的做法是给技能引入命名空间和分级管理类似包管理工具的思路。技能分为三级core、domain、personal。core是通用能力比如文件读取、图片压缩、Markdown转HTML这些跨场景都能用的技能数量控制在15个以内。domain是业务领域的专用技能比如金融场景的“对账差异分析”、电商场景的“优惠券核销校验”每个域内技能只对特定任务可见。personal是单个用户或单个项目定制的私有技能不进入公共注册表只有显式指定时才可加载。这样做的好处不仅仅是整洁更重要的是缩小了每次选择器的候选范围。我可以在代理初始化时根据当前运行环境的业务域标签直接过滤掉不相关的domain和个人技能。候选范围从60个降到10个之后模型选择准确率的提升是立竿见影的。5.2 版本管理的细节技能回滚比代码回滚更复杂技能的版本管理和普通代码版本管理有一个本质差异技能的变更不只有代码层面的影响还会改变模型对触发条件的理解。也就是说同一个description微调几个字就可能让技能从“正常”变成“频繁误触发”。所以我在技能版本号上采用了语义化版本规范但额外约定了一条description字段的任何改动都必须至少升一个minor版本不能只改description就提交patch版本。因为description直接影响模型行为它属于接口变更不是bug修复。另外每次技能升级我都保留至少最近两个版本的注册索引快照。假如一个技能在v2.1.0被改坏了我可以一键切回v2.0.2但不能切回v1.x因为依赖它的旧调用方可能已经升级了新输入规范。这个“只回退一个版本”的策略在实践里很好用不会让你陷入版本地狱。5.3 评估闭环技能选得对不对得可以量化技能系统最大的优势就是可以做独立评估这一点比整段对话评测靠谱得多。我搭了一个很轻量的评测脚本每次技能库有变更时会自动跑一遍回归测试。测试集分三组正向用例明确应该命中技能A的输入、负向用例不应该命中任何技能测试负向条件是否有效、混淆用例两个相似技能的对决看模型是否选对。每组至少二十条全部写入一个evaluation_cases.json文件用模型判断或人工抽查来打标。跑评测时我只关心两个指标技能选择准确率选对了没有和无效触发率不需要技能时是否误触发了。每次改description或增删技能我都会把这两个指标的对比结果记录到变更备注里。如果某次改动让准确率掉了一个点就算功能本身正常我也不会发布这个版本。这套评估闭环执行了大概一个月效果已经超出预期。最大的收获是我再也不用靠“觉得模型好像变傻了”这种模糊感受来判断技能库的健康度了。6. 项目落地过程中的几个真实体会技能系统的设计思路讲完了最后分享几个落地过程中的真实体会用来收尾再合适不过。第一技能的粒度平衡是调出来的不是设计出来的。我一开始把“发票识别”设成一个大技能后来发现它内部其实包含了“图像预处理”“OCR识别”“字段抽取”“金额校验”四件事。模型经常因为想跳过预处理直接抽字段而出错。拆成四个小技能之后准确率反而提升了但调用链变长了。最终我选择了折中方案保留一个高层的“发票处理”技能内部通过状态机串联四个步骤对外只暴露一个入口。太细和太粗都有问题只能在自己的业务里反复试。第二描述文本的质量直接决定技能系统的上限。代码写得再漂亮依赖再干净如果description写得模糊模型该误触发还是误触发。我现在要求每个技能描述必须包含三句话什么时候用、什么时候不用、输入从哪里来。写不全这三句话的技能不允许进注册表。第三所有优化都要以评测数据为准不要靠感觉。我经常发现自己在“觉得模型变好了”和“觉得模型变傻了”之间反复横跳直到引入评测闭环之后才稳定下来。没有数据支撑的调优都是赌运气有数据支撑的调优才叫工程。如果你的代理项目也卡在“能力无法稳定复用”这个阶段可以试试从最小技能包开始把一个高频操作拆出来、注册好、跑通两条用例再慢慢扩展。技能系统不用一次设计到位但它值得在整个项目成型前就开始搭。等技能数量上了两位数你会庆幸当初做了这个决定。
分享:

看完干货,该让你的企业上线了

免费需求沟通 · 48 小时内出具建站方案 · 河南本地可上门