资讯动态

Agent技能库设计:把零散提示词收编成可测试、可复用的能力模块

发布时间:2026/10/21 18:30:28 来源:尧图企业网站定制
很多刚开始做Agent的人都会踩同一个坑Demo里跑得好好的一上真实任务就翻车。我在整理一套内部代号为agent-skills的项目时最初的实现方式异常朴素——把所有判断都写在提示词和流程分支里一个任务只要稍微复杂点模型的表现就开始忽上忽下。后来我把项目里几十段零散提示词全部翻出来发现真正的病根不是模型而是我把“技能”和“普通对话”混在了一起模型不知道该在什么时候调用什么能力调用完也不知道下一步该等谁。agent-skills这个项目的目标其实很直白把大模型能执行的动作封装成一个个独立、可描述、可测试的技能模块。每个技能有固定的声明文件、实现代码、参数定义和测试用例Agent在运行时会根据任务上下文从技能库里挑选、加载、执行。把零散Prompt收编成结构化技能之后我那套跨平台系统的任务成功率和稳定性都有了明显提升后来很长一段时间的迭代都是在这个框架上继续加技能。这篇文章不打算讲太悬的理论更多是把我在整理agent-skills时踩过的坑和沉淀下来的做法交代清楚。适合正在搭Agent框架、做办公自动化、搞智能客服或写个人助理这类项目的朋友也适合那种看过一堆概念文章、但不知道该怎么落到代码里的同学。你可以按章节顺序读也可以直接跳到翻车模式那部分对比一下自己有没有中招。1. 为什么每套Agent系统都该有自己的“技能库”1.1 硬编码Prompt时期的失控现场最开始所有人都是拿大模型当“超级if-else”用。我也一样把某办公自动化场景下的所有分支写进一大段系统Prompt里面塞满“如果你遇到A情况就执行X遇到B情况就执行Y”之类的规则。表面上是给模型列了操作手册实际跑起来会发现模型对规则的解释经常超纲。我印象最深的一次是“安排会议”的Prompt。我在里面加了一句“如果对方第一天没有回复就自动发送提醒”初衷是想减少人工跟进。结果那次改动上线之后模型在所有对话里都倾向于自动发提醒连用户发来一条普通问候也被当成“可能要开会”。这已经不是提示词工程能救的问题了——规则一旦多了模型对优先级的判断就会乱而修改其中一条规则往往会连带影响其他看似无关的行为。硬编码Prompt的另一个痛点是没法量化。某段提示词到底起了多大作用它是否稳定触发了目标动作这些我都看不出。系统里只有对话日志没有“调用了什么能力、参数是什么、返回了什么”的痕迹。一旦输出结果不对唯一的排查手段就是反复改Prompt试来试去成本高且不可复现。1.2 技能、工具、插件的边界别再叫混了在动手整理agent-skills之前我花了很长时间区分几个容易混淆的概念。这不是抠字眼而是不同叫法对应完全不同的设计方式。工具是最小的原子单元通常是一个无状态函数模型通过函数名直接调用比如“获取当前天气”“发送邮件”。插件则偏向外部系统集成往往带配置和生命周期比如给IM平台装一个能收发消息的扩展。而技能更像一个完整的能力包它可能包含多个步骤、依赖底层工具、携带自己的参数Schema还有明确的适用边界和验证用例。类型抽象层次是否带状态典型例子工具单一函数无状态查询天气API、计算两个日期差插件外部系统集成可能有配置对接消息平台、连接文档系统技能完整目标能力可能包含内部状态根据紧急程度安排任务优先级Prompt片段自然语言指令无“请你根据公式估算周期”把这个边界理清之后我设计的技能大致遵循一个原则技能是工具的组合是对“一类目标”的完整封装。比如“安排任务优先级”底层可能调用了“读取项目字段”“查询截止时间”“计算工作量”三个工具但对外暴露给模型的只有技能本身。模型的决策单元从“该调哪个函数”变成了“该用哪项能力”语义粒度更匹配自然语言任务。1.3 从“工时估算”看技能化的收益用一个具体案例来说明。我项目里原本有一个“估算工期”的需求系统把项目信息塞进Prompt让模型根据一个公式自己算输出预计完成日期。公式写在提示词里模型的算术还不稳定经常算错。后来我把这个过程做成了第一个正式技能目录名就叫estimate_duration。技能内部封装了任务列表解析、工期计算、节假日剔除逻辑最终返回结构化结果。Agent运行时只需要调用这个技能然后把返回结果拼进回答。改造之后计算准确率几乎不再受模型推理能力影响因为真正的计算发生在确定性代码里模型只负责“判断该不该算”和“把结果翻译成自然语言”。这个例子的收益其实有三层第一行为可测了我可以通过构造测试输入来验证估算逻辑不再依赖某次对话运气第二能力可替换了底层算法想换掉不影响技能对外接口第三可组合了另一个“生成项目周报”的技能可以直接复用估算结果而不是再让模型算一遍。这些收益在技能多了之后会越来越明显因为每个新技能都是稳定积木。2. 决定技能生死的是那300字的描述文件2.1 描述里的第一件事不是“怎么用”而是“什么时候用”模型调用技能本质上是在做一次语义匹配先看用户任务再扫一遍技能列表找到最接近的描述然后决定是否调用。这个匹配过程依赖的就是技能描述。很多人的技能描述写成功能说明书只写“这个技能可以做什么、返回什么参数”结果模型对适用场景把握很差。什么时候该用、什么时候不该用这两个信息必须写清楚。我见过一个“查询项目进度”的技能描述里写“用于查询项目进度”于是用户问“项目为什么延期”时模型也会优先调它而不是先做根因分析。后来我把描述改成“用于查询项目当前的计划进度、实际进度以及偏差当用户想了解项目为什么会延期时请优先调用根因分析技能”调用准确率立刻上来了。一条很实用的经验描述要帮模型拒绝而不只是帮模型接受。在描述文件里明确列出不适合调用的场景哪怕只有一两句话也能避免大量误调用。2.2 技能描述模板字段越多失控越少我在agent-skills里给每个技能维护一份SKILL.md用YAML头部记录结构化字段。模板经历了多轮迭代目前固定下来这些内容--- name: estimate_duration description: 根据任务列表和团队历史数据估算项目预计完成日期 trigger_when: - 用户询问“大概什么时候能完成” - 用户希望判断某任务是否会延期 not_use_when: - 用户只询问当前日期或星期 - 用户需要人员排班/资源分配 output: 返回预计完成日期、置信区间、估算依据 parameters: tasks: type: array description: 任务列表每项包含任务名称和剩余工作量 start_date: type: string description: 开始日期格式YYYY-MM-DD default: today tags: [project, schedule] ---description字段是模型最先读到的内容要写得像一个具体场景下的决策规则而不是一个泛泛的能力罗列。trigger_when里的每一项都应该能用“当用户说X时”来造句。not_use_when则是反向拦截模型看到这些条件时会降低调用概率。output字段也容易被忽略。模型如果不知道技能会返回什么它可能调用完之后不敢直接把结果写进回答反而自己重新“推理”一遍导致返回值和最终答复不一致。把输出格式写清楚等于告诉模型“拿到了这个结构就直接引用它”。2.3 参数Schema宁可啰嗦不可含糊技能描述里最容易翻车的其实是参数定义。模型会依据参数描述来构造调用如果你的描述不精确模型就会用自己的“常识”来填空填出千奇百怪的值。举个真实例子。我早期给“任务优先级”参数只写了“优先级”没有限定取值。结果模型有时候填high有时候填1有时候填urgent把下游解析程序逼疯。后来我改成完整枚举约束{ priority: { type: string, enum: [p0, p1, p2], description: 任务优先级p0表示最高优先级p2表示最低 } }配合description里的语义说明之后参数解析错误率直接降为零。另一个心得是给每个参数配上示例值。模型对示例的敏感度非常高尤其当真实业务字段的格式复杂时一个“正确的示例”比十行说明文字有用。参数Schema的设计原则可以概括为用枚举限制取值范围用默认值兜底用示例值统一格式用说明文字解释业务语义。四者缺一不可。3. agent-skills的目录结构、命名规范与版本管理3.1 用目录结构把声明、实现、验证分开技能数量一旦超过十个文件摆放就会决定维护效率。agent-skills采用了一套极简的目录约定核心思路是把“描述”“实现”“验证”三者彻底分开skills/ estimate_duration/ SKILL.md impl.py requirements.txt tests/ test_impl.py golden_cases.json assets/ prompt_snippets.md summarize_thread/ SKILL.md impl.py requirements.txt tests/ test_impl.py golden_cases.jsonSKILL.md是给模型看的也是技能的唯一入口描述impl.py是确定性实现代码不掺任何模型推理逻辑tests目录下放单元测试和一组“金标用例”用来锁定技能的调用边界assets里可以放一些辅助材料比如提示词片段、示例脚本。这个结构的价值在于降低了“改一个技能”的心理负担。以前我改一段Prompt总要小心翼翼怕影响其他逻辑现在改动被自然隔离在技能目录里该技能的测试跑过就可以放心发布。3.2 命名规范动词开头全小写下划线技能命名是个容易被低估的工程点。模型在技能列表里做语义匹配时名称是最醒目的候选信息。我观察过几次实验把“duration_estimation”改成“estimate_duration”之后模型在相关查询上的调用率显著上升。原因是动词开头的名字更接近动作候选模型更容易把它判断为“可以执行的事”。命名规范我固定在三条动词开头全小写加下划线长度控制在4到28个字符之间。避免使用拼音缩写和生造的英文组合因为模型对真实词更能理解。技能名称同时也是代码里注册用的键名一旦发布就不能乱改所以前期命名要耐心。另外我建议名称里不带系统名、不带版本号。“project_estimate_duration_v3”这种名字会让模型困惑技能内部版本信息应该放在SKILL.md的版本字段里而不是揉进名称。3.3 技能版本和依赖小毛病都出在“我以为没变”技能也会演化参数格式变了、描述措辞改了、底层算法换了。如果版本管理靠“我记得改过”来维持迟早出事故。我给每个技能加了语义化版本号规则很简单参数不兼容或行为重大变化时升大版本只是描述微调或修复内部逻辑时升小版本纯文档更新则升补丁号。技能之间还可能出现依赖关系。比如“生成项目周报”技能依赖“estimate_duration”技能输出的估算结果。这种依赖不能靠约定要在技能的声明文件里显式写明依赖列表并在注册阶段做依赖检查。否则某个技能升级后下游技能拿到的字段格式对不上排查起来非常痛苦。依赖管理还有一个坑Python依赖冲突。每个技能是不是要装在自己的虚拟环境里取决于项目规模。我的做法是技能格外重视requirement锁定避免两个技能分别拉了不同版本的同名库导致运行时行为互搏。4. 注册、加载与运行时调度技能库的骨架4.1 注册表全局清单与按域注册Agent启动时首先需要一份技能注册表。我做过几个版本的注册实现最简单靠谱的还是用一个字典驱动SKILL_REGISTRY {} def register(skill: Skill): if skill.name in SKILL_REGISTRY: raise ValueError(fduplicated skill name: {skill.name}) SKILL_REGISTRY[skill.name] skill def get_skill(name: str) - Skill: return SKILL_REGISTRY.get(name)这层注册的价值在于启动阶段就能发现很多低级错误比如技能名称重复、声明文件缺失、参数Schema不是合法JSON。发现这类问题要直接抛错不要静默跳过否则Agent运行中突然找不到技能比启动失败更难排查。随着技能增多全局一张表会变得难以维护。我后来加了按域划分把技能分成project、communication、analytics等几个域模型调度时先根据任务类型缩小候选集。这不只是在组织代码也是在降低模型的决策难度——从40个技能里选一个比从120个里选一个准确率高得多。4.2 加载策略懒加载能省多少成本技能加载不是免费的。解析SKILL.md、导入依赖、初始化API客户端每一项都要时间。如果Agent启动时把所有技能全部加载一遍项目里的技能到四十多个时启动耗时已经变得不可接受。我改成了懒加载启动时只注册技能元信息和实现文件的路径真正被调用时才执行import。这样做之后单次任务的平均技能加载成本从“全部加载”降到了“只加载被选中的一两个”综合耗时省掉大半。代价是第一次调用某技能时会稍微卡一下我用自己的经验把单个技能的导入时间限制在一秒以内卡顿感基本可接受。懒加载还有个隐藏好处某个技能依赖的第三方库损坏时只要没人调用它主流程就不受影响。这在生产环境里很实用不至于因为一个冷门技能挂掉导致整个Agent不可用。4.3 调度策略让模型自选但要有护栏技能调度我经历过三个阶段。最早完全交给模型有什么技能列表就让它自己选结果模型经常选错后来加了规则前置某些明确场景由代码决定走哪个技能模型兜底处理模糊场景最后形成了现在的混合策略规则优先、模型兜底。规则优先很好理解。“用户明确点了某个按钮”或者“关键词命中某个固定动作”这种场景不需要模型判断直接路由到对应技能既快又准。模型负责的是开放式意图场景比如用户用各种句式表达“帮我看看项目什么时候做完”这时候让模型去做语义匹配。护栏是必须的。技能执行要统一套上超时控制、并发限制和调用次数上限。我给每个技能分配一个默认超时时间超时直接返回错误信息给模型而不是让调用挂在那里耗token。同时记录每次调用的trace_id这个ID会把用户请求、模型选择、技能执行、最终回答串成一条完整链路排查问题时的价值极高。5. 实测中躲不开的三类翻车模式5.1 误调用模型在不该用时用了技能Agent技能最经典的翻车是误调用。一个用户问“今天是周几”系统却调用了“安排日程”技能返回了一个日程清理动作然后模型一本正经地告诉用户已经安排好了完全答非所问。排查这类问题我有固定套路。第一步看日志从trace里找到模型选择了哪个技能。第二步对照用户意图这是不是技能描述里定义的触发场景。第三步检查技能描述确认是不是没有写排除条件。大多数误调用的根因都出在描述缺少not_use_when而不是模型“变笨了”。修复方式也简单往描述里加反例。比如“当用户只询问当前日期或时间时不要调用本技能”。加完反例之后我会在golden_cases里补充对应的反例用例防止后续改动时问题复发。5.2 空转技能执行了回答却没用上结果另一种翻车更隐蔽技能成功执行、返回了正确结果但模型在最终回答里完全没提这个结果甚至给出了相反结论。我把这种叫做空转——技能像是白干了。这类问题的根因通常有两个。一个是技能描述里没有说清楚返回结构的业务含义模型拿到一个JSON数组不知道应该直接引用还是重新解释。另一个是技能返回值过于“机器化”比如返回了一堆原始字段模型无法自然翻译。解决办法是让技能返回值带两层一层raw保留机器可用的原始数据一层summary是简明的自然语言结论。模型在生成回答时优先把summary转述给用户。这等于把“解码”工作前置到技能内部而不是留给不可控的模型推理。5.3 死循环同一个技能反复执行死循环是最烧钱的翻车。技能被调用后返回了一个参数校验错误Agent认为“任务没完成”于是不换参数、原封不动地再次调用同一个技能直到token耗尽或者超时被熔断。我排查过一次死循环发现根因很单一技能的错误返回不够细分。模型看到“调用失败”能理解的信息太少既不知道是参数错还是环境错也不知道该不该重试。我后来把所有技能错误统一成两种类型参数错误和环境错误。参数错误意味着无论重试多少次都不会成功Agent应该停止调用并向用户解释环境错误则允许有限重试。同时我在调度层加了单任务内同技能最大调用次数默认不超过3次。双重保险加下来死循环基本绝迹。5.4 一次完整的多技能协作故障排查最后看一个多技能协作翻车的完整排查链路。场景是用户要求在某项目里自动生成并发送周报结果Agent先调用了“生成周报”技能成功返回文本接着调用“更新日历”技能却因为权限不足失败Agent没有中断反而又重新调用“生成周报”技能最后给用户输出了一份旧数据的周报。按trace时间线复盘问题不在单个技能内部而在技能之间缺少数据契约。两个技能本来应该发生在同一个工作流里并且有严格的先后顺序更新日历失败后就应该终止整条链路并把失败原因告诉用户而不是让模型自作主张回退重试。修复时我把这几个技能组合成了一个上层工作流技能叫generate_weekly_report_workflow内部定义好执行顺序和失败中断规则。模型只需要调用一个入口技能整条链路的控制逻辑收归代码不再由模型自由发挥。从那之后这类跨技能协作故障率明显下降。6. 技能库的测试与回归让Agent的行为可复现6.1 从最小单元测试到技能级测试Agent系统的测试很容易被人忽视因为大家默认模型输出不可控。但技能库本身可以被测试而且必须被测试。至少两个层面要覆盖技能内部实现代码的单元测试以及技能对外接口的技能级测试。单元测试针对impl.py里的确定性逻辑比如参数解析、日期计算、结果格式化。这些逻辑不依赖模型测试起来和普通后端代码没有区别。技能级测试则是直接调用技能接口传入一组构造好的参数校验返回结构是否符合SKILL.md声明的output格式。这个阶段不需要真实模型参与只需要一个可编程的调用入口。我会在每个技能的tests目录下放尽量多的边界用例空参数、极端日期、非法优先级。因为这些边界最容易在模型构造参数时被撞到而模型构造出的参数往往比人类更像“刁钻测试”。6.2 用golden case锁住“该不该调用”技能测试里最有Agent特色的是一组用来锁调用边界的golden cases。语法很简单每个case就是一段用户问题加一个期望结果应该调用技能或者不应该调用技能。[ {input: 项目大概什么时候能做完, should_call: true}, {input: 帮我排一下下周的工作, should_call: false}, {input: 还剩多少工作量需要处理, should_call: true}, {input: 今天星期几, should_call: false} ]每次技能描述改动之后我都会离线跑一遍全部golden cases。方法是用一个模拟调度器把case里的用户问题、技能描述列表喂给模型看模型的选择与期望是否一致。这里的核心指标不只是“该调时调了”还有“不该调时没调”。后者往往更能暴露描述写的太宽的问题。这组case要随着真实对话不断扩充。每发现一个误调用或漏调用就把它沉淀成case形成一道回归防线。它是Agent技能可靠性提升最笨也最有效的方法。6.3 回归测试的自动化接入测试不能只靠手跑。技能库的CI我分了三档unit是纯代码单测耗时最短每次提交都跑skills是技能级测试和golden case调度测试耗时适中有技能文件改动时跑e2e是端到端测试会拉起完整Agent框架、真实模型和模拟服务成本最高只在关键技能改动或发布前跑。需要特别注意的是模型版本对测试的影响。同一个技能描述在不同版本的大模型上表现可能不同。所以回归报告里一定要记录模型版本号。技能描述改动在同一模型下生效不等价于在所有模型下都生效这点必须保持清醒。我还给技能加了“冒烟清单”里面覆盖最高频调用的十个技能和两条关键业务链路。发布前后各跑一遍冒烟能快速识别出“改了一个技能炸了另外一条链路”的连锁问题。7. agent-skills能走多远我的个人体会技能库消化到一定程度之后你会发现最难的不是写技能而是定义技能的边界。我的体会是技能应当“先窄后宽”。初期宁可让技能只管一个非常具体的场景也不要一上来就做一个“万能技能”。窄技能描述简洁、测试充分、模型容易理解等你积累了大量窄技能再通过上层工作流技能去组合它们效果远比一开始追求大而全要好。还想分享一个经验技能库本质上是团队约定不是纯技术产物。即使只有一个开发者在维护也要把技能命名、描述规范和测试要求写成文档。因为我吃过亏——一个月之后回来看自己写的技能已经完全搞不清当时为什么这么设计。规则、目录、版本记录都是写给未来自己看的备忘录。最后说三个实用小技巧。第一所有技能调用日志里一定要带trace_id并且把技能名、参数摘要、耗时、返回状态都记录下来排查问题的时候你就知道有多香。第二给每个技能加一个总开关某个技能在真实环境中表现不稳时可以先从注册表里禁用掉不阻塞主流程。第三如果某些技能长期不被模型选中你要考虑的不是加更多描述而是修剪技能数量。候选集越大模型的选择准确率越低精简技能库本身就是一种性能优化。Agent技能库这条路我走了很长时间踩过的坑基本都写在上面了。这套方法不一定适合所有场景但如果你正被“模型很聪明但系统很乱”的问题折磨不妨从最小一个技能开始把可控的部分彻底收归代码把不可控的语义判断留给模型。这样分工清楚了系统自然会稳下来。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