位置:首页 > 进阶教程 > 如何用AI绘制架构图:Prompt工程一线实践进阶指南

如何用AI绘制架构图:Prompt工程一线实践进阶指南

时间:2026-08-18  |  作者:星际追番人  |  阅读:0

一个反直觉的开场

先说个反直觉的结论:让 LLM 画出一张「能用」的架构图,最不重要的就是模型本身。

我第一次做这功能的时候,思路特别朴素。Draw.io 的文件不就是个 XML 吗?我把一个完整的 .drawio 文件丢给模型当样例,说「照这个格式,把项目架构画出来」。

结果你猜怎么着?十次里有六次是废的。要么 id="0" 这个根节点忘了写,要么连线穿过中间的方块,几条线挤在一起糊成一团,要么干脆 XML 都没闭合。

我一度以为是模型不行。直到我换了三家模型,发现失败模式一模一样,我才反应过来:不是模型笨,是我把活儿派错了。

模型擅长「这块放哪、连到谁」这种创意决策。但你让它去抠 XML 嵌套层级、平衡画布坐标、给每条线算绕行路径,这其实是在拿它的短板硬刚。

后面这套从 40% 到 90% 的演化,核心就一句话:把机械的活儿交给代码,把创意的活儿留给模型,然后用 Prompt 把两者之间的接口对齐。下面一步步拆。

1. Draw.io XML:一个 650 行才出一张图的格式

先看清楚对手。Draw.io(现在叫 diagrams.net)底层是 mxGraph XML——一套描述图形节点、连线、布局的标记语言。一个完整文件长这样:

<diagram name="架构图" id="diagram-1"><mxGraphModel dx="1422" dy="794" grid="1" page="1" ...><root><mxCell id="0"/> <mxCell id="1" parent="0"/><mxCell id="2" value="前端" style="..." vertex="1" parent="1"><mxGeometry x="40" y="40" width="160" height="60" as="geometry"/>mxCell><mxCell id="3" style="..." edge="1" parent="1" source="2" target="4"/>root>mxGraphModel>diagram>

真正承载「信息」的就是那几个 。外面套了 四层壳,外加两个雷打不动的根节点。

一张基础三层架构图,完整 XML 能写到 650 行。其中六成是每次都长一个样的模板。

你让模型从头到尾生成这整坨,等于让它在四层嵌套里不出一个错。这不现实。

2. 「反向剔除」:让代码做 boilerplate,让模型做创意

这是整个设计里我最想安利的一个洞察。

大多数人的 Prompt 思路是正向的:把完整输出格式摆给模型,让它照抄。但面对 Draw.io 这种壳比肉多的格式,这条路是死胡同。

你抄得越全,出错点越多。

catbuddy 的做法正好相反——从完整 XML 里,把模型不需要操心的部分全部剔掉。

代码承担包装层。 display_diagram 工具收到模型吐出来的裸 mxCell 片段后,自动包成一个完整文件:

