news 2026/9/11 4:29:31

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

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
从提示词到技能包:用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.md

SKILL.md是技能包的入口,也是代理最先读的文件。它用YAML格式写元数据(名字、描述、格式版本),用Markdown正文写具体的操作指令。src/目录放辅助脚本,比如处理CSV、调API这类模型不擅长做"精确计算"的活儿。templates/放输出模板,规定最终结果的组织形式。

这套结构和传统插件的最大区别,在于它把"说明"和"实现"分开了。传统插件里,接口文档是给开发者看的,模型只能看到函数名和参数描述。而技能包里,那份SKILL.md是专门写给模型看的,可以用自然语言描述场景、步骤、边界、注意点,信息密度远超一个函数签名。

2.2 SKILL.md的YAML元数据:name、description、file_format

SKILL.md 的开头是一段YAML frontmatter,相当于技能包的"简历"。我用一个实际例子说明:

--- name: csv-cleaner description: 适用于CSV数据清洗场景。当用户需要处理缺失值、去除重复行、修复日期格式、统一文本编码时使用。如果用户只是想查看CSV内容,不要使用本技能。 file_format: 1.0.0 ---

几个字段各有用处:

  • name:技能包的唯一标识,代理和日志系统都靠它定位。
  • description:这是最重要的字段,没有之一。代理靠它判断"当前任务要不要用这个技能"。描述写得太笼统,代理会在不该用的时候乱用;写得太窄,代理又认不出来。一个好的description要写清楚"适用于什么场景"和"不适用于什么场景"。
  • file_format:技能包格式的版本号,方便以后做兼容升级。

有的技能包还可以加modeldependencies之类字段,但我建议从最小集开始,别一上来就把清单写复杂。

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='', encoding='utf-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='', encoding='utf-8') as f: writer = csv.DictWriter(f, fieldnames=fieldnames) writer.writeheader() writer.writerows(rows) print(f"总行数: {total_rows}, 去除重复: {removed_rows}, 缺失值处理: {missing_before}") if __name__ == '__main__': parser = argparse.ArgumentParser() parser.add_argument('--input', required=True) parser.add_argument('--output', required=True) parser.add_argument('--fill-missing', action='store_true') parser.add_argument('--dedupe', action='store_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_directory="skills/", plugin_name="csv-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_id="local-model", ai_model_id="qwen2.5:7b", url="http://localhost:11434/v1", api_key="ollama", # 本地服务不校验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代理处理具体业务,但总觉得效果飘、不好维护,试着把专业流程从提示词里搬出来,做成技能包,会有种豁然开朗的感觉。如果你正好又在搭配本地模型,那这个方案的价值会更明显——它不挑模型聪明不聪明,只要求模型能读懂说明书、照着执行,这对本地部署的落地场景来说,已经是够用的门槛了。

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/11 4:29:22

Chrome侧边栏WebUSB投屏:免安装替代QtScrcpy

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 4:27:57

Java栈完全指南:从JVM运行时到算法实战与工程实践

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 4:26:48

3 分钟跑通 Maestro 移动测试自动化:Android、iOS 与 Web 的 E2E 指南

3 分钟跑通 Maestro 移动测试自动化:Android、iOS 与 Web 的 E2E 指南 【免费下载链接】Maestro Painless E2E Automation for Mobile and Web 项目地址: https://gitcode.com/GitHub_Trending/ma/Maestro 写过 UI 自动化的人都懂:测试里塞满 sleep(),界面慢半拍就闪挂,…

作者头像 李华
网站建设 2026/9/11 4:25:10

Hyperview Python二次开发:CAE自动化处理实战

1. Hyperview Python二次开发概述Hyperview作为一款专业工程仿真后处理软件,其Python二次开发能力为工程师提供了强大的自动化工具链。通过Python脚本控制Hyperview,我们能够实现模型数据与结果文件的批量导入、处理和分析,大幅提升CAE工程师…

作者头像 李华
网站建设 2026/9/11 4:19:01

多模态大模型开发实战:从模型选型到微调部署全解析

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/11 4:18:50

2K与1080P差异详解:从PPI点距到显卡接口适配,升级前必看

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华