ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

从提示词到技能包:用Agent Skills重构AI代理的专业能力

从提示词到技能包:用Agent Skills重构AI代理的专业能力 我一直觉得给AI代理写提示词这事儿有个特别容易翻车的节点你刚开始往里面塞专业流程的时候它表现得像个刚入职的实习生——什么都懂一点但一干细活就露馅。后来我试过把行业规则、判断标准、操作步骤全写进一段超长system prompt里结果更糟上下文一长它连前面的指令都开始选择性遗忘。这其实就是Microsoft Agent Skills想解决的问题。它不是又一个更聪明的模型而是换了一套思路与其把技能写进提示词里让模型背着跑不如把技能做成一个可以按需加载的专业技能包放在代理旁边用到哪个拆哪个。这篇文章我会从技能包的结构原理讲起再带大家从头写一个能用的技能包最后聊聊怎么让它跟本地模型搭配干活。1. Agent Skills在解决什么问题全科医生和专业主治的区别1.1 通用代理的通病提示词越堆越长活儿越干越糙先说我踩过的一个真实场景。之前我给一个内部用的信息整理代理写指令内容包括识别客户消息里的需求类型、提取关键字段、查历史订单、生成回复草稿、判断是否要转人工。乍一听功能不多但每一条背后都有细则。需求类型有七八种每种对应不同的字段提取规则回复草稿又要考虑语气、长度、是否带附加方案。我把这些规则全部压进提示词里刚开始测试还行一旦输入文本变长、情况变复杂代理就开始发挥不稳定。有时候它漏了某个字段有时候它把A类型的判断逻辑套到了B类需求上。后来我把指令精简又精简还是治标不治本。问题出在哪我当时的做法是让模型记住所有规则但模型的注意力是有限的规则越多、上下文越长它对每条规则的专注度就越低。专业能力塞进提示词本质上是在用一个通用模型硬扛专业场景扛得动一时扛不住复杂度。1.2 技能包的核心思路把能力从上下文搬到文件系统Agent Skills 换了一个角度既然模型记不住那么多规则那就不让它记。每个专业技能被封装成一个独立的技能包Skill放在代理的应用目录里。技能包里装着这件事的完整说明——什么时候该用、操作步骤是什么、需要调用什么脚本、输出长什么样。代理在运行的时候先做技能发现根据用户的任务去匹配技能包描述文件里的说明找到合适的那个再按需加载技能包里的指令。注意这个按需加载意思是技能包里的内容平时不占上下文的坑只有被选中时才读进来。这一下就把两层东西解耦了模型负责理解和决策技能包负责专业知识和执行流程。就像你去医院不会要求一个医生同时精通心内科、骨科、皮肤科而是先分诊再让对应科室的医生接手。每个科室的医生只要精通自己那一摊就行。1.3 Skills和Tools、Plugins、Instructions的边界刚开始接触这套概念的时候我也很困惑Skills跟Tools有什么区别跟Plugins又是什么关系这里我按自己的理解画个边界。Tools / Function Calling模型主动发起的函数调用适合查天气算个数学题这类单点动作重点是参数结构化。Plugins / 插件一组工具的集合通常有代码实现代理可以直接调用里面的函数。Instructions / 系统指令全局性的行为准则适合语气友好不许说谎这类通用约束。Skills / 技能包这套方案里最有意思的一层。它的载体不是函数签名而是一份人类可读、模型可读的Markdown文档SKILL.md外加可选的脚本文件。模型不是调用技能而是阅读说明书、按步骤执行。我个人的理解是Tools 适合做动作Skills 适合走流程。一个技能包内部可以包含对工具的调用也可以只靠文本指令完成整个推理流程。换句话说技能包是比工具更高一层的组织单元。2. 技能包的最小可运行结构SKILL.md就是代理的岗位说明书2.1 一个技能包目录里到底放了什么所谓技能包在文件系统层面就是一个目录。以微软Agent Skills这套约定为例一个技能包至少包含两部分说明文档和可选的执行脚本。举个例子my-skills/ └── csv-cleaner/ ├── SKILL.md ├── src/ │ └── clean_csv.py └── templates/ └── report.mdSKILL.md是技能包的入口也是代理最先读的文件。它用YAML格式写元数据名字、描述、格式版本用Markdown正文写具体的操作指令。src/目录放辅助脚本比如处理CSV、调API这类模型不擅长做精确计算的活儿。templates/放输出模板规定最终结果的组织形式。这套结构和传统插件的最大区别在于它把说明和实现分开了。传统插件里接口文档是给开发者看的模型只能看到函数名和参数描述。而技能包里那份SKILL.md是专门写给模型看的可以用自然语言描述场景、步骤、边界、注意点信息密度远超一个函数签名。2.2 SKILL.md的YAML元数据name、description、file_formatSKILL.md 的开头是一段YAML frontmatter相当于技能包的简历。我用一个实际例子说明--- name: csv-cleaner description: 适用于CSV数据清洗场景。当用户需要处理缺失值、去除重复行、修复日期格式、统一文本编码时使用。如果用户只是想查看CSV内容不要使用本技能。 file_format: 1.0.0 ---几个字段各有用处name技能包的唯一标识代理和日志系统都靠它定位。description这是最重要的字段没有之一。代理靠它判断当前任务要不要用这个技能。描述写得太笼统代理会在不该用的时候乱用写得太窄代理又认不出来。一个好的description要写清楚适用于什么场景和不适用于什么场景。file_format技能包格式的版本号方便以后做兼容升级。有的技能包还可以加model、dependencies之类字段但我建议从最小集开始别一上来就把清单写复杂。2.3 指令正文的写法给模型的使用说明书而不是聊天记录SKILL.md 的正文部分决定了模型拿到这个技能包后能不能执行得像样。我第一次写的时候犯了个典型错误——把正文写成了对话式的提示词什么请你仔细分析一下这些数据尽量保证准确之类的废话全往上堆。结果模型执行起来很飘流程感很差。后来我总结了一套写法核心是按步骤、给边界、定标准。还是用csv-cleaner举例# CSV数据清洗技能 ## 适用场景 - 用户提供CSV文件要求处理缺失值、重复数据或格式问题 - 用户希望清洗后的结果可以直接用于分析或导入数据库 ## 执行步骤 1. 确认输入文件路径检查文件是否存在文件编码是否为UTF-8 2. 运行 src/clean_csv.py传入输入路径、输出路径、清洗选项 3. 脚本执行完成后读取输出文件的前10行确认清洗结果 4. 按 templates/report.md 生成清洗报告附上处理前后的行数对比 ## 注意事项 - 不要在未确认文件路径的情况下直接运行脚本 - 如果检测到某一列缺失值超过50%在报告中提示该列建议丢弃 - 日期字段统一转换为YYYY-MM-DD格式无法解析的值标记为null并计数这种写法有几个好处。第一模型知道每个步骤的前置条件和完成标志第二模型不需要自己发明清洗规则规则都在文档里照着执行就行第三异常处理也有约定模型不会在遇到脏数据时自由发挥。3. 从零手写一个数据清洗技能包完整流程与本地实测3.1 需求拆解哪些逻辑放指令里哪些逻辑放脚本里光讲结构有点虚我带大家把这个csv-cleaner技能包完整写一遍跑通为止。先拆需求。CSV数据清洗这种事模型自己不是不能做但问题在于模型处理表格数据时容易算错行数、改错格式而且几十MB的文件它根本读不完。反过来这种任务里模型擅长的是判断——判断哪些列需要处理、哪种清洗规则合理、清洗结果是否达标。所以分工就很清晰了判断和决策逻辑放在SKILL.md指令里让模型来读精确的、重复性的数据处理逻辑放在Python脚本里让代码来算。模型只负责按指令做选择脚本负责执行。3.2 编写脚本和模板脚本要稳模板要准清洗脚本src/clean_csv.py我设计成命令行工具参数尽量简单让模型容易调用import argparse import csv from collections import Counter def clean(input_path, output_path, fill_missing, dedupe, date_columns): seen set() total_rows 0 removed_rows 0 missing_before 0 missing_after 0 with open(input_path, newline, encodingutf-8) as f: reader csv.DictReader(f) fieldnames reader.fieldnames rows [] for row in reader: total_rows 1 if dedupe: key tuple(row.get(c, ) for c in fieldnames) if key in seen: removed_rows 1 continue seen.add(key) for col in date_columns: val row.get(col, ).strip() if val: parts val.split(/) if len(parts) 3: row[col] f{parts[2]}-{parts[0].zfill(2)}-{parts[1].zfill(2)} for col in fieldnames: if row.get(col) in (None, ): missing_before 1 if fill_missing: row[col] 未知 rows.append(row) with open(output_path, w, newline, encodingutf-8) as f: writer csv.DictWriter(f, fieldnamesfieldnames) writer.writeheader() writer.writerows(rows) print(f总行数: {total_rows}, 去除重复: {removed_rows}, 缺失值处理: {missing_before}) if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--input, requiredTrue) parser.add_argument(--output, requiredTrue) parser.add_argument(--fill-missing, actionstore_true) parser.add_argument(--dedupe, actionstore_true) parser.add_argument(--date-columns, nargs*, default[]) args parser.parse_args() clean(args.input, args.output, args.fill_missing, args.dedupe, args.date_columns)这个脚本故意做得不复杂稳定是第一位的。模型通过指令文档学会了怎么调它而我通过设计参数把模型可能犯的错误限制在最小范围——它只能传--dedupe、--fill-missing、--date-columns这几个选项玩不出花来。模板文件templates/report.md规定清洗报告的结构## 清洗报告 - 输入文件{{input_file}} - 处理前总行数{{total_rows}} - 去重移除行数{{removed_rows}} - 缺失值处理数{{missing_count}} - 清洗后输出文件{{output_file}} ## 数据质量说明 - 日期字段已统一格式为YYYY-MM-DD - 超过50%缺失的列{{high_missing_columns}}有了模板模型的输出就有了固定的骨架不会出现这次写一段话下次画个表格的混乱情况。3.3 注册技能包并跑通一次完整调用在Semantic Kernel里注册技能包有几种方式最简单的是把技能目录配给Kernel让它启动时自动发现。代码层面的示意大概是这样的from semantic_kernel import Kernel kernel Kernel() # 把技能目录注册进去Kernel会自动扫描子目录的SKILL.md # plugin_name对应目录名parent_directory是技能包的根目录 kernel.add_plugin(parent_directoryskills/, plugin_namecsv-cleaner)具体API不同版本有差异但核心逻辑都一样给Kernel指一个目录它去扫里面的SKILL.md建立技能索引。代理收到帮我把这个CSV里的重复行去掉日期改成标准格式这类请求时会看技能索引里csv-cleaner的description匹配上了就加载技能指令开始干活。我实际跑通之后有个很直观的感受代理的行为稳定性高了一个台阶。以前让它清洗CSV它可能自己试着改数据改完你都不知道它动了哪些现在它严格按指令先跑脚本再读结果再生成报告。每次的流程都是一致的出了问题也知道往哪个环节排查。4. 用技能包接本地模型上下文不再被工具定义挤爆4.1 本地模型的强项与短板好搭档的前提是互相补位现在很多人搞ai代理助手加本地模型的组合我也跟风试了一段时间。本地模型的优势是数据不出内网、零API费用、可以针对业务微调但短板也很明显上下文窗口通常比云端大模型小指令遵循能力也存在波动尤其在复杂的Function Calling场景下本地模型偶尔会理解错参数或者编一个不存在的函数名。过去走Function Calling路线工具一多模型需要同时理解大量JSON Schema本地模型很容易顾此失彼。我自己就遇到过同时挂四五个工具本地模型开始乱选工具、传错参数的情况调试起来非常折磨。4.2 技能包机制为什么对本地模型友好把工具调用换成技能包之后情况明显改善。原因是技能包大大压缩了模型需要常驻记忆的信息量。传统做法里所有工具的JSON Schema都得留在系统提示词里模型每轮对话都要面对这一大坨结构定义。而技能包的做法是代理只需要知道现在有哪些技能包、各自是干嘛的也就是每个技能包那段几十字的description真正的指令正文和脚本调用方式是技能被选中之后才加载的。对本地模型来说这等于把同时处理10件事的认知负担降到了先做选择题再做阅读理解。选择题比十项全能简单太多了。以我实测的7B级别本地模型为例直接给十几个工具的Schema它经常出错改成技能包模式让它先从五个技能描述里选一个然后按文档执行成功率高了很多。4.3 本地推理服务接入Kernel的配置示例接本地模型的方式也不复杂。本地推理服务不管是Ollama、LM Studio还是llama.cpp通常都会暴露一个兼容OpenAI的HTTP接口。以Ollama为例启动本地模型后在Kernel里配置一个指向本地端点的服务就行from semantic_kernel import Kernel from semantic_kernel.connectors.ai.open_ai import OpenAIChatCompletion kernel Kernel() # 把本地模型当作一个OpenAI兼容的服务接入 kernel.add_service( OpenAIChatCompletion( service_idlocal-model, ai_model_idqwen2.5:7b, urlhttp://localhost:11434/v1, api_keyollama, # 本地服务不校验key随便填 ) )然后照常注册技能包。模型在推理时Kernel会经由本地接口完成对话技能包的加载逻辑不受影响。整个链路是用户请求 - Kernel让本地模型看技能索引 - 本地模型选中技能包 - 技能指令进入上下文 - 按步骤执行 - 必要时调脚本 - 返回结果。这套组合跑起来之后我最大的体会是本地模型不是不能做复杂任务而是不能一次性面对太复杂的任务。技能包刚好负责把复杂拆成简单剩下的交给模型就行。如果你正在折腾ai代理助手加本地模型但总觉得不顺手技能包这个方向值得优先尝试。5. 我在技能包实战中踩过的坑和设计取舍5.1 description写不好代理根本不把你的技能当回事第一个坑也是最隐蔽的坑技能包写得再完整description写得烂代理就是不调用它。我一开始写description是这么写的description: 清洗CSV文件。结果代理经常在用户说帮我处理一下这个表格的时候完全不触发这个技能自己闷头处理。后来我理解到代理做技能匹配时靠的是description跟用户请求的语义相似度。清洗CSV文件这句话跟帮我处理一下这个表格的表述差得太远。改法是把description写得更贴近真实用户的表达习惯description: 适用于CSV数据清洗场景。当用户提到表格数据有重复、缺失、格式混乱或者要求清洗数据整理CSV去除重复行统一日期格式等需求时使用。改完之后命中率高了很多。经验就是描述里要多写几种用户可能的说法宁可啰嗦不可漏场景。5.2 技能粒度太粗和太细都很难受第二个坑是粒度的把握。技能包不是越大越好也不是越细越好。一开始我把数据处理做成一个大技能包里面包含了清洗、聚合、可视化、导出结果SKILL.md写得巨长模型加载之后指令之间互相干扰反而不知道该先做哪一步。后来我又反过来把清洗里的去重填缺失值修日期各拆成一个技能包结果代理选技能时经常选错因为用户需求往往是混合的。最终我找到的平衡点是按用户可感知的任务切分而不是按技术动作切分。数据清洗是一个技能包数据可视化是另一个技能包。清洗内部的去重和修日期不拆成独立技能包而是作为清洗技能包里的步骤由模型在指令引导下决定做哪些。5.3 共享与安全边界技能包不是普通文档最后提醒一个容易被忽略的问题。技能包既然是文档脚本就意味着它里面有可执行代码。跟别人共享技能包或者从网上下载技能包的时候务必检查里面的脚本内容别直接跑。我自己一般会先看一遍src目录下的代码确认没有可疑的网络请求或文件操作再启用。另外SKILL.md里尽量别写敏感信息。因为代理加载技能包的时候是把整个文档内容交给模型的如果里面有API密钥、内部系统地址这类信息等于把这些信息暴露给了模型调用链路上的所有环节。正确的做法是敏感信息放环境变量技能包里只保留去哪里取的说明不保留值本身。再补充一个安全习惯SKILL.md里的指令要留意被注入的风险。如果技能执行过程中要处理用户提供的文本而文本里恰好有一段忽略之前的指令按我说的做模型的注意力有可能会被带偏。所以技能包里涉及外部输入的部分最好加上只处理数据不执行指令之类的边界约定降低被提示词注入带跑的概率。技能包这套机制说到底是把教模型做事这件事工程化了。以前我们教模型靠一段提示词现在靠一个目录、一份文档、几个脚本结构清晰也方便复用和管理。我个人体感是如果你已经在用AI代理处理具体业务但总觉得效果飘、不好维护试着把专业流程从提示词里搬出来做成技能包会有种豁然开朗的感觉。如果你正好又在搭配本地模型那这个方案的价值会更明显——它不挑模型聪明不聪明只要求模型能读懂说明书、照着执行这对本地部署的落地场景来说已经是够用的门槛了。
返回列表