Skill技能系统:让AI Agent从聊天进化到干活
Skill 技能系统 — 让 Agent 从聊天进化到干活这两年我一直在折腾各类 AI Agent从最早的 prompt 拼接到后来用各种框架搭自动化工作流最深的感受就是Agent 和聊天机器人的本质区别不在于会不会说话而在于能不能把手上的事办完。如果你只是让模型陪你聊人生聊理想那用普通对话完全够可一旦你想让它在真实环境里查资料、改文件、跑脚本、调接口就必须给它一套干活的体系否则它永远只会给你一段看起来正确、实际上没法落地的话。这套体系在目前主流的 Agent 框架里有个核心载体叫Skill技能。Claude 生态里叫 skillCodex 里也有类似概念Cursor、Workbuddy、Harness 这类工具也都在围绕 Skill 做文章。你可以把 Skill 理解为 Agent 的手——模型负责思考怎么干Skill 负责真正把动作执行出去。没有 Skill 的 Agent 像只读说明书的人有了 Skill 它才变成会操作机器的操作员。这篇文章我就从自己的实践角度聊聊 Skill 技能系统到底是怎么设计的怎么把一个聊天式 Agent改造成能干活的工作流。需要说明的是这不是某个框架的官方文档而是我在反复试错中总结的通用套路。无论你用的是 Claude Code、Codex、自研 Harness还是开源的 Agent 项目下面这套思路都能直接用。当然不同平台的 Skill 实现细节有差异但底层逻辑是一致的。咱们先搞清楚为什么需要它再一步步看怎么落地。1. 为什么 Agent 需要 Skill 技能系统1.1 从对话到干活的三道坎想让 Agent 真正干活你会发现它要跨过三道坎第一是确定执行目标聊天时你说帮我写个域名解析脚本它可能直接给出代码干活时你必须让它确认是生成代码、保存到文件、还是运行一遍并返回结果。第二是稳定调用工具聊天时模型只要在回复里写几段代码就行干活时它需要真正调用文件读写、命令行、HTTP 请求这些外部能力。第三是处理异常和反馈聊天时你顺着话聊就行干活时脚本崩了、接口变了、权限不够它都得察觉到并调整策略。我最初做 Agent 的时候走的是把所有工具塞进 system prompt的野路子。做两个工具还行等工具到十个以上模型就开始选择困难——它经常分不清该用哪个、用了之后怎么衔接下一步。而且 prompt 越长模型越容易忽略边缘功能结果核心工具反而经常不被触发。Skill 系统的意义就是把工具调用逻辑使用说明打包成一个独立单元让 Agent 按需加载而不是一次性背下所有说明书。这就像让一个新员工干活你不能把全公司的制度手册揉成一团丢给他。你得给每个岗位配上独立的操作手册和工具包——要做数据清洗就去拿清洗工具包要做报表就去拿报表模板。Skill 就是这一个个岗位工具包。1.2 Skill 和普通提示词的区别很多朋友问Skill 不就是一段特殊 prompt 吗我的回答是Skill 是一个自带执行逻辑和验证闭环的可运行模块而普通提示词只是一段话。举个例子你写一段提示词帮我读取 CSV 并统计每列缺失值模型可能给你一段 Python 代码但也可能直接给你文字建议甚至告诉你可以用 pandas 干这事。就算它给了代码你还要自己复制、保存、运行、看结果。而一个训练有素的数据清洗 Skill会自己完成找到文件、写脚本、运行、读取输出、校验结果、给出结论甚至失败后自动换一种方式重试。实际上Skill 更接近传统软件开发中的函数或插件。它有清晰的输入接口、执行步骤、输出标准。在设计上Skill 关注的不是说什么而是做什么。没有 Skill 的 Agent 是口嗨型的说什么都头头是道一做正事就掉链子有 Skill 的 Agent 至少能把事做出来就算做得不完美也会给你一个可修正的中间结果。另外Skill 也为不同场景提供了隔离性。你可以在同一个 Agent 里同时挂载数据处理和自然语言转 SQL两个技能它们各自维护自己的上下文、文件和依赖互不干扰。这比把几十个功能混在一个 Prompt 里强太多——至少改一个功能不用重写整个 Prompt也不会因为新增技能导致旧技能失效。2. Skill 技能系统的核心组成2.1 触发与匹配Agent 怎么知道该用哪个 Skill在大部分框架里Agent 不会每时每刻都在调用所有 Skill它要先判断当前该用哪个。这里的核心机制是基于 Skill 的描述和与当前任务的语义匹配。每个 Skill 都应该有一个简洁但信息充分的说明字段它像函数签名一样告诉 Agent这个技能是干什么的、适合什么场景、需要什么前置条件。我踩过的坑是描述写得太口语化模型匹配不到。比如你写处理一下数据模型可能不知道是该用数据清洗还是数据可视化。后来我改成当用户需要加载CSV/Excel并统计缺失值、重复值、基本分布时使用可输出字段报告和图表。这样训练后命中率立刻上来了。所以描述的重点不是文艺而是让模型能在任务描述和技能描述之间建立明确映射。还有的框架采用关键词触发或路由模型机制比如定义好 trigger phrase命中关键词就强制走某个 Skill。这种方式适合意图特别清晰的任务比如抓取网页可以直接触发爬虫 Skill。但如果任务有歧义靠关键词容易误伤。我一般用的是语义相似度优先关键词兜底的混合策略——先让 Agent 自行判断如果匹配置信度低就再看有没有关键词命中。另外Skill 的加载顺序也会影响匹配效率高频技能靠前放低级错误能少很多。2.2 执行流程从决策到动作的状态机一个成熟的 Skill 不应该只是调用一个 API 然后返回结果因为真实世界的任务往往是多步骤的。比如你要做一个网页摘要技能执行流程是抓取网页 - 清洗HTML - 截取正文 - 调用模型概括 - 整理输出。如果这个流程没有状态管理一旦中间某一步崩溃Agent 不知道从哪继续只能从头跑一遍——这既浪费 token又容易引入新错误。所以好的 Skill 系统会内置一个轻量级状态机。状态包括pending待执行、running执行中、waiting_input等待补充信息、succeeded成功、failed失败。Agent 每次执行 Skill 时会把当前状态和执行进度记录下来失败时可以从断点重试。我在自研 Harness 的时候只做了最简单的步骤标记状态存在 JSON 文件里效果已经很显著重试成本降了一半左右。另外执行过程中的日志也很重要。Agent 为什么失败是代码语法错误、网络超时、还是模型输出格式不对每步日志都要保留。这不仅是排查的依据也是后续优化 Skill 的一手资料。我见过不少团队只关心最终结果日志全丢最后问题出现了只能干瞪眼。所以设计 Skill 时记得为每一步写清楚输入、输出、耗时、错误信息。2.3 输入输出契约让 Skill 变成可复用的函数写过程序的都懂接口契约的重要性。Skill 也是一样它应该有明确的输入参数和输出格式。比如一个发邮件技能输入应该是收件人、主题、正文、附件路径输出应该是发送成功与否、邮件 ID 或错误信息。如果你的 Skill 输入是模糊的给张三发一封介绍我们产品的邮件那就没法稳定复用了——模型每次都要猜猜错了整条链路就断了。我的经验是把输入参数分成必填项和可选项在 Skill 描述里明确标注。比如爬虫技能必填是 URL选填是最大抓取深度、延迟、请求头。这样模型在调用时就会知道哪些信息需要向用户确认哪些可以从上下文里推断。有时候用户没说 URLAgent 就得反问这比它自作主张抓一个错误的网页要靠谱。输出格式也要固定。我一般都用 JSON 结构返回因为后续 Skill 或 Agent 主流程能直接用 key 取值避免解析自然语言的不可靠性。例如统计类技能统一输出{ total: 100, missing: 5, columns: [a,b] }主流程拿到就能渲染成报告或继续做下一步。这不是学院派建议而是被生产环境毒打后的教训——自然语言输出在跨 Skill 调用时十有八九会翻车。3. 如何从零编写一个可用的 Skill3.1 定义功能边界与触发描述动手写 Skill 的第一个动作不是打开编辑器而是想清楚它的功能边界。边界太窄比如只能统计 CSV 行数实用性低边界太宽比如处理所有数据文件Agent 使用时反而分类困难。我常用的判断标准是一个 Skill 应该能在 3 步之内讲清它做什么。从本地 CSV 加载数据检查缺失值和重复值输出一个 JSON 总结——这就是一个边界清晰的技能。边界定好后写触发描述。这里有个技巧不要用空泛的词像数据质量分析可能不如数据质量检查/清洗/统计具体。描述里要带上任务动词 数据对象 输出物类型。举个例子当用户要求对本地表格数据CSV/Excel进行质量检查、缺失值统计、重复值检测时使用。输入为文件路径输出为JSON格式的统计数据表。这样的描述模型在意图识别时命中率极高。另外考虑多个触发场景也很重要。同一个技能可以在不同语境下被调用。比如计算某个周期内销售额和对比两周数据差异用的可能是同一个数据聚合技能。所以描述里可以多列几个同义场景但注意别写成毫无边际的小说。我会在描述里加一个similar_tasks字段专门放常见触发说法这招实测很有效。3.2 编写可执行的任务清单与工具调用逻辑定义完边界和描述下面就是写真正的执行逻辑了。有两种主流实现方式一种是自然语言步骤清单Agent 自己读着步骤去调用工具另一种是预编译脚本/函数Skill 直接绑定一段 Python 或 Node 代码Agent 只需要传参。我建议根据任务的稳定性来选择如果任务是高度标准化的比如文件格式转换Web 请求直接用代码实现速度和成功率都更好如果任务需要模型做上下文判断比如从一堆日志里找出可疑行为那就用自然语言指导 中间工具配合。自然语言版的执行步骤要写得非常具体不能让模型自由发挥。例如清洗公司销售数据这个 Skill 的步骤可以是先调用list_files找到目标文件然后用read_csv读取前 50 行做抽样检查接着调用run_python执行预先定义好的清洗代码最后生成报告。每一步都要写明用什么工具、输入什么变量、期望得到什么结果。而脚本版的 Skill 则要重点关注依赖管理。我在写爬虫 Skill 时会默认在技能目录里放一个requirements.txt启动时检查装没装好。有的框架支持隔离环境比如每个 Skill 用独立 venv这能避免依赖冲突。对于脚本我还习惯把所有输出集中到一个output/文件夹方便后续调试和检查。3.3 Skill 的测试与迭代上线前必须做三件事写完 Skill 不是万事大吉测试是重中之重。我的流程分三步。第一步是单指令验证只让 Agent 执行一个 Skill 的简单任务比如读取 test.csv 并统计缺失值检查每一步的日志和输出。第二步是边界值验证故意给空文件、超大文件、错误编码文件、带恶意代码的文件看 Skill 是否优雅报错而不是崩溃。第三步是组合流程验证让 Agent 连续调用多个 Skill比如先抓网页再用摘要技能确认两个 Skill 之间的数据格式能互相兼容。我把踩过的坑总结成了表格放在 4.3 节后面但这里要先强调两个容易忽略的地方。第一是**幻影成功**——脚本执行完了但实际没有达到预期比如爬虫返回 200 却是验证码页面。我的解决办法是在 Skill 里增加校验环节用正则检查返回内容是否包含目标信息没有就标记失败。第二是token 耗尽导致半途而废长任务尤其常见。我的做法是让 Skill 在执行到关键里程碑时强制把中间结果落盘这样即使对话超时也能从落盘文件继续恢复而不是全部重来。4. 实战让 Agent 执行一个典型的干活任务4.1 任务示例抓取多个网页并生成结构化摘要我先选一个很有代表性的任务带大家完整走一遍**让 Agent 从指定的五个新闻页面抓取正文去掉导航和广告每篇生成 200 字摘要最后汇总成一个 Markdown 表格。**这个任务涉及网络请求、HTML 解析、自然语言生成、文件输出典型的全能型小项目。如果没有 SkillAgent 大概率会给你一段爬虫代码然后让你自己跑。如果有了完善的 Skill它应该能一条龙搞定。我按第 3 节的套路来设计两个 Skillweb_fetch和html_extract。web_fetch负责下载页面并保存到本地输入是 URL输出是文件路径html_extract负责从本地 HTML 提取正文输入是文件路径和正文长度需求输出是纯文本。然后还有一个summarizer技能——这部分会调用模型自身的摘要能力所以可以做成自然语言步骤不一定要写死代码。这样三个技能组合在一起就能完整跑通。这种拆法的好处是职责单一。web_fetch不需要知道摘要怎么生成html_extract不关心网页是怎么来的。出了问题时我能立刻定位到是哪一层出了问题是网络不通、正文解析失败、还是摘要格式不对。这就是模块化的价值——你不需要从头把所有代码都看一遍。4.2 实现过程定义、编码、挂载三步走第一步定义web_fetch的 skill 元信息name: web_fetch description: 下载指定URL指向的网页内容并存储为本地HTML文件。输入必须提供完整的http/https链接输出为本地文件绝对路径。适用于需要分析网页内容、提取信息、生成摘要之前的预抓取步骤。 input: url: 必填字符串 timeout: 选填默认15秒 output: file_path: 本地HTML文件路径 status_code: HTTP状态码第二步编写执行逻辑。我用的框架支持 Python 脚本所以核心代码如下这是最小实现实际生产我还会加随机 User-Agent 和重试机制import requests, sys, uuid url args[url] timeout args.get(timeout, 15) save_dir os.path.join(os.getcwd(), output, pages) os.makedirs(save_dir, exist_okTrue) resp requests.get(url, timeouttimeout, headers{User-Agent: Mozilla/5.0}) resp.encoding resp.apparent_encoding file_path os.path.join(save_dir, f{uuid.uuid4().hex}.html) with open(file_path, w, encodingutf-8) as f: f.write(resp.text) result { file_path: file_path, status_code: resp.status_code, content_length: len(resp.text) }注意我把保存路径放在项目目录的output/下而不是任意目录这能避免 Agent 把文件写到奇怪的位置。很多框架默认允许 Agent 访问整个文件系统这是一把双刃剑——一定要给 Skill 限定好读写范围否则技能越写多越容易出安全事故。第三步把 Skill 挂载到 Agent。我通常用 YAML 配置文件统一管理skills: - web_fetch - html_extract - summarizer挂载之后我会让 Agent 先描述一下自己的技能列表确认它能正确感知到这些技能的存在。有时候模型会忽略某些技能这大概率是描述写得不够清楚或者是技能之间的描述冲突了。4.3 运行效果复盘从翻车到稳定第一次跑的时候果然翻车了。Agent 老实调用了web_fetch但后面它没有直接接着用html_extract而是试图自己用 Python 代码去解析网页结果因为编码问题报错了。我分析了日志发现原因是html_extract的描述里没有明确指出该技能应该与 web_fetch 的输出路径配合使用。于是我在描述里加上一句输入字段 file_path 通常由 web_fetch 技能生成可直接传入。改完再跑链路通了三个技能按顺序执行完最终输出了 Markdown 表格。这个教训很有代表性Skill 的描述不仅影响意图匹配还影响技能之间的衔接。你写的每个技能都像文档里的一张参数表后续技能要能识别前序技能输出的字段名。如果你在web_fetch里把输出字段叫file_path在html_extract里输入也叫file_path那么 Agent 就能很自然地串起来。如果你起了一堆不同名字它就要做额外的对齐判断模型毕竟不是程序判断一多就出错。最终运行效果让我满意的是整个过程 Agent 都没有让我动手复制代码或改路径它自己发现了页面里有一段 HTML 是乱码于是重新调用了一次html_extract指定了 UTF-8 编码去解析。这种自主迭代修复的能力正是 Skill 系统带来的核心价值——它给了 Agent 一个执行-检查-纠错的闭环。5. 常见问题与避坑指南5.1 Skill 不生效百分之八十是描述问题很多朋友跟我反映Skill 明明挂载了但 Agent 就是不用。我一般让他们先自查三件事描述里有没有说清楚输入是什么、输出是什么、什么时候用有没有跟其他技能撞车还有技能描述是否和用户问题的措辞差异太大。如果这些都调整了还是不行就在调试模式里看看 Agent 实际拿到了哪些 Skill 的元信息有时候是框架因上下文长度被截断把后面的技能裁掉了。这里有一个我常用的技巧在触发描述里加上负面示例比如当用户仅咨询新闻内容而不需要保存网页时不要使用web_fetch。这能显著降低误调用率。模型对不要做什么的理解往往比对要做什么更直接。5.2 多技能协作时怎么避免串台当 Agent 有多个 Skill 时另外一个高频问题是串台——调用了技能 A却传了技能 B 的参数。我在 2.2 节提到过输入输出契约这里再补充一招让技能的输出字段带上技能名前缀。比如web_fetch输出web_fetch.file_pathhtml_extract输入html_extract.file_path。虽然字段多了字但模型不容易弄混。我见过生产环境中因为字段名都是result导致全流程崩溃的案例加前缀后基本绝迹。另外如果一个 Agent 同时需要依赖顺序执行多个 Skill可以写一个编排脚本或者叫 pipeline而不是直接让模型自由发挥。编排脚本里明确指定先调哪个、再调哪个、如何传递参数模型只在脚本无法覆盖的异常分支里介入。这种能自动化就自动化的思路让整个系统的稳定性提高了一个量级。5.3 安全与权限别让 Skill 变成脱缰野马Skill 系统给了 Agent 很大的能力也意味着更大的风险。我强烈建议给每种 Skill 配置最小权限。例如web_fetch只需要网络访问和写入指定 output 目录的权限那就不要让它有直接删除文件的权限直接执行 shell 命令的技能更要严格控制输入参数防止注入。我在脚本里还会做一层校验比如 URL 必须以 http/https 开头、文件路径必须在 allowed 目录下。这不是过度设计而是被现实教过的——模型会出于善意执行一些危险操作比如尝试删除临时文件结果删错了地方。在安全这块我给自己定了几条规矩第一所有 Skill 可执行的命令必须列入白名单第二任何外部输入都要校验再拼接第三输出数据如果不涉及必要隐私尽可能脱敏。对于企业级项目建议再加一层人工审批机制高危操作必须人确认后 Skill 才会继续。千万别觉得这些麻烦Agent 越能干越需要缰绳。5.4 常见报错速查表为了节省大家排查时间我把这两年处理过的问题整理成了表格。问题现象可能原因处理方式Agent 调用了 Skill 但结果为空输出字段名不符或脚本路径错误检查日志中真实的输出确认字段名一致Skill 执行到一半失败网络超时、依赖缺失、文件不存在增加重试机制和明确错误提示检查依赖安装Agent 不识别新挂载的 Skill描述冲突或上下文超长被截断精简描述把高优先技能放前面必要时格式化上下文两个 Skill 互相干扰共享全局变量或依赖冲突隔离技能运行环境输出字段加前缀脚本执行成功但结果不符合预期校验缺失模型误判成功在脚本中增加结果校验逻辑使用正则或断言长任务 token 耗尽步骤太多或输出过长里程碑落盘拆分长 Skill 为多个短 Skill表格里的每一行我都实际碰到过。尤其是执行成功但结果不符预期这是最隐蔽的坑。比如爬虫返回了 200但页面其实被重定向到了登录页统计代码跑完没有报错但读的文件是旧版本。所以无论用什么框架Skill 内部一定要有结果自检不要信任没报错就等于做对了。5.5 关于 Skill 生态的个人体会最近一段时间Claude、Codex、Cursor 都在大力推广自己的 Skill 生态很多做 Agent 工具的团队也都在往这个方向靠。我觉得 Skill 更像是 Agent 时代的插件协议谁能让用户以最小的成本把想法变成可复用的技能谁就能在生态里占据位置。对开发者来说现在掌握 Skill 的设计方法就等于提前拿到了下一批生产力工具的钥匙。但也要提醒一句Skill 不是越多越好。技能多了模型匹配的负担会加重误用率提高。我个人的经验是一个垂直领域的 Agent 挂载 5 到 10 个精心打磨的 Skill 就够了超过 15 个就要考虑分层。比如把通用工具和领域工具分开或者按照任务流程建立不同层级的 Skill 目录。保持技能库的小而精远比大而全重要。最后分享一个我今天还在用的小技巧给 Skill 写文档的时候把用一段话介绍这个技能是干什么的这一项让 AI 反向生成三个不同详略程度的版本——一个极简版用于 Agent 快速匹配、一个标准版用于人阅读、一个详细版用于多技能协作时的参考。你在为 Agent 写技能也是在为未来的自己留操作手册。这一点点额外的投入可以省下后面无数次调试的工夫。