
这段时间我在折腾AI Agent落地绕来绕去最后卡在一个词上skills。如果你也在玩Agent相关的东西应该能感觉到光靠一个巨大的system prompt已经越来越难让模型稳定完成特定任务了。Skills这个概念的走红不是偶然它本质上是把“某个领域里怎么做一件事”沉淀成独立、可复用、可测试的能力包。这篇文章我想用一个完整案例把“商品信息抽取Skills”从设计、实现到调试的全过程拆开来讲过程中会讲清楚设计方案背后的取舍也把踩过的坑一并交代了。先说清楚这篇文章适合谁如果你想给Agent加“垂直技能”又不想每次都在Prompt里堆一堆逻辑或者你已经在用各类Agent框架但对技能包的内部结构还比较模糊再或者你纯粹好奇为什么“会聊天”的模型能变成“会干活”的工具——这篇都能给你一个清晰的参考路径。1. 先搞清楚Skills到底是个什么东西1.1 一个类比新实习生与岗位手册把Agent想象成一个刚入职的实习生。第一天你丢给它一叠公司资料它会懵但如果给它一本《岗位工作手册》里面写清楚“什么场景用什么流程、用哪些工具、每一步做到什么标准”它很快就能上手。Skills干的就是这件事。没有Skills的Agent就像只有“通用常识”的实习生你问它什么它都懂一点但真要独立处理一个专业任务它就露怯了。有了SkillsAgent相当于拿到了“专项操作手册”它知道什么情况下该翻开这本手册手册里规定了先做什么、后做什么、用什么工具、输出什么格式。这套机制的价值在于常识是模型自带的但“专业能力”是需要后天装配的。我在实践中最大的感受是Skills并不是一个“锦上添花”的配置而是决定Agent能不能从“玩具”变成“工具”的关键分水岭。原因很简单越是垂直的任务越需要稳定的流程和标准化的输出而这些恰恰是通用对话模型不擅长的。1.2 为什么不能把“技能逻辑”直接写进Prompt里有人会说既然Skills本质上也是一段指令那我直接写进System Prompt不就行了理论上可以但实际操作中你会碰到一堆问题。维度Prompt里硬写独立Skills维护性改一处逻辑可能牵连全局每次都要重新调试长Prompt局部修改、独立发布不影响其他逻辑复用性复制粘贴到另一个Agent时所有相关规则都得跟着搬直接挂载到目标Agent上即插即用可测试性只能靠手工对话验证改完心里没底可以建测试集改完自动跑回归上下文开销每次对话都全量加载Token消耗大按需触发触发时才加载对应指令故障排查出了问题不知道是指令问题还是模型问题有明确的输入输出边界定位速度快这个对比不是纸上谈兵是我在真实项目里换过一遍之后的体会。早期我习惯把某个任务的所有规则都堆在System Prompt里一开始还能跑通但一旦任务复杂到十几个步骤、几十条规则Prompt就变得极其脆弱——模型会出现“后半段规则记不住”“不同规则互相矛盾”之类的情况。后来我把这些逻辑拆成独立的Skills一次只加载一个技能包稳定性一下子提升了不少。2. 拆开Skills的壳一份合格技能包的完整构成一份能稳定运行的Skills通常包含四个核心部分描述文件、指令模板、工具函数、校验与反馈。缺少任何一个都会在实际使用中暴露问题。2.1 描述文件给Agent看的“简历”描述文件是整个Skills的门面Agent靠它来判断“当前这个任务我该不该出手”。它一般是一段YAML或JSON格式的元数据包含技能名称、描述、标签、版本号等。name: product_info_extractor description: - 从电商商品详情页URL中提取结构化商品数据适用于标题、价格、规格、库存、 评价等字段的抽取。当用户给出商品链接并希望获取商品信息时使用。 tags: - e-commerce ->def fetch_product_page(url: str) - str: 获取商品页面HTML处理超时与反爬 # 实际代码中需要处理请求头、超时重试、代理等细节 ... def extract_fields(html: str, selectors: dict) - dict: 按CSS选择器提取指定字段返回精简结构化数据 ...这里面有个工程细节函数签名一定要写清楚参数说明。LLM在调用工具时会根据函数名和参数注释来生成调用参数。如果参数说明写得模糊模型就可能传错格式。2.4 校验与反馈最后的质检员这部分是很多人会忽略的但我认为它是Skill能否长期稳定运行的关键。LLM的输出有随机性同一个输入在不同次调用里可能产生不同的结果。校验层就是给LLM的输出加一层保险。校验层通常做三件事格式校验用JSON Schema检查输出是否符合预期结构字段校验检查必填字段是否有值、值域是否合理自动回退如果校验失败可以用错误信息作为反馈让LLM重新生成一次{ type: object, required: [title, price, availability], properties: { title: {type: string, minLength: 1}, price: {type: number, minimum: 0}, availability: {enum: [in_stock, out_of_stock, pre_order]} } }校验失败时的回退策略要结合业务场景来设计。我的经验是最多允许LLM重试一次如果第二次仍失败就返回结构化错误比如{error: extract_failed, reason: ...}比让它硬编一个错误结果要诚实得多。3. 从零实现一个商品信息抽取Skills理论说了不少这一节咱们动手做一个完整的技能包。我以“商品信息抽取”为例从目标定义到测试调试的全过程把每个环节的关键决策都讲透。3.1 明确目标与边界在写任何代码之前先定义清楚这个技能要解决什么问题输入一个商品详情页的URL输出结构化的JSON包含标题、价格、规格、库存、评价等信息不做什么不做比价、不做商品推荐、不生成营销文案边界很重要。我见过太多技能包做着做着就开始“画蛇添足”明明用户只是要商品信息技能跑完之后还要附带一段“这款商品性价比很高”的废话。这种输出在演示时看着挺像回事但一旦接入自动化流程就是干扰项。3.2 写描述文件的实操要点描述文件我通常控制在15行以内。太长了Agent可能抓不住重点太短了又容易触发不精准。核心就是一句话让意图识别机制在0.5秒内判断出“这个技能要不要上场”。name: product_info_extractor description: - 从电商商品详情页URL中提取结构化商品数据适用于标题、价格、规格、库存、 评价等字段的抽取。当用户给出商品链接并希望获取商品信息时使用。 不处理比价、推荐、营销文案生成类需求。 tags: - e-commerce ->import requests from bs4 import BeautifulSoup def fetch_product_page(url: str, timeout: int 10) - str: 获取商品页面HTML带超时与重试机制 headers { User-Agent: Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 } for attempt in range(3): try: resp requests.get(url, headersheaders, timeouttimeout) resp.raise_for_status() return resp.text except requests.RequestException: if attempt 2: raise ... def extract_fields(html: str, selectors: dict) - dict: 按CSS选择器提取字段返回精简数据 soup BeautifulSoup(html, html.parser) result {} for field, selector in selectors.items(): elem soup.select_one(selector) result[field] elem.get_text(stripTrue) if elem else None return result真正运行的时候还需要处理验证码、动态加载、接口签名等问题这就需要根据具体站点来适配了。核心思路不变让工具函数返回给LLM的内容尽量小、尽量结构化。校验层放在工具调用之后、最终输出之前。我的做法是写一个专门的校验方法先跑JSON Schema再人工检查几个关键字段的非空条件。校验不通过时把错误信息拼接成一条反馈给模型重试一次。3.5 本地测试与调试流程测试Skill和测试普通代码的最大区别是你无法保证同样的输入永远得到同样的输出。所以我的做法是建一个很小的测试集固定三个典型用例正常商品页所有字段完整验证基本抽取能力缺货商品页availability应为out_of_stock验证边界处理反爬页面验证超时重试、错误输出的兜底跑测试的时候我会打开日志观察几个关键点技能有没有被触发、模型调用了哪些工具、工具返回了什么、最终输出的JSON是什么样的。日志这一步特别关键因为Skill的故障很多时候发生在“链条中间”光看最终输出很难定位是哪一环出了问题。调试过程中有个超实用的参数调整把temperature调低。抽取类任务本质上不需要“创造力”temperature设为0.1左右可以让模型输出更稳定。max_tokens也要控制好我一般给1024防止模型“话痨”在JSON后面加解释。4. 实战踩坑实录高频问题与排查技巧写Skill这件事光看教程是不够的很多问题只有自己踩一遍才知道怎么处理。这一节把我在实战中遇到的几类高频问题整理出来每一个都附上排查思路和解决方案。4.1 技能不触发描述文件的关键词失配现象用户明明给了一个商品链接也说了“帮我提取信息”但Agent就是不调用这个Skill反而凭裸模型的能力硬答。排查思路先打开Agent的意图识别日志看看它对用户输入的标签判断是什么。如果判断结果里根本没有“提取”“商品信息”这类标签说明描述文件里的触发词和用户实际表达不匹配。解决方案在description里加入等价触发词。比如用户可能会说“看看这个多少钱”“读一下这个商品的参数”“把这个链接里的信息整理出来”这些口语化表达都要预判到。另一个常见原因就是description太长核心触发词被埋在末尾Agent在判断时没有充分关注到。把触发词提到前两行问题往往就解决了。4.2 字段输出不稳定校验层和Few-shot的双重兜底现象title每次都能抽取出来但price时不时变成null或者把“原价”当成了“到手价”。这类问题有两个层面。第一层是页面结构变化代码选择器没匹配到对应元素。这个要用工具函数层面的容错来解决比如多备几个选择器按优先级尝试。第二层是模型对字段语义的理解偏差。这种情况下我会在指令模板里增加针对该字段的few-shot示例明确展示“原价的数值长这样到手价的数值长那样”。我的实测结论是每增加一个针对性示例该字段的准确率都会肉眼可见地提升。4.3 工具函数异常导致死循环现象页面无法访问fetch_product_page抛异常Agent发现调用失败后又尝试调用一次再失败再试陷入死循环。根因指令模板里没有说明“遇到异常该怎么办”。模型只知道“要完成提取任务”不知道“提取不了时可以放弃”于是只能反复重试。解决方案在指令模板里明确写一条规则——“如果 fetch_product_page 调用失败直接输出 {error: page_unreachable}不要重试”。同时在工具函数层面把异常捕获转换成返回值而不是抛异常让模型接收到的是一个明确的错误信号。双管齐下之后这个问题几乎不会再出现。4.4 长指令下的“注意力稀释”问题现象技能包一开始版本内容不多能稳定工作。随着需求增加往指令模板里加越来越多的规则结果发现后面的规则经常被模型忽略。这是大语言模型的通病对长上下文的注意力不是均匀分布的越靠后的内容被执行的概率越低。解决办法是把重要规则提前把“必须无条件执行”的规则放在指令开头把一些补充性的说明放到后面。还有一个技巧在指令末尾加一个“输出前检查清单”让模型在生成结果之前逐项确认能在一定程度上缓解尾部指令被忽视的问题。4.5 常见问题速查表现象可能原因处理方案Skill完全不触发描述文件触发词缺失或埋太深精简描述、高频触发词前置偶尔触发但不稳定description和用户表达语义匹配度不够增加等价表达词字段抽取错误页面结构变化或模型理解偏差更新选择器、增加few-shot示例必填字段为null工具函数没抓到或模型不确定校验层加回退、重试一次工具调用死循环异常场景无兜底规则指令中明确“失败即输出错误”输出带额外文字输出格式约束不到位强调“仅输出JSON”并配合校验层长指令尾部失效注意力稀释核心规则前置末尾加检查清单5. 进阶心得Skill的管理与迭代技能包写出来只是开始真正体现工程水平的是后续的管理和迭代。这里分享三个我在实践中摸索出的经验希望能帮大家少走弯路。5.1 一个Skill一个仓库Skill和普通代码一样需要版本管理。我的做法是每个Skill单独建一个仓库语义化版本号管理每次修改bump一个小版本。好处是回滚方便而且可以清晰地追溯“这个版本为什么抽取质量下降”。有一次我给某个技能加了两个新字段结果老字段的抽取准确率反而掉了就是因为新指令干扰了模型对旧字段的理解。有了版本控制一行命令就能回滚到稳定版本。5.2 用测试集代替口头验证“我试了两个链接没问题啊”这种验证方式是最危险的。一个Skill服务的是几十上百种页面形态两个用例远远不够。我建议至少准备5到10个测试用例覆盖正常、异常、边界等场景。每次修改Skill之后统一跑一遍测试集看成功率和字段准确率的变化。这个习惯养成之后你会发现“改了A却弄坏了B”的情况能提前被发现。5.3 命名规范与标签体系技能包多起来之后命名和标签就是管理效率的关键。我习惯用“动词_对象”的格式比如extract_product_info、summarize_review做到“看一眼名字就知道它是干嘛的”。标签体系则按业务域划分电商相关的技能统一打e-commerce标签内容生成类统一打content-generation。这套体系建立起来之后团队协作时的沟通成本会直线下降。最后再分享一个小技巧给每个Skill写一个简短的README记录设计初衷、适用边界、依赖清单和维护人的想法。等三个月后你回头看自己写的技能包会感谢当初那个愿意写文档的自己。我在实际使用中另一个很深的体会是Skills的“边界感”比内容更重要。明确告诉模型“什么时候不要用我”往往比教它“怎么做”更能减少错误触发。一开始我总想着把规则写全结果技能包越做越臃肿排查问题越来越难。后来学会做减法只留下最核心的指令和最必要的规则质量和稳定性反而上去了。Skills这套东西并不复杂但做好它需要的是反复打磨的耐心以及一套支持你持续迭代的工程习惯。