ARTICLE · INTELLIGENCE

战地情报 · 详情页

来自尧图项目组的一线实战观察与深度解析

Agent Skills实战:让AI Agent稳定执行复杂任务的操作指南

Agent Skills实战:让AI Agent稳定执行复杂任务的操作指南 先说个真实经历。我最早接触 Agent 编程时总有一种错觉模型那么聪明只要把任务交代清楚它应该什么都能搞定。结果真跑起来就翻车让它抓个网页正文它要么自己瞎编一段 HTML要么把超长的 URL 直接塞进命令里让它处理 CSV 里的脏数据它这轮能跑通下一轮换一批数据又开始自由发挥。后来我意识到问题的本质模型缺的不是智商而是一套可以稳定复用的“操作流程”。于是我把那些高频、重复、必须精确执行的能力从对话里一个个抽出来固化成了独立的技能文件这就是我这个叫 “skills” 的项目核心就是围绕 Agent Skills 这种形态把自然语言指令、脚本逻辑和模型调用打通。这个东西适合谁凡是日常在用 AI 编程助手、自动化脚本、或者做 AI Agent 应用开发的都能从这套玩法里找到灵感。它不是某个特定框架的专属方案而是一种可以迁移的思路学会了之后你自己也能随手封装一个合手的技能。1. 项目概述这个“skills”项目到底解决了什么问题1.1 从一次失败的 Agent 对话说起事情是这样的我想让 Claude 帮我从一篇文章里提取正文并转成 Markdown。任务听起来很简单但模型反复犯同一个毛病——它读到了页面里大量的导航、广告和推荐链接根本分不清哪些是正文。我试着在提示词里不断强调“只要正文不要无关内容”效果还是不稳定。最让人崩溃的是它会把抓取结果里的某些片段当成命令去执行输出一些奇怪的格式。后来我换了个思路既然模型不擅长“精确复现一套流程”那我就把这套流程打包成一个技能规定好输入、输出和中间步骤模型的任务只剩下“判断什么场景用这个技能”和“把参数传进去”。这个转变非常关键。从“让模型思考怎么做”变成“让模型知道什么时候用什么”模型的表现一下子稳定了输出格式也统一了调试成本大幅降低。这个小小的经历直接催生了我的 skills 项目。它的核心理念就是把那些已经验证过、不需要模型临场发挥的操作固化成可复用的技能文件让模型去调用而不是重新发明轮子。你会发现这种思路和我们平时写代码的习惯很像。你不会在每次需要排序时都手写一遍快速排序而是调用现成的函数。Agent 也需要这样的“函数库”Skills 就是它的函数库。区别在于函数的调用者是程序员而技能的调用者是模型所以技能的组织方式必须尊重模型的理解习惯这也是我在后面章节里反复强调的核心点。1.2 一个核心问题Skills、MCP 和 Memory 到底有什么区别做 AI Agent 应用的人很容易被一堆概念搞晕Skills、MCP Tools、Memory、System Prompt看起来都是给模型加能力的它们到底有什么区别如果分不清边界项目结构很容易乱成一锅粥。我做了个表你可以对照着看维度Skills技能MCP Tools工具Memory记忆System Prompt系统提示词本质可复用的操作流程脚本模型可调用的外部函数/接口跨会话存储的事实与偏好定义模型角色和基础行为的一段指令加载时机按需加载模型判断场景后主动选择启动阶段注册到工具列表对话前注入或动态检索每次对话始终存在典型产物SKILL.md 若干脚本一个 HTTP 接口或 SDK 封装托管的事实记录、向量库索引角色说明、输出规范、禁用项模型介入程度模型负责“选择”和“传参”脚本负责执行模型仅决定“调不调”参数由程序传递模型读取并使用信息模型完全按文本指令行事适合场景多步、固定、需要精确执行的流程对接外部系统、实时数据查询记住用户偏好、项目长期状态全局约束、风格控制、安全边界从这个表能看出来Skills 和 MCP Tools 最容易被混淆。我的理解是MCP Tools 更像是“插头”它把外部能力比如数据库、GitHub API、浏览器控制接入模型而 Skills 是“作业指导书自动工具箱”它包含明确的步骤描述并内嵌实现脚本。你在项目里完全可以两者混用MCP 负责实时取数Skills 负责把取到的数据按固定流程处理成最终结果。1.3 这个“skills”项目适合谁参考我一开始以为这种玩法只适合专业 AI 工程师后来越做越发现它其实对三类人都有价值。第一类是 AI 应用开发者。如果你正在构建多步骤 Agent或者你的 Agent 经常要重复处理格式固定的任务Skills 能帮你把稳定逻辑从模型的不稳定输出里剥离开大幅减少一次性的“废 token”。第二类是重度使用 AI 的普通用户特别是会一点 Python 或命令行的朋友。你可以把日常重复的工作比如“把会议录音转成待办清单”“把周报数据汇总成表格”固化成技能以后只需要对模型说一句话就能触发整套流程。第三类是想做知识管理的朋友。Skills 本身就是一种结构化的知识沉淀方式它迫使你把隐性经验显性化写下来、测试过、迭代过最后变成可交付的东西。比起收藏夹里吃灰的文章这种方式踏实得多。我建议你从自己的实际痛点出发而不是为了玩而玩。拿我自己的项目举例我的技能库里使用频率最高的永远是那几个解决真实问题的技能而那些随手写的试水技能基本都在角落里吃灰。2. 核心设计一个高质量 Skill 应该具备的完整结构2.1 SKILL.md写给模型看的第一份说明书如果只有一个概念要记住我会说是 SKILL.md。它是整个技能库的入口文件也是模型最先读取的内容。模型通过阅读这个 Markdown 文件来判断“这个技能是干什么的什么情况下该用怎么用”。所以它本质上是一份写给模型看的说明书而不是给人看的代码文档。这两者的区别非常大人看文档喜欢详细、严谨模型看说明书则更需要清晰、明确、可执行。SKILL.md 里最重要的东西是“描述区”即告诉模型这个技能何时生效的描述文本。我实测下来描述写得好不好直接决定触发率。如果你写的是“这个技能可以处理数据”那模型大概率不清楚什么时候调用但如果你写的是“当用户需要从 URL 提取正文、去噪并存为 Markdown 时使用该技能”模型在遇到对应场景时就会第一时间想到它。描述里最好带上具体的关键词和典型场景甚至可以给出一两个示例对话这比抽象的描述有用得多。另外我发现一个非常有效的写法在 SKILL.md 里显式列出“什么情况下不要使用”这个技能。比如网页正文提取技能你可以在下面注明“不要用于 PDF 文件不要用于需要登录的页面不要用于动态渲染的 JS 页面”。这能防止模型在错误场景下强行调用技能省掉很多麻烦。2.2 scripts把“精确计算”从模型手里拿走Skill 的第二大部分是 scripts 文件夹用来放实际的执行脚本。这里有个设计原则我一直很坚持凡是可以写成确定性逻辑的操作都尽量写成脚本不要让模型手动计算。举个例子你让模型从一堆文本里提取所有日期它可能今天用正则明天用自然语言理解结果总是不一致。但如果你把日期提取的逻辑封装成一个 Python 脚本模型只需要调用这个脚本并传参无论什么时候执行结果都是一致的。这种“确定性”对于生产环境非常重要它能保证同样输入得到同样输出。脚本的语言可以很灵活Python、Bash、Node.js 都行。选型的标准很简单你本地环境跑得最顺、依赖最少的语言就是最合适的。很多时候一个简单的 Bash 命令就能解决的事没必要为它装上几十个 Python 包。但反过来如果你要做复杂的文本处理或数据分析Python 生态确实无可替代。还有个细节脚本怎么和模型通信。我实践下来的最佳方案有几种。最简单的是命令行参数适合参数少的场景其次是标准输入适合把一大段文本喂给脚本最后是临时文件适合复杂的结构化数据。输出结果一定要打到 stdout用普通文本或 JSON格式要说清楚。不要尝试在脚本里做交互式输入模型执行脚本的过程中不会像人一样有耐心一步步等待输入。2.3 命名、目录与版本从第一天就保持整洁技能库小的时候无所谓一旦超过十个技能命名和目录的坑就会集中爆发。我这里直接给出一套实践过的规则你照着做基本不会乱。首先目录命名统一小写用连字符分隔比如web-extract、csv-clean。不要用空格不要用驼峰更不要用中文否则在命令行和模型解析时很容易出问题。其次每个技能目录必须具备最小三件套SKILL.md、scripts/目录、以及一个可执行的入口脚本。有的技能还需要示例数据我会放在examples/文件夹里这样联调时模型可以直接取用不需要额外构造。版本管理方面我一开始只用 Git 管整个仓库后来发现单个技能也需要版本标注。我的办法是在每个 SKILL.md 开头加一个version字段更新时同步改同时在 Git 的提交信息里写明变更原因。这样做的好处是如果某个技能新版本在模型侧表现不佳你能快速比对差异回滚。更重要的是维护一份 CHANGELOG记录每个技能什么时候加的、改了什么、踩过什么坑。这个习惯救了我好几次强烈推荐。3. 实操过程从零搭建一个可用的技能库3.1 先搞定环境哪些运行时和目录是必须准备好的动手之前先把环境理顺。我的技能库以 Python 为主所以本机要有 Python 3.9并且建议用虚拟环境管理依赖避免各个技能需要的包冲突。如果你只写 Bash 脚本那环境要求就低得多Linux/macOS 基本开箱即用Windows 用户可能要配置一下 WSL 或者 Git Bash。另外一个容易被忽略的事情是模型执行脚本时的当前目录不一定是你项目根目录脚本里涉及路径的地方尽量用绝对路径或者在脚本开头自动切换到自身所在目录。配置模型端也不复杂。以我常用的方式来说我会在相关配置里声明一个技能目录让模型在每次对话时扫描该目录下的 SKILL.md 文件形成一个“可用技能清单”。加载策略上我不建议一次性把所有 SKILL.md 内容全塞进上下文那样又慢又费 token。更好的做法是只加载摘要索引模型判断需要某个技能时再去读取完整的 SKILL.md 和脚本。这一步是整个系统性能的分水岭。最后记得给所有脚本加上可执行权限Windows 用户尤其要注意脚本的关联程序是否正确。我遇到过最无语的情况是脚本本身没问题但因为系统默认用记事本打开了.py文件导致模型怎么执行都失败。这种低级错误排查起来特别浪费时间。3.2 第一个技能网页正文提取从丑陋 HTML 到干净 Markdown我用网页正文提取这个技能来做示例因为它场景明确、代码量适中、实用率极高。这个技能的定位是给定一个 URL脚本抓取页面、识别正文区域、去掉导航和广告、输出干净的 Markdown。我选了 Python Readability 库来实现因为这个库专门做正文提取比我手写正则靠谱多了。先创建目录结构mkdir -p ~/skills/web-extract/scripts cd ~/skills/web-extract然后写 SKILL.md。这是这个技能的“门面”我花了很多时间打磨描述部分--- name: web-extract version: 1.2.0 description: 当用户需要从 URL 中提取文章正文、去除非正文内容如导航、广告、推荐并转换为 Markdown 时使用此技能。适用于新闻文章、博客和技术文档页面。不适用于 PDF、需要登录的页面或纯 JavaScript 渲染的页面。 --- # Web Extract 提取指定 URL 的正文内容并转换为干净的 Markdown 格式。 ## 何时使用 - 用户提供一个 URL要求总结、翻译或提取正文 - 需要将网页内容保存为 Markdown 文件 - 需要分析多篇文章内容但希望只保留正文 ## 参数 - url: 必填待提取的网页地址 ## 使用步骤 1. 运行 python scripts/extract.py --url url 2. 脚本会输出提取后的 Markdown 内容 3. 如果脚本失败检查 URL 可访问性不要自行编造页面内容 ## 示例 - 用户帮我提取 https://example.com/blog/hello 的内容并总结 - 模型运行 python scripts/extract.py --url https://example.com/blog/hello然后基于输出内容进行总结接着写提取脚本。这个脚本要负责抓取页面、解析正文、转 Markdown并且容错处理要到位#!/usr/bin/env python3 网页正文提取脚本 import argparse import sys import requests from readability import Document import html2text def extract(url: str) - str: try: resp requests.get(url, timeout15, headers{ User-Agent: Mozilla/5.0 (compatible; skills-bot/1.0) }) resp.raise_for_status() except Exception as e: return fERROR: 请求页面失败: {e} doc Document(resp.text) title doc.short_title() content_html doc.summary() converter html2text.HTML2Text() converter.ignore_links False converter.body_width 0 markdown_content converter.handle(content_html) return f# {title}\n\n{markdown_content.strip()} if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--url, requiredTrue) args parser.parse_args() result extract(args.url) if result.startswith(ERROR): print(result, filesys.stderr) sys.exit(1) print(result)这里有几个细节值得说明。首先是 User-Agent很多站点对默认 requests 的 UA 会拒绝访问设置一个常见的浏览器 UA 能规避大部分拦截。其次是超时必须加否则遇到了响应极慢的站点脚本会一直挂在那里既占资源又拖慢整个流程。最后是错误处理脚本失败时要把错误信息打印到 stderr 并以非零码退出这样模型能感知到失败不会拿着空结果继续瞎编。3.3 第二个技能CSV / Excel 数据清洗让模型告别手搓正则第二个我强烈推荐做的技能是数据清洗。原因很简单日常表格数据里充满了各种脏数据日期格式不统一、姓名前后有空格、数字列混入了文本、缺失值不知道填什么。让模型用 Python 在线处理这些内容虽然理论上可行但既费 token 又容易出错而且模型每轮都可能给出不同方案。做成技能之后你只需要告诉模型“帮我清洗 sales.csv”它就会自动调用统一的清洗逻辑。这个技能的脚本设计要更通用一些。我采用的方式是读入 CSV 或 Excel 文件自动推断列类型对文本列做去空格和常见格式修正对数值列做类型转换对日期列尝试解析为统一格式最后输出一个新文件并打印一份清洗前后的对比摘要。关键点在于“可解释性”模型和用户需要知道清洗过程中到底改了什么所以摘要必须清晰。SKILL.md 的能力描述我会这样写当用户提到数据清洗、格式统一、缺失值处理或者给出一个 CSV/Excel 文件路径并希望整理数据时使用这个技能。同时我也会在“不要使用”部分注明如果用户明确要求特定清洗规则不要用默认清洗先运行技能后看摘要再人工决定是否调整。实际脚本的核心逻辑并不复杂关键是把每一步变更记录下来最后统一输出报告。你可以用 pandas 来读文件用 dateutil 宽松解析日期。开头还是老规矩脚本失败时向 stderr 输出错误并退出模型看到异常码就知道调用失败了。3.4 调试这件事怎么判断一个技能是“好用”还是“能用”很多人的技能库停留在“能用”水平主要原因是没做完善的联调验证。我开发一个技能会按三个层级来调试。第一层是脚本层。直接命令行执行脚本用真实的输入跑一遍确认输出正确、格式稳定错误处理有效。这一步暴露最多的问题大多数脚本 bug 在这一层就能发现。第二层是单技能联调。在只加载这一个技能的前提下向模型发出典型任务请求看它能不能正确触发技能、正确传参、正确解读输出。这里我会刻意准备“触发边界”的测试例如用近似但实际不匹配的请求看模型会不会错误触发如果会就说明描述文字有歧义需要调整。第三层是混合场景测试。把技能放进完整的技能库里同时加载多个技能观察模型在真实复杂任务里会不会选错技能。这一步能暴露技能描述之间的干扰和歧义。我在调试中最大的体会是技能描述中的“何时使用”和“何时不使用”这两个区块价值远比预想中大。它们不仅能提升触发准确率还能在多个技能同时存在时帮模型建立清晰的决策边界。如果你发现模型经常选错技能十有八九是描述里缺少了明确的排除条件。4. 常见问题与排查技巧实录4.1 模型就是不调用技能问题出在哪里最常见的问题是“为什么我明明有网页提取技能模型还是不用非得自己瞎猜”这种时候我一般会按顺序排查三件事。第一技能描述里是否包含了用户实际使用的关键词。模型的触发机制很依赖语义匹配如果用户说的是“抓一下这个链接”而你的描述里只有“从 URL 提取正文”匹配度可能就不够。我的做法是在描述里把可能的表达方式都枚举一遍抓取、提取、读取网页、保存正文、转 Markdown、总结链接内容全部覆盖。第二技能索引是否正常加载。如果你一次性加载了几十个技能模型“视野”里可能根本没看到这个技能。这时候要检查索引生成逻辑确认 SKILL.md 的摘要被正确收录。第三是否有其他技能“抢活”。如果同时存在一个叫“通用爬虫”的技能描述又写得很宽泛模型很可能优先选那个。这时候不是技能写错了而是技能边界重叠了需要通过修改其中一个的描述来增加区分度。排查完之后我最常用的一招是在 SKILL.md 里加一段“典型用户请求示例”把模型可能收到的原始用户输入直接写进去。实测下来这段示例对触发率的提升立竿见影。4.2 上下文太长、token 消耗太高做对这几步能明显缓解用 Skills 一个普遍抱怨是上下文消耗明显增加。我最早也是直接把所有技能的完整 SKILL.md 都加载进去结果模型还没开始干活光理解技能就烧掉不少 token。后来我调整成两级加载策略。先说方案每个技能目录里增加一个INDEX.md内容只有技能名、用途、触发条件这三项控制在每项一两行。系统启动时只把每个技能的 INDEX.md 拼成一个总索引塞进上下文。模型判断可能用到某个技能时再递归读取该技能完整的 SKILL.md 和脚本。如果你使用的是有文件读取能力的 Agent 或可以二次开发的应用框架这一步实现起来并不难。另一个有效做法是压缩 SKILL.md 本身。一个常见误区是技能文档写得和教材一样详细其实没必要。留给模型看的文档只需要这是什么、何时用、参数有哪些、入口脚本怎么调、失败怎么办。那些冗长的背景解释和设计考量放到给开发者看的 README 里不要出现在模型上下文里。我把这个原则称为“给模型看的文档要像快捷键说明不要像用户手册”。4.3 技能之间互相“打架”命名和描述边界是关键当技能超过十个之后大概率会遇到一个尴尬场景两个技能似乎都能处理同一个任务。比如我有“网页正文提取”和“链接批量转 Markdown”从功能上看后者应该包含前者但又是完全独立的流程。模型有时候行为飘忽不定今天走这个技能明天走那个技能输出格式都不一样。解决思路有两个。一是功能归并如果两个技能处理的任务高度重叠最好合并成一个用参数区分行为而不是拆成两个让模型自己选。二是描述隔离确认存在重叠但面向不同场景时在各自的“不要使用”部分把边界写死。例如“链接批量转 Markdown”明确写“不处理单个文章链接的提取与总结这种需求请使用 web-extract”反过来web-extract 也写明“不支持批量处理”。还有一种我踩过的坑是脚本输出格式不一致。比如 A 技能输出 JSONB 技能输出纯文本模型处理起来很容易迷茫。建议所有技能的输出尽量统一为要么输出最终结果要么输出带RESULT:前缀的标准格式。这样模型解析起来几乎零成本也方便后续做自动化判断。4.4 路径、权限和跨环境问题比模型逻辑更容易翻车说实话我遇到的技能执行失败绝大多数不是模型的问题而是环境问题。典型的有三类。一类是工作目录不对。模型执行脚本时如果工作目录被改到了其他地方脚本里用相对路径读文件就会失败。我要求在脚本开头规范自身路径把脚本所在目录设为工作目录。第二类是解释器不一致。有些脚本用 Python 3.12 的语法但默认启动的是 3.8 环境直接语法报错。我的做法是在项目根目录维护一份统一的环境说明并且在 SKILL.md 里写明“本技能需要 Python 3.9 及以下依赖包”让模型执行前先检查环境。第三类是权限问题Windows 下尤其明显。脚本有时会被杀毒软件拦截或者需要通过关联程序打开而不是直接执行。处理方式就是换成绝对路径调用解释器例如python3 /path/to/script.py而不是.直接执行。这些环境问题虽然琐碎但它们才是决定技能库能否稳定运行的关键。4.5 排查问题速查表现象优先排查方向常见解法模型不调用技能描述触发词、索引加载、技能重叠补充关键词示例、检查 INDEX、修改边界描述技能执行报错路径、解释器、依赖缺失脚本内显式切换工作目录、统一用绝对路径调用输出格式混乱脚本输出协议不统一统一输出前缀、失败信息打到 stderrtoken 消耗过大全量加载 SKILL.md改为 INDEX 两级索引压缩 SKILL.md 长度技能选错描述边界不清增加“何时不使用”部分合并重叠技能脚本超时缺少超时控制所有网络请求显式设置 timeout增加整体执行超时5. 后续还能怎么玩把技能库变成你自己的外挂大脑说到扩展方向我最近在尝试两件很有意思的事情。一件是把技能库变成“个人自动化流水线”比如接收一封邮件附件后自动调用 CSV 清洗技能再把结果写入数据库整个过程通过一个调度脚本串联起来。另一件是深入探索多技能组合让模型自己编排技能调用顺序先网页提取再数据清洗最后生成报告摘要。这种组合一旦跑通Agent 能处理的任务复杂度会成倍上升。踩过足够多的坑之后我现在的体会是技能库不是越大越好而是要平滑地贴合你的真实工作流。我见过有人一上来就建了 50 个技能结果大多数在吃灰还严重拖累模型性能。我更推荐先把日常使用频率最高的三五个操作做成技能用一段时间迭代稳定了再慢慢加。技能的质量远比赛博数量重要一个精心打磨的核心技能价值顶得上几十个随手写的玩具技能。最后再分享一个我个人的小习惯每过一段时间我会重新读一遍自己写的 SKILL.md尝试以一个完全不知道项目背景的新人视角来审视它。如果我也能通过这份文档准确完成操作说明它够格如果我自己都看得云里雾里那模型更不可能用好它。这种反复打磨才是技能库真正变得好用的开始。
RELATED READING

延伸阅读

更多一线实战笔记与深度复盘,助您持续精进