前阵子有个挺闹心的场景我在对话里让智能体帮我分析一份三百多行的 Markdown 技术文档它回了一段很漂亮的总结但我还是想亲手在思维导图里拖一拖节点、看看哪些章节讲得虚、哪些分支其实可以合并。以前遇到这种需求我的第一反应是复制到在线工具里转成图片再导出结果分支一多密密麻麻全是字放大缩小来回折腾根本没耐心看。后来我花了一个晚上直接给自己常用的这套智能体框架写了个个人 Skill——输入一份 Markdown输出一个可以直接拖拽浏览的交互式思维导图 HTML。全程本地执行不依赖在线转换服务。写完我才意识到这类“格式转换 可视化”的小能力才是 Skill 机制最有价值的用法之一。这篇不打算写成文档式的教程就按我实际做这件事的顺序来拆一开始为什么非要折腾这个、Markmap 的解析规则有哪些坑、Skill 在智能体里到底以什么形态存在、核心脚本怎么写最后是排错实录和一次完整调用示例。如果你也维护自己的智能体环境或者正准备往框架里加工具型 Skill这篇应该能帮你省掉不少试错时间。1. 为什么是 Markdown 转思维导图背后是三类真实需求1.1 从长文到结构人脑需要“树的视图”先说最常见的场景。无论是分析技术方案、梳理项目复盘还是读别人的开源文档第一件要做的事通常是看清整体结构它分了几大块每块下面又有哪些子话题哪些地方有冗余。Markdown 本身是线性文本虽然有标题层级但一屏看不下几百行的时候标题之间的从属关系很容易在滚动中丢失。思维导图天然就是一棵树把#、##、###映射成树的层级一眼就能看出文档骨架。我之前试过几种“伪方案”用编辑器自带的折叠功能逐级展开慢用渲染插件把文档转成静态 HTML 再截图只能看不能交互用在线脑图工具手动抄一遍结构费时且容易抄漏。对于“快速了解陌生文档结构”这件事Markdown 直接转交互式脑图是最自然的路径因为没有任何额外编辑成本。1.2 知识整理场景里的隐藏痛点第二个场景是学习笔记的二次整理。很多人用 Markdown 记笔记时层级其实是乱的有时用四级标题表示子点有时用无序列表表示并列分支。把这些笔记变成脑图后结构混乱的问题会立刻暴露出来比如某个根节点下面突然挂了一串孤零零的三级标题或者列表层级和标题层级混在一起导致树形很不平衡。我在动手写这个 Skill 之前其实是被一个更具体的痛点逼的。当时要评审一个跨模块重构方案文档写了一万多字信息密度很高。我想在开会前把方案的依赖关系、风险点、实施顺序可视化出来但耐心手工整理完大纲后发现已经花了一个小时而文档本身都还没细读。那一刻我很确定这类“格式转结构”的事情不应该手工做应该让工具来。1.3 为什么不做成在线服务而是做成 Skill也可能有人问在线工具那么多为什么偏要做一个 Skill我的判断是三个字不划算。首先是隐私问题内部技术文档、复盘记录这些内容丢到第三方站点去解析本身就很难接受。其次是稳定性问题在线服务说不定哪天接口就变了或者有单次转换长度限制。最后是交互效率问题在线工具和智能体对话之间隔着一道人工搬运工序而 Skill 能让智能体直接读取文件、调用脚本、回填结果用户在对话里就能完成整个闭环。另外还有个很实际的好处命令可以被复用。这个 Skill 不只是给智能体用的我自己的脚本也可以直接调用同一个转换模块比如批量给笔记目录生成脑图索引。把转换能力沉淀成 Skill而不是临时写一次性脚本后续扩展会轻松很多。2. Markmap 的解析规则不是所有 Markdown 都会变成树枝2.1 标题层级决定树的主干Markmap 的核心逻辑说起来并不复杂它先用解析器把 Markdown 转成抽象语法树再把这个 AST 映射成思维导图的树结构。映射规则里最直观的就是标题层级#对应根节点##是根节点的子节点###再往下挂一层。如果你有一份层级工整的 Markdown比如# 项目重构方案 ## 背景现状 ## 目标设计 ### 性能指标 ### 可维护性 ## 风险清单那么生成的树就是“项目重构方案”下挂“背景现状”“目标设计”“风险清单”三个分支其中“目标设计”再分出“性能指标”和“可维护性”。这就是 Markmap 最舒服的输入状态。但这里有个容易被忽略的关键点标题不能跳级。如果你的文档里出现了#直接接###Markmap 不会报错但树形会变得很难看那个###节点会强行挂在根节点下面从视觉上丢失了它原本想表达的从属关系。这个问题我会在第 5 部分详细讲处理方案因为它是实际使用中最常见的结构问题。2.2 列表、段落、引用块各自的下场除了标题Markdown 的列表也会被映射成分支节点。无序列表项通常成为当前标题下的子节点而有序列表同样如此只是渲染时带有序号。嵌套列表会继续往下扩展层级这意味着你可以用纯列表结构画出一棵大树而不需要真的写一堆标题。实际使用时很多人喜欢用列表记录并列要点Markmap 对这种用法支持得不错。比较隐蔽的是段落和引用块。Markmap 认为一段普通段落是某个节点下的补充说明它在思维导图里不会成为新的分支而是出现在节点的说明区域鼠标悬停时能看到。也就是说你的一大段描述性文字不会被塞进树形的主干而是成为叶子节点的注脚。这个设计其实合理但如果核心观点写在段落里而标题本身没提炼关键词那么生成的脑图看起来就会很“干”。引用块的情况也类似。开头的引用内容在大部分 markmap 版本里不会渲染成独立分支只会被当作附注文本。我当初以为引用块能变成一种高亮节点实测发现它连分支都算不上。所以在让 Skill 转换之前最好先想清楚你的信息到底放在标题层级上还是藏在段落细节里。2.3 表格和数学公式这两种特殊语法最容易翻车如果你用 Markdown 写过技术文档肯定没少用表格。但 Markmap 对表格的支持非常有限在我用的解析版本里表格会被当成一段特殊内容处理并不会按行列展开成脑图分支。也就是说对比表格这种信息密度极高的内容转成思维导图之后几乎等于丢失。想保留表格信息最好在转换前做预处理把表格拆成列表式的键值对或者把每行转成一个分支。数学公式也是重灾区。$...$或者$$...$$这类公式在某些 markmap 版本里能正常显示但在部分渲染引擎里会直接变成乱码或者空白。如果你的文档里有大量公式强烈建议先做一次用例测试确认目标版本的渲染效果再决定要不要批量转换。从我自己的经验看公式最稳妥的处理方式是转成图片或保持原文不动而不是指望脑图来展示。了解这些解析规则之后你会发现 Markmap 本质上更适合“层级清晰、重点是短标题和列表”的文档。遇到表格、公式、长段落密集的内容转换质量会明显下降。这不是工具的错是输入格式和输出模型之间的匹配问题理解了这一层后面写转换脚本时就知道哪些地方要预处理。3. Skill 在智能体里的形态一个目录加一份说明文件3.1 为什么 Skill 要独立存在而不是塞进系统提示词很多刚开始玩智能体的人第一反应是把所有能力都写进系统提示词里比如“你是一个擅长转换 Markdown 为思维导图的助手请使用以下步骤……”。这种方式在小规模场景下确实能跑但一点都不优雅。系统提示词越长模型越容易在无关任务上被干扰而且修改一条规则往往要重新做整轮对话测试。Skill 的核心理念是把“能力”变成文件系统里的独立模块一个目录、一份元信息描述、一个或多个可执行脚本。模型只需要在合适的时机读取这个 Skill按说明调用脚本然后读取返回结果。这样做的好处是职责清晰提示词负责意图理解脚本负责确定性执行两者不用互相污染。我把这个 Markdown 转脑图的 Skill 放在个人技能目录下它和普通插件式工具的区别在于skill 的说明文件决定了智能体什么时候该用、怎么用、输出什么格式。这是一个薄适配层通用框架完全不需要知道 markmap 的细节。3.2 元信息设计告诉框架“什么时候用它”我的 Skill 描述文件叫SKILL.md第一版长这样--- name: markmap_converter description: 将 Markdown 文档转换为交互式思维导图 HTML 文件 when_to_use: 当用户需要可视化长文档结构、导出思维导图、快速预览 Markdown 大纲时 version: 0.1.0 ---这份 frontmatter 里的description和when_to_use特别重要因为它们决定了模型的触发判断。最初我写得很宽泛结果智能体在用户提到“思维导图”三个字时就触发哪怕用户只是想让我口头描述结构。后来我把触发条件收窄成“用户希望在浏览器里拖拽查看文档层级或者需要生成可视化大纲文件”误触发率明显下降。正文部分则写操作步骤比如确认输入文件路径、调用转换脚本、检查输出文件是否生成、把文件路径和基本统计信息反馈给用户。为了避免模型在执行时自由发挥我特意在正文里注明“不要试图修改用户的原始 Markdown 文件只生成新的 HTML 文件”。这类边界约束写清楚以后执行稳定性会好很多。3.3 触发流程从自然语言到脚本执行在我的框架里Skill 的执行链路大致是这样的用户在对话里提出需求比如“把docs/roadmap.md变成思维导图输出到dist/map.html”。模型根据SKILL.md的description判断这个请求匹配markmap_converter。模型从对话里抽取参数包括源文件路径、输出路径、可选的标题参数。模型调用对应的执行脚本把参数传给脚本。脚本执行完成向模型返回 stdout 和错误码。模型根据脚本输出向用户汇报生成结果。这个链路里最容易出问题的环节是参数抽取。用户可能只给源文件路径没给输出目录所以我约定没指定输出路径时默认输出到源文件同目录下的markmap_output文件夹并自动加_map后缀。这个默认约定省去了很多来回追问。4. 转换脚本踩通的关键路径从 Markdown 到可交互 HTML4.1 Skill 的目录结构实际落地时Skill 目录大概是这样的skills/ markmap_converter/ SKILL.md src/ md2map.js template.html assets/ d3.min.js markmap-view.min.js test/ sample.md把模板、脚本、前端资源分开放是个好习惯。assets目录里放的是本地化的 d3 和 markmap-view 文件为什么要本地化后面踩坑部分会细说。test目录用来放测试用 Markdown我每次改完模板都会用同一份样例跑一遍回归测试。4.2 核心转换脚本脚本本身不复杂重点在“解析 Markdown、生成 JSON 树、灌进 HTML 模板”三步。我的实现用的是 Node.js因为 markmap 生态本来就是 JavaScript 的直接调用最省事。核心代码如下const fs require(fs); const path require(path); const { Transformer } require(markmap-lib); function convert(mdPath, htmlPath) { const md fs.readFileSync(mdPath, utf8); const transformer new Transformer(); const { root } transformer.transform(md); let template fs.readFileSync( path.join(__dirname, template.html), utf8 ); const dataJson JSON.stringify(root).replace(//g, \\u003c); const title path.basename(mdPath, path.extname(mdPath)); const html template .replace(__TITLE__, title) .replace(__DATA__, dataJson); fs.mkdirSync(path.dirname(htmlPath), { recursive: true }); fs.writeFileSync(htmlPath, html, utf8); console.log(OK: ${htmlPath}); } const [,, mdPath, htmlPath] process.argv; convert(mdPath, htmlPath || mdPath.replace(/\.md$/, _map.html));这段代码里最值得留意的是.replace(//g, \\u003c)这一行。因为root转换出来的数据会以 JSON 形式直接嵌进 HTMLscript标签里如果 Markdown 原文里有/script字符串或者更常见的字符浏览器解析 HTML 时会在脚本执行之前就把它当标签打断直接白屏。转成\u003c是 JSON 字符串转义的标准做法能安全避免这个问题。4.3 HTML 模板设计只做一件事模板文件template.html的核心结构很直白!DOCTYPE html html langzh head meta charsetUTF-8 title__TITLE__/title style body { margin: 0; } #mindmap { width: 100vw; height: 100vh; display: block; } /style /head body svg idmindmap/svg script src./assets/d3.min.js/script script src./assets/markmap-view.min.js/script script const data __DATA__; window.markmap.Markmap.create(#mindmap, null, data); /script /body /html这里的资源引用我用了相对路径./assets/意味着生成的 HTML 文件需要和assets目录放在一起才能正常打开。如果你希望生成单个独立 HTML可以直接把 d3 和 markmap-view 的内容也内联进模板代价是文件体积增大不少。我个人倾向于保持相对路径模式因为一个 HTML 配一个资源目录后续更新前端库的时候不用批量重新生成文件。4.4 参数设计和调用约定脚本支持两个位置参数源 Markdown 路径和输出 HTML 路径。如果不传输出路径默认在源文件当前目录生成原文件名_map.html。我在 Skill 描述文件里特别强调模型调用时要先确认源文件存在、可读再执行脚本。这样做是为了防止模型拿到不存在的路径空跑一遍脚本然后面对一堆错误输出。Skill 调用方式在不同框架里表现不一样但通常都是一个简单的命令node skills/markmap_converter/src/md2map.js docs/roadmap.md dist/roadmap.html实测下来整个转换过程对几百 KB 的文档只需要几百毫秒。真正耗时间的反而是模板里引用的前端资源加载但只要资源文件在本地这一部分也能压到几十毫秒以内。整体体验和在线工具比差距是质的。5. 真实踩过的五个坑每个都是渲染失败的教训5.1 标题跳级导致树形错乱预处理降级第一次拿真实文档测试时我发现生成的思维导图层级完全不对。排查了半天人肉肉眼看了原始 Markdown 才发现文档里有个章节直接从#跳到了###中间那层##根本不存在。Markmap 解析器对这种跳级标题处理得很机械直接把三级标题挂在根节点下从视觉上完全看不出来。解决办法不是让用户修改原文档而是在转换脚本里做预处理扫描所有标题如果发现某个标题的层级比前一个标题大了一级以上就把它的级别强制降到“上一层 1”。这个预处理逻辑实现了之后所有文档的树形都变得符合直觉了。我一开始还以为这个是 Markmap 的 bug后来想想它只是忠实呈现了 Markdown 的层级缺陷。5.2 CDN 资源离线白屏本地资源化初版模板里我偷懒直接引用了公共 CDN 上的 d3 和 markmap-view。在自己电脑上测试一切正常后来换到内网环境打开生成的 HTML页面完全白屏控制台一堆网络请求失败。原因不用多说目标机器访问不了外网。虽然现在很多环境都能联网但“生成的 HTML 是要拿给别人打开的”这个场景决定了 File:// 协议下必须零外网依赖。我把需要的两个库下载成本地文件放进 assets 目录模板改成相对路径引用。从这以后生成的 HTML 拿到任何一台机器上双击就能打开再也没出现过白屏问题。5.3 HTML 内联数据里的/script转义问题这个坑是肉眼最容易忽略的。Markdown 文档内容千奇百怪如果正文里恰好有一段 HTML 示例里面包含/script标签那么转换后的 JSON 数据嵌进模板后浏览器会把这段字符串当成真正的脚本结束标签后面的 JS 代码全部失效。表现是页面结构出来了但没有任何分支节点只有空白框架。修复方式就是前面代码里写的那行.replace(//g, \\u003c)。这个转义必须作用于整个 JSON 字符串因为 JSON 解析之后会自动把\u003c还原成渲染器拿到的数据依然正确。这个教训让我养成了习惯只要往 HTML 里嵌动态数据一律先做尖括号转义不要心存侥幸。5.4 大文档渲染卡顿折叠与深度限制有一次我把一整年的周报 markdown 扔进去转换生成的文件有好几 MB打开之后浏览器直接卡住了半分钟拖动分支时掉帧严重。原因一方面是节点数量破千另一方面是很多周报段落写得特别长每个节点都携带了大量说明文本渲染压力成倍上升。解决方案分两层一是在模板里给思维导图初始化时开启折叠特性让非根节点默认闭合用户点击再展开二是在转换脚本里加了一个可选参数--max-depth超过指定层级的节点统一折叠成摘要节点。这样即使文档很大首屏渲染也能保持流畅用户按需展开感兴趣的分支。这个思路和懒加载是一回事不把全部信息一次性推给用户。5.5 Markmap 版本 API 差异锁定依赖版本写脚本的时候我最初参考的是网上比较老的示例代码用的是markmap-lib的旧 API比如transform返回的格式和Markmap.create的调用签名都有差异。我装依赖的时候又默认装了最新的包结果接口对不上折腾了半个多小时。后面我直接把依赖版本锁死在知名的稳定版本上{ dependencies: { markmap-lib: 0.18.8, markmap-view: 0.18.8 } }锁版本的本质是给当时的成功状态拍张照片。后续升级依赖时我会单独跑一遍回归测试再决定要不要升。别小看这一点智能体 Skill 的脚本如果哪天因为依赖漂移突然挂了排查成本远超省下的那点升级时间。6. 实测一次完整调用从一条指令到可交互的脑图 HTML6.1 输入样例为了验证 Skill 的稳定性和通用性我特意找了一份结构不算规整的文档做测试。原文开头有引言中间混了表格、长段落和多层列表标题层级也没有完全按规范来。节选如下# 平台改进建议 ## 现状分析 当前系统的主要瓶颈集中在数据同步环节跨区域延迟明显。 | 模块 | 耗时占比 | 风险 | | 同步 | 45% | 高 | | 队列 | 25% | 中 | ### 相关依赖 - 同步服务 - 分区策略 - 重试机制 - 监控告警 ## 改进方向 建议分三个阶段推进这份文档结构上并不干净表格和引用块都有正好测试 Skill 的容错能力。6.2 调用过程与返回信息在对话里我给智能体的指令是把 platform_improvement.md 转成思维导图输出到 output/improvement_map.html智能体根据 SKILL.md 的描述识别出应该调用 markmap_converter抽取了源路径和目标路径然后执行了转换命令。几秒后它在对话里返回了这样的消息已生成思维导图output/improvement_map.html 主分支数量3 个 节点总数21 个 注意原文档中的表格已按预处理规则转为列表分支引用块内容已折叠到节点说明中。这里“主分支数量3 个”其实是脚本在输出时额外打印的统计信息被智能体拿来做了摘要。这个做法是我在写脚本时特意加的不只是在文件系统里留下一份 HTML而是把生成结果的关键量反馈回对话里让用户对输出有一个预期。6.3 效果分析和效率对比打开生成的 HTML左侧是根节点“平台改进建议”右侧依次展开“现状分析”“相关依赖”“改进方向”。表格里的“模块”“耗时占比”“风险”被转成了三个子节点引用块内容挂在“改进方向”节点下面作为详情说明。虽然格式上不如手工梳理的脑图整洁但作为“理解文档结构”的第一步已经远超预期。按照我平时的操作习惯手工把这份文档转成脑图大约需要十分钟还要忍受复制粘贴的枯燥过程。现在整个过程被压缩成一句自然语言指令加几秒等待。更重要的是这个 Skill 可以被反复调用任何文档进来都是同一标准处理不会因为我的精力下降而漏掉某个分支。效率提升的真正来源并不仅仅是自动化转换这一个动作而是把“文档结构可视化”从偶发的、需要额外步骤的体力活变成了智能体对话里的原生能力。它让“我想看看这份文档的长什么样”这种冲动可以无摩擦地被满足。做成这个 Skill 之后我越来越确定一件事给智能体写扩展时别总想着塞复杂规则先把“把 A 转成 B”这种确定性转换做稳价值就已经很大了。如果你也想给自己的框架加类似能力我建议从最简单的 Markdown 到 HTML 转换开始跑通整个 Skill 调用链路之后再考虑折叠、深度限制、表格预处理这些进阶功能。后面我打算给模板加上更多交互选项比如一键导出 PNG、节点搜索、分支配色方案这些都是在 markmap 现有 API 上做文章复杂度不高但每次改进都能让日常使用的舒适度更上一步。