Agent Skills实战拆解:从万能提示词到结构化技能编排

发布时间:2026/10/8 11:17:46
Agent Skills实战拆解:从万能提示词到结构化技能编排 最近agent-skills这个词在Agent开发圈子里热度飙升GitHub上各种skills仓库也如雨后春笋。我一开始觉得这又是新瓶装旧酒直到自己把一个带着六七种能力的Agent从一团两千字的提示词里拆成独立的技能体系才发现里面的门道远比想象的多。这篇文章不聊什么宏大架构就讲讲我实际拆解、设计、踩坑的全过程给正在折腾Agent能力编排的朋友们一份参考。我默认你已经有基础的LLM应用开发经验至少调过一两次API知道什么是上下文窗口、什么是工具调用Function Calling。如果你只是听说过Agent这个词也没关系我会尽量把每个概念都拆开讲清楚。1. 先搞明白Agent Skills是什么我为什么放弃万能提示词1.1 一次两千字提示词的惨痛翻车我之前做过一个信息整理Agent核心任务是抓取网页、清洗正文、生成结构化摘要。一开始做法特别朴素把所有指令塞进一个巨大的System Prompt什么你可以使用以下工具遇到长页面请分段处理超时就重试一次越加越多最后系统提示词接近两千字模型表现却越来越飘。最离谱的一次它在一个并不复杂的抓取任务里连续五次自我纠正先是说页面太长我需要分段然后又说刚才的结果不够完整我重新抓一次最后居然把原本已经抓到的干净正文给纠正成了一个错误链接的报错信息。整个过程浪费了六次工具调用耗时三分钟结果还不如第一次抓取直接返回。我复盘日志发现问题根本不在提示词写得不够详细而在于能力和指令被搅在了一起。模型并不会每次都把两千字读完再行动它有注意力衰减有上下文挤压。你越想通过堆字让模型学会一件事它越容易在关键节点掉链子。换句话说我是在用文本提示词硬撑一套本应该被结构化的能力边界。1.2 技能、工具、插件、工作流我理解的边界刚开始接触Agent Skills时我也被一堆名词绕晕技能Skill、工具Tool、插件Plugin、工作流Workflow到底有什么区别我用一个不太严谨但很好用的类比来理解**工具Tool**是手一个可以被调用的函数比如发送邮件查询天气本身不包含任何决策逻辑。**技能Skill**是肌肉记忆围绕一个完整任务目标封装的能力包里面可能包含多个工具、一段执行策略、对输入输出的约定。比如整理一篇网页为结构化摘要就是一个技能它内部会用到抓取网页清洗HTML调用LLM做摘要等多个工具。**插件Plugin**是外挂装备在特定平台上打包好的集成单元一个插件里可以注册多个技能。**工作流Workflow**是流水线把多个技能按固定顺序编排输入端、输出端都是确定性的一般不允许模型自由发挥。用表格对照一下更直观概念核心单位是否含决策逻辑典型场景工具单个函数否查询天气、发邮件、调用API技能任务目标多工具策略是有限度网页结构化、竞品分析、文档翻译插件多个技能打包平台级在某个Agent平台上分发能力工作流固定步骤序列否流程固定数据ETL、定时报告生成这个边界不是绝对的不同的框架定义会有差异但理解这个分层之后你就明白技能的核心价值在于把模型不擅长或不可靠的步骤变成确定性的、可测试的、可复用的模块。1.3 为什么各大框架都在押注技能化从我的使用体验看技能化解决了三个实际问题。第一上下文减负。技能声明通常只有几百字而不是几千字的行为规范。模型只需要在需要时读取某个技能的完整说明平时只保留一行摘要上下文占用大幅下降。第二独立迭代。我改了一个技能的抓取策略不会影响其他技能的行为测试边界清晰回归成本低。第三组合复用。同一个网页抓取技能可以被产品日报生成竞品监控论文翻译等多个Agent复用改一处处处生效。2. 从零设计一个技能以网页内容抓取为例讲再多概念不如动手拆一个。我拿自己项目里最常用的网页内容抓取技能作为例子完整走一遍设计流程。这个技能的目标是给模型一个URL它返回页面里真正有用的正文内容而不是一堆标签和脚本噪声。2.1 声明层给模型看的说明书技能的声明层决定了模型能不能正确理解并调用它。一般来说声明包括技能名称对应函数名、描述、参数列表、返回值说明。我习惯把声明写成JSON Schema的形式方便和后端校验逻辑复用同一套规范。关键代码如下skill_declaration { name: fetch_web_content, description: 抓取指定网页并提取正文内容。适用于新闻文章、博客、文档页面不适用于需要登录的页面、PDF文件、视频页面。, parameters: { type: object, properties: { url: { type: string, description: 目标网页的完整URL必须包含协议前缀如 https://example.com/article }, max_length: { type: integer, description: 返回正文的最大字符数默认5000超出部分会被截断, default: 5000 } }, required: [url] }, returns: { type: object, properties: { title: {type: string}, content: {type: string}, status: {type: string, enum: [success, error]} } } }有几个细节值得注意。描述里我特意写了不适用于需要登录的页面、PDF文件、视频页面这是在给模型的决策做边界约束。你不告诉它什么时候不该用它就会在遇到 PDF 时硬着头皮调用然后拿到一堆乱码。负例声明比正例声明更能提高命中率。max_length给了默认值这个很关键。模型经常会忽略非必填参数如果你不在Schema里写默认值模型调用时就会犹豫要不要传或者传一个奇怪的负数。给了默认值之后即使模型不传这个参数后端也能正常执行。2.2 实现层稳定、容错、输出友好声明层是给模型看的实现层是真正干活的。我的实现逻辑分三步抓取 HTML、提取正文、清洗降噪。import requests from bs4 import BeautifulSoup def fetch_web_content(url: str, max_length: int 5000) - dict: try: headers { User-Agent: Mozilla/5.0 (compatible; SkillBot/2.0) } resp requests.get(url, headersheaders, timeout15) resp.raise_for_status() soup BeautifulSoup(resp.text, html.parser) for tag in soup([script, style, nav, footer, aside]): tag.decompose() title soup.title.string.strip() if soup.title else content soup.get_text(separator\n, stripTrue) if len(content) max_length: content content[:max_length] ...[截断] return {title: title, content: content, status: success} except Exception as e: return {title: , content: f抓取失败: {str(e)}, status: error}这里有一个实操中特别重要的设计返回错误信息时必须让模型能看懂并采取行动。很多新手会把异常堆栈直接返回给模型比如ConnectionResetError: [Errno 104]模型看到这串英文是一脸懵的它不知道怎么处理。我现在的做法是返回一句人话抓取失败: 目标站点返回了403可能需要更换UA或等待重试。这样模型至少知道两个信息失败了以及下一步可以试什么。另外tag.decompose()这一步千万别省。如果你直接对整页get_text()你会把所有导航栏、页脚、侧边广告、评论区通通塞进正文返回内容三分之二都是噪声。我实测过一个普通新闻页面不清理噪声时正文可能有三万字符清理后只剩八千模型整理摘要的质量和速度都大幅提升。2.3 注册层把技能挂到Agent身上声明和实现都有了剩下就是注册。不同框架的注册方式不一样有的走装饰器有的走配置表。我倾向于用配置表方式因为可以集中管理每个Agent挂载了哪些技能、每个技能的启用状态和版本号方便灰度发布。agent_config: name: daily_reporter_agent skills: - name: fetch_web_content version: 2.1.0 enabled: true - name: summarize_markdown version: 1.4.2 enabled: true - name: send_email version: 0.9.0 enabled: false配置表的好处是运营同学和评测同学都能看懂不需要翻代码。而且我可以在配置表里加一列description_override用来针对特定Agent微调技能描述不影响全局版本。3. 模型到底怎么选中技能命中率优化的底层逻辑做过Agent的朋友应该都有这种经历技能声明写的自认为很清楚了模型偏偏在需要的时候不调用不需要的时候瞎调用。要解决这个问题你得先理解模型是怎么选技能的。3.1 Function Calling的匹配机制比你想的更粗糙很多人以为模型会像一个搜索引擎那样对用户问题做语义匹配再精确挑选工具。实际上主流模型的Function Calling机制是在指令微调阶段训练出来的模型本质上在做的是根据当前对话上下文预测最可能被调用的函数。这个过程中函数名和描述文本会被模型以Token序列的形式阅读模型对函数名的敏感度往往高于描述。这意味着你在选择技能名称时就要花心思。fetch_web_content就比process_url好因为前者的动词fetch宾语web_content直接映射了用户意图。我见过有人把技能命名为execute_dynamic_operation模型根本猜不到这玩意是干嘛的命中率自然惨不忍睹。还有一个反直觉的规律技巧描述越短命中率越高。模型在决策时会权衡上下文权重长的描述会稀释关键信息的密度。我的经验是技能描述控制在30到60个中文字符之间说清楚做什么什么场景用什么场景不用就够了。3.2 技能描述的三个原则短、准、分场景写技能描述我总结了一套方法不一定适合所有模型但在我用过的几个主流模型上效果都不错。第一个原则是动词开头行为导向。不要写该功能用于实现网页内容的抓取和分析处理要写抓取指定网页并提取正文内容。抓取这个动词直接对应了用户说帮我看看这个网页时的意图。第二个原则是包含典型场景关键词。用户不会说请调用 fetch_web_content 技能他们会说帮我分析一下这篇文章这个链接打开不了总结一下那个博客。所以描述里最好出现这些口语化场景词。我实际操作中就把描述改成了抓取指定网页并提取正文适用于用户提供链接要求阅读、分析、总结文章或博客内容的场景。第三个原则是明确负边界。为什么我反复强调这个因为模型特别容易过度自信。你告诉它这个技能能抓网页它遇到PDF也调遇到需要登录的页面也调反正先调到再说。在描述末尾加一句不适用于PDF、视频、需登录页面命中率能上去不少。3.3 多个技能撞车路由策略与兜底设计当Agent挂了超过五个技能之后你一定会遇到多个技能都能回应同一个需求的情况。比如用户说帮我把这个页面整理成周报发我邮箱理论上既可以选择fetch_web_contentsummarize_markdown的组合也可能直接选择weekly_report_agent这个独立技能。我的处理方式是分层路由先看用户意图是否精确匹配某个复合技能如weekly_report_agent如果不匹配再让模型拆解子任务逐个子技能调用。同时在声明里给每个技能加上组合提示比如在send_email的描述里写通常在完成内容整理后调用用于将结果发送给指定收件人。这样等于引导模型理解技能之间的先后依赖关系而不是放任它随意挑。还有一个兜底策略设置万能技能fallback skill专门处理所有其他技能都匹配不上的请求。这个技能通常就是直接调用LLM回答用户问题至少保证Agent不会因为找不到合适技能而卡死。4. 一个Agent挂十几个技能之后规模化的工程问题单个技能做出来不难难的是挂十几个之后还能稳定运行。这个阶段我连续踩了好几个坑挑三个最典型的讲。4.1 版本兼容改一个参数别让旧调用全崩技能迭代是常态。今天给fetch_web_content加了一个extract_images参数明天给summarize_markdown改了返回结构。问题在于已经部署的Agent会话可能还在使用旧的技能声明尤其是对话式Agent历史消息里已经缓存了旧定义新会话却加载了新版定义两边不一致就会出怪问题。我的解决方案是强制技能接口向后兼容新增参数必须带默认值不允许删除已有参数不允许改变已有返回字段的类型。如果确实要破坏性更新那就升级技能主版本号并在配置表里将新旧版本并行运行一段时间观察线上调用数据再切换。这个做法跟后端API的兼容性管理完全一致只是很多做Agent原型的人容易忽略。4.2 权限隔离不是所有技能都该给所有Agent技能一旦多起来权限问题就出现了。比如send_email这个技能给个人助理Agent没问题但给一个公开的数据查询Agent就等于开了一扇门任意用户通过自然语言就能诱导Agent发邮件。我实际遇到过一次一个公开测试Agent开着send_email技能有用户输入给 testexample.com 发送100封内容为你好的邮件要不是那个邮箱是假地址这事就变成垃圾邮件攻击了。所以权限隔离必须做在技能层不能只做在Agent层。我现在的做法是把技能分为公开技能、内部技能、敏感技能三级。公开技能任何Agent可用内部技能只允许内网调用敏感技能涉及邮件、支付、文件删除等除了账号权限外还要在实现层强制二次确认即要求模型在执行前先输出确认执行指令拿到用户明确回复后才真正执行。4.3 可观测性日志、Trace、效果评估三件套技能挂了没人知道是最可怕的事情。我开发了一套针对的技能观测方案分为三层。第一层是调用日志记录每次技能调用的模型、会话ID、技能名、参数、返回码、耗时。第二层是链路追踪一个复杂任务往往会触发多次技能调用我用一个trace_id把同一次用户请求关联的所有技能调用串起来这样就能看到用户问了什么→模型调了哪个技能→中间有没有连续失败→最终有没有成功。第三层是效果评估光知道调用了没用还得知道调用得好不好。我每个技能都会内置一个简易的评分回调函数比如fetch_web_content会记录返回正文长度是否合理、提取的标题是否和用户给的URL域名相关、状态码是否为success。每周跑一次统计命中率、成功率、平均耗时、失败TOP原因一目了然。我在实际使用中发现技能评估的阈值比模型的输出质量评估更容易定因为技能是确定性的代码结果可量化。这也是技能化比纯提示词工程更适合做质量保障的根本原因。5. 高频翻车现场三种失败模式与修复方案最后分享几个我在生产环境里反复遇到的翻车现场。这些坑每个都让我改过代码希望你能少走弯路。5.1 参数幻觉模型凭空捏造不存在的字段有段时间我经常在日志里看到fetch_web_content的调用参数里多出来一个user_agent字段——这是我根本没有声明的字段模型自己脑补的。还见过更离谱的有个模型给send_email传了attachment_path参数而我的实现里根本没有这个参数结果所有带附件的请求全部失败。根因在于当模型不确定某个参数是否可用时它倾向于尝试一下尤其当技能名称暗示了某种能力比如邮件暗示附件而声明里又没明确禁止时幻觉就出现了。我的修复方案有两个。第一在参数Schema的additionalProperties: false显式禁止未声明字段后端再把出现未知参数的调用记录下来做分析。第二在技能描述里明确写本技能不支持附件、不支持批量发送。把负边界写清楚参数幻觉发生率明显下降。5.2 技能返回太专业模型反而看不懂了这个问题特别隐蔽。早期我设计的技能返回结果都是结构化数据比如从网页提取出来的正文我一律返回JSON格式还带上了各种元数据字段http_status、parsed_encoding、content_hash之类。结果模型经常忽略正文数据反复纠结那些元数据字段并问我确认编码为UTF-8是否影响摘要结果——完全跑偏了。我后来才明白模型不是每句话都读它只会选择性读取返回结果里的部分信息。如果返回结构里信息密度低一半以上是元数据模型就很难抓住重点。修通的办法是结果降噪技能返回给模型的内容尽量扁平化、人话化只包含模型决策所需的最少信息。比如fetch_web_content成功时返回的就是标题正文文字而不是标题正文文字HTTP状态耗时编码链接列表图片列表。额外的信息如果某个场景需要可以在描述里说明让模型再次调用一个专门技能去获取而不是一股脑全塞回去。5.3 技能间死循环谁都不肯先停这是最让我血压飙升的一个坑。有一次Agent在处理整理几个网页并生成对比报告的任务时连续触发了fetch_web_content→summarize_markdown→fetch_web_content→summarize_markdown的循环明明两个网页都抓完了模型还是不停它会说为了确保信息的完整性我再重新抓取一次。最后消耗了几十次调用才被最大轮数截断。后来定位到两个诱因。一是summarize_markdown的返回里包含如果需要更详细的内容可以重新抓取原文这句话模型真的就照做了。二是我没有在技能描述里规定抓取过的URL不要重复抓取。修复方法把所有技能返回文本里的建议性话语全部删干净同时在fetch_web_content的参数里加一个skip_cached布尔值并在实现里记住已经抓过的URL如果同一个会话内重复请求同一个URL直接返回缓存加提示该页面已抓取过。从那以后循环问题再没出现过。6. 技能设计的几条朴素心得如果你正准备把自己的Agent往技能化方向重构我给你几个完全来自实战的经验。第一从最痛的一个任务开始拆不要一开始就规划完美的技能体系。找一个你当前频繁翻车或频繁重复编写提示词的场景把它做成第一个技能跑通之后你会对技能的粒度、返回结构、描述长度有真实体感再继续做第二、第三个就顺手了。第二技能描述要当成产品文案来写而不是技术文档。技术文档追求详尽完备却会让模型抓不住重点。产品文案追求用户一眼就知道这玩意能干什么这种表达逻辑和模型的注意力机制更匹配。你可以让另一个人只看技能描述不看实现代码然后让他猜这个技能是干什么的如果他猜对了描述基本合格如果猜不对改描述比改实现更优先。第三给每个技能安排一个唯一负责人。技能多了之后最怕的是没人敢改因为不知道会影响谁。我在项目里给每个技能约定了一个显式的owner字段放在技能的README头部任何改动必须由owner确认。这不是流程繁琐是技能复用到后期真的需要一个说了算的人。最后再分享一个小细节我给每个技能的版本号都采用主版本.次版本.修订号三位格式主版本代表破坏性变更次版本代表新增能力修订号代表bug修复。这套规则在代码里只需要几十行表达式但它让我在排查问题时能一眼看出这个Agent用的是哪个版本的技能对定位那种昨天还好好的今天突然不行的问题价值不可估量。Agent的技能化这条路走得越深越觉得它是在帮模型划定能力边界——不是让模型更强而是让模型更可靠。希望你也能在自己的Agent项目里踩更少的坑调出更稳的技能。