2026/10/20 21:14:32

给大语言模型配上Agent Skill:设计、实现与踩坑全解

给大语言模型配上Agent Skill:设计、实现与踩坑全解 给大语言模型配过 Agent 的人大概都遇到过这种尴尬提示词写得很漂亮角色设定也齐全可真让它查数据、发通知它就变成复读机只会说“我很抱歉无法完成”。问题不是模型笨是 Agent 缺了 Skill。这期 AI 核心知识就把 Agent Skill 讲清楚它是什么、怎么设计、怎么实现、有哪些坑。先说结论想让大模型真正“干活”不能只靠提示词给它洗脑而要给它一套可以被程序真实执行的能力组件这就是 Agent Skill 的定位。下面这些内容适合两类人一类是想把大模型接进自己业务的开发者另一类是刚接触智能体、想知道“工具调用到底怎么工作”的初学者。我会尽量不堆术语用大白话把原理和实操都说透。1. 为什么给大模型配了提示词它还是“不会干活”1.1 提示词改变的是“话术”改变不了“动作”一切要从大语言模型的本性说起。这门技术本质上是在做“文本接龙”训练目标是根据上文预测下一个字你给它一串 prompt它返还一段看起来合理的文字仅此而已。所以当你问它“今天适合穿什么”这种需要实时信息的问题时它会一本正经地编一个天气因为它的知识里根本没有“今天”这个概念。有人会想那我用提示词告诉它“不要编造不知道就说不知道”不就行了实际效果是它确实会少编一点但遇到真正需要外部信息或操作的任务比如查订单、写数据库、发通知模型依然做不到因为它没有手它的输出只能是文字。这里有个很恰当的类比一个刚入职的实习生你给他讲了一大堆岗位职责和注意事项相当于写了很长的提示词他说话很有礼貌、逻辑也很清晰但让他去系统里导一份报表他做不了因为没人给他开系统权限也没人教他具体操作。提示词改变的是“话术”Skill 提供的才是“动作”。1.2 Agent Skill 到底是什么那 Agent Skill 究竟指什么简单说它是给大语言模型智能体准备的一个“可复用能力包”这个能力包里通常包含三样东西什么情况下使用、需要哪些参数、具体如何执行。你可以把它理解成工具箱里插着的一把螺丝刀箱子旁边贴着说明卡用来拧十字螺丝刀头直径 3mm。当模型意识到当前任务需要外部信息或操作时它不会自己去执行而是输出一个结构化的“调用请求”把控制权交给程序。程序根据请求找到对应的 Skill 逻辑并执行再把结果交回给模型模型基于真实结果组织语言回复用户。“模型负责决策和表达程序负责执行和拿数据”这句话是理解 Agent Skill 的关键也是后面所有设计原则的基础。1.3 Skill、工具调用、插件、工作流到底什么关系很多刚接触这块的朋友会被几个概念绕晕。我用一张表格把区别捋清楚概念本质典型例子函数调用 / 工具调用大模型输出结构化参数程序去执行模型说“调用 get_weather参数 city101010100”Skill技能一个完整的能力单元包含触发条件、参数契约、执行逻辑、返回处理查天气、查订单、生成报表、发通知插件一组 Skill 的集合往往还带界面或配置入口一个“数据查询插件”里包含查询数据库、导出 CSV、生成图表三个 Skill工作流多个 Skill 按一定顺序编排成一个流程写周报流程先拉取工时数据再汇总项目进度最后生成 Markdown所以你可以这么记Skill 是最小能力单元插件是收纳盒工作流是流水线工具调用是技能与模型之间的通信协议。实际项目里我更喜欢先以 Skill 为单位做设计和测试稳一个再加一个比一次性堆十个功能出来更可控。2. Agent Skill 的核心构成一次技能调用的完整生命周期2.1 三段式触发条件、参数契约、执行逻辑一个标准的 Skill无论用什么框架实现都有三段结构。第一段是触发条件也就是在技能描述里对模型讲清楚“什么时候该用我”。你可以在描述里写“当用户询问天气信息时使用包括当前天气、未来几天温度、降雨概率等”。模型会拿这段描述来判断是否触发。描述越明确触发越稳定。第二段是参数契约目前主流大模型基本都支持工具调用机制参数用 JSON Schema 描述告诉模型这个技能需要什么字段、字段类型、是否必填、有哪些可选值。参数契约写得越清楚模型给出的参数越规整。第三段是执行逻辑这是真正跑在程序里的代码负责查数据库、调外部接口、算结果、做权限校验。执行逻辑必须确定且可控因为模型只保证了“说话”的合理性并不保证“执行”的安全性这个责任永远在程序侧。2.2 模型到底怎么“决定”用哪个 Skill实际运行中一次技能调用大概是这样走通的用户输入一条消息比如“帮我看看明天天气怎么样”模型结合系统提示中列出的技能清单分析这条消息更适合哪个技能模型不直接回答而是先输出一个结构化的调用指令例如“调用 get_weather参数 city用户城市, date明天”程序侧拦截到这段指令去路由表里找到 get_weather 对应的函数并执行真实数据返回后程序把它以一条“工具结果”消息的形式回填给模型模型拿到结果用自然语言组织最终答案“明天多云最高 18 度穿夹克合适。”这个过程很像一个接线员用户打总机说想查天气接线员转给天气服务部门部门把结果报回来接线员再转述给用户。转述可以适当润色但数据必须来自执行逻辑这一侧而不是接线员自己编。我在项目里一直强调这个边界——让模型尽量自由地组织话术但永远不要让它自由地编造执行结果。2.3 调用失败时怎么办技能调用不是每次都能成功。超时、接口限流、参数格式不对、权限不足都会导致执行失败。这时候很多人会犯一个错误直接抛异常让整个对话崩掉。正确的做法是让失败也以结构化结果的形式返回给模型。比如程序侧捕获到天气接口超时就返回一个标准结果statustimeouterror“天气接口超时”。模型拿到这个结果会自己组织一句安抚用户的话“现在天气服务不太稳定我稍后再帮你查一次”这样用户体验和日志排查都能兼顾。我建议在实现阶段就把正常返回结构和异常返回结构统一成一个格式比如都包含 status、data、error 三个字段。反正模型只需要读这三个字段规则越固定模型越不容易发挥。3. 自己动手写一个 Skill以“查天气 穿衣建议”为例3.1 写之前先想清楚输入和输出很多小白上来就写代码结果参数契约定义得乱七八糟模型经常给不出合法参数。我的建议是倒过来先定义一份清晰的输入输出再动手写执行函数。拿“查天气 穿衣建议”这个 Skill 来说输入可以设计成{ type: object, properties: { city: { type: string, description: 城市名用户可以传中文城市名或城市编码, examples: [某市] }, date: { type: string, description: 日期格式 YYYY-MM-DD默认今天 } }, required: [city] }注意 description 要写清格式和示例这能显著提升模型生成参数的准确率。我在做模拟项目时发现加了 example 之后参数非法率能下降一半以上。输出的设计同样关键。执行函数返回给模型的数据不要一股脑全塞最好是已经加工过的精简信息城市、天气、温度区间、风力、穿衣建议。穿衣建议甚至可以在程序侧先生成一段基础文案模型只负责微调措辞这样能减少很多幻觉。3.2 落地代码注册表加分发函数实现上我推荐用一个注册表模式。先定义一盘技能字典key 是技能名value 是执行函数这样后续加新技能只需要往字典里塞一个函数主流程完全不用动。import json import datetime def get_weather(city: str, date: str None) - dict: # 这里是调用外部天气接口的逻辑 # 为了演示就返回一个模拟结果 return { status: success, data: { city: city, date: date or str(datetime.date.today()), temp_range: 14~18, condition: 多云, wind: 3级, suggestion: 早晚偏凉建议穿夹克或薄毛衣 } } REGISTRY { get_weather: get_weather } def dispatch_skill(skill_name: str, arguments: str) - dict: func REGISTRY.get(skill_name) if not func: return {status: error, error: funknown skill: {skill_name}} kwargs json.loads(arguments) if isinstance(arguments, str) else arguments try: return func(**kwargs) except Exception as e: return {status: error, error: str(e)}然后在你和大模型交互的主循环里截获模型返回的工具调用指令调用 dispatch_skill再把结果作为一条新消息放回上下文即可。这里要强调两点参数解析一定要做异常处理因为模型给出来的 JSON 偶尔会带多余符号函数内部绝对不能直接抛异常给用户看到必须经由 dispatch_skill 转成结构化结果。3.3 测试时要注意的细节我测试 Skill 一般会用一张用例表从易到难逐步过用例期望行为潜在问题“查一下某市明天天气”触发 get_weathercity某市date明天日期要正确换算成 YYYY-MM-DD“穿什么出门合适”能触发缺省 city 时模型应主动追问漏 city 参数需要提示模型补问“看看这个文件内容”不触发天气技能应走其他技能或直接拒绝描述写太宽导致误触发“帮我查天气顺便定个提醒”触发两个技能是否支持多工具并行调用如果你发现模型经常选错技能我的第一建议永远不是调参数或换模型而是回头改技能描述。描述里加一句“当用户没有指定城市时先追问城市名称”通常比你在提示词里绕半天更有效。4. 实测中最容易翻车的几个点踩坑记录与对策4.1 参数格式不一致第一个翻车点模型给出的参数值不一定符合你的接口预期。比如用户说“后天天气怎么样”模型可能会把日期算成一个字符串但有的天气接口要求的是城市编码有的要求城市名加省份还有的要求精确到小时。我踩过的坑是前端问用户城市用户回答了一个中文城市名但数据源只认城市编码模型把这个中文名传了过来程序在转换表里查不到直接把整次请求拦了下来。后来我在参数契约里加了 enum 和 example并且在执行侧加了一段归一化函数先尝试中文名转编码再决定是否返回“查不到此城市”的结果。记住程序侧做一次宽容的参数归一化比反复调提示词划算得多。4.2 技能返回的数据太“原始”模型读不懂第二个翻车点更隐蔽执行函数返回了一堆原始数据模型读着读着就“消化不了”了。我做过一个模拟项目让 Agent 查询日报表数据库返回了八十行记录全部直接塞进上下文结果模型回答得前言不搭后语还频繁编造数字。正确的思路是程序侧先做裁剪或聚合只返回和当前问题强相关的片段。比如用户问“本周故障报告”程序侧先统计好总数、平均解决时长、Top 5 故障类型再以几行文字返回给模型。别把数据库表字段原封不动扔给它那等于让一个实习生看一仓库原始单据他不乱猜才怪。4.3 循环调用与上下文爆炸第三个坑是技能互相触发形成死循环。A 技能内部调用了查询接口查询结果里有一个“需要进一步分析”的标记模型看到后发起 B 技能调用B 又触发了 A于是一轮接一轮直到上下文塞满最后模型开始胡言乱语。我在主循环里一定会加两个保护最大工具调用轮次一般设 5 轮左右以及一个简单的调用链记录发现同一技能在短时间内重复触发就直接熔断。熔断后让模型输出“这个问题我暂时处理不了可能需要你先确认一些信息。”这样比让它无限循环体面得多。4.4 多技能冲突与优先级最后是技能多起来之后的冲突。当你的 Agent 同时拥有天气查询、新闻搜索、数据库查询等多个技能用户一句“帮我查一下”模型可能随机选一个。这个我实测非常明显不写清边界的技能列出来后大概率触发的是排在最前面的那一个。解决办法是给每个技能描述都加“该技能用于……当用户提到……请使用……”的句式同时考虑按场景动态暴露技能。并不是所有技能都要一次性全部注入给模型。比如用户刚进入订单页面就只暴露订单相关技能让模型的选择空间变小准确率自然就上去了。5. 进阶Skill 的组合、安全边界与“什么时候别用 Skill”5.1 把多个 Skill 组合成工作流单个 Skill 解决一个点多个 Skill 按顺序组合才能实现一个完整任务。比如“生成周报”这个场景实际需要三个步骤拉取工时数据、汇总项目进度、生成 Markdown 草稿。这里有两种编排方式一种是让模型自己决定调用顺序另一种是程序侧用代码把顺序写死。我的建议很直接前期先写程序侧编排把顺序控制住。比如定义一个数组依次执行每个 Skill并把上一个 Skill 的结果传给下一个。等链路稳定了再尝试让模型自由编排。原因很简单模型自由编排灵活但不可控一旦某一步失败你连“到底失败在哪一步”都很难排查。先跑通有序的执行再加灵活性是最稳妥的路径。5.2 安全与权限边界Skill 本质上就是权限。每开放一个技能就等于给模型多开了一扇操作外部系统的门。如果这个技能里有删除操作、有敏感字段读取那风险就不仅是“模型答错题”了而是真实的数据事故。我在项目里做了三个基本限制第一破坏性操作拆成“确认 执行”两步让用户先确认再实际运行第二Skill 执行侧统一做参数白名单校验和越权检查比如普通用户只能查自己的订单第三所有技能调用都记录日志方便事后回溯。记住模型只是负责转述的接线员权限审核一定要放在真正干活的程序这一侧。5.3 什么时候不该用 SkillSkill 不是越多越好有些情况其实不需要它。比如一个纯文本创作场景模型直接生成内容就够了引入工具调用只会增加延迟和失败概率。再比如涉及强一致性的计算场景用户问“3.7 乘以 0.8 等于多少”你不应该让模型心算应该直接让程序侧算模型的数学能力再强也会犯错。我判断一个任务要不要配 Skill主要看三点需不需要实时外部数据需不需要操作外部系统需不需要确定性计算结果三个问题只要有一个答案是“是”就值得做 Skill如果全是“否”老老实实走普通对话就行。这个判断标准帮我避免了很多过度设计。最后分享一点个人体会。我在多个模拟项目里反复调整过 Skill 的设计踩过很多轮之后最重要的一句话是一个技能好不好用不看名字多酷炫而看三个“稳不稳”——触发稳不稳、参数稳不稳、失败处理稳不稳。触发不稳定模型就会乱选工具参数不稳定程序侧就会被脏数据塞满失败处理不稳定整个对话体验直接崩掉。如果你正准备给自己的 Agent 加技能我建议从一个小而完整的技能开始跑通一次“模型发起调用、程序执行、结果回填、最终回答”的完整链路再慢慢加数量。别急着堆功能边界清楚、返回干净、失败可控这三件事做好了Agent 才真正像一个可靠的工具箱。