ARTICLE DETAIL

资讯详情

深耕网站视觉设计与运营推广的一线实战洞察。

agent-skills:从提示词工程到可复用技能包,让AI Agent稳定落地

agent-skills:从提示词工程到可复用技能包,让AI Agent稳定落地 如果你最近在折腾 AI Agent大概没少撞上同一堵墙模型本身已经很能说了但真让它按你团队的流程把活儿干完它要么漏步骤要么把规则忘得一干二净你往系统提示词里多写几句约束它又开始自由发挥给出一个“听起来很合理、实际没法用”的结果。agent-skills 这个方向就是专门来解决这个问题的——它把 Agent 干活所需的能力做成一个个独立、可复用、可测试的“技能包”让模型在合适的场景里调用合适的技能而不是靠临场发挥。这篇文章想把我在 agent-skills 项目上的设计思路、实操过程和踩坑记录完整写下来适合正在做 Agent 应用或者想让团队 AI 工具真正落地的开发者参考。1. 内容整体设计与思路拆解1.1 从“对话模型”到“技能执行”agent-skills 要解决的核心问题很多人第一次做 Agent 时会把所有业务流程直接写进 system prompt比如“你是一个客服助手第一步查询订单状态第二步判断是否可退换第三步生成处理结果”。这套方案在演示环境里跑得通一旦进入真实业务问题马上冒出来模型对重复规则会产生注意力衰减长提示词里靠后的规则经常被忽略更尴尬的是不同任务的边界模糊模型容易把 A 任务的规则套到 B 任务上。我的判断是问题的根源不在于提示词写得不够好而在于我们让模型承担了太多“记忆”和“执行顺序”的责任。agent-skills 的核心思路是把“工作流程外置”。一个技能包不再是一段文字而是一个带目录、脚本、测试的独立模块。模型要做的事情只有一件判断当前用户请求应该匹配哪个技能然后调用这个技能。至于这个技能内部有几步、每一步怎么做、需要什么参数都不需要模型临时推理而是由技能包自身定义好。换句话说agent-skills 不是给模型更多指令而是给模型一块可以随时取用的“操作面板”它知道什么时候按哪个按钮就够了。我在实际设计中最先确定的一个原则是技能必须可以被单独测试。过去调试一个 Agent 业务流出问题后你分不清是模型理解错了还是数据源出错了还是提示词写得不清楚。但技能一旦被拆出来我就可以直接运行它的脚本看输入输出是否正常。这样模型负责的部分和工程负责的部分被清晰切开定位问题的成本一下子降了下来。这也是为什么 agent-skills 在项目结构上不像一个“插件”更像一个“测试友好的工具包集合”。1.2 技能的分层设计从原子操作到完整工作流在面对一堆业务需求时我习惯先把技能分成三个层次。底层是原子技能例如“读取文件”“调用某个 API”“执行 SQL 查询”中间层是领域技能封装了特定业务规则例如“检查订单是否符合退款条件”“判断一段文本是否包含敏感词”上层是工作流技能它把多个技能按顺序组合在一起完成一个端到端的目标例如“生成本周项目周报”“批量处理客户投诉”。一开始我差点把“周报生成”做成一个大而全的技能里面既包含 Git 操作、又包含数据聚合、还包含模板渲染。后来发现这种做法是给自己挖坑一旦模板要改整个技能都得重新验证如果另一个场景也需要“Git 操作”但不需要“模板渲染”又得复制一份代码。所以我现在倾向于让每个技能只做一件事复合需求通过上层技能去编排。分层之后还有一个额外好处每一层都可以独立写测试底层出问题不会连累上层逻辑。分层设计也影响技能的触发策略。原子技能往往适合做成常驻工具让模型随时调用工作流技能则需要更严格的触发词不能用户说一句“查一下数据”就直接把整套周报流程跑起来。我在 SKILL.md 的元信息里专门留了一个when_to_use字段就是用来写清楚“什么情况下才应该使用这个技能”这个字段比 description 更关键它能帮模型排除掉大量错误选择。1.3 为什么我把 agent-skills 做成一堆 Markdown 和脚本你可能好奇为什么不直接用一个复杂的编排框架为什么非要用 Markdown 加脚本这种“简陋”的组合。我的理由很直接技能包最大的价值是易写、易读、易维护。Markdown 是人和大模型都能理解的语言大模型可以直接读取并优化 SKILL.md脚本则负责做那些模型不擅长的确定性计算比如日期运算、数据过滤、文件合并。这两者组合恰好覆盖了“语义理解”和“精确执行”两个互补的能力域。相比一个几千行的 JSON 配置文件Markdown 加脚本的目录结构还有一个隐藏优势方便代码评审。团队里其他成员不用理解整个 Agent 框架只需要打开一个技能目录就能看清楚这个技能干什么、依赖什么、怎么测试。而且每个技能包都是自包含的放到任意 Agent 工程里都能通过统一加载器注册。这种“模块化 可复制”的思路让技能库可以像 Git 仓库一样做版本管理出了问题回滚一个目录即可不会影响其他正在运行的技能。我当时也对比过用数据库存技能定义但最终放弃因为数据库不利于追踪历史变更更不利于离线开发和测试。用文件系统做存储天然支持 diff、branch、review这套工作流开发团队早就习惯了没必要为了“统一配置”去重新发明一套方案。2. agent-skills 核心细节解析与实操要点2.1 技能包标准结构拆解一个标准技能包我通常按下面的目录结构组织skills/ └── weekly-report/ ├── SKILL.md ├── scripts/ │ ├── collect_commits.py │ └── render_report.py ├── templates/ │ └── report_template.md.j2 ├── tests/ │ ├── test_collect_commits.py │ └── fixtures/ │ └── sample_commits.json └── requirements.txtSKILL.md是整个技能包唯一必须存在的文件它是 Agent 理解技能的入口。里面会写清楚这个技能叫什么、什么时候用、输入输出是什么、执行步骤是什么。scripts/目录放的是可执行代码我不建议把代码直接内嵌在 SKILL.md 里因为脚本往往要调试独立成文件更方便。templates/目录放输出模板比如周报的 Markdown 模板。tests/目录放自动化测试和模拟数据这一层被很多人忽略但它恰恰是最能保证技能长期可用的部分。requirements.txt记录脚本依赖避免出现“在我机器上能跑到你机器上就跑不了”的问题。每个技能包必须自包含不能偷偷依赖另一个技能包内部的脚本。我见过有项目直接在技能 A 里去调用技能 B 的scripts/common.py刚开始没问题后来技能 B 被重构技能 A 立刻挂掉而且报错信息非常难懂。所以我在团队里立了一条规矩跨技能复用能力必须把公共部分提取成独立包或者通过外部接口调用而不是互相引用文件。2.2 SKILL.md 怎么写才真正被 Agent 读得懂SKILL.md 不是给程序员看的接口文档而是给模型看的使用手册。所以我写它的时候会刻意做到“把模型当成一个聪明的实习生”来交代而不是当成一个“SQL 解析器”。下面是我常用的一个结构示例--- name: weekly-report-generator description: 当用户需要汇总指定时间段内的代码提交、PR 和关键进展并生成 Markdown 周报时使用。 when_to_use: 用户提到“周报”“weekly report”“汇总本周工作”“这个迭代的进展”等表达时优先匹配。 input: start_date: 开始日期格式 YYYY-MM-DD可省略默认 7 天前 end_date: 结束日期格式 YYYY-MM-DD可省略默认今天 project: 项目编号或仓库名可省略 output: Markdown 格式的周报文件保存到 outputs/weekly_report.md dependencies: - python 3.10 - git - requests --- # 周报生成技能 1. 解析输入参数。若 start_date 为空取当前日期减 7 天若 end_date 为空取当前日期。 2. 调用 scripts/collect_commits.py传入 start_date、end_date、project获取提交记录 JSON。 3. 调用 scripts/render_report.py传入提交记录 JSON 和模板文件生成最终 Markdown。 4. 将 Markdown 写入 outputs/weekly_report.md并向用户返回文件路径。frontmatter 里的name是技能的唯一标识description是给模型做初筛用的when_to_use要写得具体最好带上用户常见说法里的关键词。真正执行时模型会先读description和when_to_use判断要不要选择这个技能选中之后再读正文里的步骤。所以正文里的步骤描述不需要过于口语化但要把“按顺序做哪几件事”讲清楚。我踩过的一个坑是description 写得太抽象比如“生成周报”这四个字。结果用户说“总结一下这周的工作”模型完全没有把这个请求和“生成周报”对应上。后来我把描述改成“汇总指定时间段内的代码提交、PR 和关键进展并生成 Markdown 周报”并在 when_to_use 里增加了常见变体表达匹配率立刻提升。你可以在描述里放 3 个左右的触发词变体但不要堆砌否则模型会被无关词干扰。2.3 脚本与工具的接入方式技能包里的脚本承担的是模型“不擅长”但“必须精确”的工作。比如日期计算、文本格式化、网络请求处理。脚本在设计时必须遵循一个原则无交互、参数化、结果可预期。Agent 调用脚本时不可能像人一样在终端里回答“是否继续”所以脚本的输入应该全部来自命令行参数或标准输入输出应该写到标准输出或指定文件。我通常用 Python 写脚本因为它的标准库足够覆盖大部分场景团队也好维护。一个最小可用的脚本入口大概是这样的python scripts/collect_commits.py --start 2025-01-01 --end 2025-01-07 --output tmp/commits.json脚本解析参数后把数据源里的记录读出来经过过滤和聚合最后以 JSON 写入tmp/commits.json。Agent 不需要关心脚本内部是怎么实现的它只需要知道这个命令能返回 JSON 文件。这样做的好处是当模型需要“调用技能”时我可以在运行时里直接把这个命令封装成一个工具函数甚至不需要让模型真的去理解代码逻辑。Python 版本和第三方库的版本是需要提前锁定的。我在requirements.txt里会写上明确的版本范围例如jinja23.0,4.0避免某次重装环境后技能突然跑不起来。如果你所在团队的 Agent 运行在 Docker 容器里我建议把每个技能包依赖构建到一个基础镜像里而不是等运行时再临时安装。2.4 技能测试用例让 Agent 学会“自检”测试用例是我后期才补上的补完之后我才意识到它有多重要。测试有两个作用一是给开发者做回归验证改完代码后跑一遍就知道有没有破坏原有行为二是给 Agent 一个“自检”的判据当调用技能后返回异常模型可以主动运行测试来判断是环境问题还是数据问题从而触发重试或兜底逻辑。我习惯在每个技能包里放一个tests目录里面至少覆盖两条路径正常路径和边界路径。以周报技能为例正常路径是给一个包含提交记录的时间段验证输出 Markdown 里出现了对应条目边界路径是给一个没有任何提交的时间段验证输出里能正确显示“暂无提交”而不是直接抛异常。运行测试的命令要固定下来我一般写成cd skills/weekly-report python -m pytest tests/ -q如果 Agent 运行时检测到技能脚本返回失败它可以先跑一次测试如果测试失败就直接告诉用户“技能环境当前不可用请检查依赖”而不是把一堆半生不熟的结果硬返回。这种设计能把 Agent 从“盲目重试”中解放出来也大大减少了我半夜被线上告警吵醒的次数。3. 实操过程与核心环节实现3.1 从场景出发手写一个“周报生成”技能我会用一个非常常见的场景来说明完整落地过程生成项目周报。需求是用户说“生成上周周报”Agent 需要从 Git 仓库里获取代码提交记录从项目管理接口获取 PR 和任务状态然后按团队模板生成一份 Markdown 周报。这个技能非常适合作为第一个练手项目因为它涉及外呼数据、参数解析、模板渲染和文件输出几乎覆盖了 agent-skills 的全部关键点。在设计阶段我先定义输入和输出边界输入类型说明start_datestring开始日期格式 YYYY-MM-DD可省略默认 7 天前end_datestring结束日期格式 YYYY-MM-DD可省略默认今天projectstring项目编号或仓库名可省略输出说明一个 Markdown 文件按模板渲染包含提交统计、PR 列表、工作小结我把边界想得很清楚这个技能只负责汇总和渲染不负责分析代码质量也不负责给改周报的人提供修改建议。边界越清晰模型越不会在调用技能之后画蛇添足。3.2 技能包落地全过程第一步是创建目录按照前面说的结构搭好空壳。第二步是写 SKILL.md把元信息、输入输出和步骤写好。第三步是实现脚本我用 Python 写了一个collect_commits.py核心思路是通过git log获取提交信息然后按日期过滤并输出 JSON。import argparse import json import subprocess from datetime import datetime, timedelta def collect_commits(start, end): cmd [ git, log, f--since{start}, f--until{end}, --prettyformat:%H|%an|%ad|%s, --dateshort ] result subprocess.run(cmd, capture_outputTrue, textTrue, checkTrue) commits [] for line in result.stdout.strip().splitlines(): sha, author, date, subject line.split(|, 3) commits.append({sha: sha, author: author, date: date, subject: subject}) return commits if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--start, requiredTrue) parser.add_argument(--end, requiredTrue) parser.add_argument(--output, requiredTrue) args parser.parse_args() data collect_commits(args.start, args.end) with open(args.output, w, encodingutf-8) as f: json.dump(data, f, ensure_asciiFalse, indent2)这里故意用subprocess.run配合参数列表而不是把命令拼成一个字符串主要是为了防止特殊字符把整条命令拆开从而产生安全问题。下一步是写render_report.py它会读入提交记录 JSON基于templates/report_template.md.j2渲染出最终 Markdown。模板里我会预留“本周完成”“风险与阻塞”“下周计划”几个区块即使数据缺少也会保留标题结构。最后一个环节是写测试。我在tests/fixtures/下放了一个sample_commits.json在测试里用真实模板渲染一次然后断言输出文件里包含预期的小节标题。从这个案例可以看到整个技能包的开发其实和普通软件开发没有本质区别只不过消费方从人变成了 Agent。3.3 在 agent 运行时里注册与调用技能包本身不会自动生效它需要一个加载器把它注册到 Agent 的工具列表里。我写了一个很轻量的加载函数def load_skills(skills_dir): skills [] for dir_path in Path(skills_dir).glob(*/SKILL.md): meta parse_frontmatter(dir_path / SKILL.md) meta[path] dir_path.parent skills.append(meta) return skills加载器会把每个技能的name、description、parameters转成 model API 需要的 function definition。这样模型在收到用户消息后先根据触发词决定要不要调用“周报生成技能”一旦决定调用运行时就把前端传来的参数解析为脚本需要的命令行参数并执行。我会把 SKILL.md 的完整正文放在一个单独的工具参数里让模型在调用前“阅读手册”但这种方案会消耗更多 token所以更推荐只在必要时注入。实际项目里我更倾向用“两级索引”来组织技能第一级是只有名称和一句话描述常驻在系统提示词里保证模型能快速判断该用哪个技能第二级是技能全文只有模型选中某技能后才作为工具说明注入。这样技能库扩展到几十个之后上下文也不会爆掉。3.4 为什么我建议先手动跑一次完整链路接入 Agent 之前我一定会先在终端里把技能包完整跑一遍。先单独执行collect_commits.py确认能拿到预期 JSON再执行render_report.py确认能生成 Markdown最后跑一遍pytest确认测试通过。这个过程看起来很原始却能提前挡住 80% 的问题。脚本能不能跑、路径对不对、依赖缺没缺这些在 Agent 介入前就能被暴露出来。跑通之后我还会把一次完整的输出保存起来当作这个技能的 golden sample。以后每次改动技能我把新旧输出做一次 diff就能快速看出影响范围。这个习惯帮我避免过好几次“模型说技能执行成功但输出格式已经完全不对”的诡异问题。你可以把 golden sample 放到tests/fixtures/expected_report.md里在测试里强行比对这比纯靠人的眼睛可靠得多。4. 常见问题与排查技巧实录4.1 技能文件存在但 Agent 就是不调它这是我在多个项目里遇到频率最高的问题。技能包老老实实躺在skills目录里测试也过了但用户输入“生成上周周报”时模型就像看不见这个技能一样直接凭空生成一段周报。第一次遇到这种问题时我花了一个下午去翻模型日志最后发现是 description 里的触发词和用户表达对不上。用户说“这周干了啥”我的描述里只写了“周报”“weekly report”模型自然匹配不到。排查这个问题时我按三步走先打印技能注册列表确认技能确实被加载再用一条包含明确触发词的 prompt 直接测试看模型会不会选择该技能如果还是不行就打开模型调用的日志看它在几十个工具描述里到底有没有考虑过这个技能。大多数时候问题出在描述太窄我会把用户可能的口语化表达补进when_to_use比如“这周干了啥”“总结一下进度”“周报搞一下”。这比修改模型参数性价比高得多。4.2 上下文爆炸技能文档太长导致模型“迷失”技能库一旦超过十个最直接的问题不是模型不会选而是系统提示词太长。如果把每个 SKILL.md 的完整正文都塞进系统提示词一次请求可能多消耗几千甚至上万 token模型对后面的内容也会逐渐不敏感。我经历过一次线上事故某个任务的技能文档特别长导致模型在处理这个任务时把前面的安全规则忘掉了输出了不符合预期的内容。解决办法就是前面提到的“两级索引”系统提示词里只放每个技能的name、description和when_to_use摘要技能全文按需加载。我还给自己定了一个量化标准单技能的 description 尽量控制在 200 字以内SKILL.md 正文字数控制在 1500 字左右。脚本和模板不算上下文它们只是被调用的资源。如果你的团队里有很多结构相似的技能还可以用向量检索做粗排但我会保留一个关键词规则的硬过滤避免纯语义检索把明显无关的技能捞出来。4.3 技能之间互相打架命名、依赖与冲突技能数量上去后第二个高频问题是命名和依赖冲突。我见过两个技能都定义了get_data.py这样的通用文件导致加载时互相覆盖也见过技能 A 依赖requests的版本是 2.25技能 B 却要求 2.31统一环境升级后技能 A 直接不可用。规避方法有三个。第一技能名和脚本名加前缀比如weekly_report_collect.py而不是collect.py降低冲突概率。第二依赖隔离每个技能包要么使用独立虚拟环境要么在容器里分别构建退一步说至少要在requirements.txt里固定版本并定期全量回归测试。第三建立跨技能调用规范不允许直接读其他技能目录下的文件必要的能力提升为公共库。这样做短期内会多一些重构工作长期能保住系统的可维护性。4.4 权限与安全技能不是任何脚本都能执行技能包的最大安全风险是脚本获得了过高权限。Agent 一旦被诱导执行某个技能脚本可能在服务器上删除文件、修改数据库、访问内网服务。我在生产环境里采用了几条硬性规则技能脚本使用专门的低权限系统用户运行重要的破坏性操作需要在 SKILL.md 里写明二次确认机制脚本禁止直接把用户输入拼进 shell 命令统一用subprocess参数列表。我自己还有一个习惯所有技能脚本的网络请求都必须走白名单代理或网关从源头上避免访问意料之外的外部地址。比如周报技能里的requests.get只会访问公司内部的项目管理 API域名在配置里写死任何从用户输入中提取的动态域名都会被直接拒绝。下面是常见风险场景与缓解方案的对应表风险场景缓解方案用户输入拼进 shell 命令用参数列表方式调用子进程禁止字符串拼接脚本删除业务文件运行账号最小权限删除前强制二次确认技能依赖被恶意篡改锁定依赖版本容器镜像做不可变构建外部 API 返回恶意内容对响应做 schema 校验防止脏数据反噬这些措施不会让技能系统变成铜墙铁壁但能挡住绝大多数偶然事故和低水平攻击。做 agent-skills 时我一直把它当作“给 Agent 造了一只手”手能干活也就能闯祸控制权限和控制输入输出格式同样重要。5. agent-skills 生态与进阶方向5.1 与 MCP 的定位差异很多刚开始接触 agent-skills 的人会把它和 MCP 搞混实际上两者解决的层级不一样。MCP 是一个标准化的工具调用协议它解决的是“模型如何与外部工具交互”的问题类似给工具装上了统一的 USB-C 接口。agent-skills 则更关心“一个复杂任务应该如何被拆解为可沉淀、可教学的操作步骤”它强调的是技能内容本身而不是传输层协议。在我的项目里这两者是互补关系。我可以用 agent-skills 定义“周报生成”这个技能包再把其中的脚本调用暴露成一个或多个 MCP 工具让 Agent 通过标准协议调用。技能包的核心资产是 SKILL.md 和脚本逻辑底层走不走 MCP只是接入方式的差异。理解了这层关系你在设计系统时就不会陷入“二选一”的纠结。5.2 社区如何共享技能包技能包天然适合用 Git 仓库共享。我见过一些团队把技能库做成一个 monorepo每个技能一个目录通过分支和 PR 协作也见过有人把经过验证的技能包发布到内部制品库其他人通过包管理工具拉取。社区里已经能看到不少 curated skills 仓库它们通常会给每个技能标注适用场景、依赖要求、测试覆盖情况这和开源软件的分发逻辑几乎一样。评估一个外部技能包能不能用我会先看三样东西有没有清晰的触发条件描述有没有测试用例依赖清单是否完整。如果只有一段 SKILL.md 正文没有脚本和测试我通常会把它当作参考模板而不是可复用技能。技能包和代码库一样没有测试的模块进入生产环境就是在给未来埋雷。5.3 从“个人技能库”到“团队技能市场”我最初做 agent-skills 只是为了自己开发 Agent 时少加班后来发现它真正能放大价值的地方在团队协作。运营团队沉淀一个“竞品信息收集”技能数据团队沉淀一个“SQL 审核”技能研发团队沉淀一个“变更发布预检”技能所有技能放到同一个内部仓库里大家按统一规范提交和评审。这时候技能库就变成了团队的自动化能力中心而不是某个开发者电脑上的私人脚本。团队技能市场要跑起来关键不在技术而在流程。至少要有技能写作规范、评审人机制、版本发布节奏。我和团队定的流程很简单先写 SKILL.md再开会评审触发词和边界然后补脚本和测试通过 CI 后再发布。这个流程看起来比“直接把脚本丢给模型”重但一旦技能数量超过十个没有流程管控就会变成一团乱麻。技能市场听起来很远实际上从一个团队里最常用的三五个技能开始攒三个月就能看出效果。我在实际项目里把 agent-skills 跑起来之后最明显的变化不是模型变强了而是我的调试思路变得清晰了每个任务都有边界、输入输出和测试出问题不再是玄学而是可以定位、可以回滚、可以让别人接手。如果你也想把手边的 Agent 从“偶尔好用”推向“稳定能用”我的建议是从你最常做的那个重复劳动开始把它拆成一个技能包先跑通、再优化。技能的价值不只是让模型更听话更是让整个团队对 AI 的能力边界有了共同的语言。
返回列表