资讯动态

OpenDeepWiki中的Agent Skills:模块化技能封装与工具调用实践

发布时间:2026/10/18 10:42:18 来源:尧图企业网站定制
1. 先说清楚Agent Skills 到底是干什么的我最早看到 Agent Skills 这个概念时第一反应是这不就是给大模型写 prompt 吗后来在 OpenDeepWiki 这种知识库类项目里实际跑过一遍才发现事情没那么简单。它解决的是一个很实际的问题——AI agent 在什么场景下、按什么规则、调用什么工具、拿到什么结果。如果没有一套机制来约束和管理agent 面对一堆工具时会乱来要么不知道该用哪个要么把一个简单任务拆成十几次无效调用要么在错误的路线上反复横跳。OpenDeepWiki 这类项目的核心场景是让 AI 自动整理、检索、生成和更新 wiki 内容。你可以把整个系统想象成一个数字化团队调度器是 LLM文档库是 wiki 里的页面和知识块工具箱是各种可执行脚本和 API。Agent Skills 机制就是给这个团队发的岗位说明书和操作规程。传统的做法是把所有工具描述塞进 system prompt让模型自行选择。但当工具数量超过十个、二十个这套方法就会迅速失效原因有两个第一描述太多会挤占上下文窗口模型反而抓不住重点第二工具之间可能存在隐含依赖关系比如先查索引再搜正文模型从零推理这种依赖关系非常不可靠。Agent Skills 改变的是组织形式它把工具、使用说明、约束条件和典型调用场景打包成一个整体让模型先通过轻量级的技能描述做粗粒度决策再在命中的技能内部寻找精确工具。这就好像你进一家大型图书馆不是在一排排书架里漫无目的地乱逛而是先看导览图找到对应楼层再按索引号精确取书。我在实际部署中也验证了一件事引入 Skills 之后tool call 的成功率和我手动 review 时的可接受度都明显提升。它不是魔法只是把模型自由发挥变成了模型做选择题而选择题的答案范围是我们可控的。2. OpenDeepWiki 的 Agent Skills 设计逻辑以 SKILL.md 为单位的模块化封装2.1 为什么是一个技能一个目录而不是一个技能一个配置项OpenDeepWiki 沿用了业界比较成熟的做法每个技能就是一个独立目录目录内至少包含一个SKILL.md文件此外可以根据需要附带脚本、参考文档、模板和测试用例。这种目录即技能的组织方式最大的好处是自包含。我见过很多项目把技能配置写进一个大 JSON 或 YAML 文件里字段越来越长最后根本没人敢动。OpenDeepWiki 的处理方式更接近 Unix 哲学做一件事把它做好并且每个技能拥有自己的依赖环境。比如一个pdf 转 Markdown技能它的 Python 依赖只需要在自己的requirements.txt里声明主程序加载时完全不用关心底层库是否冲突。你甚至可以给每个技能目录单独设一个虚拟环境或容器做到严格的隔离。从工程维护的角度来说这种结构降低了认知负担。新增一个技能就是新增一个目录删除一个技能就是删除整个目录。Git 的 diff 也干净得多评审者一眼就能看出某个技能的变化影响范围。2.2 SKILL.md 的文件结构与字段语义一份标准的SKILL.md通常由两部分组成YAML frontmatter和Markdown 正文。frontmatter 管元信息正文管操作指导。OpenDeepWiki 的加载器会先用 YAML 解析 frontmatter然后用 Markdown 解析正文分别走不同的处理流程。frontmatter 里最常见的字段是name和description但真正决定这个技能能不能被正确触发的是description的写法质量。我见过有人写 Use this skill for PDF processing这种描述在语义匹配时基本等于没说。一个好的 description 应该具备三要素适用场景、输入类型、输出类型。我自己在 OpenDeepWiki 里实践下来一个比较可用的写法类似--- name: pdf-to-markdown description: 将 PDF 文件转换为结构清晰的 Markdown 文档保留标题层级、表格和代码块。适用于需要把 PDF 稿件、论文或报告导入 wiki 知识库的场景。 ---注意我加入了适用于把 PDF 导入 wiki 知识库这个场景描述。模型在决定是否调用该技能时实际是在做语义匹配而不是关键词匹配。场景描述越具体命中率越高不过这也会带来我后面要讲的误触发问题需要用一个参数来平衡。正文部分同样很有讲究。它不是写给人看的而是写给模型看的操作手册。我建议正文里明确写清楚脚本入口、参数格式、退出码含义、典型用例、需要避免的错误做法。模型在执行技能时通常会把正文和脚本一并交给 LLM 读取所以正文的语言要用命令式、无歧义避免或许可能这类模糊词汇。2.3 技能目录里的左膀右臂scripts 与 reference在 OpenDeepWiki 的实践中SKILL.md只是起点真正干活的是scripts目录下的可执行文件。我习惯把所有可执行逻辑放在scripts/下按功能拆分例如scripts/fetch_pdf.py下载和规范化 PDFscripts/parse_pdf.pyPDF 解析和 Markdown 转换scripts/clean_markdown.py清理无效字符、合并断行有些技能还需要静态参考信息比如企业内部 API 的字段规范、常见错误码表、正则模板等。这些不适合硬编码进正文放在reference/目录里更合适。加载器可以把 reference 文件作为按需读取的资源而不是全部塞给模型。这样能节省上下文窗口尤其是当参考文档特别长的时候。我踩过一个真坑早期把所有的参考文档都内联进SKILL.md正文结果一个技能加载后占用了将近 2 万 token导致后续对话严重降速。后来我把reference/改成按需加载只有模型判断需要时才用工具去读文件内容问题立刻缓解。这个设计思路其实和操作系统的虚拟内存很像先映射再换页而不是一次性全部加载进内存。3. 完整链路拆解从 SKILL.md 到 AI 工具调用中间到底发生了什么3.1 第一阶段启动时的技能注册与索引构建OpenDeepWiki 启动时会扫描配置的 skills 根目录对每个技能子目录执行加载流程。这一步我称之为注册阶段。核心动作有三个解析SKILL.md的 frontmatter、校验字段完整性、把技能写入内存索引。索引不是简单地把名字和描述存下来它会做一层语义向量化。OpenDeepWiki 可以用本地 embedding 模型也可以用远程 API两者各有利弊。本地模型延迟低、数据不出域但对中文长文本的语义理解通常弱一些远程 API 效果更好但是每次启动都要重新拉向量冷启动会慢几秒。我自己的选择是如果 wiki 知识库以中文为主优先用效果更好的远程 embedding如果是内部保密项目就用本地模型加自定义词典。注册阶段还有一个容易被忽略的细节技能依赖检查。SKILL.md的 frontmatter 里可以声明requires字段表示这个技能依赖另一个技能。比如网页归档技能可能依赖URL 规范化技能。依赖检查会在注册时构建一个有向无环图如果检测到循环依赖就直接报错并拒绝启动。我记得第一次看到这个设计时觉得有点过度设计直到某次我为了图省事让两个技能互相引用对方的脚本结果系统直接拒绝加载我才理解这是必要的安全措施。3.2 第二阶段用户请求触发技能选择当用户向 OpenDeepWiki 发起请求比如把这份 PDF 里的会议纪要整理成 wiki 页面流程就进入触发阶段。系统先把用户请求和当前对话上下文拼接成一个路由提示和技能索引里的描述做匹配。匹配逻辑通常是两路并行一路是 embedding 相似度另一路是 LLM 的零样本判断。这里有个有趣的设计OpenDeepWiki 并不是直接强行挑选一个技能而是允许技能不确定时由模型追问澄清。比如用户说这个文档你看一下系统会向用户确认你希望做摘要、翻译还是格式转换这看起来多了一步对话实际反而减少了错误执行带来的返工。不过过度追问也会很烦人。我后来调了一个参数skill_selection_threshold当某个技能的匹配分数明显高于其他技能时比如高出 0.15 以上就直接选择不再追问。当匹配分数都很低但用户意图又比较明确时系统才会进入澄清模式。这个阈值不能拍脑袋定我是拿历史日志里人工标注的选择正确性做了一次小规模回归测试才定下的数值。3.3 第三阶段技能上下文注入与工具体系绑定技能被选中后进入上下文注入阶段。OpenDeepWiki 会把该技能的SKILL.md正文加载进对话上下文同时对该技能可用的一组工具进行能力绑定。注意这一阶段并不是把系统里所有工具都暴露给模型而是只暴露当前技能声明的工具白名单。这一点对安全性至关重要。我举个具体例子。系统里有文件系统管理技能和数据库查询技能。如果模型在普通对话中就拥有所有工具可能会出现帮忙整理一下 B 站收藏夹这类请求被模型误解为需要操作文件的诡异行为。OpenDeepWiki 的技能机制通过白名单隔离确保文件系统管理技能只能发起文件读写类的工具调用数据库相关的工具它连看都看不到。工具绑定的底层实现本质上是把工具的 JSON Schema 注入到模型输入中。OpenDeepWiki 在注入时会做一次最小化裁剪如果某个工具在本次技能调用中不需要某个参数就把它从 schema 里临时移除。这样做的原因是参数越少模型的选择难度越低生成合法 tool call 的成功率越高。3.4 第四阶段模型生成工具调用与循环执行注入完成后模型根据用户请求和技能上下文生成结构化工具调用请求。这是整条链路里最考验工程细节的环节。OpenDeepWiki 的处理方式是采用 OpenAI 兼容的 function calling 协议模型返回一个tool_calls数组每个元素包含工具名和参数 JSON。系统解析后在本地进程中执行对应的 Python 函数或命令行脚本。执行环节有几个关键点。第一超时控制。长任务必须设置合理超时比如 pdf 转换脚本我设成 120 秒网络请求类工具设成 10 秒。超时后不能直接丢弃任务而是把超时信息作为错误结果回传给模型让模型决定重试还是换方案。第二失败反馈。执行失败时返回退出码和 stderr 尾部内容并附上一段可能原因与建议动作的固定文案帮助模型快速修正。第三循环上限。一次任务中允许模型多次工具调用但需要设置最大迭代次数我一般设成 8。超过上限就把当前中间结果返回给用户并附上未完整完成标记而不是让模型继续空转。下面是一个简化的工具调用执行伪代码我把它贴在项目文档里也方便同事 reviewdef execute_tool_call(tool_name, arguments, context): if tool_name not in available_tools: return ToolResult(successFalse, errorftool {tool_name} not allowed) if time.time() - context.start_time context.max_duration: return ToolResult(successFalse, errorexecution timeout) try: result available_tools[tool_name].execute(**arguments) return ToolResult(successTrue, dataresult) except ToolExecutionError as e: return ToolResult(successFalse, errore.stderr_tail())3.5 第五阶段结果回收与知识库写入工具调用返回结果之后模型还会继续生成文本对结果做摘要、格式化甚至直接写好新的 wiki 页面草稿。这个阶段最容易被忽视也最能体现 OpenDeepWiki 作为知识库工具的特点它不只是帮你调用一个工具然后把原始输出丢给你而是会把工具的输出重新理解、结构化、去重之后再融入知识库。举例来说pdf 转换技能返回的可能是几百行冗长的 Markdown模型会先识别里面的标题层级清理无效换行删掉页眉页脚然后判断是否与现有 wiki 页面重复。如果重复可以选择追加而不是新建。这个认知后处理是由一个不同于工具执行的模型回合来完成的。从工程角度讲OpenDeepWiki 采用了tool_call - tool_result - model_final_response的循环结构每个 tool call 和对应的 tool result 在消息队列里保持严格的先后顺序确保模型不会看到混乱的执行上下文。4. 在 OpenDeepWiki 里从零配置一个 Agent 技能以网页内容抓取与归档为例4.1 目录结构与最小实现理论讲完了来看看具体操作。假设我现在要在 OpenDeepWiki 里实现一个网页内容抓取与归档技能目标是用户给出一个 URL技能抓取网页正文转成干净的 Markdown存储到 wiki 的指定目录并生成一张索引条目。第一步创建技能目录skills/ └── web-archiver/ ├── SKILL.md ├── requirements.txt └── scripts/ ├── fetch_url.py └── parse_to_markdown.py这个结构很简洁但也足够跑通链路。fetch_url.py负责下载网页并处理重定向、编码识别parse_to_markdown.py负责剥离标签、提取正文、输出标准 Markdown。4.2 编写有效的 SKILL.md不只给模型看也是给系统看的SKILL.md的内容决定了模型的执行质量和系统的路由效率。我贴一个自己调过几版后的写法--- name: web-archiver description: 抓取指定网页内容并转换为干净的 Markdown 格式存入 wiki 资料库。适用于需要保存外部文章、博客、文档页面作为知识库素材的场景。 --- # Web Archiver 该技能用于抓取并归档网页内容。执行时请严格遵循以下步骤 1. 使用 scripts/fetch_url.py 下载目标网页参数 url 必须包含协议头https://。 2. 调用 scripts/parse_to_markdown.py 将 HTML 转换为 Markdown。 3. 检查输出的 Markdown 是否包含正文核心段落。若只有导航或版权信息视为失败返回错误信息。 4. 将 Markdown 内容保存到 wiki 的 archive/ 目录文件名格式为 YYYYMMDD-slug.md。 5. 在 archive/index.md 中追加一条引用链接并附带一行摘要。 注意 - 不要抓取需要登录的网页。 - 遇到 404 或 403 时直接返回状态码不要重试超过一次。 - 所有输出必须为中文或保留原文不进行翻译。这里有一个容易忽略的点正文中的步骤是给模型看的执行秩序模型在生成工具调用参数时会严格参考它。比如我写参数 url 必须包含协议头就是为了防止模型在工具调用时漏掉https://导致脚本直接报错。这种提前在文档中声明约束的做法比在代码里做参数校验更早地拦截错误因为模型能在生成调用前就自我纠正。4.3 脚本实现的一些细节我来看fetch_url.py的一个核心片段。这个脚本要处理的是请求头伪装、超时、以及编码探测。import argparse import requests def fetch(url): headers { User-Agent: Mozilla/5.0 (compatible; OpenDeepWikiBot/1.0) } resp requests.get(url, headersheaders, timeout10) resp.raise_for_status() if not resp.encoding or resp.encoding ISO-8859-1: resp.encoding resp.apparent_encoding return resp.text if __name__ __main__: parser argparse.ArgumentParser() parser.add_argument(--url, requiredTrue) args parser.parse_args() print(fetch(args.url))这里的apparent_encoding是 requests 库从字节流推测编码的能力对中文页面尤其重要。很多网页不声明 charset 或声明错误没有这行的话正文会出现中文乱码而模型拿到乱码后整个链路就废了。另外要注意脚本输出要精简只输出 HTML 正文不要把访问日志、调试信息也打到 stdout。因为 stdout 会作为工具结果回传给模型多余信息会干扰模型的判断浪费 token。4.4 配置技能白名单与执行权限技能目录创建好后需要在 OpenDeepWiki 的配置文件里注册它。配置的关键是白名单机制。比如我的配置如下{ skills: { enabled: [web-archiver, pdf-to-markdown], tool_policies: { web-archiver: { allow_network: true, allow_file_write: true, allowed_paths: [archive/], max_execution_seconds: 30 } } } }这里allowed_paths是一个很有效的防护措施该技能只能往archive/目录里写文件就算模型被恶意 prompt 诱导尝试写入其他路径系统在文件系统层面也会拦截。我强烈建议你不会图省事就把这个字段设成[/]一旦设成全路径技能失控时影响范围就不可控了。4.5 验证链路从日志到端到端闭环配置完之后的验证过程我分三步走。第一步是加载验证。启动 OpenDeepWiki 后看日志里是否出现类似skill web-archiver registered, 2 tools bound的信息。如果显示注册失败多半是 frontmatter 格式问题或依赖包缺失。第二步是手动触发验证。直接在测试环境发一条消息打开这个链接 https://example.com/article保存到 wiki 资料库。观察日志中的 intent 路由结果确认系统正确选中了web-archiver技能而不是错误地落到其他技能上。然后观察 tool call 的参数URL 是否带了协议头、超时是否生效。第三步是结果质量验证。打开生成的 Markdown 文件检查标题、段落、链接是否完好再打开archive/index.md确认索引条目是否追加成功。这一项是对认知后处理环节的检验模型有没有把原始网页内容变成适合 wiki 的格式。我还习惯在验证阶段把模型概率参数调低一点比如 temperature 调成 0.2减少随机性确保两次验证结果一致。确认链路稳定后再调回正常战斗参数。5. 最容易翻车的五个点与我的规避经验5.1 description 写得太宽泛技能变成万能工具这个问题最常见的表现是技能描述写了帮助用户处理各种文档结果模型遇到任何文档类请求都优先选它哪怕用户只是想找一个简单的文本替换工具。我早期在 OpenDeepWiki 里配过文档工具技能description 里写了处理 PDF、Word、Markdown 等各类文档后来在日志里发现大量误路由全部是模型把简单任务抛给了这个重技能。规避办法是描述里加非适用场景。比如description: 抓取网页并归档为 wiki 页面。不适用于本地文件转换、PDF 解析或数据库查询。当用户只是询问网页内容摘要时优先使用通用摘要能力。这段不适用于的负向描述能显著降低误触发率。我做过一个小实验在 100 条测试请求里加了负向描述后误触发从 14 次降到了 3 次。负向描述的成本几乎为零收益却非常明显。5.2 引用上下文无限膨胀技能正文脚本参考文件全部塞给模型有些技能的脚本很多每个脚本还有各自的说明文档如果全部塞进上下文token 消耗会非常夸张。更麻烦的是模型会因为上下文噪声太大而迷失焦点在工具调用时参考了不相关的脚本说明。我的经验是SKILL.md 正文只写执行逻辑和约束脚本的详细参数说明按需获取。比如在正文里写关于参数细节请通过 read_tool_docs 读取 scripts/fetch_url.py 的头部注释这样模型只在需要时才加载脚本说明。这个方法虽然多了一次工具调用但整体 token 消耗往往更低因为不是每次任务都要读所有脚本文档。5.3 工具白名单设得太宽模型开始自己发明需求有一段时间我在配置里给某个技能开放了所有文件读写权限结果在测试中模型为了完成一个简单的文本摘要任务居然先读取了一大堆与任务无关的 wiki 页面又创建了三个临时文件。这不是模型不聪明而是工具的搜索空间太大模型倾向于尝试多样化路径。规避办法是基于功能最小化原则配置白名单一次工具调用只暴露完成任务所必需的 2 到 3 个工具。如果需要更多工具要么拆分技能要么在流程中分阶段暴露。我在 OpenDeepWiki 里把网页抓取和内容清洗拆成了两个技能前者只管抓后者只管洗中间通过一个临时文件传递数据。这样每个技能的上下文干净了很多一次任务耗时反而更短。5.4 循环上限设置不合理长任务被粗暴截断有次我处理一个特别大的 PDF整整有三百多页。技能跑完前 7 次工具调用还没完成第 8 次被循环上限截断生成的 wiki 页面只有前半部分。用户收到页面时没有看到任何错误提示因为模型在最终回复里只写了已完成。这就是隐式失败。解决方法是三管齐下第一max_iterations增大到 12 到 15但要配合超时第二在 SKILL.md 正文里要求模型在长任务中分阶段汇报进度比如已完成前 50 页转换继续剩余部分第三在最终回复格式里增加一个字段completion_status可以取值completed或partial这样即使被截断用户至少能看出内容不完整。这个字段对后续人工接管也很有价值。5.5 技能之间出现隐性依赖改一个技能连带炸另一个OpenDeepWiki 的技能设计强调自包含但实际中它们不可能完全独立。比如网页抓取技能产出的临时文件放在data/tmp/内容清洗技能也读取这个目录两件事本身没问题但如果哪天我改了临时文件命名规则就会导致第二个技能悄悄失败。我的规避习惯是跨技能共享的任何资源都在各自 SKILL.md 里显式声明并加一个独立的标记文件。比如在data/tmp/下写一个.metadata.json记录生成时间、源技能、文件格式要求。下游技能读取时先校验这个 metadata发现格式不匹配就直接报错而不是硬着头皮解析。这样的设计让技能的失败模式从悄悄出错变成显式报错排查时间能省一大半。6. 一些我自己觉得值得收藏的实用经验很多人把 Agent Skills 当成一个配置技巧其实它是一个系统工程。在 OpenDeepWiki 里实践下来我总结出几个值得长期坚持的习惯第一条每个技能都要有自己的回归测试。我会为重要技能写一组模拟请求跑完对比输出结果和 expected 文件。这样每次改动SKILL.md或脚本之后都能快速确认没有破坏已有能力。测试框架不复杂用 pytest 加几个 fixture 就行重点是把技能上下文注入和工具调用 mock 掉只测逻辑部分。第二条日志里一定要能追踪每次工具调用的链路 ID。OpenDeepWiki 的日志如果只有角色消息排错会非常痛苦。我习惯把一次完整任务分配一个trace_id从用户请求、技能选择、每次工具调用到最终写入全部打上同一个 ID。这样无论哪一环出问题都能在日志里拉出一整条链路来复盘。第三条定期检查技能命中率分布。我每个月会拉一次日志统计哪些技能被频繁触发、哪些技能从创建后就没被用过。那些长期没被触发的技能很可能存在两个问题描述里包含了过时的场景或者它的功能已经被其他技能覆盖。不管哪种情况都应该尽快调整而不是任它占着资源。关于这个机制能走多远我想多说一句。未来 OpenDeepWiki 这类系统大概率会支持更复杂的技能编排——比如技能 A 执行完自动触发技能 B形成流水线。这很诱人但前提是单技能的可靠性先达标。如果每个技能单独执行时都有 90% 的正确率串联三个技能后整体正确率就只有 72.9%这是很吓人的衰减速度。所以我的建议是先把单技能正确率做到 98% 以上再考虑编排。我自己在 OpenDeepWiki 中的优化重心也一直放在单技能内让描述更精准、脚本更健壮、错误反馈更清晰。最后分享一个小技巧。SKILL.md正文不只是给模型看的OpenDeepWiki 的界面会把它渲染成可读的文档用户也能看到。这意味着你可以让技能文档一物两用既充当模型的操作指南又充当用户的这是什么、能做什么、限制是什么的说明页。所以写作时尽量用简洁的标题、明确的步骤和必要的代码示例不要写那种只有机器才能读懂的字段堆砌。毕竟一个连同事都看不懂的技能定义模型大概率也执行不好。

读完文章,也想定制专属网站?

尧图设计师 24 小时内与您沟通定制方案

免费获取报价 →
↑