
1. 内容整体设计与思路拆解先说结论Agent Skills 本质上是在给大模型套上一层“确定性外壳”。大模型很聪明但不可控它知道很多知识却经常在真正需要执行具体任务的时候东拉西扯。而技能包Skill就是把你想要它执行的某一类动作包装成一套“描述文件 可执行脚本”的组合。它最大的价值不是让 AI 更“聪明”而是让 AI 更“可靠”。我接触 Agent Skills 这个概念是从一个具体场景开始的我需要让 AI 从一堆非结构化的表格数据里提取客户信息并写入数据库。第一版用纯提示词效果惨不忍睹——模型偶尔会自己发挥把字段名改了甚至编造一条不存在的记录。后来我换了一种思路把“提取字段、校验格式、写库”这个动作做成了三个独立的 Skill每个 Skill 内部有自己的校验脚本LMM大语言模型只负责判断“该调哪个技能”剩下的脏活累活全部交给确定性代码。这一改准确率直接拉到 99% 以上。整篇文章我想按一条完整的实操链路来组织先带你把 Agent Skills 的概念拆清楚然后搭一个最小可运行的 Hello World再一步步写出三个能直接上手的实战技能最后把技能组合成一个能协作的“AI 队友”。为了让你看得懂也抄得走我会把我踩过的坑、弃掉的方案、改过三版的设计决策全部讲出来。这样你读完不仅能跑通 Demo还能知道自己下一步该往哪儿使劲。1.1 核心需求拆解技能包本质上是给模型“立规矩”如果你把 Agent 看作一个刚入职的新人那 Skills 就是他桌面上的“岗位操作手册 辅助工具包”。新人不需要知道公司完整的产品逻辑他只需要在收到任务的时候翻开对应页签照着步骤走实在不确定时再调用一个现成函数。没有 Skill 的裸 Agent 相当于把一个博士生直接扔到流水线上——他懂理论但他不知道你们公司的贴标签流程、不知道数据库的表结构、不知道“通过率”这个指标在你们这里指的是“转化率”而不是“准确率”。所以 Agent Skills 的四个核心层次是意图识别层模型读技能描述判断当前任务和哪个技能匹配。参数提取层模型从用户话术中抽取技能需要的字段。逻辑执行层技能脚本用确定性的代码完成任务不依赖模型的临场发挥。结果回传层脚本把执行结果按约定格式返回给模型模型再做总结或下一步调度。这四个层次里最容易翻车的是参数提取层。模型经常把“2023 年 1 月”提取成“2023-01-01T00:00:00.000Z”或者把“张三”提取成“张先生”。解决思路是给模型提供尽量明确的参数描述同时在技能内部做一层防御性校验——校验不过就返回错误信息让模型自己重试或者向用户确认而不是静默出错。1.2 方案选型为什么是“技能包”而不是“插件”或“提示词”我先对比几个容易混淆的概念。插件Plugin是外部工具的 API 封装负责和第三方系统通信提示词工程Prompt Engineering是纯靠文本引导模型输出而 Agent Skills 介于两者之间它既包含给模型看的自然语言描述又包含给机器跑的确定性代码通常还带一套参数约定和输出模板。用一张表就能看得很清楚方案是否依赖模型判断执行确定性可复用性典型场景纯提示词完全依赖低低闲聊、文本改写、简单问答插件中由框架路由高中调用外部 API、连接数据源Agent Skills中高模型选择、脚本执行高高多步骤业务任务、数据加工、代码操作我做技术选型时特别看重一个指标把不可控的模型调用次数降到最低。纯提示词方案里模型要同时承担“理解任务、规划步骤、执行判断、生成输出”四件事任何一环不稳定都会炸。技能包方案把“执行判断”交回给代码模型只保留“理解任务”和“生成最终答复”两件事。打个比方纯提示词是你让实习生从零做一份财务报表技能包是你把 Excel 模板和取数逻辑都准备好实习生只需要问对问题、填对数字。另外还有一个很实际的考量——团队协作。纯提示词是散落在各个 Agent 配置里的文本很难做版本管理和回归测试。技能包是文件系统里的实体每个技能有独立的目录、版本号、测试用例可以和代码库一起走 CI/CD。这一点在项目规模变大之后非常关键我后面会在实操环节单独展开。2. 动手前准备从 Hello World 技能开始2.1 环境搭建与第一个技能骨架动手之前先把环境准备好。我的推荐组合是Python 3.10 以上 一个支持技能包机制的智能体框架目前主流框架基本标配了 Skills 目录约定再加一个小型向量库用于技能检索可选但推荐。技能包的基本目录结构我建议这样组织skills/ ├── skill_hello/ │ ├── SKILL.md │ ├── skill.py │ └── requirements.txtSKILL.md给模型看的自然语言描述包括技能用途、何时调用、参数含义、输出格式。skill.py技能的实际执行逻辑接收参数、干活、返回结果。requirements.txt技能依赖的第三方库框架会自动安装。这一套结构和 Claude 系框架的 skill 约定保持一致迁移成本很低。你如果之前用过 Anthropic 的 Claude Skills直接沿用它的目录结构和描述规范就行如果用的框架不原生支持也可以自己在系统提示词里注册技能列表让模型知道存在哪些可用技能。2.2 第一个技能给新人准备的“自我介绍”很多人写第一个技能喜欢做个“加法计算器”或者“获取当前时间”我推荐反过来——做一个最简单的文本处理技能让它打印一句问候但要把参数传递和返回格式都完整走一遍。这样你能最快掌握技能的最小闭环。先看SKILL.md怎么写# 技能友好问候 ## 描述 用于生成一条友好的个性化问候语。当用户需要向某人打招呼、欢迎新成员或发送日常问候时使用。 ## 何时使用 - 用户明确要求“打招呼”、“问候”或“欢迎” - 需要生成包含对方名字的问候消息 ## 参数 - name字符串必填问候对象的姓名 - occasion字符串可选问候场景例如“入职”、“生日”、“日常”。默认为“日常”。 ## 输出格式 返回一个 JSON 对象 { status: success, message: 生成的问候语, tone: 友好/正式/轻松 }再看执行脚本skill.pydef greet(name: str, occasion: str 日常) - dict: templates { 日常: f你好{name}今天过得怎么样, 入职: f欢迎加入团队{name}期待和你一起合作。, 生日: f生日快乐{name}祝你今天超开心。, } tone 友好 if occasion 入职: tone 正式 elif occasion 生日: tone 轻松 return { status: success, message: templates.get(occasion, templates[日常]), tone: tone, } def main(params: dict) - dict: name params.get(name) occasion params.get(occasion, 日常) if not name: return {status: error, message: 缺少必填参数name} return greet(name, occasion)这个极简单技能的运行闭环是这样的用户说“帮我欢迎新同事李雷”模型阅读SKILL.md的描述后判断这匹配“友好问候”技能于是抽取参数name李雷、occasion入职调用skill.py拿到返回的 JSON再包装成自然语言回复给用户。第一次跑通这个闭环你会发现 Agent 至少有两个明显变化第一提问“更守规矩”了——比如你问“你叫什么名字”它不会直接回答“我叫 AI”而是可能触发技能条件判断第二回复的内容稳定了——问候语格式由模板控制不会每次措辞都变。2.3 说清楚三个设计关键点我在写第一个技能时踩过三个小坑值得你提前知道参数校验不能只靠模型自觉。模型提取参数时偶尔会鬼迷心窍比如用户说“替我和王总打个招呼”模型可能把name提取成“王总”而不是“王”。所以在技能脚本里一定要做还有没有更稳的方案。最好的做法是参数缺失时返回一个带missing_fields的 JSON让模型看到缺了什么并主动追问用户。输出格式一定要用 JSON。脚本返回给模型的内容需要机器可读让模型容易解析和包装。如果你返回一段长文本模型下一步的总结就会变形——它会自由发挥补充一些你根本没写的细节。JSON 能约束模型的注意力集中在关键字段上。SKILL.md 是给模型读的不是给人读的。你写得越详细模型就越容易准确匹配和调用。我看到很多人把描述写成“此技能用于生成问候语”太泛或者“这个技能包含 10 个函数分别处理不同场景”太复杂。好的做法是列出“何时用、何时不用”的明确条件让模型按提示词招聘广告一样去匹配。3. 核心实现三个能直接上手的实战技能3.1 技能一JSON 数据清洗与校验第一个实战技能我选了超高频且几乎必踩坑的场景——JSON 数据清洗。做 AI 应用的人都会遇到这种情况用户上传一份乱七八糟的 CSV 或半结构化文本你需要提取字段并校验格式。传统方案是写一堆正则和一串 try-except每次改字段都要重新调参技能包方案把它变成可复用的独立能力。这个技能的目标是给定一段原始文本和一个目标字段模板自动完成提取、清洗、校验三个动作输出结构化 JSON。SKILL.md片段# 技能JSON 清洗与校验 ## 描述 从非结构化文本中提取信息并按照用户指定的字段模板生成标准化 JSON。常用于联系人导入、订单信息解析、日志结构化等场景。 ## 参数 - raw_text字符串必填需要解析的原始文本 - fieldsJSON 对象数组必填目标字段列表每个字段包含 name、type、required - type 支持 string、int、email、phone、date ## 输出 { status: success, data: { ... }, warnings: [缺失字段: xxx] }实现逻辑节选关键部分import json, re from datetime import datetime VALIDATORS { email: lambda s: re.match(r^[\w\.-][\w\.-]\.\w$, s) is not None, phone: lambda s: re.match(r^1[3-9]\d{9}$, s) is not None, date: lambda s: __validate_date(s), } def __validate_date(s: str) - bool: try: datetime.strptime(s, %Y-%m-%d) return True except ValueError: return False def extract_and_validate(raw_text: str, fields: list) - dict: # 这里做一个简化依赖模型抽取主实体脚本做格式校验 data {} warnings [] placeholder json.loads(raw_text) # 实际场景 raw_text 不一定是 JSON可能来自 OCR 或语音 for field in fields: val placeholder.get(field[name]) dtype field[type] if val is None: if field.get(required): warnings.append(f缺失字段: {field[name]}) continue if dtype int: try: val int(str(val).replace(,, )) except ValueError: val None warnings.append(f字段 {field[name]} 不是合法整数) elif dtype in (email, phone, date): if not VALIDATORS[dtype](str(val)): val None warnings.append(f字段 {field[name]} 格式非法) data[field[name]] val return {data: data, warnings: warnings}这个技能的设计重点是“校验结果分三类”成功字段、缺失字段、非法字段。单独拎出来一个warnings列表是为了避免数据请求方误以为整个操作都失败了。我在实际项目中遇到过这种情况本来只需要告诉用户“邮箱格式不对”结果脚本抛了异常导致整条流水线崩溃——这就是典型的“坏一处、毁全部”。一个健壮的清洗技能要把“局部失败”当作正常返回而不是异常抛给上一层。和现有工具链怎么配合如果你有现成 AI 项目这个技能可以直接接入它的预处理环节。数据进去之前先过一遍清洗技能模型拿到的是标准化输入输出自然更稳定。如果你用的是低代码平台如 Coze 或 Dify也可以把这套清洗逻辑封装成自定义节点但那个是另一条路线了这里先不展开。3.2 技能二代码变更审查AI Code Reviewer第二个技能我给开发朋友准备的代码变更审查。现在每个人都在聊 AI 编程助手但大部分助手只会“生成代码”真正能“审查代码”的很少。原因是生成代码是创造性任务模型自由发挥空间大审查代码是规范性任务必须依据固定规则这正是技能包的强项。这个技能要做的事情是输入一个 git diff 或一组变更文件脚本拉取 diff 内容并跑一些静态检查然后返回结构化评审意见。设计思路模型负责把用户的问题转成参数repo_path、base_branch、changed_files。技能脚本负责跑git diff、找变更行、运行 pylint 或 eslint 这类既有工具。脚本把工具的绝对输出报错、警告、警告等级、位置结构化后返回给模型让模型再包装成自然语言的评审意见。skill.py核心逻辑import subprocess, json def run_review(repo_path: str, base_branch: str main) - dict: diff_cmd [git, diff, f{base_branch}...HEAD, --name-only] files subprocess.check_output(diff_cmd, cwdrepo_path, textTrue).split() issues [] for f in files: if f.endswith(.py): output subprocess.run( [pylint, --output-formatjson, f], cwdrepo_path, capture_outputTrue, textTrue ) pylint_issues json.loads(output.stdout or []) for item in pylint_issues: issues.append({ file: f, line: item.get(line), severity: error if item[type] error else warning, message: item.get(message) }) return {status: success, issues: issues[:50], total_count: len(issues)}这里有一个关键工程决策不让模型直接判断“这段代码有没有 bug”。模型在审查代码时经常被变量命名迷惑看到名字取得好就误以为逻辑没问题。技能脚本的做法是让静态工具给出硬性信号未定义变量、语法错误、未使用引用等再由模型在“有信号”的基础上做语义分析。等于是把模型从“收到问题再搜记忆”的模式转换成“收到证据再下判断”的模式。顺便说一句代码审查技能还有一个隐藏用途——给 Agent 自己用。当我们构建一个“AI 写代码队友”时Agent 每生成一段代码可以自动触发一次审查技能拿到测试结果和 lint 反馈后再自行修订。这是让 Agent 从“一次性生成”进化到“自我迭代”的关键机制。3.3 技能三会议纪要与任务拆解第三个技能是专门给团队协作场景用的会议纪要自动生成与任务拆解。接一个语音转写文本技能负责把它结构化成摘要、结论、待办事项并且按负责人分组。这个技能的价值在于它可以解决人类整理纪要时的“复述困难症”和“责任踢皮球”问题。实现分为三层模型将转写文本按发言段落切分识别关键词决定、待办、负责人、截止日期。脚本提取出的任务按负责人分堆检查是否有任务缺截止日期。输出固定格式的 Markdown 纪要模板附任务清单。脚本可以主要负责格式校验和重复项合并def organize_minutes(tasks: list[dict], summary: str) - dict: owners {} for t in tasks: owner t.get(owner, 未分配) if owner not in owners: owners[owner] [] owners[owner].append(t) incomplete [t for t in tasks if not t.get(deadline)] return { meeting_summary: summary, grouped_tasks: owners, missing_deadline_count: len(incomplete) }模型在这个流程里做的是理解和归纳脚本做的是分组和完整性检查。这种分工很值得体会模型的任务拆“质”脚本的任务拆“量”——如果 20 条待办里有 3 条没有截止日期脚本直接统计并触发追问模型不需要“意识到”这个问题。在团队里实际用起来之后你会发现一个新的价值这些技能其实是在帮你沉淀团队规范。比如某个组规定所有任务必须有负责人和截止日期这个要求写进了 SKILL.md 和脚本校验里以后每次都强制执行比任何行政命令都好使。3.4 组合拳让多个技能协同工作上面三个技能单独拿出来都能用但它们真正的威力来自组合。我们来设计一个完整的“AI 队友”场景某开发者把一份客户反馈的语音记录扔给 Agent要求“整理成工单并提交给开发组”。这个流程需要三个技能协同会议纪要技能或叫“内容结构化”先把语音内容整理成要点。代码变更审查技能不直接参与但我们可以设计一个后续步骤系统根据纪要自动生成一个变更分支草稿。JSON 清洗技能最后把结构化工单数据落库并用标准格式回传。这种调用链在框架里通常这样注册# 伪代码示例 from agent_framework import Agent, Skills agent Agent(nameteam_helper) agent.register_skill(meeting_minutes) def minutes(params): # 对应会议纪要技能 ... agent.register_skill(json_cleaner) def cleaner(params): # 对应 JSON 清洗技能 ... # 组合先结构化再清洗落库 def handle_voice_note(audio_text: str): minutes agent.call(meeting_minutes, {raw_text: audio_text}) tasks minutes[grouped_tasks] cleaned agent.call(json_cleaner, {raw_text: json.dumps(tasks), fields: [...]}) return cleaned这种组合设计有一个好处每一环都可由独立团队开发和维护。某团队只负责优化清洗技能另一个团队只负责优化纪要模板互不干扰。哪怕其中一个技能崩了其他环节仍然可以单独使用。4. 调试与迭代把技能打磨成“队友”技能从 Hello World 到能干活中间一定要经过多次调试。我总结了一套调试口诀先看日志再改描述最后改代码。4.1 观察日志是调试的第一中心技能运行时的日志里最重要的三样东西模型选择了哪个技能、传入了什么参数、脚本返回了什么结果。大部分问题一眼就能看出来——模型根本没选对技能或者参数传成了字符串而不是数组又或者脚本把 JSON 当成了可执行代码。我在调试阶段养成了一个习惯给每个技能在返回结构里加一个trace字段记下模型的判定理由。例如{ status: success, data: {...}, trace: { model_observation: 用户要求整理客户名单匹配到 json_cleaner 技能, param_extraction: {name: 张三, email: zhangsanexample.com} } }这就像给 Agent 加了黑匣子模型为什么这么判断、有没有犹豫、参数抽取有没有擦边全看得一清二楚。等技能跑稳了再关掉trace节省 token。调试中最高频的问题之一是模型参数抽取不准。常见场景用户说“把这份名单里所有邮箱格式不对的人和缺失手机号的人列出来”模型会把“邮箱格式不对”拆成一个 bool 字段把“缺失手机号”拆成另一个 bool 字段结果脚本根本不知道你在说什么。解决办法是在字段描述里写清楚“这是一个过滤条件不是输出字段”。下面是我整理的一张高频问题速查表现象可能原因排查顺序技能完全不被调用SKILL.md 描述太宽泛模型匹配不上1. 检查描述“何时用”是否明确 2. 检查技能参数名是否太抽象技能被调用但参数错误模型把用户原话直塞1. 检查字段类型是否写全 2. 考虑加示例值脚本抛异常、流水线卡死缺少对非法输入的容错1. 在脚本里包 try-except 2. 返回 error 状态而不是抛异常输出不同场景下格式不一致SKILL.md 输出模板描述不严格1. 在 SKILL.md 里写死字段列表 2. 脚本做一次规范化再返回多个技能互相冲突不同 SKILL 描述边界不清1. 检查各技能“何时使用”是否有重叠 2. 在系统提示词里声明优先级技能调试时不停烧钱模型反复调用同一失败技能1. 加最多重试次数 2. 失败后返回结构化错误信息不循环4.2 把“队友”调教的越用越好用这里分享一个比较进阶的思路技能要学会“自我记录失败”。我通常在技能脚本里加一个简单的事件日志表记录是哪个场景、哪个参数组合失败了。一段时间后把失败样本汇总反哺 SKILL.md 的描述——比如“当用户提到 xx 时不要把它当作 xxx 参数”。这其实是在把隐性的模型犯错经验沉淀为显性的技能边界。举个例子我第一次写 JSON 清洗技能时漏了一种场景用户上传的文本里夹杂着一句“以上信息仅做内部使用请勿外传”。模型每次都会把这句话当成字段内容提取出来导致数据里多一条垃圾记录。我后来在 SKILL.md 外加了一条规则“忽略所有与数据无关的声明性语句”并在脚本里加了一组停用词过滤。从那之后这种错误就再没发生过。这套方法论让我意识到Agent Skills 的维护模式和普通代码不一样普通代码是“改逻辑”技能包是“改边界”——边界越清楚模型的匹配越准输出越稳定。它是你和大模型之间的一个协议你可以把任何一个生产场景沉淀成可复用的技能组件然后让它们组合成你专属的 AI 队友。4.3 性能与成本别让技能包成为“烧钱机器”最后提醒一个大家在 Demo 阶段最容易忽略的问题技能描述越长、参数校验越复杂模型为“决定是否调用”需要读的内容就越多token 消耗自然水涨船高。我见过一个项目把 SKILL.md 写了两千多字每次对话光技能路由就白烧几百 token。我的经验是给 SKILL.md 设一个“只读关键信息”的限制描述控制在 400 字以内参数表最多列 8 个字段输出模板用最精简的 JSON 示例。如果你确实需要详细说明可以单独建一个DESIGN.md放在技能目录里给人类维护者看而 SKILL.md 只保留模型读得动的内容。这就像给同事写交接文档太短他看不懂太长他懒得看真正有价值的是一页纸的“快速上手 边界提醒”。技能包也一样它不是留给 AI 的一份宣言而是你和 AI 之间签的“岗位职责合同”。我个人在实际操作中的体会是Agent Skills 能不能真的变成你的 AI 队友关键不在于单个技能写得多花哨而在于你是否愿意花时间观察模型的行为、调整技能的边界、沉淀失败的经验。技能是一层壳真正的队友是那个不断调校它的你。