手写Agent Skills:从Prompt到可复用智能体技能的完整拆解 逛技术社区的时间久了你会发现一个有趣的现象凡是和 AI Agent 沾边的教程标题一个比一个夸张。七天从小白到大神吊打付费少走 99% 弯路这类话术看多了容易让人疲劳。但抛开包装Agent Skills 这个概念本身确实是值得开发者认真研究的一块内容。吴恩达在 DeepLearning.AI 上推出 Agent Skills 课程后配套的手写风格 PDF 讲义在技术圈里流传很广很多人第一次意识到原来 Agent 的能力不应该靠每次把 Prompt 写得越来越长来堆砌而是要把能力拆成独立、可复用、可校验的技能单元。这才是 Agent 工程化里更关键的一步。这篇文章不准备复述课程视频而是把 Agent Skills 当成一个工程概念来拆解。我会讲清楚它解决什么问题一个 Skill 的内部结构是什么样如何手写一个能用的 Skill接入 Agent 时要避开哪些坑。如果你正在做 AI Agent 相关开发或者准备从写 Prompt进阶到设计 Agent 能力阶段这篇文章值得收藏。1. 为什么 Agent 项目会越写越乱先看一个很常见的场景。你用 LangChain、Coze 或者直接调用大模型 API 做了一个 Agent一开始只是让大模型根据用户问题返回答案。后来需求多了要加联网搜索、要加文档问答、要加代码执行、要加日报生成。代码越写越多Prompt 越来越长Agent 的主流程里塞满了各种工具的 if-else 判断。每次新增能力都要动主逻辑改一处就可能影响另一处。这个问题的根源不是代码能力不够而是能力的组织方式出了问题。传统 Tool工具的方式是把一个函数注册给大模型让大模型在合适的时机调用。比如定义一个search_web(keyword)函数大模型需要搜索时就会调用它。这个模式简单有效但它解决的是单次调用的问题。实际情况是Agent 的很多任务并不是一次调用能完成的。比如帮助用户生成一份竞品分析报告需要先搜索、再总结、再排版、再做对比表格这中间可能涉及多次大模型调用和多步工具操作。如果把这些逻辑全部写在 Agent 的主循环里主流程会迅速膨胀。Agent Skills 的核心思路就是这个复杂过程的一种优雅解法把完成某一类任务的能力整体封装成一个 Skill。Agent 不需要知道 Skill 内部有多少次调用只需要知道我有一个生成竞品分析报告的技能它的输入是竞品关键词输出是一份 Markdown 报告。用一句话概括Tool 是原子能力Skill 是复合能力。这个区分的价值在于Skill 让 Agent 的能力变成了可插拔的模块。项目新增一个需求不再需要改动主流程只需要增加一个 Skill 文件然后在注册表里声明一下。团队协作也变得更简单一个人负责写翻译技能另一个人负责写周报生成技能互不干扰。2. 基础概念Tools、Skills、Workflow、Agent 到底有什么区别很多人在读 Agent Skills 相关文章时会被一堆术语绕晕。这里用一个表格把核心概念放在一起对比先建立整体印象。概念粒度和目标执行方式典型场景Tool原子化能力一次调用函数执行可被大模型直接调用网络搜索、计算器、数据库查询Skill复合能力完成一类任务内部可能包含多次工具调用和 LLM 调用写周报、生成竞品分析、代码审查Workflow固定流程强调执行顺序按预设节点依次执行数据清洗流水线、定时发布任务Agent自主决策根据目标动态规划大模型自主选择并组合工具或技能客服机器人、个人助手、自动编程这里需要特别强调 Tool 和 Skill 的区别这是最常见的混淆点。Tool 是点的能力。它非常纯粹入参和出参都很明确。大模型知道在合适的时候调用它但大模型并不关心 Tool 内部怎么实现也不需要对 Tool 的执行过程做复杂的规划。Skill 是面的能力。它往往包含一个完整的任务流程内部可能有多次 LLM 调用、多次 Tool 调用甚至内部还可能调用其他 Skill。Skill 自带一段描述文本说明自己擅长什么、不擅长什么、输入是什么、输出是什么。Agent 在规划任务时会把 Skill 当成一个可以整体委托的子任务。Workflow 和 Skill 的区别在于确定性。Workflow 是人工预设好的固定流程每一步做什么都写死了Skill 则更像一个带描述的黑盒能力包它内部如何执行可以包含一定的自主性。用一个生活化的例子来理解Tool 是锤子一个单一功能的工具。Skill 是会做木工一种复合能力里面要用到锤子也要用到锯子、尺子还要有如何操作这些工具的经验。Workflow 是分步安装流程第一步做什么、第二步做什么全部固定。Agent 是包工头他会根据最终目标自主挑选工具决定使用哪些 Skill。很多人以为 Agent Skills 是一个需要安装的框架其实它是一个组织 Agent 能力的设计模式。你完全可以用原生 Python 实现一个精简版这也是我下一部分要拆解的。3. 一个 Skill 的标准结构先看一个 Skill 在文件系统里长什么样。以生成学习复盘笔记这个技能为例我通常会把它的目录设计成下面这样reading_note_skill/ ├── SKILL.md # 技能描述文件给 LLM 看的说明 ├── skill.py # 技能的核心实现逻辑 ├── skill_init.py # 技能注册、初始化、入参校验 └── skill_test.py # 技能的自动化测试这个结构和吴恩达 Agent Skills 教程中提到的设计思路是相通的一个 Skill 不只是一个代码文件而是一组文件的组合。SKILL.md 是灵魂。大模型通过读取它来判断这个技能是否适合当前任务、应该如何调用。这个文件通常包含技能名称、功能描述、参数定义、触发条件、使用示例。可以把它理解为一份写给大模型看的产品说明书。skill.py 是核心逻辑。这里放真正干活的代码。它接收一个上下文 dict内部可能调用大模型 API也可以调用其他工具最后返回一个字符串结果。skill_init.py 负责承上启下。它把 Skill 的描述、入口函数、参数校验逻辑包装成一个统一的数据结构方便在 Agent 中注册和管理。skill_test.py 是质量保障。因为 Skill 是要被大模型动态调用的你无法预测每次调用时传入什么参数所以自动化测试尤其重要。需要说明的是这里的函数名和目录名是为了演示通用思路不同项目可以有自己的规范。重点在于每个 Skill 的描述和实现必须分离实现负责干活描述负责让大模型理解你能干什么。这个设计的精妙之处是把大模型的自由发挥限定在选择调用哪个 Skill这个层面而 Skill 内部的关键逻辑仍然由人工编写的代码来保证。结果是整体表现更稳定关键步骤不会被大模型自由发挥搞砸。4. 环境准备与前置条件这篇文章中的示例代码不依赖特定框架也不绑定某家大模型厂商的 SDK。它的核心是一个 Python 脚本可以在本地直接运行。你需要的环境如下Python 3.10 或更高版本主要是为了使用类型注解和 dataclass 特性。一个终端环境Linux、macOS、Windows 均可。如果希望把 Skill 接入真实的大模型需要准备一个可调用的大模型 API 接口如果只是想测试 Skill 的工程结构示例代码内置了简单的文本处理逻辑不需要真实 API 也能跑通。依赖方面示例代码只用 Python 标准库不额外安装任何第三方包。这可以排除版本冲突和环境问题让注意力集中在 Skill 结构本身。在实际项目中你大概率会用 LangGraph、CrewAI、OpenAI Agents SDK 等框架来管理 Agent。但这篇文章先不引入框架原因是框架会掩盖很多设计细节。先用原生 Python 把 Skill 的骨架跑通再迁移到任何框架都会很轻松。5. 手写一个可以运行的 Agent Skill现在开始写一个完整的 Skill 示例。这个 Skill 的作用是接收一段原始文本比如一篇讲稿、一段会议纪要输出一份结构化的学习复盘笔记。先创建一个新目录目录名就是 Skill 的名字。这里我定义为reading_note_skill。mkdir reading_note_skill cd reading_note_skill5.1 编写 SKILL.md这个文件是 Skill 的身份证也是大模型判断是否调用该技能的依据。文件路径为reading_note_skill/SKILL.md。--- name: reading_note_skill description: 将一段原始文本转换为结构化的学习复盘笔记适合讲稿、文章、会议纪要等内容整理 version: 0.1.0 --- # 技能概述 reading_note_skill 负责把用户提供的文字材料转成结构化的学习复盘笔记。 当用户输入中包含复盘笔记帮我整理讲稿总结这段内容等意图时优先使用本技能。 # 参数说明 - input_text必填字符串类型。需要整理的原始文本。 - audience选填字符串类型。目标读者的身份描述例如技术新人、进阶开发者默认值是开发人员。 # 输出说明 返回 Markdown 格式的复盘笔记包含 - 一句话总结 - 核心要点列表 - 行动建议 # 示例 用户输入 今天学习了关于缓存穿透的知识核心原因是查询了不存在的数据…… 技能输出 # 学习复盘笔记\n 目标读者开发人员\n\n## 一句话总结\n今天学习了关于缓存穿透的知识……\n写 SKILL.md 的关键原则是不要只描述技术实现要描述业务意图和触发条件。大模型不是通过函数名理解你的 Skill而是通过这段描述来判断是否调用它。描述写得越具体命中率就越高。5.2 编写 Skill 的核心实现文件路径为reading_note_skill/skill.py。from typing import Dict, List import re def run_skill(context: Dict[str, object]) - str: Skill 的唯一入口函数接收上下文 dict返回 Markdown 字符串结果。 input_text context.get(input_text, ) audience context.get(audience, 开发人员) if not input_text: raise ValueError(input_text 不能为空) paragraphs _split_paragraphs(input_text) summary _generate_summary(paragraphs) points _extract_points(paragraphs) lines [ # 学习复盘笔记\n, f 目标读者{audience}, f 材料段落数{len(paragraphs)}\n, ## 一句话总结, summary, \n## 核心要点, ] lines.extend(f- {point} for point in points) lines.append(\n## 行动建议) lines.append(- 针对核心要点中的每一项尝试一次最小化实操。) lines.append(- 记录执行过程中遇到的问题并对比要点找原因。) lines.append(- 如果材料中有明确的数据或结论建立自己的短时验证实验。) return \n.join(lines) def _split_paragraphs(text: str) - List[str]: 按空行切分文本过滤掉空段落。 parts [p.strip() for p in re.split(r\n\s*\n, text) if p.strip()] return parts def _generate_summary(paragraphs: List[str]) - str: 这里做最简单的一句话总结真实项目中可以替换为一次大模型调用。 if not paragraphs: return 材料为空无法生成摘要。 first paragraphs[0].replace(\n, ) return first if len(first) 80 else first[:80] …… def _extract_points(paragraphs: List[str]) - List[str]: 基于关键词的核心要点提取起到演示作用可根据业务自由改写。 keywords [原则, 方式, 流程, 问题, 注意, 结论, 建议, 技巧, 原因] result [] for para in paragraphs: clean para.replace(\n, ) for keyword in keywords: if keyword in clean: result.append(clean if len(clean) 60 else clean[:60] ……) break return result or [当前材料较短未提取到明显的观点句。]这段代码的核心逻辑是纯 Python 实现的没有调用外部 API。在真实产品中_generate_summary和_extract_points完全可以替换为 LLM 调用让大模型去理解文本语义。代码结构上有几个值得注意的点run_skill是唯一对外暴露的入口所有参数通过context字典传入。这样做的好处是统一了调用约定将来不管接入什么框架都只需要把上下文字典组装好传进来。_split_paragraphs负责清洗输入防止空段落干扰后续逻辑。输出统一为 Markdown 字符串。这是刻意为之的设计因为大模型和前端渲染都对 Markdown 非常友好把 Skill 的产出格式标准化后续接展示层会很顺利。5.3 编写注册与校验文件文件路径为reading_note_skill/skill_init.py。from dataclasses import dataclass, field from typing import Callable, Dict, Optional dataclass class SkillMeta: Skill 的元信息结构供 Agent 注册中心使用。 name: str description: str parameters: Dict[str, object] field(default_factorydict) version: str 0.1.0 handler: Optional[Callable] None def initialize_skill(handler: Optional[Callable] None) - SkillMeta: 官方注册入口。传入核心处理函数返回完整的 Skill 元信息。 return SkillMeta( namereading_note_skill, description将一段原始文本转换为结构化的学习复盘笔记, parameters{ input_text: { type: string, required: True, description: 需要整理的原始文本, }, audience: { type: string, required: False, description: 目标读者身份默认开发人员, }, }, handlerhandler or run_skill, ) def validate_input(context: Dict[str, object]) - None: 入参校验Agent 在调用 Skill 前会先执行此函数。 if not isinstance(context, dict): raise TypeError(fcontext 必须是 dict当前是 {type(context)}) if input_text not in context: raise ValueError(缺少必填参数 input_text) if not isinstance(context[input_text], str): raise TypeError(input_text 必须是字符串) if audience in context and not isinstance(context[audience], str): raise TypeError(audience 必须是字符串)校验函数看起来简单但在生产环境里它能救你一次。大模型并不总是按你在描述文件里定义的参数类型来传参它可能传一个数组、传一个空对象甚至漏掉必填参数。在进入核心逻辑之前做一层防御性校验是所有 Skill 都该有的习惯。注意initialize_skill函数里handler or run_skill这一行的处理它允许调用方在注册时覆盖默认实现便于测试时注入 Mock 对象。5.4 编写自动化测试文件路径为reading_note_skill/skill_test.py。from skill import run_skill from skill_init import validate_input def test_skill_normal_input(): context { input_text: 今天学习了缓存穿透问题。核心原因是查询了不存在的数据。\n\n解决方式包括缓存空值和布隆过滤器。, audience: 后端开发, } result run_skill(context) assert result.startswith(# 学习复盘笔记) assert 目标读者后端开发 in result assert 核心要点 in result def test_skill_empty_input(): validate_error False try: validate_input({input_text: }) except ValueError: validate_error True assert validate_error def test_skill_invalid_type(): validate_error False try: validate_input({input_text: [列表, 不是字符串]}) except TypeError: validate_error True assert validate_error if __name__ __main__: test_skill_normal_input() test_skill_empty_input() test_skill_invalid_type() print(所有测试通过)这个测试文件的粒度比较轻但已经覆盖了三类最重要的场景正常输入、空输入、错误类型输入。真实项目中建议继续增加边界情况测试比如超长输入、特殊字符、并发调用等。6. 在 Agent 中接入 Skill一个 Skill 文件本身不能独立工作它需要被 Agent 发现并调度。这一节演示一个最精简的加载和调度逻辑。创建agent_runner.py放在reading_note_skill的上级目录里。from typing import Dict, Optional import importlib class AgentRuntime: 极简 Agent 运行时只演示 Skill 的加载和调度。 def __init__(self): self._skills: Dict[str, object] {} def register_skill(self, module_name: str) - None: 动态加载一个 Skill 模块并调用其 initialize_skill 完成注册。 module importlib.import_module(module_name) skill_meta module.initialize_skill() self._skills[skill_meta.name] skill_meta print(f已注册 Skill: {skill_meta.name}描述{skill_meta.description}) def dispatch(self, context: Dict[str, object]) - str: 根据用户消息中的关键词选择一个匹配的 Skill 执行。 user_message context.get(message, ) for skill_meta in self._skills.values(): # 这里简化处理描述和名称中出现相关关键词就触发 # 生产环境通常由 LLM 根据描述做语义匹配 hit_keywords [复盘, 笔记, 整理, 总结] for keyword in hit_keywords: if keyword in user_message and skill_meta.name reading_note_skill: validate_result self._validate(skill_meta, context) if validate_result is not None: return validate_result return skill_meta.handler(context) return 未找到匹配的 Skill请补充更多信息。 def _validate(self, skill_meta, context) - Optional[str]: module importlib.import_module(reading_note_skill.skill_init) try: module.validate_input(context) return None except (ValueError, TypeError) as exc: return fSkill 入参校验失败{exc} if __name__ __main__: runtime AgentRuntime() runtime.register_skill(reading_note_skill.skill_init) test_context { message: 帮我整理一下今天学习 Agent 的复盘笔记, input_text: 今天学习了 Agent 的基本概念。Agent 是能自主决策的程序。\n\n它的核心模块包括规划、记忆和工具调用。, audience: AI应用开发者, } output runtime.dispatch(test_context) print(\n Agent 输出 ) print(output)这段代码演示了 Agent 运行时的两个关键职责注册和调度。注册通过importlib.import_module动态加载 Skill 模块并调用模块中的initialize_skill拿到统一的SkillMeta结构。这个设计的好处是Agent 运行时本身不感知 Skill 的具体实现细节新增加一个 Skill 只需要一行注册代码。调度采用关键词匹配这是为了演示而做的简化。真实项目中你应该让大模型根据SKILL.md中的描述和用户输入做语义匹配而不是依赖固定的关键词。因为用户表达意图的方式千变万化关键词匹配在复杂场景下会漏判。_validate方法在调用 Skill 前执行入参校验这是非常关键的工程习惯不要让不合法输入进入核心逻辑否则你在日志里看到的问题症状会非常混乱。7. 运行结果与效果验证运行 Agent 示例执行以下命令python agent_runner.py正常情况下的输出大致如下已注册 Skill: reading_note_skill描述将一段原始文本转换为结构化的学习复盘笔记 Agent 输出 # 学习复盘笔记 目标读者AI应用开发者 材料段落数2 ## 一句话总结 今天学习了 Agent 的基本概念。 ## 核心要点 - 今天学习了 Agent 的基本概念。Agent 是能自主决策的程序。 - 它的核心模块包括规划、记忆和工具调用。 ## 行动建议 - 针对核心要点中的每一项尝试一次最小化实操。 - 记录执行过程中遇到的问题并对比要点找原因。 - 如果材料中有明确的数据或结论建立自己的短时验证实验。怎么判断这次运行是成功的第一注册日志打印出来说明 Skill 模块可以被正常导入和初始化。如果这一步失败优先检查目录层级和__init__.py是否存在。第二输出中包含一句话总结核心要点行动建议三个固定板块说明核心逻辑完整执行。第三目标读者正确显示为AI应用开发者说明audience参数传递生效。如果你把input_text从上下文中去掉再运行一次dispatch会捕获到校验异常并返回提示信息这就是防御式校验在起作用。真实项目中验证阶段不应该只看一次输出建议准备 5 到 10 组不同风格的输入样本覆盖正常输入、超短输入、超长输入、特殊字符输入、缺少必填参数等场景确保 Skill 的稳定性。8. 常见问题与排查思路在开发和部署 Agent Skills 的过程中下面这些问题出现频率最高。问题现象可能原因排查方式解决方案Agent 完全不会调用已注册的 SkillSKILL.md 描述不够具体或者 Agent 的上下文里没装载 Skill 描述检查描述文件中的触发条件和示例重写 description增加典型用户提问示例Skill 被调用但入参经常出错validate_input 校验缺失或过于宽松增加日志打印实际收到的 context完善必填参数、类型、范围校验输出格式不稳定核心实现依赖 LLM 自由输出缺少后处理打印原始返回结果对比预期格式增加模板约束、用代码做结构化后处理多个 Skill 描述之间存在重叠一个任务同时匹配多个 SkillAgent 选错检查各 Skill 的 description 边界明确每个 Skill 的职责范围补充不擅长什么新加的 Skill 线上不可用部署时漏掉文件或 import 路径不一致查看启动日志中的注册信息统一用注册中心管理运行前做注册自检调用 Skill 时上下文体积过大把大量历史消息全部塞入 context检查入参大小在传给 Skill 前做裁剪和摘要只传必要字段这里最想强调的问题是描述重叠。随着项目里的 Skill 数量增多很容易出现两个 Skill 描述相近的情况。比如一个生成日报和一个生成周报如果描述不够清晰Agent 很可能把周报任务发放给日报 Skill。解决办法是在 SKILL.md 里主动写不该做什么。例如在生成日报 Skill 的描述里明确写本技能只处理按天维度的汇报不适用于按周、按月格式。周报请调用 generate_weekly_report_skill。这种负向描述对 LLM 的调度准确率提升非常明显。另一个容易被忽视的问题是上下文加载。如果 Agent 在每次请求时把所有 Skill 的描述文件全部塞给大模型token 消耗会迅速上升而且大模型的注意力会被分散。更合适的做法是分两级先用一个轻量级的摘要列表让大模型粗选再在确定使用某个 Skill 时加载它的完整 SKILL.md。9. 最佳实践与工程建议把 Agent Skills 落地到真实项目时下面这七条建议可以直接用。9.1 坚持单一职责一个 Skill 只负责一类任务。不要做出一个万能整理技能它能写笔记、能翻译、能生成周报。单一职责可以让 Skill 的描述更清晰测试更简单维护成本更低。9.2 SKILL.md 是写给人看的更是写给模型看的描述文件的质量直接决定 Skill 的命中率。建议至少包含功能描述、参数说明、输出格式、典型使用示例、不适合使用的场景。写完后可以自己扮演大模型读一遍想象一下如果只看这段描述你知道什么时候该调用它吗9.3 输入输出双校验不仅输入要做校验输出也要做。Skill 执行完后最好用一段逻辑检查结果是否符合预期格式。例如要求输出 Markdown至少检查是否有标题、列表结构。不符合时就重试或抛出异常而不是把脏数据直接返回给用户。9.4 安全敏感操作必须显式声明如果 Skill 会执行代码、删除文件、发送网络请求、修改数据库必须在 SKILL.md 中用醒目方式声明风险并在代码里增加二次确认机制。Agent 的自动化能力越强越需要关注误操作风险。9.5 把 Skill 当独立库来管理每个 Skill 建议有独立的版本号、作者信息、依赖说明。团队内可以搭建私有的 Skill 仓库像管理 Python 包一样管理 Skill。升级 Skill 时也要考虑向后兼容性避免某个 Agent 实例因为 Skill 参数变化而突然失效。9.6 控制上下文体积在传给 Skill 前对输入做必要的裁剪。特别是处理长文档时不要让 Skill 每次接收完整的几百页内容。可以先做分块和检索只把与任务相关的片段传给 Skill。9.7 关注可观测性每次 Skill 调用都应该记录关键日志被哪个 Agent 调用、入参是什么、耗时多少、输出是否通过校验、有没有发生重试。这些日志是 Agent 应用排障的重要依据。调试时用一句话能说清楚哪个 Skill 在什么时候对什么输入做了什么比看可靠度评分有用得多。10. 总结与后续学习方向Agent Skills 不是一个需要特意安装的框架而是一种组织 Agent 能力的工程范式。它把复杂的复合任务封装成带描述的独立技能单元让 Agent 的能力边界更清晰也让团队协作更顺畅。这篇文章从概念到代码完整拆解了一个 Skill 的结构和接入方式。你可以照着示例自己写一个简单的 Skill比如文档翻译器、日报生成器或代码评审助手。跑通之后再回头去看吴恩达 Agent Skills 课程里的细节你会更容易理解他为什么强调描述文件和输入输出校验这些容易被忽视的环节。下一步如果你打算深入方向有三个一是研究多 Skill 之间的编排与依赖关系二是跟踪开源社区里 Skill 市场的成熟度三是建立一套评估体系来衡量 Skill 的调用准确率和输出质量。趋势上看Agent 的能力单元化是确定方向早点动手积累的工程经验后面会很有价值。如果这篇文章对你有帮助建议收藏备用。你在实践 Agent Skills 时遇到过什么奇怪的坑也可以在评论区聊聊。