function ensureMxFile(xml: string): string {const trimmed = xml.trim()if (trimmed.startsWith(')) return trimmedif (trimmed.startsWith(')) {return `${trimmed}`}// 最常见的分支:模型只给了裸 mxCell,代码把四层壳和根节点全补上return `` +`${trimmed}`}

三个分支覆盖了模型可能的三种输出。有的模型「自作聪明」加了点壳,有的没加。不管哪种,代码都能兜住。

模型只负责裸 。Skill 文件里这句话写得很白:

Generate ONLY bare elements — 别带任何壳标签,系统会自动包成合法的 .drawio

再配三条铁规则:

  • ID 从 "2" 开始递增("0"/"1" 系统占了);
  • 所有 mxCell 是平级兄弟不许嵌套;
  • 顶层元素 parent="1"

这样一来,你压根不用告诉模型什么是 。它只要关心「这块放哪、连到谁」。

为什么这条路走得通

  • 认知负担小:模型只需理解 一种元素,不用扛五层嵌套的模板;
  • 错误面小:每层壳都是潜在出错点,剔掉四层壳就消灭了四类错误;
  • token 省:生成的 XML 缩短约 40%,同样的上下文窗口能画更复杂的图。

一句话:别教模型生成完整 XML,让代码去补它不擅长的机械部分。

3. 四轮迭代:可用率 40% → 90% 是怎么磨出来的

「反向剔除」解决了「壳」的问题。但「肉」本身,也就是布局、连线,还是模型生成。

这部分纯靠 Prompt 打磨。我们一共迭代了四轮。

每一档的提升,全都来自从失败案例里提炼具体规则,而不是把「请画得好看一点」这种废话说得更大声。

第 1 版:零约束,可用率 40%

最初的 Prompt 极简——「你是 Draw.io 图表专家,请分析架构生成 XML」,附一个三节点示例。

结果约 40% 可用。主要翻车点包括:

  • 忘根节点;
  • 容器内子元素 parent 指错;
  • 连线漏了 source/target
  • XML 特殊字符没转义。

第 2 版:加布局约束,可用率 55%

这一版加了空间规则:所有元素 x=0~800, y=0~600、起点 x=40,y=40、相邻间距 150~200px。

结果提升到 55%。布局不再挤成一坨了,但连线还是灾难——多条线重叠、双向连线互相穿过。

第 3 版:连线五规则,突破到 75%

这是我们投入最多的一版。盯着上百个失败案例看,最后提炼出 5 条连线规则,写进 Skill:

## Edge Routing — 5 Rules1. 平行连线错开:同两节点间的线用 exitY=0.3 / exitY=0.7 岔开。2. 双向连线:A→B 从右出(exitX=1),B→A 从左出(exitX=0)。3. 每条边都显式设 exitX/exitY/entryX/entryY。4. 用 waypoint 绕开障碍:在起点终点之间的图形旁边绕路。5. 连在边的中点,别连角落(不要 entryX=1,entryY=1)。

第 4 条 waypoint 绕行,专治「连线穿过中间节点」这个高频毛病。给模型一个带 拐点的示例,它就知道怎么让线绕开挡路的方块。

结果跳到 75%。连线质量已经到了「基本不用手动调」的水平。

第 4 版(当前):完整 Skill + 工具链,90%

当前版把整套规范沉淀成一个可插拔的 Skill 包。关键又加了四样:

  1. 分层颜色表:前端蓝 #dae8fc、后端绿 #d5e8d4、数据橙 #ffe6cc、基础设施紫、外部红——模型不用「猜」配色,查表就行;
  2. Swimlane 完整示例:游泳道是出错率最高的元素,给整段示例,避免「子元素 parent 指错容器」;
  3. 三种连线示例:基础连线、容器连线、带 waypoint 绕行,覆盖九成场景;
  4. 工具链衔接:明确写「下一步 Call display_diagram({ xml })」,告诉模型生成完该干嘛。

当前约 90%。剩下 10% 的锅,主要是超复杂图(20+ 节点)撞 ID 冲突或超出画布。

这里最关键的一句话是:这四轮迭代背后真正的方法论,其实是告诉模型「不要做什么」,很多时候比告诉它「要做什么」还管用。

原因也不复杂。模型的默认动作,往往正是最容易出问题的那一套。它会下意识把平行线画成重叠状态,也会默认让连线走直线,直接穿过方块。

与其费劲去正向描绘一个“理想结果”,不如先把那条最容易跑偏的路堵死。

像「连在中点,别连角落」「双向线一个从左出一个从右出」这样的要求,本质上都是在做反向剔除。不是不断叠加新规则,而是在有针对性地清理模型的坏习惯。

4. 三个工具的「委托式」分工

Prompt 只是一半,另一半是工具链。catbuddy 给画图配了三个独立的 Agent Tool,分工干净得像流水线。

display_diagram ——造。适合中等复杂度(10~15 节点以内)一次成图。它入口处有个有意思的校验,不是用 XML parser,而是直接看开头字符:

function looksLikeDrawioXml(xml: string): boolean {const trimmed = xml.trim()return trimmed.startsWith(')|| trimmed.startsWith(')|| trimmed.startsWith(')}

这比 XML 解析宽容,比不校验严格。万一模型脑子一抽吐出 Mermaid 或 Markdown 表格,直接拒了并给明确指引,而不是把一坨非法 XML 写进文件。

append_diagram ——续。大项目 30+ 节点的图,模型一次输出会被上下文窗口截断。这个工具允许分片追加,每片都先抽出完整 再逐条验证:

function validateCells(existingXml: string, cells: string[]): string | null {if (cells.length === 0) return 'No complete elements found in xml'const seen = new Set()for (const cell of cells) {const id = cellId(cell)if (!id) return 'Every appended mxCell must ha ve an id attribute'if (id === '0' || id === '1') return 'Do not append root cells id="0" or id="1"'if (seen.has(id)) return `Duplicate cell id in appended fragment: ${id}`if (hasCell(existingXml, id)) return `Cell already exists in diagram: ${id}`seen.add(id)}return null}

ID 缺失、撞根节点、片内重复、跟已有图重复——四种情况各给一句人话错误,模型一看就知道哪错了。

edit_diagram ——改。图出来后用户常要微调。它做的是基于 ID 的精确增删改,核心是个用 lookahead 断言的正则:

function cellRegex(cellId: string): RegExp {const id = escapeRegExp(cellId)return new RegExp(`]*bid=["']${id}["'])[^>]*(?:/>|>[sS]*)`,'m',)}

用 lookahead 而非捕获组,保证只命中目标 ID 那个 cell,绝不误伤别人。删除时还会级联清掉引用它的连线,不留悬空的线头。

三个工具各管一摊:

  • display 负责造;
  • append 负责续;
  • edit 负责改。

每个工具的 description 里都带完整示例和错误指引,模型自己就能判断什么时候用哪个。

5. get_shape_library:按需加载的「图例手册」

画云架构图(AWS、K8s),你要的不只是方块和连线——你要 EC2 图标、S3 图标、Lambda 图标。Draw.io 内置 1000+ 专业图标,但每个的 style 语法都不一样。

如果把所有图标语法全塞进 System Prompt,不光烧 token,还会在模型根本用不到的时候硬塞给它,干扰判断。

catbuddy 的做法是按需加载——get_shape_library 工具让模型需要时主动查:

const safe = sanitizeName(raw)const filePath = path.join(libDir, `${safe}.md`)const resolved = path.resolve(filePath)// 路径遍历防护:防模型用 ../../ 读到系统文件if (!resolved.startsWith(path.resolve(libDir))) {return 'Error: invalid library path.'}const content = fs.readFileSync(filePath, 'utf-8')return content

这个图标库本质上并不复杂。说白了,就是一组纯 Markdown 文件,比如 aws4.mdkubernetes.mdflowchart.md

每个文件里主要放两类信息:一类是对应服务的 shape 名称,另一类是 style 语法说明。

除此之外,还配了一份包含 100+ 条目的分类清单。像 Compute(ec2、lambda…)、Storage(s3、efs…)、Database(rds、dynamodb…)、Networking(vpc、api_gateway…)这些都在里面。

整体设计的三个关键点

  • 文件即图例:不用改代码,加个 .md 就支持新图标库;
  • 路径遍历防护:sanitizeName + startsWith 双保险,堵死 ../../
  • 友好降级:库不存在时返回的是可用库列表,而不是干巴巴一句「not found」。

6. 三层校验:不靠「解析→报错→重生成」循环

你可能以为,画错了就「生成 → 解析 XML → 失败 → 把错误喂回模型 → 重画」对吧?catbuddy 偏偏没有这个循环。

取而代之的是三层渐进式校验。

第一层:Prompt 约束

这一层是预防性的。在模型动笔前,就先消掉最常见的歪路。

  • 「只生成裸 mxCell」灭包装错误;
  • 「ID 从 2 递增」灭 ID 冲突;
  • 「转义 < >」灭转义错误。

第二层:工具校验

这一层负责拦截。注意 display_diagram 那句错误消息的措辞——它不只说「格式错误」,而是把模型最容易犯的替代方案点名列出来:

if (!looksLikeDrawioXml(rawXml)) {return 'Error: display_diagram.xml must be actual Draw.io XML. ' +'Do not pass Markdown, ASCII diagrams, Mermaid, or plain text. ' +'Generate valid Draw.io XML and retry.'}

这给了模型足够上下文去「理解自己错在哪」,然后在下一次 tool call 里改对。

第三层:正则校验

这一层负责核实。edit_diagram 不信任模型传来的 cell_id 一定存在,每次都做真实性检查。

找不到就回「cell 不存在,建议先用 read_file.drawio 看实际有哪些 ID」。

为什么不用「解析→修正」循环?因为把解析器错误喂回模型,效率太低。

XML parser 报的是「line 47: unexpected token」,模型很难从这反推出「哦是我第 12 个 mxCell 的 parent 写错了」。

三层校验的策略反过来——让每一层的错误消息本身就是一个微型修正 Prompt:发生了什么 + 可能原因 + 下一步建议,全用人话写,前缀统一 Error: 方便模型判断这是错误而非正常结果。

7. 顺手总结:可复用的四条 Prompt 工程原则

这套画图实践里,有四条原则其实跟「画图」无关。换个结构化生成场景,照样能用:

  • 反向剔除:系统做 boilerplate,模型做 creative。生成 JSON 别让它写外层壳、只写 items;模板里 60% 是机械重复的,那部分就不该模型生成。
  • 示例密度:一个好示例 > 十条文字规则。Skill 里近一半篇幅是示例,每种元素都给样例;模型从示例提模式的能力远强于从规则推导。
  • 错误即微 Prompt:报错是给模型看的,不是给人看的。每条错误带「发生什么 + 可能原因 + 下一步」,统一 Error: 前缀。
  • 分层约束:别堆一个巨型 System Prompt。Skill(全局知识)+ Tool description(局部)+ Shape Library(领域)+ 错误消息(反馈),每次 tool call 只看相关那部分。

最后这条尤其想强调:catbuddy 的画图能力是四个信息来源拼起来的,不是一坨 2000 字的 System Prompt。

模型决策时综合这四层,但每次只需要看到当下相关的那一层。这样比塞一个巨无霸 Prompt 高效得多,也好维护得多。

这篇讲了什么?

  1. 让 AI 画架构图,靠的是工具链 + Prompt 工程,不是特殊模型能力。核心哲学是「反向剔除」——代码用 ensureMxFile() 补全 //根节点这些机械模板,模型只生成核心的 ,错误面和 token 都砍掉约 40%。
  2. Prompt 迭代四轮,可用率 40%→55%→75%→90%。每次提升都来自从失败案例里提炼具体规则(连线错开 exitY=0.3/0.7、waypoint 绕行),而非笼统喊「画好看点」。方法论是「告诉模型不要做什么」——因为它的默认行为往往就是你不想要的那种。
  3. 三个工具分工 + 三层校验兜底。display_diagram(造)/append_diagram(续)/edit_diagram(改)形成完整工具链,get_shape_library 按需加载 100+ 云图标语法;校验不走「解析→重生成」循环,而是 Prompt 预防 → 工具拦截 → 正则核实三层,每条错误消息都是一个微型修正 Prompt。

下一篇预告:进阶应用到这儿就收尾了——harness 的核心器官(心脏、手脚、眼睛、记忆、韧性、进阶)我们全拆完了。从第 13 篇起,整个系列转入产品化:怎么把这套 harness 变成桌面、Web、手机都能用的产品。

先看最基础的一环——一条消息的旅程:同一条消息,在磁盘里、在网络上、在你屏幕上,其实是三张完全不同的面孔,为什么要这么设计?再聊流式渲染怎么做到丝滑不卡顿。

免责声明:文中图文均来自网络,如有侵权请联系删除,心愿游戏发布此文仅为传递信息,不代表心愿游戏认同其观点或证实其描述。

相关文章

更多

精选合集

更多

大家都在玩

热门话题

大家都在看

更多