首页 / 资讯中心 / 文章详情

Agent Skills实战:给大模型封装一双能干活的手

Agent Skills实战:给大模型封装一双能干活的手 ★ FEATURED ARTICLE
1. Agent真正缺的不是大脑是一双能干活的手前段时间跟一个做内容工具的朋友聊天他兴致勃勃地跟我展示他们刚上线的AI助手说模型多聪明、上下文多长、生成多流畅。我随手丢了个任务过去帮我把这个网页里的正文抓下来去掉导航和广告转成干净的Markdown存档。结果助手回了一段很得体的歉意——它说它非常理解这个需求然后给了我一篇八百字的操作指南教我自己怎么手动复制粘贴。问题出在哪出在大多数Agent只有嘴没有手。这就是agent-skills这个概念最近在圈子里越聊越多的核心原因。所谓的agent-skills通俗讲就是一套让大模型真正具备执行能力的技能库把模型能理解的意图翻译成它能调用的真实工具动作再把这一整套动作封装成可复用、可插拔、可共享的标准化模块。你可以把它想象成给Agent配了一套乐高工具包想让它整理网页就给它网页转Markdown这块积木想让它批量处理文件就给它文件批处理这块积木。模型负责指挥技能负责动手。这个思路跟传统的function calling、tool use一比差别还挺明显的。传统做法更像是临时工——模型现用现查每个任务都要重新描述一遍工具怎么用、参数怎么传调试一次痛苦一次。skills的思路则是正式员工——把一类操作沉淀成固定岗位说明书SKILL.md模型一看就知道这个技能是干什么的、什么时候该叫它、参数怎么给调用成本和稳定性都有本质提升。我后来自己动手把几个常用能力封装成了技能跑了大概一个月的真实任务这篇文章就把整个过程中的设计思路、踩坑经历和排查链路原原本本分享出来。如果你也在做Agent应用或者正准备给自己的助手加一双手这篇应该能帮你省掉不少弯路。2. 技能不是写个脚本那么简单它是一套完整的岗位说明书在动手封装第一个技能之前我一直有个误区以为所谓的skill就是把一段能跑的代码丢给模型就行。直到实际跑了几个任务才发现模型需要的不只是工具而是怎么用工具的完整说明否则它根本不知道该在什么时机调用、参数怎么填、结果怎么判断。2.1 一个技能的标准四段式结构我研究了市面上几个主流Agent技能项目的组织方式发现做得好的技能包基本都是四件套组成作用类比SKILL.md技能说明书告诉模型这个技能能干什么、怎么用、参数怎么定义岗位职责描述可执行脚本真正干活的那段代码Python、Shell、Node都行员工的双手参数Schema定义入参格式、类型、必填项交接单验证元数据可选技能版本、依赖、适用场景员工档案这里面最容易被忽略但也最关键的是SKILL.md。它不是写给程序员看的文档是写给模型看的使用手册。模型的推理能力再强如果没有一份清晰、没有歧义的说明书它就只能靠猜。2.2 SKILL.md里最重要的三个字段我踩过不少坑之后总结出来三个字段是一定要写清楚的name技能名。看起来简单但命名是有讲究的。我之前有个技能叫web_extractor模型在两个任务里分别把它理解成了提取网页标题和抓取整站内容后来改成fetch_webpage_as_markdown带上了动作和产出物误调用的概率立刻降了很多。命名里最好直接包含输入是什么、输出是什么别搞文艺范。description何时使用。这个字段直接决定模型会不会在正确的时机调用你。最忌讳写这是一个网页抓取工具这种废话。正确写法是给出明确的触发条件比如当用户请求将网页内容保存为Markdown格式或需要提取网页正文、去除广告导航时使用。不适用于解析本地PDF文件、抓取需要登录的页面。把适用和不适用都写清楚模型才能判断该不该用。parameters参数定义。这里有个容易犯的错——按程序员的习惯用短变量名。我的第一个技能参数叫q想着是query的缩写结果模型传参的时候时不时就会犹豫甚至传错。改成query_url、output_format这种语义完整的命名之后传参成功率肉眼可见地提高了。参数描述里还要写明格式要求比如必须是完整的http或https链接不支持相对路径这能省掉后面一长串的报错排查。2.3 脚本是模型的手但大脑始终是模型还要强调一点技能里的脚本不需要聪明需要的是稳定。你的脚本只是模型执行动作的通道真正的判断、规划、文本理解都在模型侧完成。所以脚本的输入输出一定要简单粗暴输入清晰明确的参数输出结构化的结果纯文本、JSON、或标准Markdown文件路径别在脚本里做复杂的条件分支更别让脚本自己智能决策。复杂逻辑放到模型那里脚本就是一把螺丝刀不是瑞士军刀用好一个动作就够了。3. 实战把一个网页内容整理需求封装成可用技能光讲结构有点虚我拿一个真实跑通了的技能——网页转Markdown整理归档来完整走一遍。这个技能在我平时的工作流里出现频率最高很多素材收集、内容存档的需求都会用到。3.1 先想清楚边界什么时候该用技能什么时候让模型直接答动手前得先划清边界。像帮我总结一下这个网页讲了什么这种事根本不需要技能模型自己就能读URL内容然后总结。真正需要技能的场景是要把网页变成什么产物——存成文件、转成结构化格式、批量处理——也就是需要动手操作的时候。所以我在SKILL.md的description里是这样写的name: fetch_webpage_as_markdown description: 将用户指定的网页抓取为结构化Markdown文件并保存到指定目录。 当用户需要存档、整理、下载网页内容为可编辑文本或要求把网页转成markdown、 保存这篇文章时使用。不适合于网页内容已通过其他API获取的场景。 parameters: type: object properties: query_url: type: string description: 完整的网页链接必须以 https:// 开头不支持相对路径。 save_dir: type: string description: 保存文件的目录路径默认 ./downloads default: ./downloads required: - query_url这个description写出来的效果是模型一旦识别到存、转、下载这类动作意图就会调技能否则它自己读内容直接答不会打扰技能。这就是边界感。3.2 脚本实现稳定优先别加花活脚本我选Python原因很简单requests、BeautifulSoup、html2text这些库生态成熟谁的环境里都能装出问题的概率最小。核心逻辑其实短得可怜import sys, json, os, argparse import requests from bs4 import BeautifulSoup import html2text def fetch_and_convert(url: str, save_dir: str) - dict: headers {User-Agent: Mozilla/5.0 (compatible; AgentSkill/1.0)} resp requests.get(url, headersheaders, timeout20) resp.raise_for_status() soup BeautifulSoup(resp.text, html.parser) for tag in soup([script, style, nav, footer, aside]): tag.decompose() # 尝试定位正文主体找不到就退回全量 main soup.find(article) or soup.find(main) or soup.body converter html2text.HTML2Text() converter.ignore_links False converter.body_width 0 md converter.handle(str(main)) os.makedirs(save_dir, exist_okTrue) slug url.split(/)[-1][:80] or index path os.path.join(save_dir, f{slug}.md) with open(path, w, encodingutf-8) as f: f.write(f# 来源: {url}\n\n{md}) return {status: ok, path: path, char_len: len(md)} if __name__ __main__: p argparse.ArgumentParser() p.add_argument(--query_url, requiredTrue) p.add_argument(--save_dir, default./downloads) args p.parse_args() result fetch_and_convert(args.query_url, args.save_dir) print(json.dumps(result, ensure_asciiFalse))几个细节是试出来的教训值得记一笔User-Agent一定要伪装。很多站点对requests裸访问直接403带上浏览器UA之后再没遇到过这种问题。先剥掉script/style/nav/footer再转Markdown。不加这一步html2text会把一堆导航链接和页脚信息也带进正文输出文件脏得没法看。输出直接打JSON。我在设计技能时定了一条原则脚本与控制台交互只用JSON模型解析起来零成本。别打一堆可读性文本让模型去猜状态JSON里status字段一把梭正确还是失败一目了然。3.3 给模型兜底异常处理也要写在说明里这一步是很多初做技能的人会忽略的。脚本虽然能正常抓取但总会有例外情况目标网页404了、反爬拦截了、超时了。这些异常脚本会抛给模型模型如果没见过这种报错格式就会开始胡言乱语。我的做法是在SKILL.md最后加一节错误处理注意事项明确告诉模型当脚本返回 status 非 ok 时把 error 字段原样转述给用户不要尝试修复URL。 如果返回的是 404告诉用户页面不存在如果是 403说明站点有反爬建议用户换一个来源。这一步等于给模型一份售后处理预案模型在异常状态下的反应会从自由发挥变成按流程走稳定性能上一个台阶。4. 第一次真实调用就翻车了一条完整的排错链路技能封装好了之后我满怀信心地让它去抓一个平时常看的行业资讯页。结果第一跑就翻车了而且翻得很典型。整个过程排查下来我觉得这条链路本身就很有参考价值放出来给大家当一个排错demo。4.1 翻车现场日志里躺着一行诡异的参数我把任务发过去把这篇XX网的文章存成Markdown放桌面。Agent调用了fetch_webpage_as_markdown日志里显示它传入的参数是这样的{ query_url: https://xxx.example.com/articles/2024/01/15/hello-world, save_dir: ~ }我桌面路径是 /Users/me/Desktop可它传的是~。窗口没展开脚本里的os.makedirs(~)直接在当前工作目录下建了个名为~的文件夹文件没落地到桌面。我第一反应是脚本不健壮得做路径展开。但想了想不对问题根源不在脚本在于SKILL.md里没有告诉模型参数必须是绝对路径不支持~等shell缩写。4.2 逐层回溯不是脚本问题是说明书有歧义我要强调一个排查原则Agent技能调用的错误七成以上不在代码层而在说明书层。代码报错只是表象真正的问题是模型没得到足够清晰的约束。按照这个思路我当时按三层逐步排查先排除脚本本身——单独用正确参数跑能抓到能存到脚本没问题。这层排除之后焦点立刻转移到模型调用环节。再看参数来源——模型是从用户的那句话里提取的路径。用户的自然语言说的是桌面模型做了个合理但不准确的推断把~当成桌面的缩写输出了。这不是模型的智力问题是我的schema描述里没有规定save_dir必须是绝对路径且必须由真实工作目录推导。最后检查SKILL.md的parameters描述——老实说我的parameters里save_dir的描述确实只写了保存文件目录路径默认./downloads对路径格式完全没约束。说明书给了模型太大的自由裁量空间它就按着最省事的方式发挥了。4.3 修复让说明书不给模型留自作主张的空间定位到根因后我把parameters里save_dir的描述改成这样save_dir: type: string description: 输出目录的绝对路径必须先进行路径规范化如将~扩为完整用户目录。 如果用户表达的是桌面等常见目录请映射为对应绝对路径。禁止传入~或.等相对缩写。同时我还在脚本里加了一道保险save_dir os.path.abspath(os.path.expanduser(args.save_dir))两道保险下去再跑同样的任务Agent传的是 /Users/me/Desktop脚本也能兜底处理异常输入。至此这条排错链路才算完整走完。4.4 这个坑后来反复出现过泛化的教训这个路径问题看着是个小case但它背后的模式其实很常见。我后来又遇到过多例类似的模型自作主张翻车description里写了提取文件标题没说提取的是HTML的title标签模型擅自把h1当成标题。参数命名用了target模型在多个场景下分别理解为URL、文件路径、甚至文件夹名。技能说明里没写处理完成后必须输出文件绝对路径模型返回了文件已保存四个字下游流程全断。这类问题的修复思路完全一样给模型的每一条约束都要具体到无法二义理解的程度宁可我写说明书时多花两分钟不让模型调用时报错两小时。我把这条经验固化成了技能封装的checklist每次新建技能之前先过一遍后面翻车率确实降了一个量级。5. 从单技能到技能链Agent工程化绕不开的进阶问题单个技能跑通只是第一步。真正在项目里实战你会发现Agent一次任务往往需要连续调用多个技能这时候技能与技能之间的配合方式才是决定整个系统是否好用的关键。5.1 技能组合的三种姿势选错会后悔我试下来技能组合基本有三种方式复杂度递增灵活性也递增线性流水线。技能A输出是技能B的输入一个接一个。比如网页转Markdown之后接提取要点摘要。这种姿势最简单缺点是中间结果需要模型来回搬运多一个环节多一次token消耗也多点选参数出错的概率。主技能嵌套子技能。在一个SKILL.md里声明可以调用其他技能。比如做一个周报生成的主技能内部会调读取工作日志、抓取项目进度页、生成结构化周报三个子技能。模型拿到主任务的意图后自己会去规划子技能的调用顺序。这种姿势适合任务本身有固定流程的场景。纯模型自主编排。不预设组合方式给模型一个较大的技能池让它在每步动态决定下一步调什么。灵活性强但不可控对模型能力和说明书质量要求极高。我目前只在探索性项目里用正式工作流还是以有结构的设计为主。5.2 技能之间的交接上下文比代码更脆弱多技能协作最大的坑是上下文丢失。模型调完技能A拿到一堆JSON输出在下一次决策时如果上下文窗口被压缩了就可能在调技能B时传错参数。我处理这个问题的办法是技能输出尽量自包含。每个技能的返回结果里带上这份输出是什么、下一步建议做什么的最小元信息。比如网页转Markdown返回的JSON里除了path、char_len还加上一句summary用模型生成的一行内容摘要这样下游技能拿到这个输出后不用回查之前的对话也能理解上下文。5.3 Skills和MCP的关系很多人把这两个概念搞混了聊到这里很多人会问说了半天这个skill概念和最近炒得火热的MCPModel Context Protocol到底有什么区别我用一句话总结MCP是插座标准skill是插头组合。MCP定义的是工具、资源和模型之间怎么通信、怎么暴露数据是协议层的东西。而skill是业务层的东西它规定的是什么场景下调用哪些MCP工具、按什么顺序、怎么处理结果。换句话说MCP解决的是模型怎么连上工具skill解决的是模型什么时候用哪些工具来完成任务。实际项目里两者不是竞争关系而是配合关系。我通常的做法是用MCP server把远程工具规范化暴露出来然后在skill的SKILL.md里写明这个技能会用到哪些MCP工具。这样既保持了工具的独立性又让技能行为可预测。5.4 什么场景值得沉淀成技能什么场景不值得最后说点务实的建议。不是所有操作都值得封装成技能我给自己定了一个三次原则一个操作如果同类型出现不到三次别急着封装让模型自己发挥就好超过三次才考虑固化成技能。真正的skills思维不是把所有东西都做成技能而是知道哪些能力是高频复用的肌肉记忆把肌肉记忆固化成标准动作剩下的随机应变交给模型。这个度把握好了Agent工程化做起来会舒服很多。我自己跑了一个多月下来最大的感受就是技能库的维护成本大头不在写脚本而在打磨说明书。跟给新同事写交接文档一样你把用词抠到多细你之后返工就有多省。
阅读完成 · 觉得有帮助?
咨询建站