
1. 为什么纯聊天的 Agent 只是“半成品”1.1 聊天型 Agent 的三道天花板做过一段时间 AI 应用的人应该都有这种体验把一个大模型接上对话框之后它确实什么都能“聊”但只要你让它真正完成一件有交付物的事——比如“把这份 CSV 里的重复用户去掉按省份聚合出报表再生成一封邮件草稿”——它就开始在文字里打转。给出的建议很专业但没有一件事是真正被执行的。这就是聊天型 Agent 的根本局限它不是能力不够而是结构上就缺了“行动”这一层。我把这类 Agent 的问题归纳成三道天花板。第一道是记忆天花板。聊天型 Agent 的所有状态都依赖上下文窗口一次会话里塞多少东西它就记住多少。可一旦任务持续多轮、中间还要读写文件、查询状态、校验中间结果上下文就会被撑爆。更麻烦的是上下文里的信息是“自然语言描述”不是结构化状态模型容易在长对话后期忘记前面已经做到哪一步。你让它先做 A 再做 B 再根据 B 的结果做 C它经常做到 B 的时候就开始自由发挥了。第二道是行为天花板。模型本身的输出只有 token它没有手没有权限没法写文件、跑命令、调接口。要让模型“干活”必须有人把它的意图翻译成真实的系统动作。早期做法是把 API 一个个暴露给模型但这本质上是在让模型“自己点按钮”点错、漏点、重复点都是常态。第三道是可靠性天花板。自由文本输出是不稳定的同一个任务让模型跑十次十次可能给你十种格式的结果。如果下游还有程序要消费这些结果解析成本会高到你怀疑人生。这三道天花板的共同根源是模型负责了太多的“决策”和“执行细节”而系统本身没有把“怎么干一件具体的事”固化下来。1.2 Skill 的第一性把“教会一次”固化成“永久能力”Skill 这个设计本质上就是来拆这三道天花板的。我第一次认真理解 Skill是在折腾 Claude 和 Codex 的扩展能力时。当时我反复问自己同样是把一个任务教给模型我写一段很长的 system prompt 不就行了吗为什么还要专门搞一套 Skill 体系后来我意识到关键区别在于Prompt 是教你“听懂”Skill 是教你“干活”。更好的类比是公司里给新员工写 SOP。你不会只给新员工一段“你要好好对待客户”的价值观宣讲你会给他一张表客户投诉进来之后第一步先登记工单第二步判断严重级别第三步分发给对应组第四步在规定时间内回访。有了这张 SOP新人哪怕没经验也能按流程产出及格的结果。Skill 对 Agent 来说就是这张 SOP。一个完整的 Skill通常会同时包含三样东西一段“什么时候用我”的描述让 Agent 能在正确的场景里把它找出来一组“具体怎么做”的步骤说明把模型需要完成的子任务拆成可执行的顺序若干“真正动手”的脚本或命令行调用把模型的决策落到对文件、接口、系统的实际操作上。也就是说Skill 把“知识”和“行动”绑在了一起。模型不再需要临时思考“这一步该怎么处理这个数据文件”它只需要学会“识别该用哪个 Skill然后按 Skill 里的步骤走”。而步骤里的稳定部分比如文件解析、数据清洗、API 调用都已经由脚本固化好了不依赖模型当时的状态和心情。这也是为什么我在这篇文章的标题里强调“从聊天进化到干活”——聊天只需要模型有语言能力干活则需要模型在一个有约束、有流程、有反馈的系统里工作而 Skill 就是这个系统里最基本的能力单元。2. Skill 和 Function Calling、Tool、Plugin、Harness 到底是什么关系2.1 很多人把 Skill 当 Tool 用方向就错了在网上搜“skill”和“agent”相关的内容时能看到大量的提问“Skill 和 agent 有什么区别”“Skill 和 Function Calling 有什么区别”“Harness 和 Agent 有什么区别”。这些词看起来很像但解决的问题完全不在一个层次上。我先给出一张对照表再展开说。概念核心作用粒度典型形态Function Calling让模型学会调用外部函数的机制原子接口一次一调函数定义 参数 schemaTool可被模型调用的一组外部能力单点能力搜索、计算器、API 封装Plugin平台级的功能扩展单元集成单元带生命周期带安装/启用/卸载的完整模块Skill面向“任务”的完整操作方案任务级包含多个步骤和脚本描述文档 步骤说明 脚本资产Harness承载 Skill 和 Agent 运行的运行时环境整个执行环境调度循环、状态管理、执行沙箱你看 Function Calling它是模型与外部世界交互的“协议层”。一次调用解决一个动作比如“查天气”。它不负责回答用户“要出行的这个周末天气适不适合露营”也不负责决定“要不要顺便帮你订个露营地”。Tool 更是一个“工具箱”里的单个工具。模型可以决定“现在该用搜索”于是调用 Search Tool搜索完发现还需要“打开网页”于是再调用 Browser Tool。每个 Tool 都是独立存在的但没有人告诉模型“在整个任务里你要先用搜索再打开网页然后提取文本最后按指定格式汇总”——这个“流程编排”的责任模型自己去扛了结果就是它经常编排错。Skill 则完全是另一层东西。它是把“完成一个任务所需的全部决策和操作”打包成了一个整体。一个 Skill 内部可以包含描述文档、步骤拆解、提示词片段、Python 脚本、Shell 命令、输出模板甚至验收标准。Agent 只要定位到正确的 Skill执行路径就被 Skill 内部定义好的流程牢牢兜住。2.2 为什么业界越来越强调 Skill 而不是 Tool最近几个月观察下来的趋势很明显主流 Agent 产品都在往 Skill 方向收敛。Claude 有 SkillsCodex 项目里有 skills 目录Cursor 的推荐里也大量出现 skill 类扩展。原因很简单——Tool 解决的是“能不能做”Skill 解决的是“做得稳不稳”。一个典型例子你给 Agent 配了 CSV 读取工具、数据清洗工具、图表绘制工具、Markdown 生成工具。理论上它什么都能干。但实际跑起来你会发现它不知道清洗到什么程度算合格不知道图表标题该用什么格式不知道最后输出文件应该放在哪个目录。工具越多模型的选择负担越重出错的概率也越高。Skill 的思路是把这些工具调用和决策逻辑“封装进流程”。我写一个weekly_report_generator的 Skill里面的文档明确规定先读哪个文件字段缺失怎么处理数据量超过多少行需要抽样图表配色用什么风格最终 Markdown 存在哪里。模型在大部分环节里不需要自由发挥它只需要执行。所以如果你正在做 Agent 开发我建议你重新审视一下自己的架构如果现在还停留在给模型堆工具的阶段那只是完成了“能力接入”距离“可靠的自动化干活”还差一个 Skill 层的封装。2.3 Harness 在其中的位置搜“harness 和 agent 区别”“deepseek harness 用 skill”的人不少。我简单说下我的理解。Harness 更像是一个“容器”。它负责运行 Agent 的主循环管理 LLM 调用的上下文拦截模型的工具调用请求并把 Skill 文档和脚本加载进可执行环境。你可以把 Harness 理解为 Skill 的“运行时”——Skill 定义了任务该怎么做Harness 则保证这些步骤真的被顺序执行、错误能被捕获、结果能被回传。这也引出一个开发时的习惯问题写 Skill 时不能只考虑“模型读不读得懂”还得考虑“你的 Harness 认不认得这套格式”。不同框架对 Skill 的加载方式有差异有的要求 SKILL.md 里写固定的 YAML frontmatter有的要求必须有单独的 scripts 目录。动手前先把宿主环境的约定摸清楚能省很多事。3. 手把手从零写一个可落地的 Skill3.1 先定目标与边界理论说了那么多现在开始操作。我以一个最典型的场景为例“月度运营数据汇报生成”。输入是一份 CSV 格式的运营数据表包含日期、渠道、用户数、订单数、GMV 等字段输出是一份 Markdown 格式的月度汇报草稿外加一个图表数据 JSON 文件。为什么选这个场景因为它在工作里出现的频率极高又恰好能体现 Skill 的价值数据读取是确定的清洗规则是确定的输出格式是确定的——一切都不该让模型临时发挥但大多数 Agent 在实际处理时偏偏喜欢临时发挥。开始写之前先想清楚三个问题这个 Skill 的输入是什么格式是什么哪些字段必须存在这个 Skill 的输出是什么给谁看哪些情况属于这个 Skill 的边界遇到就该拒绝执行而不是硬处理我建议你把这些问题的答案写在一张纸上再开始建目录。很多人上来就写代码写到一半发现边界不清晰回头改结构的成本非常高。3.2 Skill 的目录与文件结构一个规范的 Skill 目录我通常这样组织monthly_report_skill/ ├── SKILL.md ├── scripts/ │ ├── preprocess.py │ ├── generate_chart_data.py │ └── render_report.py ├── assets/ │ ├── report_template.md │ └── chart_config.json └── tests/ ├── sample_data.csv └── test_preprocess.py分工很明确SKILL.md是给模型看的“说明书”也是整个 Skill 的入口文件scripts/放真正执行的脚本每个脚本只干一件事便于测试和替换assets/放模板和配置文件把“内容”和“代码”分开tests/放测试数据和测试脚本方便你验证也方便模型在不确定时自检。这份结构不是强制的每个框架有自己的约定比如 Codex 的 Skills 目前更接近一个包含 SKILL.md 的独立仓库。但只要遵循“描述在外、脚本在内、测试兜底”的原则大致方向就不会错。3.3 SKILL.md 怎么写才容易被模型正确触发SKILL.md 是整个 Skill 的灵魂。同样的能力描述写得好不好直接决定模型会不会在正确时机使用它。我见过的最常见问题是描述写得太“功能导向”比如# Monthly Report Generator Generates monthly report.这种描述不是不能用但太弱了。模型面对一个模糊任务时需要靠 description 判断“这个 Skill 是否适用于当前场景”。只写一句“生成月度报告”模型不知道什么时候该调用也不知道给它的参数应该长什么样最后大概率就是瞎猜。我自己写 SKILL.md 习惯了用下面这种结构--- name: monthly-report-generator description: 当用户需要基于运营数据生成月度汇报时使用。适用场景包括按月度维度汇总渠道、用户数、订单数、GMV并输出 Markdown 汇报草稿和图表数据 JSON。 --- # Monthly Report Generator ## 适用条件 - 输入必须是 CSV 文件包含且仅包含以下字段date, channel, users, orders, gmv。 - 如果数据缺失字段先执行 scripts/preprocess.py 做字段补齐补齐不了则拒绝执行并说明原因。 - 本 Skill 只处理月度汇总不处理周报、日报。 ## 执行步骤 1. 阅读输入的 CSV 文件确认字段齐全。 2. 运行 python scripts/preprocess.py input.csv output_clean.csv 做数据清洗。 3. 运行 python scripts/generate_chart_data.py output_clean.csv chart_data.json 生成图表数据。 4. 运行 python scripts/render_report.py output_clean.csv chart_data.json report.md 生成汇报草稿。 5. 将 report.md 的内容返回给用户并附上 chart_data.json 的文件路径。 ## 输出格式 - 汇报包含本月总览、渠道环比、Top 3 渠道明细、异常提醒。 - 所有百分比保留一位小数。 - 如果某渠道 GMV 环比下降超过 20%在“异常提醒”中标红提示。这里有几个关键技巧值得展开说。第一适用条件要写“哪些不能用”。很多人在描述里只写“能用什么场景”不写边界结果模型在明明不适合的场景里也强行调用。加上“只处理月度汇总不处理周报、日报”这种负面约束能明显降低误触发概率。第二执行步骤要写成模型不需要动脑也能照做的样子。每一步都给出具体的命令模板、参数占位符、输出文件路径。这看起来啰嗦但正是这种确定性让 Skill 在不同模型、不同版本上都能稳定产出结果。第三输出格式要给细则。模型非常擅长“看起来交付了”但格式细节经常偷懒。你定义了“保留一位小数”“Top 3 渠道明细”“异常提醒标红”它就有明确的执行参照物。3.4 核心脚本从“参考实现”到“防御式编程”SKILL.md 给了模型流程指引但真正让结果稳定的是脚本把脏活累活接过去。我拿preprocess.py举例这个脚本负责数据清洗。写的时候要特别注意脚本绝不能假设数据是干净的。import csv import sys import json from collections import Counter def load_csv(path): with open(path, newline, encodingutf-8-sig) as f: reader csv.DictReader(f) rows list(reader) if not rows: raise ValueError(CSV 文件为空) return rows def clean(rows, required_fields): # 字段缺失检查 missing [f for f in required_fields if f not in rows[0]] if missing: raise ValueError(f缺少必要字段: {missing}) # 数字字段清洗去逗号、去货币符号、处理空值 for row in rows: for field in [users, orders, gmv]: val row.get(field, 0).strip().replace(,, ).replace($, ) row[field] float(val) if val else 0.0 # 日期字段排序确保输出稳定 rows.sort(keylambda r: r.get(date, )) return rows if __name__ __main__: input_path, output_path sys.argv[1], sys.argv[2] rows clean(load_csv(input_path), [date, channel, users, orders, gmv]) with open(output_path, w, newline) as f: writer csv.DictWriter(f, fieldnamesrows[0].keys()) writer.writeheader() writer.writerows(rows) print(json.dumps({status: ok, rows: len(rows)}))写这类脚本有几个经验编码问题一定要处理带 BOM 的 CSV 是家常便饭utf-8-sig能省很多事。数字字段里混入逗号、货币符号、空值太常见了。清洗逻辑要把这些情况全兜住。脚本必须输出结构化状态信息比如{status: ok, rows: 10}。这样模型在执行完脚本后才能判断“是否需要继续下一步”而不是闷头往下冲。宁可报错终止也不要静默产出错误结果。raise ValueError听着不友好但对 Agent 来说明确报错比悄悄出错好一万倍。3.5 验证 Skill三种测试路径Skill 写完不能直接上生产。我习惯跑三遍验证。第一遍是脚本级验证。直接手动执行python scripts/preprocess.py tests/sample_data.csv /tmp/clean.csv确保每个脚本单独跑都能得到预期结果。只要有一个脚本报错都不用麻烦模型。第二遍是模拟调用验证。把 SKILL.md 里的步骤当成 API 文档照着它手动执行一遍确认步骤里写的命令、参数占位符都是真实可用的。很多人这一步偷懒结果模型按 SKILL.md 生成的命令执行时才发现路径错了、参数名不匹配。第三遍是端到端验证。让自己所在框架的 Agent 真实调用这个 Skill输入一个稍复杂的任务描述观察模型是否能正确定位 Skill、按步骤执行到最后。这遍是检查 SKILL.md 的描述是否足够清晰的终极手段——如果模型在“该不该用这个 Skill”“参数怎么填”上犹豫基本就是描述写得不够好。4. Agent 看到了 Skill 却不用问题出在哪4.1 模型调用 Skill 的完整决策链路开发 Skill 一段时间后你会发现最大的困惑不是“写不出来”而是“写出来了模型不用”。要解决这个问题得先理解模型在什么情况下会调用一个 Skill。一次完整的调用决策链路大致是这样的模型读取用户当前请求结合历史对话形成“意图表示”模型扫描候选 Skill 列表逐个比较每个 Skill 的 name 和 description 与当前意图的匹配度如果匹配度高模型会生成一次调用动作并把参数按步骤填入执行结果返回后模型根据返回内容决定下一步做什么。这条链路里真正容易被我们忽视的是第二步。模型并不是“看到了 Skill 列表”它是“读到了你给的每条描述文本”。描述文本的质量比 Skill 本身的实现质量更影响调用率。4.2 描述与路由搜索 query 的写法才是正解我自己的经验是Skill 的 name 和 description 要按搜索引擎的写法来而不是按文档目录的写法来。什么意思搜索引擎的 query 是反直觉的用户不会搜“MonthlyReportGenerator”他会搜“这个月运营数据报告怎么生成”“帮我做一张月度 GMV 变化的图”。所以你的 description 里要多用自然语言的长尾短语覆盖用户可能的说法。我推荐的做法是描述里至少包含三组信息触发场景的自然语言描述比如“月度汇报”“月度运营数据汇总”核心名词和同义词比如“GMV”“订单”“渠道”“环比”一个反例说明比如“只处理月度数据不处理日报”。这样写出来的 description 信息密度高模型用语义匹配时更容易命中。另外不要忽视参数 schema 的设计。Skill 如果需要输入文件路径、日期范围、汇总维度等参数schema 里该标required的必须标该给enum的必须给。模型填参数报错的大部分原因都是 schema 约束太松给了模型过度自由发挥的空间。4.3 执行结果回写与错误恢复模型调用 Skill 之后链条并没有结束。脚本产生的输出需要被模型读取才能生成最终面向用户的答复。这里有两个常见问题。第一个问题是输出太长。脚本打印了一大堆日志模型要读完才能判断结果但上下文窗口有限日志被截断后模型就“失忆”了。我的习惯是脚本只输出结构化摘要比如处理了多少行、输出了哪些文件、有没有异常详细日志写进文件而不是塞给模型。第二个问题是执行失败后的恢复路径。你搜过 “agent execution terminated due to error” 这个热搜词应该能理解我的意思——真实环境里错误太多了文件不存在、权限不足、接口超时、依赖没装。Skill 设计时必须预设错误恢复方案。我的做法是在 SKILL.md 里写明“执行失败后该怎么做”。比如## 错误处理 - 如果 preprocess.py 报错检查 CSV 是否为空、字段是否齐全修复后重试一次。 - 如果重试仍失败停止执行向用户说明失败原因并给出需要补充的信息而不是猜测继续。这一步是在给模型划定“错误时的行为边界”避免它在出错后自行创造一套不稳定的流程。5. 踩坑实录让 Skill 从“能跑”到“稳跑”5.1 坑一描述太“文艺”模型总是误触发我早期写的一个 Skill 是专门做“邮件草稿润色”的description 写的是“polish email drafts with tone awareness and clarity improvements”。听起来很漂亮但实际运行时用户只是随手说了句“帮我把这段话改通顺一点”模型就自动调用了这个 Skill结果把一段本来只是微调的文字重写了一大遍。后来我把 description 改成了更“笨”的写法description: 当用户明确要求修改一封邮件的语气、结构或礼貌度时使用。如果用户只是要求改错别字或通顺度不要使用本 Skill直接修改即可。加了“什么时候不要用”之后误触发率明显下降。这个教训我复用过很多次描述里给反例比给正例更能约束模型行为。5.2 坑二Skill 输出把上下文塞爆有一版 Skill 要读取一个 5 万行的日志文件来汇总异常脚本确实跑出来了 JSON 结果但模型需要把结果读进上下文才能回复用户。5 万行塞进去的结果就是后续对话全程变慢最后直接超过上下文限制。这事的解法很简单Skill 的设计者必须在脚本侧做“面向模型的压缩”。我先在脚本里聚合出摘要比如“共发现 3 类异常最多的一类出现 421 次样例见输出文件”然后只把这个摘要返回给模型。完整的明细写进文件用户需要时再去读。记住一个原则模型是汇报者不是数据仓库。凡是要给模型看的内容都应该是筛选、汇总、提炼后的原始数据放在文件里而不是塞进对话里。5.3 坑三脚本权限过大酿成安全事故Skill 的核心价值是让 Agent“动手”但“动手”意味着代码有真实的系统权限。我见过有人写的 Skill 直接在脚本里用os.system执行任意用户输入的命令这等于把安全大门敞开了。你搜“agent安全”时会看到很多讨论其实问题的根子不是 Agent 本身而是 Skill 脚本的权限设计。我的几条底线脚本不要用管理员权限运行给一个受限用户账号更稳妥脚本里禁止执行来自模型输入层的任意 shell 命令必须做严格的参数校验和白名单涉及写文件的操作路径要固定白名单目录防止路径穿越输出结果里的敏感信息像 API key 之类的一律脱敏后再回传。Skill 不是玩具会和真实系统打交道权限设计得保守一点永远比你事后补救便宜。5.4 坑四异步任务没人管卡死了也看不出来有些 Skill 会触发长时间任务比如让脚本跑数小时的数据处理或者调用外部 API 等回调。第一次实际使用这种异步 Skill 时我踩过坑模型已经把任务交出去了然后它自己在对话里“等结果”但脚本那边因为网络抖动一直没返回整个 Agent 就像卡死了一样。后来我在 SKILL.md 里强制加了两条规则异步任务必须在规定时间后主动检查状态写清楚“超过 X 分钟后查询一次进度最多查询 N 次”如果查询 N 次后仍无结果标记为失败并告知用户而不是无限等待。这类细节不在代码里而在 Skill 的流程设计里。但恰恰是这些细节决定了你的 Agent 是“偶尔卡死”还是“一直稳跑”。5.5 调试方法论日志、Dry-run 与回放最后分享一套调试 Skill 的方法。我建议每个 Skill 至少带三层日志第一层是脚本内部的运行日志记录每一步处理的输入输出摘要第二层是 SKILL.md 执行过程的调用日志记录模型选择了哪些步骤、跳过了哪些步骤第三层是端到端的任务回放把用户输入、模型决策、脚本输出全部留存。有了这三层日志Skill 出问题时能迅速定位是“模型选错了 Skill”“脚本报错”还是“参数传错了”。我自己在工作中经常做“dry-run 模式”给 Skill 加一个前置开关让它跑流程但不真正写文件、不真正调用外部 API只打印“如果执行会做什么”。这层保护能让测试成本大幅降低也方便给其他开发者演示。6. Skill 生态盘点与个人 Skill 库的沉淀思路6.1 主流运行环境对 Skill 的支持差异现在 Skill 这个概念已经被各家采纳但落地的形态差异不小。我整理一下目前常见的方向大家选型时可以做个参照。环境/框架Skill 的承载方式典型特点Claude 类产品通过指令/项目级扩展加载 Skill强调文档描述与交互一致性适用自然语言任务Codex 类工具仓库内skills目录 SKILL.md贴近开发流程适合代码类任务Cursor 等编辑器通过插件或规则导入 Skill强调编辑场景内的快捷调用DeepSeek 等国产框架结合 Harness 加载 Skill灵活性高可自定义程度强自研 Agent 框架自行定义 Skill 的结构与加载逻辑定制性最强代价是需要自己维护这些差异不意味着谁比谁好而是提醒你Skill 不是写一份就能到处跑的。它的格式、加载方式、能力边界都跟着宿主环境走。建议先在你目标框架的最小例子里跑通一个最简单的 Skill再扩展它的结构。6.2 用“Book to Skill”的思路沉淀个人工作流最近我在热搜里看到 “book to skill” 和 “workbuddy skill” 这类词感触挺深。它们背后其实是同一个趋势把个人或团队反复操作的流程从一次性 Prompt 升级为可复用的 Skill 资产。我现在建个人 Skill 库的习惯是凡是重复超过三次的手工操作就考虑写成 Skill。比如我每个月都要做一次数据分析、写一次汇报提纲、整理一次浏览器书签以前是在对话里反复用同几段 Prompt现在我直接把这些沉淀为 Skill 文件。受益是长远的——下次做同样的事不需要重新组织语言也不需要模型重新理解一遍流程。建库的时候我建议按“领域 动词”分类比如>