化:代碼行號(hào)注入與確定性目錄生成實(shí)戰(zhàn))
1. 先說清楚這次優(yōu)化到底在治什么病DeepWiki 這類自動(dòng)文檔生成工具核心價(jià)值是讓 AI 去讀代碼倉庫、再把理解沉淀成一篇篇可維護(hù)的文檔。但跑過一段時(shí)間的同學(xué)應(yīng)該都有體會(huì)文檔“能生成”和文檔“能長(zhǎng)期用”完全是兩回事。我這次做的優(yōu)化就是針對(duì)兩個(gè)最刺手的細(xì)節(jié)——代碼行號(hào)Code Line Numbers和確定性目錄生成Deterministic Table of Contents Generation。先說代碼行號(hào)。AI 在解釋某個(gè)函數(shù)時(shí)經(jīng)常會(huì)在文檔里寫“請(qǐng)看src/service.py的 handle_request 方法”但這句話是空的讀者得自己打開文件去翻。更麻煩的是如果文檔里的代碼塊沒有行號(hào)團(tuán)隊(duì)在評(píng)審、答疑、定位問題時(shí)只能靠“大約在第 80 行附近”這種模糊表述來回溝通成本非常高。如果能在生成的 Markdown 代碼塊里帶上真實(shí)文件的行號(hào)并且在正文引用處也明確標(biāo)注“第 72 到 86 行”這個(gè)文檔的可追溯性直接上了一個(gè)臺(tái)階。再說確定性目錄。LLM 生成目錄時(shí)只要模型參數(shù)不變、輸入不變理論上結(jié)果應(yīng)該穩(wěn)定但實(shí)際跑下來會(huì)發(fā)現(xiàn)同一個(gè)倉庫昨天生成的目錄和今天生成的目錄可能順序完全不同甚至章節(jié)編號(hào)都會(huì)變。對(duì)于個(gè)人筆記這無所謂但一旦文檔要嵌入 CI、對(duì)外發(fā)布或多人協(xié)作目錄變化就意味著鏈接失效、評(píng)審反復(fù)、diff 混亂。所謂“確定性”就是希望同一份代碼在同樣的配置下無論跑多少次產(chǎn)出的目錄結(jié)構(gòu)、章節(jié)順序、錨點(diǎn)名稱完全一致。這篇文章我會(huì)按“問題拆解——環(huán)境準(zhǔn)備——行號(hào)方案——目錄方案——實(shí)測(cè)對(duì)比——踩坑記錄”的順序展開。適合正在用 DeepWiki 做自動(dòng)文檔、或者用各類 LLM 生成技術(shù)文檔并希望結(jié)果可復(fù)現(xiàn)的工程師。我會(huì)把每一步的取舍講清楚并提供可以直接抄走的腳本和配置。2. 動(dòng)手之前的準(zhǔn)備環(huán)境、基線和問題定位2.1 把 DeepWiki 跑起來并鎖定版本DeepWiki 本身是開源項(xiàng)目安裝方式不復(fù)雜但有個(gè)關(guān)鍵點(diǎn)不要直接拉 latest而是要把版本鎖死。因?yàn)?LLM 生成的穩(wěn)定性不僅取決于你的 prompt還取決于代碼版本、依賴版本、甚至 Python 版本。哪怕只是依賴庫的小版本升級(jí)都可能讓你之前調(diào)好的“確定性”消失。我當(dāng)時(shí)是這么做的git clone https://github.com/your-fork/deepwiki.git cd deepwiki git checkout v0.4.2 # 記錄你實(shí)際使用的版本 python -m venv .venv source .venv/bin/activate pip install -e .注意這里強(qiáng)烈建議 fork 一份到自己倉庫因?yàn)楹罄m(xù)可能要改少量源碼。用官方倉庫再拉分支升級(jí)時(shí)沖突會(huì)比較多。鎖版本的目的是為了讓“同一倉庫 同一配置 同一模型參數(shù)”在多次運(yùn)行下具備可比性。如果不鎖版本后面做的任何優(yōu)化都很難歸因。2.2 準(zhǔn)備測(cè)試倉庫和生成基線建議選一個(gè)中等規(guī)模、結(jié)構(gòu)穩(wěn)定的倉庫來做基線測(cè)試。我用的測(cè)試倉庫大約 30 個(gè) Python 文件、5 個(gè)目錄層級(jí)總代碼量 8000 行左右。規(guī)模太小測(cè)不出穩(wěn)定性問題規(guī)模太大又是給調(diào)試添堵。生成基線時(shí)先不做任何優(yōu)化直接跑一遍完整流程把輸出保存為baseline_v1。記住這個(gè)基線有兩個(gè)作用一是后面對(duì)比行號(hào)和目錄的改進(jìn)效果二是用來觀察“不穩(wěn)定的具體表現(xiàn)是什么”。比如我第一輪基線就跑出兩個(gè)典型問題同一個(gè)章節(jié)第二次生成時(shí)標(biāo)題從## 3.2 API 鑒權(quán)變成了## 3.2 鑒權(quán)機(jī)制代碼塊里的行號(hào)完全錯(cuò)位文檔里寫“第 15 行”但真實(shí)代碼里那個(gè)函數(shù)在第 28 行。這種不確定性靠肉眼 review 很難全部發(fā)現(xiàn)所以后面我專門寫了一個(gè)校驗(yàn)?zāi)_本自動(dòng)化對(duì)比兩次生成結(jié)果。2.3 確認(rèn)生成鏈路里的三個(gè)不穩(wěn)定點(diǎn)在動(dòng)手優(yōu)化之前我花了半天把 DeepWiki 的生成鏈路梳理了一遍最后定位到三個(gè)關(guān)鍵不穩(wěn)定點(diǎn)第一是 LLM 采樣過程。目錄、標(biāo)題這類文本生成天然有概率性即使 temperature 設(shè)為 0某些模型在 batch 推理、并行解碼時(shí)仍會(huì)出現(xiàn)微小差異。第二是 Prompt 構(gòu)造順序。文檔的生成依賴從代碼庫提取的上下文如果上下文里文件列表的順序是動(dòng)態(tài)的比如來自 set 遍歷或文件系統(tǒng)讀取順序那么最終拼接出來的 prompt 每次可能都不一樣。第三是后處理邏輯。有些章節(jié)標(biāo)題需要做 slug 化轉(zhuǎn)成錨點(diǎn)如果對(duì)中文、空格、特殊字符的處理規(guī)則不統(tǒng)一同一個(gè)標(biāo)題在不同環(huán)境下會(huì)生成不同的錨點(diǎn)。明白這三個(gè)點(diǎn)之后優(yōu)化方向就很清晰了要么在源頭把 prompt 輸入變成確定性排序要么在后處理階段覆蓋掉 LLM 的不穩(wěn)定輸出。我最終采用的是“前后夾擊”的策略兩個(gè)方案都上了。3. 代碼行號(hào)從“大概位置”到“精確錨點(diǎn)”3.1 三種行號(hào)注入方案怎么選給代碼塊加行號(hào)聽起來很簡(jiǎn)單真正落地時(shí)會(huì)發(fā)現(xiàn)有三條路線方案一讓 LLM 自己輸出行號(hào)。也就是在 prompt 里寫“請(qǐng)?jiān)诿總€(gè)代碼塊左邊加上真實(shí)行號(hào)”。我試過效果不穩(wěn)定。模型經(jīng)常把行號(hào)寫錯(cuò)尤其是遇到空行、注釋、多行字符串時(shí)它“理解”的行號(hào)和實(shí)際文件行號(hào)經(jīng)常差幾行。方案二生成后再用腳本統(tǒng)一注入行號(hào)。也就是文檔先正常生成代碼塊內(nèi)容保持原樣然后跑一個(gè)后處理腳本讀取代碼塊內(nèi)容和真實(shí)源文件比對(duì)找到對(duì)應(yīng)行號(hào)再插入到每個(gè)代碼行前面。這個(gè)方案可控性高因?yàn)橛姓鎸?shí)文件作為“唯一事實(shí)來源”。方案三基于語法樹AST精確計(jì)算函數(shù)起始行只給關(guān)鍵代碼段加行號(hào)范圍標(biāo)注。這個(gè)適合在大倉庫里做“精確導(dǎo)航”但實(shí)現(xiàn)成本高而且不同語言的 AST 規(guī)則不一樣。我最終選了方案二為主、方案三為輔。方案二解決了 95% 的問題方案三用來處理那些“同一個(gè)函數(shù)被拆成多段展示”的特殊情況。3.2 后處理腳本真實(shí)行號(hào)注入核心思路不復(fù)雜Markdown 里的每個(gè)代碼塊都會(huì)附帶語言標(biāo)簽比如python我用 Python 腳本解析文檔里的代碼塊然后把每一行代碼當(dāng)作字符串在源文件里去查找這行內(nèi)容首次出現(xiàn)的位置從而確定行號(hào)。直接看腳本import re import json from pathlib import Path def find_line_number(content: str, source_text: str, start_hint: int 0) - int: 在源文本中查找 content 首次出現(xiàn)的真實(shí)行號(hào) idx source_text.find(content, start_hint) if idx -1: # 內(nèi)容可能跨行或被格式化退化為模糊匹配 idx source_text.find(content.splitlines()[0] if content.splitlines() else content) if idx -1: return None return source_text[:idx].count(\n) 1 def process_markdown(md_path: str, repo_root: str) - str: md Path(md_path).read_text(encodingutf-8) lines md.splitlines() output [] in_code False lang code_buf [] code_start_idx 0 def flush_code(): nonlocal code_buf, in_code if not code_buf: return # 通過代碼塊第一行注釋中的路徑信息定位源文件 path_hint for cl in code_buf: m re.match(r\s*(?:#|//|--|/\*)\s*file\s*[:\s](\S), cl) if m: path_hint m.group(1) break if not path_hint: output.extend(code_buf) code_buf [] in_code False return src_file Path(repo_root) / path_hint if not src_file.exists(): output.extend(code_buf) code_buf [] in_code False return src_text src_file.read_text(encodingutf-8) # 去掉代碼塊里的 file 注釋行避免污染展示 cleaned [cl for cl in code_buf if not re.match(r\s*(?:#|//|--|/\*)\s*file, cl)] # 計(jì)算每行的源文件行號(hào) last_idx 0 for i, cl in enumerate(cleaned): line_no find_line_number(cl, src_text, last_idx) if line_no is None: output.append(f {cl}) else: # 補(bǔ)齊為4位行號(hào)方便對(duì)齊 output.append(f{line_no:4} | {cl}) last_idx max(0, line_no - 1) if line_no else 0 code_buf [] in_code False for idx, line in enumerate(lines): if line.strip().startswith(): if not in_code: in_code True lang line.strip()[3:].strip() code_buf [] code_start_idx idx else: flush_code() output.append(line) continue if in_code: code_buf.append(line) else: output.append(line) # 處理文檔末尾可能未閉合的代碼塊 if in_code: flush_code() return \n.join(output)這個(gè)腳本有幾個(gè)設(shè)計(jì)細(xì)節(jié)值得說明第一代碼塊里需要有定位信息我采用約定file path/to/file.py注釋。因?yàn)?AI 生成代碼時(shí)不一定能準(zhǔn)確回憶文件路徑所以在 prompt 里要求它“在每個(gè)代碼塊第一行注明該代碼來自哪個(gè)文件”。這樣腳本才能把代碼映射到真實(shí)源文件。第二查找行號(hào)時(shí)用了start_hint參數(shù)。每次找到一行之后下一次查找從這一行附近開始這樣既快又避免重復(fù)匹配同一個(gè)函數(shù)里的相同代碼行。第三對(duì)于“AI 生成的示例代碼并不完全等于源文件”的情況腳本做了降級(jí)處理如果整行找不到就取第一行來模糊匹配如果還是找不到就只輸出空格占位不讓文檔報(bào)錯(cuò)。3.3 邊界情況多文件、重復(fù)代碼和動(dòng)態(tài)生成內(nèi)容實(shí)際倉庫里會(huì)遇到很多讓腳本崩潰的場(chǎng)景我踩過的坑主要有三個(gè)第一個(gè)是同一段代碼在多個(gè)文件里重復(fù)出現(xiàn)。比如兩個(gè)文件都有def get_config():腳本搜索時(shí)可能匹配到錯(cuò)誤的文件。解決辦法是把搜索范圍縮小到file指定的文件其次是查找時(shí)帶上前后幾行上下文。我的做法是拼接相鄰 2 行的內(nèi)容作為搜索鍵錯(cuò)配率明顯下降。第二個(gè)是 AI 對(duì)代碼做了精簡(jiǎn)或改寫。很多文檔為了講清原理會(huì)把真實(shí)代碼壓縮成偽代碼。這種情況下硬找行號(hào)沒有意義。我的策略是如果代碼塊和真實(shí)源文件的相似度低于 70%就直接不強(qiáng)行加行號(hào)改為在代碼塊前加一個(gè)“代碼摘要”標(biāo)注說明這是經(jīng)過簡(jiǎn)化的示例。第三個(gè)是動(dòng)態(tài)生成或臨時(shí)文件。倉庫里有些代碼是構(gòu)建腳本臨時(shí)生成的不存在于源碼中。我的處理比較簡(jiǎn)單找不到文件就跳過不加行號(hào)同時(shí)把這類文件加入exclude列表避免每次生成都觸發(fā)告警。3.4 行號(hào)在頁面里的交互行號(hào)注入之后還需要讓它在頁面里真正可用而不只是顯示一堆數(shù)字。我做了兩件事一是在 Markdown 渲染層開啟行號(hào)樣式。由于 DeepWiki 默認(rèn)的渲染器不一定支持行號(hào)我直接在生成的 HTML 頁面上做了輕量級(jí)前端增強(qiáng)把|分隔的行號(hào)列變成>from pathlib import Path import re def safe_anchor(title: str) - str: # 統(tǒng)一錨點(diǎn)生成規(guī)則 title title.strip().lower() title re.sub(r[^a-z0-9\u4e00-\u9fa5], -, title) title title.strip(-) return title def generate_toc(repo_path: str) - str: root Path(repo_path) toc_lines [] def walk_dir(current: Path, level: int): dirs sorted([p for p in current.iterdir() if p.is_dir()], keylambda p: p.name) files sorted([p for p in current.iterdir() if p.is_file()], keylambda p: p.name) for d in dirs: # 跳過隱藏目錄、構(gòu)建目錄、依賴目錄 if d.name.startswith(.) or d.name in (node_modules, venv, __pycache__, dist, build): continue indent * level title d.name toc_lines.append(f{indent}- [{title}](#{safe_anchor(title)})) walk_dir(d, level 1) for f in files: if f.name.startswith(.) or f.suffix not in (.py, .md, .js, .ts): continue indent * level title f.stem toc_lines.append(f{indent}- [{title}](#{safe_anchor(f.stem)})) walk_dir(root, 0) return \n.join(toc_lines)這個(gè)腳本生成的是一個(gè) Markdown 格式的目錄可以直接放在文檔開頭。因?yàn)樗羌兾募到y(tǒng)驅(qū)動(dòng)的不經(jīng)過 LLM所以輸出是絕對(duì)確定的——同一份代碼無論跑多少次目錄都一樣。但這里有個(gè)重要問題只給目錄不告訴模型每個(gè)章節(jié)該寫什么模型可能還是把章節(jié)內(nèi)容串到錯(cuò)誤的標(biāo)題下面。所以我還會(huì)把這份目錄作為“大綱約束”注入到生成 prompt 里明確告訴模型“必須嚴(yán)格按這個(gè)目錄順序?qū)懖荒苄略龌騽h除標(biāo)題”。4.4 方案 C緩存與增量重建當(dāng)倉庫規(guī)模變大每次全量生成文檔的時(shí)間和成本都很高。為了保持“確定性”的同時(shí)控制成本我引入了緩存機(jī)制。思路是把每次生成的文檔和源倉庫的文件哈希一起存起來。下次運(yùn)行時(shí)先對(duì)比當(dāng)前倉庫的文件哈希和上次的哈希如果某個(gè)文件沒有變化就直接復(fù)用上次生成的對(duì)應(yīng)章節(jié)不重新調(diào)用模型。這樣既保證了目錄穩(wěn)定又讓增量構(gòu)建變快。緩存鍵的設(shè)計(jì)很關(guān)鍵。我用的鍵是“文件相對(duì)路徑 文件內(nèi)容 SHA256 模型版本 prompt 模板版本”。如果只緩存文件內(nèi)容一旦你改了 prompt就會(huì)拿到舊內(nèi)容所以必須把 prompt 模板版本也加進(jìn)鍵里。import hashlib import json def hash_file(path: Path) - str: h hashlib.sha256() h.update(path.read_bytes()) return h.hexdigest() def cache_key(rel_path: str, content_hash: str, model_version: str, prompt_version: str) - str: raw f{rel_path}:{content_hash}:{model_version}:{prompt_version} return hashlib.sha256(raw.encode()).hexdigest()增量構(gòu)建的難點(diǎn)在于“父目錄和子目錄的聯(lián)動(dòng)”。如果一個(gè)模塊的目錄結(jié)構(gòu)變了子章節(jié)的生成結(jié)果也需要失效。所以我做了一個(gè)簡(jiǎn)單的依賴圖任何文件的哈希變化都會(huì)讓它在目錄樹上的所有祖先節(jié)點(diǎn)緩存失效。4.5 目錄與頁面錨點(diǎn)的聯(lián)動(dòng)有了確定性目錄之后還必須確保目錄里的錨點(diǎn)和正文標(biāo)題的錨點(diǎn)對(duì)齊。LLM 生成的標(biāo)題經(jīng)過 Markdown 渲染后錨點(diǎn)規(guī)則可能和目錄生成腳本不一致。我的做法是在生成最終 HTML 之前統(tǒng)一跑一個(gè)“錨點(diǎn)規(guī)范化”步驟從目錄里提取所有標(biāo)題。對(duì)每個(gè)標(biāo)題生成一個(gè)id屬性。在正文里查找對(duì)應(yīng)標(biāo)題并寫入相同的id。如果正文里找不到某個(gè)標(biāo)題說明模型漏寫了章節(jié)這時(shí)用占位符補(bǔ)上并打一條警告日志。這樣做之后目錄點(diǎn)擊跳轉(zhuǎn)的成功率從原來的約 85% 提升到了 100%。錨點(diǎn)這個(gè)細(xì)節(jié)很多人忽略但一旦團(tuán)隊(duì)開始用目錄導(dǎo)航就會(huì)發(fā)現(xiàn)錯(cuò)一個(gè)錨點(diǎn)基本等于這個(gè)章節(jié)“失蹤”了。5. 實(shí)測(cè)結(jié)果穩(wěn)定性的提升到底有多少5.1 我的驗(yàn)證方法優(yōu)化全部完成之后我用同一個(gè)測(cè)試倉庫跑了 7 輪生成記錄兩個(gè)指標(biāo)目錄重復(fù)率兩輪生成的目錄文本完全一致的比例。行號(hào)準(zhǔn)確率隨機(jī)抽取 200 個(gè)代碼塊檢查其行號(hào)與真實(shí)源文件是否一致。測(cè)試環(huán)境保持完全一致同一個(gè) DeepWiki 版本、同一個(gè)模型 checkpoint、固定 temperature0、固定 seed、單卡推理、關(guān)閉并行采樣。5.2 優(yōu)化前后的數(shù)據(jù)對(duì)比指標(biāo)優(yōu)化前優(yōu)化后7 輪目錄完全一致占比28.5%100%目錄錨點(diǎn)點(diǎn)擊成功率85%100%代碼塊行號(hào)準(zhǔn)確率62%96.5%單輪全量生成時(shí)間22 分鐘18 分鐘加緩存后 6 分鐘需要人工 review 的文檔比例40%12%行號(hào)準(zhǔn)確率沒有到 100%原因不在腳本而在于部分 AI 生成的代碼塊是“示例代碼”不是源碼的完全拷貝。這類代碼塊按我的設(shè)計(jì)本來就不該強(qiáng)制加行號(hào)所以這 3.5% 的誤差其實(shí)屬于“合理容錯(cuò)”。我對(duì)這個(gè)結(jié)果是滿意的尤其是目錄確定性做到了 100% 之后團(tuán)隊(duì)再也不用花時(shí)間核對(duì)“這一版目錄和上一版差在哪”。文檔 diff 終于變得干凈可控。5.3 優(yōu)化帶來的額外收益一個(gè)意外收獲是確定性目錄讓后續(xù)的國(guó)際化變得簡(jiǎn)單了。之前目錄經(jīng)常變翻譯平臺(tái)上的雙語對(duì)照經(jīng)常失配。現(xiàn)在目錄穩(wěn)定翻譯記憶庫TM的命中率提高了很多翻譯成本下降了大概四分之一。另一個(gè)收益是 CI 友好。我們把文檔生成嵌入到了 CI 流程里每次 push 后自動(dòng)重新生成文檔并檢查目錄是否與上次一致。如果目錄發(fā)生變化CI 會(huì)攔截并提示開發(fā)者確認(rèn)是否有意改動(dòng)目錄結(jié)構(gòu)。這個(gè)檢查在團(tuán)隊(duì)協(xié)作場(chǎng)景下非常有用能避免有人不小心改了一個(gè)文件名導(dǎo)致整個(gè)文檔目錄全部漂移。6. 常見問題排查與避坑實(shí)錄6.1 行號(hào)漂移代碼更新后行號(hào)全錯(cuò)這是最常出現(xiàn)的問題。代碼倉庫每天都在變新增了幾行代碼之后原本的文檔行號(hào)就會(huì)往下偏移。我的處理方案是給文檔生成加上“保鮮期”——倉庫文件哈希發(fā)生變化后對(duì)應(yīng)章節(jié)自動(dòng)標(biāo)記為過期下次生成時(shí)必須重新計(jì)算行號(hào)。同時(shí)在文檔頁面頂部顯示“本頁最后校驗(yàn)時(shí)間”和“對(duì)應(yīng)的 commit hash”至少讓讀者知道這份文檔是基于什么版本生成的。6.2 目錄與正文標(biāo)題不一致這類問題通常發(fā)生在模型把標(biāo)題稍作改寫之后。比如目錄里是## 3.2 API Key 管理正文里被模型寫成了## 3.2 API Keys Configuration。前面的錨點(diǎn)規(guī)范化步驟已經(jīng)能兜住大部分問題但如果模型大量改寫標(biāo)題你會(huì)看到“正文標(biāo)題和目錄標(biāo)題不一致”的告警。我的建議是把這類告警從 warning 提升為 error強(qiáng)制生成流程中斷而不是讓一個(gè)錯(cuò)誤目錄混進(jìn)文檔庫。6.3 增量緩存導(dǎo)致“永遠(yuǎn)不更新”增量構(gòu)建的坑也很典型某個(gè)文件內(nèi)容變了但它的緩存鍵沒變于是生成的文檔一直是舊的。排查后發(fā)現(xiàn)是 prompt 版本號(hào)沒有在代碼修改時(shí)同步更新。后來我把 prompt 模板的內(nèi)容哈希也編進(jìn)緩存鍵里任何 prompt 修改都會(huì)自動(dòng)導(dǎo)致緩存失效問題徹底解決。6.4 大倉庫超時(shí)與降級(jí)策略當(dāng)倉庫文件數(shù)超過 1000 個(gè)時(shí)一次性把全部文件內(nèi)容塞給模型是不可能的。我的策略是分層生成先對(duì)目錄樹做一次全球掃描生成每個(gè)模塊的摘要再對(duì)每個(gè)模塊單獨(dú)調(diào)用模型生成詳細(xì)內(nèi)容。如果某個(gè)模塊內(nèi)容過多就繼續(xù)往下拆分。這個(gè)策略本身就依賴確定性目錄——目錄結(jié)構(gòu)穩(wěn)定拆分點(diǎn)才能穩(wěn)定否則每次拆出來的模塊都不一樣緩存和增量也就無從談起。6.5 一個(gè)關(guān)于 token 成本的提醒確定性目錄生成雖然是代碼邏輯不消耗 LLM token但它需要讀一遍文件系統(tǒng)。對(duì)于超大倉庫文件遍歷本身可能消耗幾十秒不過相比大模型推理動(dòng)輒幾分鐘這幾十秒完全值得。真正貴的是“目錄注入 prompt”之后模型可能在正文里再次生成目錄浪費(fèi)幾百 token。所以要記得在 prompt 里加一行“正文中不要再生成目錄”。7. 一些個(gè)人經(jīng)驗(yàn)總結(jié)這次優(yōu)化的核心體會(huì)是AI 生成的文檔必須要有一個(gè)“非 AI 的骨架”來兜底。代碼行號(hào)和確定性目錄本質(zhì)都是把最終結(jié)果的關(guān)鍵部分從“模型自由發(fā)揮”變成“代碼強(qiáng)制決定”。模型仍然是內(nèi)容的主要生產(chǎn)者但結(jié)構(gòu)、順序、錨點(diǎn)、行號(hào)這些“框架性信息”不應(yīng)該讓模型去決策。給正在做類似事情的同學(xué)一個(gè)建議先跑 3 次基線把兩次輸出 diff 一下你會(huì)發(fā)現(xiàn)很多你以為“沒問題”的地方其實(shí)都在悄悄變化。不要試圖在一次優(yōu)化里解決所有問題先把目錄和行號(hào)這兩個(gè)最容易讓人困惑的問題解決掉文檔的可用性就會(huì)有質(zhì)的提升。最后一個(gè)小技巧把生成文檔后的“行號(hào)準(zhǔn)確率檢查”和“目錄重復(fù)性檢查”做成一個(gè)獨(dú)立的校驗(yàn)?zāi)_本掛到 CI 上。它不是針對(duì) DeepWiki 的特定邏輯而是通用的文檔質(zhì)量守衛(wèi)未來即使換用其他生成工具這套思路也能直接復(fù)用。