圖生成實(shí)戰(zhàn):基于 XML 中間格式的可編輯系統(tǒng)設(shè)計(jì))
1. 這個(gè)項(xiàng)目到底要解決什么問題項(xiàng)目代號叫 next-draw.io AI 架構(gòu)圖。當(dāng)時(shí)起這個(gè)名字沒有綁定任何具體商業(yè)產(chǎn)品就是 next下一代加 draw繪圖加 io輸入輸出三個(gè)詞的組合。真正做起來之后才發(fā)現(xiàn)這名字歪打正著點(diǎn)中了架構(gòu)圖工具最核心的三個(gè)痛點(diǎn)怎么把輸入變成輸出怎么降低畫圖門檻怎么讓圖能持續(xù)迭代維護(hù)。這個(gè)項(xiàng)目的目標(biāo)是讓 AI 根據(jù)一段需求描述甚至一段代碼直接生成一份結(jié)構(gòu)化、可繼續(xù)編輯的系統(tǒng)架構(gòu)圖而不是給一張只能看、不能改的靜態(tài)圖片。如果你和我一樣經(jīng)歷過“開會前兩小時(shí)還在手動補(bǔ)箭頭”“新同學(xué)入職讀架構(gòu)文檔讀到懷疑人生”“代碼改了三輪圖還是半年前那版”這些場景那你應(yīng)該知道這類工具缺得有多離譜。1.1 架構(gòu)圖為什么總是畫不出來先說最扎心的現(xiàn)實(shí)不是大家不會畫圖而是畫圖的成本收益嚴(yán)重失衡。手動拖拽一個(gè)框、填一個(gè)名字看起來只要十秒鐘但一張完整的系統(tǒng)架構(gòu)圖光節(jié)點(diǎn)就有二三十個(gè)連線幾十條還要分組、標(biāo)注、調(diào)色、對齊。等你終于把框擺好、線連完需求已經(jīng)變了于是你開始新一輪拖動、縮放、對齊。這種重復(fù)勞動極其消耗耐心所以大部分團(tuán)隊(duì)的架構(gòu)圖要么畫得很淺要么根本不更新。更深一層的問題是架構(gòu)圖應(yīng)該是“持久資產(chǎn)”但實(shí)際上它往往成為“一次性產(chǎn)物”。真正的系統(tǒng)在演進(jìn)接口在變化模塊在拆分合并而圖不會自己跟著變。于是技術(shù)文檔里的架構(gòu)圖經(jīng)常成為面試官最喜歡問的“哪張圖才是對的”的笑話。這個(gè)項(xiàng)目要解決的就是讓架構(gòu)圖可以像代碼一樣被生成、被版本管理、被自動化檢查和更新。換句話說把畫圖從“手工建?!弊兂伞鞍胱詣泳幾g”。1.2 next-draw.io 的核心目標(biāo)我在設(shè)計(jì)目標(biāo)時(shí)給自己列了四條硬性要求少一條都會讓項(xiàng)目失去價(jià)值首先輸入要低門檻。用戶可以直接寫一句人話比如“用戶端通過網(wǎng)關(guān)訪問后端服務(wù)后端服務(wù)依賴訂單庫和用戶庫”也可以粘貼一段代碼目錄甚至給一份接口清單。工具要做的是理解這些信息而不是要求用戶先學(xué)會某種復(fù)雜的建模語法。其次輸出必須可編輯。AI 畫完圖人還要改。所以生成結(jié)果不能是位圖不能是一張 PNG而應(yīng)該是帶有節(jié)點(diǎn)、連線、分組、樣式屬性的文本格式方便用戶在已有基礎(chǔ)上繼續(xù)調(diào)整。第三結(jié)果必須能入庫協(xié)作。架構(gòu)圖要放進(jìn) git 這類版本管理系統(tǒng)里每次改動都能清晰對比哪些模塊新增了哪些依賴被刪掉了。圖片格式做不了這件事只有文本化、結(jié)構(gòu)化格式能勝任。第四要支持迭代修復(fù)。AI 第一次生成的結(jié)果總是會有點(diǎn)偏差要么漏了一個(gè)模塊要么把邊連錯(cuò)了。工具需要有自檢和重試機(jī)制讓用戶說一句“再檢查一下網(wǎng)關(guān)和鑒權(quán)服務(wù)的關(guān)系”就能做局部修正而不是整張推倒重來。有了這四條后面所有技術(shù)選型都圍繞它們展開。你會發(fā)現(xiàn)大部分設(shè)計(jì)取舍其實(shí)都是被這些目標(biāo)倒逼出來的。2. 整體設(shè)計(jì)思路先選對路線再談實(shí)現(xiàn)2.1 三條技術(shù)路線我為什么選 XML 中間格式做 AI 生成架構(gòu)圖輸出格式是第一個(gè)要拍板的決策。我認(rèn)真對比過三條路線各有各的坑。第一條路線是讓 AI 直接生成圖片。這個(gè)最直觀模型用一張 PNG 或者 SVG 把圖畫出來。但問題非常明顯坐標(biāo)、文字準(zhǔn)確度不可控節(jié)點(diǎn)邊界經(jīng)常糊成一團(tuán)而且生成結(jié)果完全不可編程、不可 diff用戶想改一個(gè)字都得重新生成。這就違背了“可編輯”和“可入庫”兩條核心目標(biāo)。第二條路線是生成文本描述類的 DSL 圖表。這類語法適合簡單流程圖寫起來也很快但對企業(yè)級系統(tǒng)架構(gòu)圖來說表達(dá)能力偏弱。泳道、分組、跨層依賴、復(fù)雜樣式都要靠各種技巧去湊。而且 DSL 本身是一個(gè)大生態(tài)將來想擴(kuò)展自定義組件成本會越來越高。第三條路線是生成結(jié)構(gòu)化的繪圖 XML 中間格式。XML 聽起來老氣但它有不可替代的好處本質(zhì)上描述的是一張數(shù)據(jù)表節(jié)點(diǎn)是行邊是關(guān)系樣式是屬性。解析容易校驗(yàn)容易擴(kuò)展也容易。我把 AI 直接生成最終繪圖格式作為遠(yuǎn)期目標(biāo)但在當(dāng)下階段項(xiàng)目內(nèi)部先定義一套精簡的 XML 中間結(jié)構(gòu)讓模型把“實(shí)體”“關(guān)系”“分組”“樣式”語義化地表達(dá)出來后續(xù)再加一層薄薄的轉(zhuǎn)換器把它映射成真正能導(dǎo)入第三方工具做可視化的文件。三條路線的對比我實(shí)測下來是這樣輸出形態(tài)成本可編輯性入庫存檔復(fù)雜圖支持位圖/矢量圖低差基本重畫差二進(jìn)制無法 diff差文字經(jīng)常錯(cuò)文本 DSL中中文本可改中diff 但語義弱中分組交叉很費(fèi)勁XML 中間結(jié)構(gòu)高強(qiáng)節(jié)點(diǎn)屬性全保留強(qiáng)結(jié)構(gòu)化 diff 清晰高能帶分組和自定義屬性如果你見過網(wǎng)頁 HTML 和最終渲染結(jié)果之間的關(guān)系就很容易理解我的選擇。HTML 不是給你直接看的最終畫面它是一種中間結(jié)構(gòu)瀏覽器拿到之后做布局、樣式、繪制。XML 架構(gòu)圖也是這樣AI 只負(fù)責(zé)把“網(wǎng)頁語義”寫好布局和繪制交給后面的引擎兩者解耦之后每一邊都能做好自己擅長的事。2.2 核心工作流拆解整個(gè)項(xiàng)目流程被我拆成五個(gè)步驟順序是固定的缺一步都會多米諾骨牌式崩塌。第一步輸入處理。把用戶輸入的需求文本、代碼結(jié)構(gòu)、接口清單統(tǒng)一拼接成一條結(jié)構(gòu)化 prompt。這一步看起來簡單但實(shí)際上大部分生成質(zhì)量都取決于 prompt 里有沒有把“什么是節(jié)點(diǎn)、什么是邊、什么是分組”定義清楚。第二步結(jié)構(gòu)生成。把 prompt 發(fā)給大模型要求它返回 XML 中間結(jié)構(gòu)。注意不是讓它返回示意圖而是返回一張有節(jié)點(diǎn)、有邊、有分組的結(jié)構(gòu)化數(shù)據(jù)。模型的角色被限定為“信息抽取器”而不是“畫家”。第三步解析清洗。大模型輸出不可能次次完美可能帶 markdown 包裹可能在 XML 里混入解釋文字可能節(jié)點(diǎn) id 不唯一。這一步要用腳本把原始輸出提取出來解析成內(nèi)存對象并且對不合法內(nèi)容做修正或丟棄。第四步布局計(jì)算。拿到節(jié)點(diǎn)和邊的信息之后我并不直接用模型建議的坐標(biāo)而是把實(shí)體關(guān)系交給一個(gè)獨(dú)立的分層布局引擎重新計(jì)算每個(gè)節(jié)點(diǎn)的位置。這一步能避免大量“節(jié)點(diǎn)疊在一起”“連線橫穿所有模塊”的慘案。第五步渲染回寫。把布局之后的節(jié)點(diǎn)坐標(biāo)、寬高、樣式、分組信息寫回到與第三方工具兼容的繪圖 XML 文件。用戶在畫圖界面打開就能看到一張完整圖拖一拖就能繼續(xù)調(diào)整。這個(gè)流程相當(dāng)于把 AI 的能力限制在“理解關(guān)系和生成結(jié)構(gòu)”上而把需要確定性的活交給算法。實(shí)踐證明這樣分工的成功率遠(yuǎn)高于“一步到位”的做法。2.3 模塊劃分與數(shù)據(jù)流項(xiàng)目代碼按職責(zé)劃分成六個(gè)模塊各自只做一件事提示詞管理模塊負(fù)責(zé)拼 prompt內(nèi)部維護(hù)幾個(gè)版本的模板可以針對文本輸入、代碼輸入、多輪修正輸入分別調(diào)用不同的模板片段。解析模塊負(fù)責(zé)把模型返回的字符串變成 Python 對象里面有容錯(cuò)邏輯比如自動補(bǔ)全缺失的結(jié)束標(biāo)簽、剔除多余的解釋文本。實(shí)體標(biāo)準(zhǔn)化模塊負(fù)責(zé)統(tǒng)一命名和 id。模型有時(shí)候會把“User Service”和“用戶服務(wù)”當(dāng)成兩個(gè)不同實(shí)體這個(gè)模塊負(fù)責(zé)做大小寫歸一、同義詞合并確保同一個(gè)真實(shí)系統(tǒng)只有一個(gè)節(jié)點(diǎn)。布局引擎模塊接收標(biāo)準(zhǔn)化后的實(shí)體關(guān)系輸出坐標(biāo)和尺寸。它的算法不需要多豪華但必須穩(wěn)定不能因?yàn)檩斎腠樞蜃兓妥寛D整體亂跳。渲染寫回模塊負(fù)責(zé)把內(nèi)存模型映射成最終 XML 文本包括節(jié)點(diǎn)坐標(biāo)、樣式顏色、分組層級。最后是自檢回讀模塊生成完畢之后重新解析一遍 XML統(tǒng)計(jì)節(jié)點(diǎn)數(shù)、邊數(shù)、孤立節(jié)點(diǎn)數(shù)量低于閾值就自動重新生成一次。這一層守衛(wèi)非常管用它能攔截大量“看似生成了實(shí)際結(jié)構(gòu)是壞的”情況。數(shù)據(jù)流是單向的文本進(jìn)中間結(jié)構(gòu)生成清洗標(biāo)準(zhǔn)化布局渲染最后落盤。每一步的輸出都是下一步的輸入所有中間格式都必須嚴(yán)格校驗(yàn)這能避免錯(cuò)誤在后端被放大。3. 核心實(shí)現(xiàn)細(xì)節(jié)從提示詞到可編輯圖3.1 一個(gè)穩(wěn)定的提示詞模板先說結(jié)論提示詞不要寫太多花哨的廢話重點(diǎn)是把“信息格式”講清楚。我踩過一個(gè)大坑最初讓模型“發(fā)揮創(chuàng)意”結(jié)果十個(gè)結(jié)果十個(gè)結(jié)構(gòu)都不帶重樣的。后來我把提示詞收斂成固定模板效果立刻穩(wěn)定。我現(xiàn)在用的是類似下面這種結(jié)構(gòu)你是一名系統(tǒng)架構(gòu)信息抽取器。 請把用戶輸入轉(zhuǎn)換為可編輯架構(gòu)圖的中間描述。 中間描述格式要求 diagram group idg1 label接入層/ node idn1 label用戶端 groupg1 typeclient/ node idn2 label網(wǎng)關(guān)服務(wù) groupg1 typeservice/ storage idn3 label用戶數(shù)據(jù)庫 typedatabase/ edge fromn1 ton2 labelHTTPS/ edge fromn2 ton3 labelJDBC/ /diagram 約束 1. node、storage、group 的 id 必須全局唯一。 2. edge 只能引用已經(jīng)存在的 id。 3. type 只能取 client、service、storage、message、external 五種。 4. 不要輸出任何解釋文字只輸出 XML。這個(gè)模板把“游戲規(guī)則”先定死讓模型只做填鴨式抽取。看起來沒什么技術(shù)含量但非常有效。type 限定為五種是有意為之因?yàn)楹蠖说念伾托螤罹褪前催@五種類型去映射的類型越少系統(tǒng)越好維護(hù)。關(guān)于輸入部分我會在模板后面動態(tài)追加用戶原始描述同時(shí)提醒模型“不要給用戶描述里沒提到的東西編造依賴關(guān)系”。這個(gè)提醒很關(guān)鍵沒有它模型就會腦補(bǔ)出一堆現(xiàn)實(shí)中并不存在的中間件圖一多簡直沒法看。3.2 解析和清洗模型輸出不是金標(biāo)準(zhǔn)模型返回的文本再干凈也必須經(jīng)過一道容錯(cuò)清洗。最常見的狀況是它把 XML 放在 markdown 代碼塊里有些還帶 xml 標(biāo)簽有些則直接裸輸出。我的第一步是用正則把代碼塊里的內(nèi)容抽出來。import re def extract_xml(raw: str) - str: # 先嘗試匹配帶 標(biāo)記的代碼塊 pattern r(?:xml|XML)?\s*(.*?) match re.search(pattern, raw, re.S) if match: return match.group(1).strip() # 沒有代碼塊時(shí)嘗試直接找 diagram 開頭的內(nèi)容 match re.search(rdiagram[\s\S]*/diagram, raw, re.S) if match: return match.group(0) return raw拿到 XML 字符串之后再用標(biāo)準(zhǔn)庫的 ElementTree 解析成內(nèi)存樹。解析這一步經(jīng)常掛掉原因也確實(shí)有意思模型會輸出類似diagram標(biāo)簽沒閉合、屬性值沒加引號、邊引用了不存在的 id。所以解析后必須立刻做一輪結(jié)構(gòu)校驗(yàn)。我通常在解析后打一份“結(jié)構(gòu)摘要”比如打印出總共有幾個(gè)節(jié)點(diǎn)、幾條邊、哪些邊引用了未知 id。這些信息能明確告訴我這次生成的圖是否具備繼續(xù)處理的基礎(chǔ)。如果發(fā)現(xiàn)未知 id就直接把這條邊丟棄因?yàn)椴季忠鏇]法處理懸空引用。清洗還有一個(gè)容易被忽略的點(diǎn)必須對節(jié)點(diǎn) label 做 XML 轉(zhuǎn)義。如果用戶輸入文本里包含或直接拼進(jìn) XML 會導(dǎo)致解析失敗。用 Python 的xml.sax.saxutils.escape把特殊字符轉(zhuǎn)掉哪怕生成結(jié)果里看不到區(qū)別文件本身是穩(wěn)定的。3.3 布局方案別依賴大模型給的坐標(biāo)最初我覺得大模型既然能把關(guān)系和層級抽出來那么順帶給它幾個(gè)絕對坐標(biāo)也是順手的事。實(shí)驗(yàn)做下來我發(fā)現(xiàn)模型對坐標(biāo)的直覺極差經(jīng)常輸出“節(jié)點(diǎn)A在 x80節(jié)點(diǎn)B在 x820但它們還有同一層編號”這種自相矛盾的數(shù)據(jù)。而且即使坐標(biāo)基本合理只要用戶加一個(gè)節(jié)點(diǎn)整體布局就徹底亂掉。所以我做了個(gè)重要決定模型輸出里關(guān)于坐標(biāo)的信息全部忽略只保留節(jié)點(diǎn)、邊、分組、類型、label。真正的位置計(jì)算完全交給代碼里的分層布局引擎。實(shí)現(xiàn)的思路不復(fù)雜根據(jù)邊關(guān)系建立一個(gè)“依賴層級”。沒有入邊的節(jié)點(diǎn)歸為第 0 層下游節(jié)點(diǎn)按層級 max(所有上游節(jié)點(diǎn)層級) 1計(jì)算。每一層的節(jié)點(diǎn)按順序排列同一層內(nèi)橫向均勻分布??v向間距固定比如 80px。橫向間距按 label 長度估算每個(gè)節(jié)點(diǎn)寬度至少 120px防止文字?jǐn)D壓。連線的路由交給繪圖工具自帶的自動路由布局引擎只需要保證節(jié)點(diǎn)不互相重疊。寫出來的核心函數(shù)大致是這樣def assign_level(edges, node_ids): depth {nid: 0 for nid in node_ids} indeg {nid: 0 for nid in node_ids} graph {nid: [] for nid in node_ids} for src, dst in edges: graph[src].append(dst) indeg[dst] 1 queue [nid for nid in node_ids if indeg[nid] 0] for nid in queue: for child in graph[nid]: depth[child] max(depth[child], depth[nid] 1) indeg[child] - 1 if indeg[child] 0: queue.append(child) return depth這就是一次拓?fù)渑判虻淖凅w。遇到環(huán)狀依賴時(shí)拓?fù)渑判驎粝挛刺幚砉?jié)點(diǎn)我的處理方式是給這類節(jié)點(diǎn)統(tǒng)一分配一個(gè)“循環(huán)依賴層”避免腳本死循環(huán)。對于架構(gòu)圖來說環(huán)不應(yīng)該出現(xiàn)在業(yè)務(wù)依賴?yán)锶绻霈F(xiàn)了圖反而會幫你暴露問題。布局算法穩(wěn)定之后用戶體驗(yàn)就非??深A(yù)期。無論跑多少次同樣的輸入永遠(yuǎn)不會產(chǎn)生結(jié)構(gòu)漂移不同的圖這對后續(xù)做 diff 和版本對比非常重要。3.4 樣式注入與實(shí)體標(biāo)準(zhǔn)化大模型生成節(jié)點(diǎn)樣式屬性時(shí)非常隨性今天給“用戶端”填紅色明天給“用戶端”填藍(lán)色后天可能直接不填。樣式不穩(wěn)定會讓人對工具產(chǎn)生極強(qiáng)的不信任感。我的方案是把樣式屬性從模型輸出里剝離完全交給代碼決定。項(xiàng)目里維護(hù)一個(gè)簡單的類型到樣式的映射表TYPE_STYLE { client: rounded1;whiteSpacewrap;fillColor#E3F2FD;strokeColor#1976D2;, service: rounded0;whiteSpacewrap;fillColor#E8F5E9;strokeColor#2E7D32;, database: shapecylinder3;whiteSpacewrap;fillColor#FFF8E1;strokeColor#F57F17;, message: shapemessage;whiteSpacewrap;fillColor#F3E5F5;strokeColor#7B1FA2;, external: shapecloud;whiteSpacewrap;fillColor#ECEFF1;strokeColor#455A64;, }這樣用戶看到同一類型節(jié)點(diǎn)永遠(yuǎn)是同一套視覺表達(dá)圖面色彩整潔也便于團(tuán)隊(duì)在評審時(shí)快速過濾信息。實(shí)體標(biāo)準(zhǔn)化模塊則是解決“同一個(gè)東西被當(dāng)成三個(gè)節(jié)點(diǎn)”的問題。常見情況包括英文字母大小寫差異比如users-db和UsersDB中英文混寫比如“訂單服務(wù)”和“Order Service”還有縮寫差異。我的處理策略先統(tǒng)一成小寫去掉特殊符號再把等價(jià)詞映射列表交給固定函數(shù)遇到匹配就合并同一個(gè) id。這個(gè)模塊不能做太激進(jìn)否則容易把兩個(gè)本來獨(dú)立的服務(wù)合并掉。所以我的原則是只對“完全一致但寫法不同”做合并對“看起來像但不一定相同”保留為獨(dú)立節(jié)點(diǎn)把選擇權(quán)留給用戶。用戶可以在圖上看到結(jié)果發(fā)現(xiàn)不對勁再手動合并比工具自作聰明要安全得多。4. 實(shí)操過程搭一個(gè)能跑通的最小版本4.1 環(huán)境準(zhǔn)備與文件結(jié)構(gòu)整套最小版本不需要前端頁面也沒必要引入復(fù)雜框架。我強(qiáng)烈建議第一步先做成命令行工具跑通核心鏈路之后再考慮 Web 界面和交互。準(zhǔn)備一個(gè)普通 Python 環(huán)境版本 3.10 以上就行不需要額外依賴重型庫。大模型 API 調(diào)用我用標(biāo)準(zhǔn)庫里的 urllib 實(shí)現(xiàn)避免引入一堆依賴。文件結(jié)構(gòu)安排如下next-draw-project/ ├─ prompts/ │ └─ diagram_prompt.txt ├─ src/ │ ├─ llm_client.py │ ├─ parser.py │ ├─ layout.py │ ├─ writer.py │ └─ cli.py └─ examples/ └─ demo_input.txt如果你的機(jī)器聯(lián)網(wǎng)不方便也可以用本地模型只要保持輸入輸出格式不變后端是誰其實(shí)不關(guān)鍵。架構(gòu)上把 llm_client 做薄就保留了隨時(shí)切換后端的能力。4.2 核心代碼骨架先看調(diào)用大模型的部分。假設(shè)你有一個(gè)兼容通用消息格式的接口代碼大概這樣import json from urllib.request import Request, urlopen def call_llm(prompt: str, endpoint: str, token: str) - str: payload json.dumps({ messages: [{role: user, content: prompt}], temperature: 0.2, max_tokens: 4000, }).encode(utf-8) req Request(endpoint, datapayload, headers{ Content-Type: application/json, Authorization: fBearer {token}, }) with urlopen(req, timeout120) as resp: data json.loads(resp.read().decode(utf-8)) return data[choices][0][message][content]接著是 pompt 讀取和主流程def build_prompt(user_input: str) - str: template open(prompts/diagram_prompt.txt, encodingutf-8).read() return template \n\n用戶輸入\n user_input def main(): user_input open(examples/demo_input.txt, encodingutf-8).read() prompt build_prompt(user_input) raw call_llm(prompt, ENDPOINT, TOKEN) xml_text extract_xml(raw) model parse_middle_xml(xml_text) model normalize_entities(model) xmlish write_editable_diagram(model) with open(output.xml, w, encodingutf-8) as f: f.write(xmlish) summary validate_diagram(xmlish) print(summary)用這樣一個(gè)簡單的 90 分腳本已經(jīng)能走完“文本進(jìn)XML 出”的完整鏈路。我建議第一次跑的時(shí)候盡量使用短小、邊界清晰的輸入比如“A 服務(wù)調(diào)用 B 服務(wù)B 服務(wù)連接數(shù)據(jù)庫”先驗(yàn)證鏈路通順再逐步增加復(fù)雜度。4.3 接入文檔和版本庫命令行版本跑通之后下一步一定是把它接進(jìn)團(tuán)隊(duì)文檔流程。因?yàn)檩敵龅氖羌兾谋?XML所以可以直接扔進(jìn) git。每次有人調(diào)整架構(gòu)描述并重新生成 diff 里能清楚看到哪個(gè)節(jié)點(diǎn)變了、哪條邊斷了。這個(gè)體驗(yàn)是位圖完全給不了的。在此基礎(chǔ)上我在自己的項(xiàng)目里加了一個(gè)小腳本讀取 XML 后輸出一行摘要nodes12, edges15, groups3, unlinked_nodes0這個(gè)摘要可以作為 CI 檢查的一部分比如節(jié)點(diǎn)數(shù)不小于閾值、不存在孤立節(jié)點(diǎn)。一旦有人手工刪改 XML 導(dǎo)致依賴斷裂檢查立刻報(bào)警。架構(gòu)圖和代碼庫慢慢形成互相約束的關(guān)系圖不再是擺設(shè)??梢暬蜷_的問題可以先由第三方工具的無損導(dǎo)入解決。中間結(jié)構(gòu) XML 經(jīng)過一層映射之后寫入繪圖工具兼容格式工具內(nèi)部有自動布局能力打開時(shí)再執(zhí)行一次“整理布局”就能得到非常干凈的圖。我的經(jīng)驗(yàn)是不要讓項(xiàng)目試圖覆蓋所有畫布交互那個(gè)工程量不適合個(gè)人項(xiàng)目第一階段去做。5. 常見問題與排查手冊5.1 XML 解析失敗這是我在接入各種大模型后遇到概率最高的錯(cuò)誤。典型表現(xiàn)是腳本報(bào)錯(cuò)syntax error: line 3, column 0看一眼原始輸出發(fā)現(xiàn)模型把解釋文字加在 XML 前面或者少了個(gè)閉合標(biāo)簽。處理優(yōu)先級按下面這個(gè)表來現(xiàn)象常見原因處理方式一直解析失敗模型輸出被 markdown 包裹先用正則剝離代碼塊標(biāo)簽缺閉合上下文過長導(dǎo)致模型截?cái)嘟档?max_tokens 或拆分輸入屬性沒加引號模型語法能力波動手動用正則修正讓屬性值統(tǒng)一被引號包裹被當(dāng)標(biāo)簽解釋輸入文本包含特殊字符對 label 做 XML 轉(zhuǎn)義根節(jié)點(diǎn)多了一個(gè)模板要求只輸出一個(gè) diagram截取第一個(gè)完整diagram塊碰到解析失敗不要急著改模板先打印一份原始返回內(nèi)容的前 500 個(gè)字符看看結(jié)構(gòu)。很多時(shí)候問題出在輸入文本帶了很多代碼片段導(dǎo)致模型輸出被截?cái)喽皇翘崾驹~寫得不夠好。5.2 節(jié)點(diǎn)重疊和布局偏移所有第一次接出來的圖都會出現(xiàn)“節(jié)點(diǎn)全部疊在左上角”或者“邊穿過五六個(gè)節(jié)點(diǎn)”這種狀況。原因很直接你用了模型返回的坐標(biāo)而模型根本不理解布局空間。解決辦法只有一個(gè)不要續(xù)用模型坐標(biāo)改成重啟布局引擎。我每次換輸入都強(qiáng)制刷新坐標(biāo)甚至故意在 prompt 里不提供坐標(biāo)信息就是防止模型“順手”填出幾個(gè)坐標(biāo)把我誤導(dǎo)了。如果布局引擎跑完依然重疊檢查分隔距離是否太小。我在實(shí)現(xiàn)里把橫向間距設(shè)為 40px縱向間距設(shè)為 80px節(jié)點(diǎn)高度固定 40px覆蓋大多數(shù)情況。遇到圖特別大間距可以調(diào)成按節(jié)點(diǎn)數(shù)量比例縮放。5.3 實(shí)體重復(fù)和關(guān)系丟失“用戶服務(wù)USER-SERVICE后端用戶中心”這三個(gè)名字同時(shí)出現(xiàn)在生成的圖里是最讓人崩潰的場景。我第一次碰到以為是模型抽風(fēng)后來才知道是沒做標(biāo)準(zhǔn)化。標(biāo)準(zhǔn)化模塊上線之后重復(fù)率下降得很明顯。我試過最極端的情況輸入一段包含 500 行日志描述的資料模型把同一個(gè)數(shù)據(jù)庫重復(fù)建了四個(gè)節(jié)點(diǎn)。歸一化處理配合邊引用清理最后保留一個(gè)節(jié)點(diǎn)所有邊都指向那一個(gè) id。關(guān)系丟失的問題更多出現(xiàn)在邊長過長的時(shí)候。模型在生成最后幾條邊時(shí)突然忘記了前面定義的節(jié)點(diǎn) id于是引用了一個(gè)不存在的 id。這種邊會被校驗(yàn)?zāi)K直接丟棄從結(jié)果上看就是“少了一條線”。我的處理方法是讓提示詞在結(jié)尾重復(fù)提醒“所有 id 必須是前面出現(xiàn)過的 id”同時(shí)關(guān)閉自回歸模型無關(guān)緊要的想象力效果立竿見影。5.4 生成慢和結(jié)果不穩(wěn)定調(diào)用大模型生成 20 個(gè)節(jié)點(diǎn)的圖一次可能要二十秒以上用戶體驗(yàn)很差。我的優(yōu)化思路不是改模型參數(shù)而是控制輸出范圍。在提示詞模板里加一條“節(jié)點(diǎn)數(shù)量不超過 25 個(gè)”讓模型在信息抽取階段做取舍。超過規(guī)模的輸入先讓模型做分層摘要再分塊生成局部圖最后再用布局引擎把幾塊圖合并到一起。這個(gè)方法犧牲了一定的“全自動”但穩(wěn)定性和可讀性都回來了。不穩(wěn)定問題還有一個(gè)隱藏推手模型內(nèi)置的隨機(jī)性。雖然我用 temperature0.2但不同時(shí)間運(yùn)行同一輸入仍然可能得到細(xì)微差別。后來我在注意力機(jī)制之外顯式要求模型“按輸入順序輸出節(jié)點(diǎn)”把單體節(jié)點(diǎn)與它們的邊綁定在相鄰位置。實(shí)測下結(jié)果幾乎不再跳變。6. 一些實(shí)測體會和后續(xù)擴(kuò)展方向6.1 我反復(fù)測試后的幾個(gè)結(jié)論做了這個(gè)項(xiàng)目之后我對“AI 生成架構(gòu)圖”這件事有兩個(gè)很明確的判斷。第一大模型擅長提取關(guān)系不擅長做空間設(shè)計(jì)。讓它羅列系統(tǒng)有哪些模塊、模塊之間怎么調(diào)用它能做得又快又好但讓它協(xié)調(diào)幾十個(gè)節(jié)點(diǎn)的坐標(biāo)它瞬間退化成隨機(jī)數(shù)生成器。因此架構(gòu)上必須把“關(guān)系提取”和“空間布局”徹底分離誰擅長什么就干什么。第二后處理永遠(yuǎn)比調(diào) prompt 更可靠。你可以把提示詞寫到完美但模型是非確定性的下一次輸出可能就任性而解析、校驗(yàn)、標(biāo)準(zhǔn)化這些代碼是確定性的一旦寫好就穩(wěn)定。我的成功率從最開始三成不到到現(xiàn)在基本九成以上真正的轉(zhuǎn)折點(diǎn)是寫了一個(gè)嚴(yán)格的后處理校驗(yàn)鏈而不是花了更多時(shí)間打磨 prompt。多輪迭代也很值得做。第一版生成的結(jié)構(gòu)即使有遺漏第二步讓模型“檢測當(dāng)前圖和輸入的一致性”大概率能把缺掉的邊補(bǔ)回來。該環(huán)節(jié)我建議最多做兩輪再多會給模型引入幻覺讓它生成不存在的依賴。6.2 接下來可以做的新功能當(dāng)前版本只是個(gè)骨架我下一步想優(yōu)先做幾個(gè)方向。第一個(gè)方向是把代碼倉庫直接喂進(jìn)去。解析源碼里的類、函數(shù)、服務(wù)注冊信息自動生成服務(wù)依賴圖這比讓用戶寫自然語言描述要精確得多。關(guān)鍵難點(diǎn)從文本抽取變成了語法解析但底層工作流完全不用改。第二個(gè)方向是局部編輯的對話式體驗(yàn)?,F(xiàn)在生成完只能手動改 XML不夠友好。如果用戶說“把訂單服務(wù)的顏色改掉”“把緩存節(jié)點(diǎn)放到數(shù)據(jù)庫旁邊”模型能在保留大部分結(jié)構(gòu)不變的前提下做局部 patch這個(gè)工具就真的很像團(tuán)隊(duì)里的架構(gòu)師助理了。第三個(gè)方向是架構(gòu)演進(jìn)對比。既然圖是文本、能入庫、能 diff那就能自動對比前后兩版架構(gòu)圖標(biāo)出新增模塊、失效依賴、不穩(wěn)定節(jié)點(diǎn)。這事對大型系統(tǒng)評審太有用了我后續(xù)會優(yōu)先投入。最后再分享一個(gè)我在實(shí)際使用中發(fā)現(xiàn)的小技巧永遠(yuǎn)在生成結(jié)果里保留一個(gè)“未知依賴”虛擬節(jié)點(diǎn)。當(dāng)模型拿到一條沒有來源的邊時(shí)不要直接丟棄而是掛到一個(gè)虛擬節(jié)點(diǎn)上提醒用戶這里可能漏了系統(tǒng)。這樣你不會在批量生成時(shí)因?yàn)閬G邊而悄悄損失語義架構(gòu)評審時(shí)還能“被迫”解釋清楚。這個(gè)小改動值得任何做 AI 繪圖工具的同學(xué)抄走。