做 Agent 开发的朋友最近应该绕不开 agent-skills 这个词。它解决的是一类很真实的问题单轮对话模型表现很好可一旦任务变成“查几份资料 → 整理成报告 → 再按模板发出去”模型就开始手忙脚乱。agent-skills 的思路很直白把模型要执行的那些高频、可复用的动作封装成标准化的“技能”像给 Agent 装上一套带说明书的工具箱要用哪个拿哪个。这篇文章我会从为什么需要技能化讲起到最小技能包的组成、描述怎么写、参数怎么定再到一个能跑通的原型最后是踩坑实录。适合刚开始接触 Agent 开发、或者在自研智能体应用时被工具调用折腾得头疼的工程师。1. 为什么需要技能化Agent 开发中的真实痛点1.1 从“一问一答”到“多步执行”的跳跃LLM 本质上是一个文本生成模型它擅长的是“看到上文、续写下文”。但真实的业务任务往往不是续写一段文本就能完成的它需要模型去查数据库、调用接口、读取文件、生成图表最后还要把结果整理成指定格式。这个过程中模型不仅仅要“知道”还要“做到”。我最早做的一个内部问答机器人就是个典型教训。第一版实现特别粗暴把所有调用逻辑全部写进 system prompt比如“当你需要查询工单状态时调用 get_ticket 函数参数是 ticket_id如果用户没给 ID就询问用户。” 一开始试了几个简单场景还挺像样但半个月后 prompt 已经有三千多字模型经常漏掉后面的规则有时候明明该调用函数却自己编造一个结果返回。这就是我说的第一堵墙提示词一膨胀模型就抓不住重点。后来我换成函数调用function calling方式情况好一些但很快遇到第二个问题函数越加越多十几个甚至几十个工具塞进上下文模型的选择准确率开始下降而且每个函数的“使用说明”只能通过参数描述来表达稍微复杂一点的任务逻辑根本没有地方放。技能化的做法相当于在提示词和函数之间加了一层“说明书”层面的机制把一个动作的触发条件、使用方式、注意事项都固定在技能文件里而不是每次都挤在 system prompt 里让模型自己消化。1.2 技能Skill到底是什么一个目录、一段语义、一组动作先说我的理解技能不是一个普通的工具函数它是一小段完整的“任务执行协议”通常由描述文件、可执行脚本、依赖声明三部分组成。描述文件告诉模型这个技能是干什么的、什么时候该用、怎么用、需要注意什么可执行脚本负责真正把活干完依赖声明保证执行环境是完备的。打个比方普通 function calling 相当于你给实习生一张名片告诉他“有事找这个人”而技能化相当于你递给实习生一份完整的作业指引上面写着什么场景找这个人、开口第一句话说什么、问哪些问题、拿到结果后怎么反馈。模型读到的不只是“有哪些函数”而是一本能直接照着操作的手册。这也是 agent-skills 和早期工具调用的本质区别它把模型扮演的角色从“一个调用者”变成了“一个有决策能力的前端操作员”。模型先读技能描述判断当前任务是否命中某个技能的触发条件然后按参数的 schema 填充参数调用脚本再把结果拿回来继续推理。所以技能描述写得好不好直接决定了这套机制能跑多顺——这一点后面我会花一整节讲。2. 核心细节解析技能文件结构与封装要点2.1 一个最小技能包的完整组成我习惯用这样一个最小目录来起步web-fetch-skill/ ├── SKILL.md ├── requirements.txt └── scripts/ ├── __init__.py └── fetch_web.pySKILL.md 是灵魂。它通常采用 YAML front matter 加正文说明的结构front matter 里放结构化的元信息正文部分放给模型读的自然语言说明。为什么分成两块因为结构化信息方便框架做索引和路由自然语言正文则用来补齐模型理解所需的上下文。两样东西各司其职缺一不可。看一个具体的 SKILL.md 示例--- name: fetch_web_page description: 当用户需要获取网页正文、阅读外部链接内容、抓取某个 URL 的文本内容时使用。对新闻文章、技术博客、帮助文档这类静态页面效果最佳。 parameters: url: type: string description: 要抓取的完整 URL必须以 http:// 或 https:// 开头 required: true max_chars: type: integer description: 返回内容的最大字符数默认 8000防止超长页面占用过多上下文 required: false --- # 使用说明 - 该技能负责抓取网页正文并转换为纯文本适合需要快速了解页面内容的场景。 - 如果页面是动态渲染的单页应用抓取结果可能是空文本此时应提示用户提供静态版本。 - 抓取失败时返回 HTTP 状态码和错误信息不要编造页面内容。这个文件看起来很轻但每个字段都是深思熟虑的。name 要全局唯一而且要可读description 里明确写了触发场景还附带说明了适用边界parameters 里对 url 做了格式约束对 max_chars 给了默认行为。我见到很多失败的技能包问题不在脚本实现而是这些描述写得太潦草导致模型要么不调用要么用错。后面我会结合真实案例解释每个字段的作用。2.2 描述与参数声明的设计原则description 怎么写我的经验可以浓缩成三条。第一开头三句话就要把触发场景说死不要泛泛写“该技能用于抓取网页”。要让模型能对号入座比如“当用户需要获取网页正文、阅读外部链接内容、抓取某个 URL 的文本内容时使用”。模型在做技能路由时靠的是语义匹配你给的场景描述越具体匹配越准。我见过最差的一种写法是 description 只有“抓取网页”四个字结果模型在用户问“今天天气怎么样”的时候都尝试去调它。第二把反面场景也写出来。比如“对动态渲染的 SPA 页面效果不佳”这句话虽然只有十几个字却能有效阻止模型在错误场景下硬调用省掉很多无意义的失败日志。模型的决策本质上是概率匹配你给它明确的 negative examples它就能更快排除不合适的选项。第三参数描述里要给心智模型而不是只给类型定义。比如 url 参数如果只写“string”模型不知道要不要加协议头写“必须以 http:// 或 https:// 开头”模型自动会补全用户输入的网址。max_chars 参数默认 8000 这个值也有讲究太短会把关键内容截断太长又容易撑爆上下文。8000 个字符大约能覆盖一篇中等长度博客的核心内容是我测过很多次之后折中的一个值。参数 schema 方面我的建议是必要的参数设置 required 为 true避免模型漏填可选参数要在描述里给出默认行为。另外不要在 schema 里塞太多字段。一个技能超过 5 个参数模型填错的概率就会明显上升这时候应该思考是不是技能切得太粗了。工具是给模型用的模型“用起来顺手”比“功能完备”更重要。2.3 技能仓库的目录规划与命名规范当你手里的技能超过十几个仓库的组织方式就开始影响实际效果。我见过有人把所有技能平铺在一个目录里结果模型经常在十几个相似技能中选错。后来我养成了一个习惯在仓库顶层按业务域分目录再在技能内部用统一的命名规范。目录规划上我通常这样分skills/ ├── research/ # 资料搜集相关 │ ├── fetch_web_page/ │ └── search_docs/ ├── data_processing/ # 数据处理相关 │ ├── extract_structured_data/ │ └── convert_format/ ├── communication/ # 对外交互相关 │ ├── send_email/ │ └── post_to_webhook/ └── analysis/ # 分析报告相关 ├── summarize_text/ └── gen_report/命名上我坚持“动词_对象”的格式fetch_web_page、send_email、extract_structured_data这样描述性和可检索性都能兼顾。别用 fetch、process 这类太泛的动词也不要加版本号后缀版本管理交给 git 标签去做。目录规划这件事看起来不起眼但对多技能场景下的路由准确率影响很大——同类技能之间的距离近了模型选错的概率就小了。我自己的经验是给技能命名时想像成你在给文件名起名别人模型只看名字就要大概猜出它是干什么的做不到这一点就应该改名。3. 实操过程从零搭建并跑通你的第一个技能包3.1 第一版实现“网页正文抓取”技能现在动手做一个能跑的最小实现。我用两个 Python 库requests 负责发请求trafilatura 负责从 HTML 中抽取正文。为什么要用 trafilatura 而不是拿 BeautifulSoup 自己写抽取逻辑因为网页正文抽取远比想象中麻烦需要处理导航栏、评论、广告、script 标签等各种噪声trafilatura 把这些经验沉淀成了算法实测下来正文提取准确率比手写正则高很多。这也是我写技能脚本的一个原则能用成熟库就不要自己造轮子模型链路上的东西越稳定越好。先看代码# scripts/fetch_web.py import sys import requests import trafilatura def fetch_web_page(url: str, max_chars: int 8000) - str: headers { User-Agent: Mozilla/5.0 (compatible; AgentSkillBot/1.0) } resp requests.get(url, headersheaders, timeout15) resp.raise_for_status() text trafilatura.extract(resp.text, include_commentsFalse) if not text: return 无法从该页面提取正文页面可能是动态渲染或需要登录。 return text[:max_chars] if __name__ __main__: url sys.argv[1] max_chars int(sys.argv[2]) if len(sys.argv) 2 else 8000 print(fetch_web_page(url, max_chars))有几个细节值得展开。headers 里设置 User-Agent 是非常必要的很多站点会直接拒绝带默认 UA 的请求加上一个明确的爬虫标识反而容易通过基础校验timeout 设成 15 秒防住那些响应很慢的站点把整个任务拖死extract 的 include_comments 参数要设为 False否则会把网页里的 HTML 注释一起捞进来。还有最后那一层 if not text 的兜底判断它不只是为了返回友好信息更重要的是阻止模型在拿不到正文时自行编造内容——模型一旦开始编整个任务就失去意义了。requirements.txt 里只需要两行requests2.32.3 trafilatura2.0.0这里锁版本是刻意的。技能脚本一旦跑在模型调度链路上版本漂移导致的异常会很难排查锁版本至少能保证同一个仓库在任何环境里行为一致。我吃过一次亏某个技能在本地环境跑得好好的部署到服务器上因为第三方库自动升级了小版本输出格式全变了模型拿到结果后分析逻辑全部错乱。从那以后所有技能的依赖一律锁版本。3.2 注册到 Agent 框架并验证调用技能脚本写好了接着要把它接到 Agent 的执行链路里。不同框架的注册方式不一样但原理相通把技能目录暴露给 Agent 运行时让模型在每轮推理时能看到可用的技能列表和它们的描述。拿最朴素的实现举例如果你是自己写调度逻辑核心代码就这么一点# agent_loop.py简化版 from skills.web_fetch_shell import fetch_web_page SKILLS [ { name: fetch_web_page, description: 当用户需要获取网页正文、阅读外部链接内容、抓取某个 URL 的文本内容时使用。, parameters: { url: {type: string, description: 完整 URL}, max_chars: {type: integer, description: 最大字符数, default: 8000} }, fn: fetch_web_page } ]如果你的框架支持 OpenAI function calling 或 Anthropic tool use也可以把上面的 SKILLS 转换一下塞进请求里的 tools 字段。模型返回 tool_calls 或者工具调用意图运行时再调度对应函数执行。核心还是那几件事把技能描述喂给模型模型决定调用哪个运行时把参数填进去执行再把结果回传给模型继续推理。注册完之后怎么验证我建议先跑三个测试用例。第一个用例是“给我抓一下这篇博客的正文”手动传入一个你已知内容的 URL确认输出包含关键信息第二个用例是“看看这个链接讲什么”模型能自动从上下文里提取 URL第三个用例是“把 https://example.com 的内容摘要一下”没有 example.com 时返回什么因为 example.com 内容很简单模型看到返回结果后应该能用简短的摘要完成回答不会强行编造。如果验证时发现模型不调用技能或者调用后参数乱传先别急着改代码优先检查 SKILL.md 里的描述是否够具体这通常占 80% 的问题根源。3.3 多技能组合把复杂任务拆成一条调用链单个技能跑通只是开始agent-skills 真正的价值在于组合。我以一个“竞品分析报告”任务为例来说明。用户输入“帮我调研一下 A 和 B 两个产品的官网整理一份对比报告500 字以内。”如果只有单个 fetch_web_page 技能模型也能做但每一步都要用户手动给 URL。有了技能组合的机制整个过程可以拆成这样的链路识别任务包含多个子目标需要用多个技能协作调用 search 类技能定位 A、B 两个产品的官网 URL对两个 URL 分别调用 fetch_web_page 抓取核心页面调用 extract_structured_data 提取产品定位、核心功能、价格信息调用 summarize_text 这类生成技能把提取结果整理成对比报告。模型在这个链条里扮演什么角色它不是“执行者”而是“调度员”。每一轮推理它都会看一下当前任务进展决定下一步该调用哪个技能再把上一步的输出作为下一步的输入。Agent 应用里管这个机制叫 agentic loop也就是循环执行“观察输出 → 判断下一步 → 调用技能 → 观察新输出”的过程。这里有个很关键的经验为了让模型能自主把多个技能串起来每个技能的输出格式必须稳定。比如 fetch_web_page 的输出就应该是一段干净的纯文本不要夹杂无关日志extract_structured_data 的输出应该是一个结构化的 JSON。技能输出越稳定模型后续的推理就越有把握。我在实际项目里吃过亏——某个技能输出了带 ANSI 颜色码的日志模型直接把这些日志当成正文内容拿去总结了结果报告里全是乱码。从那以后我要求所有技能脚本的输出只有两种纯文本或者 JSON其他一律打到 stderr。4. 常见问题与排查技巧实录4.1 技能存在模型就是不调用问题出在哪这个问题我遇到过太多次排查顺序基本固定先看描述、再看候选数量、最后看模型上下文。第一描述没写清楚触发场景。模型做技能路由靠语义匹配如果你的 description 写的是“获取网页信息”而用户说的是“把这篇博客的内容总结一下”模型可能会觉得这不是“获取网页信息”而是一个“总结”任务所以选了别的技能或者干脆不调用。解决方法是把 description 里的触发场景写得贴近真实用户表达甚至可以直接把用户可能的问法列几个例子进去。第二候选技能太多导致选择困难。当可用技能超过八到十个模型路由准确率会明显下降。解决方法是限制本轮候选技能数量或者按任务类型分 Agent每个 Agent 只挂三到五个技能。实测下来五六个技能候选时准确率比较理想超过十个就开始出现各种奇怪的误调动。第三模型上下文太长技能说明被“淹没”。当 system prompt、历史对话叠加在一起时排在后面的技能描述可能根本没有进入模型的有效注意力范围。解决方法是把高频技能往前排低频技能放后面同时精简每个技能的描述能一句话说完的不要写三段。这个现象在长对话场景里尤其明显我后来做产品时都会监控每轮请求里技能描述实际覆盖的 token 量及时做动态裁剪。4.2 模型传参数总是不对怎么靠描述纠偏参数问题最常见的表现是url 漏了 http 头、日期格式传错、数字字段传了文本。根因不是模型能力不行而是参数 schema 没有给模型足够的“心智模型”。我常用的纠偏手段有三个。一是在参数描述里直接给示例。“url: 要抓取的完整 URL必须以 http:// 或 https:// 开头例如 https://example.com/article/123”这一句胜过一万次 debug。二是利用 required 字段把边界卡住。如果一个参数在任务中不可缺就把它设为 required不要指望模型每次都主动问用户。模型如果发现缺少必要的参数会先询问用户补充这比它自作主张用一个默认值要可靠得多。三是在描述文件正文里写明调用前的前置条件。比如“如果用户没有提供 URL先询问用户确认完整链接后再调用”。这些规则看起来啰嗦但在关键场景里能有效减少幻觉。我自己吃过大亏让模型抓取页面用户没给 URL模型直接编了一个 URL 去调用返回 404 之后还自我圆场说“页面内容需要登录”完全是在编故事。加上“必须先询问用户”这条规则后这类问题基本消失了。4.3 技能执行环境的各种坑依赖冲突与沙箱问题技能跑在什么环境里是个容易被忽略但极其致命的问题。我的第一版技能库全部用全局 Python 环境跑结果不同技能需要的第三方库版本开始打架skill A 用 requests 2.32skill B 的旧代码只兼容 requests 2.20两个一碰撞整个 Agent 进程直接崩。后来我改成每个技能一个虚拟环境但启动开销又成了问题。折中方案是这样的按技能目录划分独立环境技能数量少时用 venv 逐个创建技能数量多时把依赖按域分组共享同一个环境。比如 research 域下的技能都依赖 requests 和 trafilatura就共用一个环境避免重复建环境的时间。沙箱方面如果你在做一个对外开放的 Agent 服务技能脚本会执行外部输入务必在独立进程或容器里跑加上超时和资源限制。给脚本注入恶意路径或长文本是常见的攻击方式这个在自用场景可能无所谓一旦产品化安全边界就得认真设计。依赖和超时的配置我通常会集中放在一个配置文件里runtime: timeout_seconds: 30 max_retries: 2 environment: per_domain allowed_networks: - public_internettimeout_seconds 设 30 看起来保守但实际测试下来网页抓取、格式转换这类技能95% 的调用都能在 20 秒内完成超出的基本是网络异常或页面过大。max_retries 设 2 次既能容忍临时网络抖动又不会让任务无休止重试。allowed_networks 主要用来限制技能脚本访问内网地址避免 SSRF 这类安全问题。4.4 一线排查速查表把上面这些问题集中成一张表格方便你直接对着查症状可能原因排查动作模型不调用技能描述场景不具体 / 候选技能太多 / 上下文过长淹没描述重写 description减少候选技能精简描述文本调用了技能但效果不对参数 schema 太宽松 / 描述未说明适用边界补参数示例描述中写反面场景参数总是缺失或错误必要字段未设置 required / 未给示例设置 required描述中写明前置条件脚本执行报错依赖版本冲突 / 环境不一致锁定依赖版本统一目录配置调用超时目标站点响应慢 / 脚本无超时机制加 timeout加重试逻辑输出乱码/夹杂日志脚本输出不干净规划统一输出格式剥离日志这张表是我压箱底的排查顺序遇到问题先对号入座一般不跑偏。实际排查的时候我还习惯把每次模型的原始调用请求和响应都留档很多问题光看日志定位不到必须还原模型当时看到的完整上下文才能找到根因。5. 关于技能化的几个边界思考5.1 什么样的能力才值得封装成技能这个问题比怎么写技能更重要。我总结了一个经验公式一个功能在一周内被我重复实现三次或者在不同 Agent 任务里被调用超过五次才值得封装成技能。一次性的任务直接写进提示词或者干脆手动处理不要为了“技能化”而技能化。还有一个判断标准是稳定性。技能是给模型执行用的如果这个动作的输入输出非常不稳定比如“理解用户情绪”这种主观任务那它就不适合技能化。技能适合承载的是动作明确、输入输出边界清晰的任务抓网页、发邮件、转格式、查数据库这类任务模型不会自由发挥也不该自由发挥。5.2 技能粒度怎么切才顺手粒度太大和太小都会出问题。切得太细比如“打开浏览器”“点击按钮”“输入文本”各算一个技能模型要调用五六次才能完成一个简单操作链路长、出错率高。切得太粗比如“完成竞品分析”作为一个技能它内部其实是一个完整子 Agent 的活单个技能塞不下。我的经验是一个技能应该对应一个可以用一句话说清楚的任务。比如“抓取网页正文”是一句话“提取结构化数据”是一句话“发送邮件”是一句话这些粒度是可用的。而“处理用户投诉”不是一个技能它是一个需要多技能协作的完整流程应该放到 workflow 或子 Agent 层面去编排。判断粒度合不合适有一个土办法如果你给这个技能写的使用说明超过一百字还没写完大概率是粒度切粗了该拆。5.3 给技能加上必要的可观测性技能一旦多了没有日志你会疯掉。我为每个技能都加了统一的日志格式调用时间、入参、出参长度、耗时、错误信息。这些日志平时不起眼但模型表现异常时它们是定位问题的唯一线索。日志要记录原始输入输出但不记录完整的大文本。我通常只记录入参里的关键字段和出参的字符数完整内容需要时再单独取。既节省存储也能在出问题时快速定位是哪个环节出了岔子。对模型驱动的链路日志还有一个额外价值你可以通过日志反推模型当时的“决策过程”看到底是哪一步理解错了然后针对性调整技能描述。从这个角度说日志不只是运维工具更是技能设计的优化依据。我每次迭代技能第一件事就是翻最近几天的调用日志看哪些描述让模型产生了误解再决定下一版怎么改。最后说一个我自己在实战里逐渐确立的标准。如果一个功能让我连续写三遍以上我就开始认真考虑封装成技能如果封装完之后发现每次还要在描述里改来改去说明粒度没切对应该退回一步重新设计。agent-skills 这个方向真正有意思的地方不在于给函数包一层壳而在于逼着你去想清楚一个问题在模型眼里这个世界是一堆什么操作的集合它需要一份什么样的“操作手册”才能稳定、可靠地把一件事干完想清楚这个技能库才会越用越顺手。
企业数字化 ERP 产品动态
相关推荐
开放式代码评审实践:open-code-review 流程设计与落地指南 做代码评审有几年了,从最开始用邮件发 patch、在群里被 着去“看看”,到后来把一套叫 open-code-review 的开放式评审流程跑进团队的日常开发节奏里,这中间的弯路我基本都走过。后来我把这套流程整理成开源实践,逐步完善成现在团… · 2026/9/26 19:05:03
AI短视频自动制作流水线:模块化架构与多平台分发实战 1. 这不是“一键成片”,而是一套可落地、能迭代的短视频生产流水线最近三个月,我帮六家不同行业的客户搭过短视频自动生产系统——从本地烘焙店老板想每天发三条探店视频,到一家医疗器械公司需要合规输出科普内容,再到教育机构要批… · 2026/9/26 19:04:57
Cursor系列(1):Cursor安装、虚拟环境与 TaoToken 配置骨架 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 19:36:43
一张丑图胜千言:用Cursor调试DirectX 12着色器时,我重新认识了多模态 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 19:36:43
企业级 AI 自动化|OpenClaw 龙虾实战与认证:TaoToken 统一 Key 接入配置指南 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 19:36:36
DBX:基于Tauri和Rust的轻量级跨平台数据库管理工具实战指南 数据库管理工具这个赛道,说实话挺卷的。Navicat、DBeaver、TablePlus、DataGrip,每一个都有一批忠实用户,也都有一堆让人抓狂的地方。我自己日常要在 MySQL、PostgreSQL、SQLite 之间来回切,偶尔还要连一下 SQL Server 帮朋友看数… · 2026/9/26 19:36:30
数据库课后习题答案别硬背:当测试用例集刷,效率翻倍 简介:万常选版《数据库原理与设计》课后习题答案资源,覆盖第2至6章及第9章,适合正在学习关系模型、数据库建模、关系数据理论与模式求精的本科生、自学者作为复习与自测材料。压缩包共7个文件,含3个doc参考答案、2个sql示例脚本、… · 2026/9/26 0:00:21
OpenClaw 替代品?Hermes Agent 踩坑实录:macOS 飞书接入 TaoToken 配置 /* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views … · 2026/9/26 0:00:40
向下兼容与向上兼容:接口设计中的兼容性策略与工程实践 一次版本升级事故,是很多团队绕不过去的坎。线上环境里,服务端明明已经上线了新版接口,老的移动端还在照着旧文档传参数。请求一到网关,校验直接拒绝,用户操作失败,客服群炸了锅,开发群里开始互… · 2026/9/26 0:00:46