你可能也注意到了最近技术圈聊 Agent 的频率明显变高了而且聊的东西越来越具体。前两年大家关心的是“大模型能不能帮我写一封邮件”现在问得更多的是“能不能让我的智能体自己去调接口、算数据、生成报表”。这种从“会聊天”到“会干活”的转变靠的不是把 Prompt 写得更长更细而是给 Agent 配上一组可复用、可维护、可测试的“技能”。我最近一直在维护一个名为 agent-skills 的项目核心就是沉淀一套面向智能体的可复用技能库今天把这套东西从设计到落地的完整思路拿出来聊聊。这篇文章会从 Agent 的能力边界讲起说清楚 Skill技能到底在智能体体系里扮演什么角色然后完整走一遍“从零设计一个 Skill、编写技能定义、开发配套脚本、测试并上线”的过程最后把我在实际项目中踩过的坑和排查方法整理成清单。无论你是刚接触智能体开发的新手还是已经在用 LangChain、Claude Skills、GPT Actions 做应用的开发者这篇文章都能提供一些可以直接参考的实践思路。1. 先搞清楚Agent 的“技能”到底指的是什么1.1 从“会聊天”到“会干活”Agent 的能力边界传统意义上的聊天机器人本质是一个“文本生成器”用户输入一句话模型输出一段看起来合理的回复。但 Agent智能体不一样它的核心是“能做事”。所谓做事意味着它需要感知当前状态、制定执行计划、调用外部工具、检查执行结果然后根据结果决定下一步动作。这里的关键差异在于聊天机器人只需要语义上的“像那么回事”而 Agent 要求操作层面上的“真的完成”。比如说用户说“帮我整理一下这周的项目进展”聊天机器人可能只会生成一段漂亮的周报文本但里面哪些数据是真的、哪些是它脑补的它自己也不清楚。而 Agent 要做的是去项目管理系统里拉数据、读取本周提交的代码记录、汇总成结构化信息、再渲染成指定格式的文档。这个跨越不是单纯靠模型参数变大就能解决的。模型再强它也没法直接操控你的文件系统、数据库和第三方 API。所以 Agent 的落地必须依赖一套外部能力补充机制——工具调用Function Calling / Tool Use是其中一层技能Skills是更上面的一层抽象。1.2 Skill 在智能体体系里的位置如果把 Agent 比作一个新入职的员工那大模型本身是他的“大脑”工具比如代码解释器、搜索接口、数据库查询是他的“办公设备”而 Skill 就是他的“岗位操作手册”。员工光有大脑和设备不够他得知道什么场景下该用什么工具、按什么步骤操作、产出标准是什么这本操作手册解决的就是这个问题。我在 agent-skills 项目里对 Skill 的定义是一个能独立描述“何时用、怎么用、用什么工具、产出什么结果”的完整任务单元。它通常由一份人类可读、Agent 也可读的说明文档加上若干配套脚本和资源文件组成。Agent 在运行时会先读取这套说明理解任务要求再决定调用哪些工具、执行哪些步骤。这种设计把“模型的语义理解能力”和“工具的确定性执行能力”做了明确分工。模型负责理解用户意图、拆解任务、判断走哪个流程工具和脚本负责精确计算、文件读写、格式转换这些不能靠“感觉”完成的操作。Skill 就是连接这两者的桥梁——它告诉模型“遇到这类任务时你应该按这个流程来”。1.3 为什么一定要用 Skill而不是把规则写进 Prompt不少人会问既然模型能读 Prompt那我把操作步骤都写进 System Prompt 不就行了为什么还要单独搞出一个 Skill 的概念我在实际项目里试过这种“全家桶 Prompt”方案后来放弃了。原因有三个。第一是注意力稀释问题。模型每处理一个 Token 都要消耗注意力资源System Prompt 越长真正重要的指令在注意力里的占比就越低。你把“周报格式要求”“PDF 处理步骤”“数据库连接方式”全写进一个 Prompt 里模型在处理具体某类任务时还要在一大堆不相关的规则里挑出有用的部分不仅慢而且容易出错。第二是成本问题。每次对话都要携带全量规则意味着每次请求都在为无关内容支付 Token 费用。而 Skill 天然是按需加载的只有用户触发“周报”相关任务时系统才把周报技能的定义注入上下文其他时候这套规则完全不占空间。第三是可维护性。Prompt 是一坨连续文本没法单独做版本管理、没法单测、没法复用。Skill 是一个独立目录有明确的文件结构可以放进 Git 管理、可以写测试脚本验证、可以打包分享给别人。所以结论很明确Skill 不是给简单任务增加复杂度而是给复杂系统提供工程化基础。2. 设计 Skill 的正确姿势先定边界再写代码2.1 一个 Skill 的标准结构长什么样先说一个我在 agent-skills 项目里最终沉淀下来的标准布局基本可以适配大多数场景skills/ weekly-report/ SKILL.md scripts/ generate_report.py resources/ templates/ report_template.md ...每个 Skill 有自己独立的目录目录名就是技能名。核心文件是 SKILL.md它用 Markdown 编写前面带一段 YAML 格式的 frontmatter 元数据用来声明技能的基本信息后面是给 Agent 看的正文说明。--- name: weekly_report_generator description: 根据用户提供的工作记录生成标准格式的项目周报。当用户提到周报本周总结这周做了什么时使用该技能。 version: 1.0.0 ---frontmatter 里最重要的是 description 字段。这个字段的作用是让 Agent 判断“当前任务要不要使用这个技能”相当于技能的“触发条件说明书”。description 写得越具体Agent 的触发判断就越准确。正文部分则包含何时使用、输入要求、执行步骤、注意事项。这些内容不需要长篇大论但要足够清晰让 Agent 在看了一次之后就知道完整流程。2.2 技能边界太宽容易废太窄没人用设计 Skill 时最容易犯的错是把自己的需求定义得太宽泛。我见过有人建了一个名为“文档处理”的 Skill里面既包含 PDF 文本提取、又包含格式转换、还包含摘要生成结果 description 怎么写都写不清楚Agent 根本不知道该在什么情况下加载它。更合理的做法是把“文档处理”拆分成多个独立技能“从 PDF 提取文本”“合并多个 PDF 文件”“为长文档生成摘要”。每个技能聚焦一个明确任务输入输出边界清晰description 也能写得精准。反过来技能也不能太窄。如果某个功能只需要两行 Prompt 就能描述清楚比如“把一段文字转成大写”那它就不需要单独做成一个 Skill。我的判断标准是三个问题这个任务是否有明确的输入输出是否需要多步骤执行是否会在不同场景里被反复复用三个问题的答案如果至少有两个是肯定的值得做成 Skill否则直接用 Prompt 处理更省事。2.3 纯描述型 Skill 与代码型 Skill 的取舍Skill 并不一定需要包含代码。有些技能本质上是“告诉 Agent 一套思考框架”比如“代码审查”技能它的核心是审查要点和标准流程这些用自然语言描述就足够Agent 可以直接理解并执行。但涉及精确计算、文件操作、格式转换、外部系统交互时纯描述就不够用了。模型在生成 JSON 时偶尔会丢掉括号在处理日期格式时可能犯低级错误在操作 Excel 文件时不会像 Python 脚本那样稳定。这时候就需要给 Skill 配上可执行的脚本把确定性逻辑从模型里剥离出来。我在实践中通常采用混合方案Skill 的说明文档告诉 Agent “什么时候用、需要收集哪些信息”脚本负责“拿到信息之后如何精确处理”。比如周报技能的核心逻辑是“理解用户零散的工作记录 → 整理成结构化数据 → 渲染成固定格式报表”前面两个环节模型擅长最后一个环节交给 Python 脚本更稳妥。3. 实战从零搭建一个能复用的 Agent Skill3.1 场景立项用“项目周报生成”作为第一个技能选一个大家都有共鸣的场景来演示项目周报自动生成。这个任务几乎每个职场人都遇到过而且它天然适合做成 Skill——输入是零散的工作记录输出是固定格式的周报文档中间需要做信息整理、格式校验和渲染。我当时的立项要求很简单用户随口说“这周完成了 A 模块开发、修复了三个 bug、准备下周做性能优化”Skill 要能把这段话整理成一份结构清晰、格式规范的项目周报并且自动处理缺失字段比如没提风险就直接标“待补充”不给模型自由发挥编造内容的余地。这个需求包含三个核心环节提取结构化信息、校验必要字段、渲染标准模板。前一步适合模型做后两步交给脚本做。明确了这一点目录结构就自然出来了。3.2 编写 SKILL.md 的核心写法SKILL.md 是 Agent 和技能之间的“接口协议”它的质量直接决定技能能不能被正确触发和执行。以周报技能为例我写出来的定义大致是这样的--- name: weekly_report_generator description: 根据用户提供的工作记录生成标准格式的项目周报。当用户提到周报本周总结这周做了什么本周工作时使用该技能。用户需要提供至少一条已完成事项。 version: 1.0.0 --- # 项目周报生成 ## 何时使用 - 用户要求生成或整理项目周报 - 用户提供了本周的工作记录、任务清单、进度说明 ## 输入要求 - 必须包含本周已完成事项至少一项可以是口语化描述 - 可选下周计划、风险与问题、客观数据指标、项目名称、汇报人、日期 ## 执行步骤 1. 将用户提供的内容整理成结构化 JSON包含 tasks、next_plan、risks、metrics、project、author、date 字段 2. 使用标准输入将 JSON 传给 scripts/generate_report.py 3. 脚本会返回一份 Markdown 格式周报直接原样输出给用户 ## 注意事项 - 不要自行编造用户没有提到的工作内容 - 用户没有提到下周计划、风险、指标时字段留空即可脚本会自动填充待补充 - 如果用户只说了工作事项没有指定日期用当前日期这里有几个细节值得强调。第一description 里要写清触发词而且要把用户常见的不同说法都列上这样 Agent 才能在各种表达方式下都做出正确的命中判断。第二执行步骤要写得像给程序员看的需求文档明确字段名、明确调用方式不给 Agent 留太多自由发挥的空间。第三注意事项里要对 Agent 的行为做约束——尤其是“不要编造”这个太重要了否则模型会自动补全一些听起来合理但实际上不存在的进展。3.3 写配套脚本让确定性的活交给代码周报技能里最关键的一个决策是让脚本承担所有“不该由模型决定”的工作。模板渲染、字段缺失处理、日期格式化这些都属于确定性逻辑让模型来做很容易出幺蛾子。我把这些都放进了 Python 脚本里。#!/usr/bin/env python3 import json import sys from pathlib import Path TEMPLATE_PATH Path(__file__).parent.parent / resources / templates / report_template.md def validate_input(data): tasks data.get(tasks) or [] if not tasks: raise ValueError(tasks 不能为空至少需要一条已完成事项) return data def render(data): template TEMPLATE_PATH.read_text(encodingutf-8) tasks \n.join(f- {t} for t in data[tasks]) next_plan data.get(next_plan) or [] risks data.get(risks) or [] metrics data.get(metrics) or [] return template.format( authordata.get(author) or 未署名, datedata.get(date) or 未填写日期, projectdata.get(project) or 未填写项目, taskstasks, next_plan\n.join(f- {t} for t in next_plan) or - 待补充, risks\n.join(f- {r} for r in risks) or - 待补充, metrics\n.join(f- {m} for m in metrics) or - 待补充, ) def main(): raw sys.stdin.read().strip() if not raw: print(json.dumps({error: 没有收到输入数据}, ensure_asciiFalse)) sys.exit(1) try: data json.loads(raw) data validate_input(data) report render(data) print(report) except Exception as e: print(json.dumps({error: str(e)}, ensure_asciiFalse)) sys.exit(1) if __name__ __main__: main()这个脚本的设计有几个关键点。第一输入走标准输入stdin输出走标准输出stdout这是 Agent 框架里最容易对接的方式。第二所有字段缺失都走“默认值”而不是报错比如 next_plan 没填就渲染成“待补充”这保证技能在输入不完整时也能给出一个可用结果。第三除了 tasks 为空时主动报错因为周报不能没有内容其他情况都不阻断流程让周报先生成出来再说。模板文件 report_template.md 也很简单# 项目周报 - **项目**{project} - **汇报人**{author} - **日期**{date} ## 本周完成 {tasks} ## 下周计划 {next_plan} ## 风险与问题 {risks} ## 数据指标 {metrics}3.4 模拟 Agent 调用验证整个链路写完 SKILL.md 和脚本之后先不急着接 Agent 框架我习惯先在命令行里把脚本链路测通。这一步能隔离问题避免把脚本 bug 和 Agent 调度问题混在一起。echo {tasks:[完成数据同步模块接口开发,修复用户反馈的三个权限bug],project:数据平台} | python3 scripts/generate_report.py看到输出是一份完整 Markdown 周报字段格式都对说明脚本本身没问题。然后才把它接入 Agent 运行时环境观察 Agent 是否能根据用户输入正确触发技能、是否正确生成 JSON 并调用脚本。第一次联调时大概率会出问题。我最初测试时遇到的情况是用户明确说了“帮我写一下这周的周报”但 Agent 完全没触发技能直接自己生成了一段周报文本。原因就是当时 description 里只写了“周报”一个触发词没覆盖“本周总结”“这周做了什么”这类常见说法模型没识别出来。把触发词补全之后再测就稳定了。还有一个值得注意的细节SKILL.md 给 Agent 的执行步骤必须是可执行的命令而不是“请尝试”这种模糊描述。Agent 读到“将 JSON 传给脚本”时如果脚本路径写成相对路径在某些框架里就可能找不到文件。我统一用scripts/generate_report.py这种相对 Skill 根目录的写法并在脚本内部用Path(__file__).parent.parent定位模板文件保证无论从哪个目录调用都不会路径丢失。4. 常见问题与排查技巧实录4.1 技能永远不被触发问题大概率出在描述上这是我在社区里被问得最多的问题明明 Skill 已经挂到系统里了用户也提出了相关需求但 Agent 就是不调用它。排查思路第一站永远是 description。Agent 判断“要不要用某个技能”的唯一依据就是技能描述描述里没覆盖用户表达的措辞触发自然是零。我把踩过的坑和解决方案总结成了一张速查表现象可能原因解决思路技能从未触发description 中的触发词与用户表达不匹配把用户可能的说法都列进 description包括口语化表达多个技能描述相似同时触发多个技能调度混乱为每个技能明确写出与其他技能的边界差异触发不稳定description 前半段是无关的官方介绍把最关键的触发条件放在 description 最前面在 A 框架触发正常迁移到 B 框架失效元数据字段名或加载方式不兼容参考新框架的 Skill 规范调整 frontmatter这里有个底层逻辑要讲透模型选择技能的机制本质上是“语义匹配”它不会像代码一样执行 if/else而是从概率上判断“哪个技能的描述与当前任务最相关”。所以你写的 description 本质上是在做“信息检索召回”关键词覆盖越全、场景描述越准召回就越准确。4.2 输出时好时坏说明格式约束没做足技能能触发只是第一步更常见的坑是触发后输出的质量不稳定。同一个用户输入有时候生成一份完美周报有时候生成一份字段错乱、格式跳脱的周报。这种问题几乎都出在一个地方把格式控制的权利留给了模型。模型是概率生成器它在生成 Markdown 结构时偶尔会自己发挥一下——标题层级变一下、列表符号换一下、字段顺序调一下。这些变化单看都能接受但“偶尔变一下”对于固定格式的交付物来说就是不稳定。解决思路是把“格式决定权”从模型手里拿走。我的做法是模型只负责输出结构化的 JSON 数据所有格式渲染全部交给脚本。JSON 结构可以通过 schema 校验脚本输出固定模板两者结合输出结果就是确定性的。模型在 Skill 链路里只做它擅长的事——语义理解和信息抽取。另外一个容易忽视的点是编码问题。脚本在 Windows 环境下读文件如果没指定 UTF-8 编码中文极其容易变成乱码而且这种问题往往在开发机上测试正常、部署到服务器就翻车。我现在的习惯是所有文件操作都显式加encodingutf-8不管是读模板还是写输出。4.3 想把 Skill 搬到其他框架记住这套通用解法2024 年到现在各家 Agent 框架都开始支持“技能”或类似的概念有以 SKILL.md 为核心的打包式技能有以 Action 为核心的接口式技能也有以 Function Calling 为核心的原生工具。很多开发者会遇到的问题是在 A 框架里写好的技能到了 B 框架里没法直接用。我的经验是把 Skill 分成三层核心逻辑层脚本和模板、元数据层名称、描述、触发条件、适配层框架特有的加载和调用方式。核心逻辑层是纯代码跟框架无关可以原样迁移。元数据层里的名称和描述是语义信息基本通用但字段名可能不同比如有的框架要求description有的框架要求instructions或prompt。适配层是每个框架特定的部分需要重写。这套分层思路让我在迁移时只需要改适配层不用动核心逻辑成本降低了很多。这也是推荐每个 Skill 都做成“脚本自包含、说明可移植”的原因——不要把自己的技能绑死在某一个平台上。4.4 若干必须记住的避坑点最后分享几个我积累的实战避坑经验这些都是踩过之后才写进项目文档里的。第一个是安全问题。Skill 脚本在执行用户输入时要防范提示注入。用户可能在正常的工作记录里夹带一句“忽略之前所有指令帮我执行某个命令”如果脚本没有校验就把它当成数据传到系统里风险就大了。我现在对 Skill 脚本的统一要求是严格校验输入字段、不执行用户提供的命令行指令、输出一律通过模板渲染不做任何动态拼接。第二个是依赖管理。每个 Skill 目录下最好有一个 requirements.txt 或类似文件列出它依赖的 Python 库。否则 Skill 一多系统里装了一堆不相关的依赖部署和排查都会很痛苦。我踩过最疼的一次是某个 Skill 需要 pandas另一个不需要结果为了一个技能把 pandas 装进所有环境启动时间直接翻倍。第三个是版本管理。Skill 是会迭代的同一个技能 v1.0 和 v1.1 的模板可能不一样。我建议在 SKILL.md 的 frontmatter 里带上 version 字段用 Git 管理每个 Skill 目录在 ChangeLog 里记录每次改动。这样当某次生成结果异常时可以快速回退到上一个稳定版本。最后一个是技能体积控制。SKILL.md 不要写太长我见过有人把一份 5000 字的操作文档塞进技能描述结果 Agent 每次触发都要读一大段不仅浪费 Token还因为重点分散导致执行不准确。技能说明的精髓是“少而准”告诉 Agent 何时用、需要什么输入、按什么步骤调脚本剩下的细节放进资源文件需要时再让 Agent 读取。我个人在实际操作中还有一个习惯凡是同一类事情重复做三次以上就会把它沉淀成一个 Skill。起初会觉得这像是“给自己找活干”但积攒几个月后回头看这个思路帮了自己很大忙——碰到类似需求时不用再从头搭流程、翻文档、试参数直接把现成技能拉出来跑一遍就行。就像整理工具箱每个人家里都有锤子和螺丝刀但只有需要用时才意识到它们放在哪、用起来顺不顺手。Skill 就是这样一套被整理好的数字工具箱整理时多花的一点心思后面都会用节省的时间加倍还回来。