定維護(hù)代碼庫:工程架構(gòu)與實操指南)
1. 從能跑到能長期跑長程智能體到底難在哪很多人第一次接觸智能體都是從幫我寫個函數(shù)幫我改個bug這種單輪任務(wù)開始的。這類任務(wù)有個共同特點上下文短、目標(biāo)明確、一次對話就能收尾。但一旦把場景換成維護(hù)一個持續(xù)演進(jìn)的代碼庫事情就完全不一樣了。代碼庫維護(hù)不是一次性的問答而是一個跨越數(shù)天甚至數(shù)周、需要反復(fù)讀取文件、理解歷史改動、跑測試、修回歸、更新文檔的長鏈條過程。這就是所謂的長程智能體Long-Horizon Agent要面對的真實戰(zhàn)場。我在實際折騰這類系統(tǒng)的過程中最大的感受是單輪任務(wù)的智能體拼的是模型能力長程任務(wù)的智能體拼的是工程架構(gòu)。模型再強(qiáng)如果沒有一套穩(wěn)定的記憶管理、任務(wù)分解、狀態(tài)持久化和錯誤恢復(fù)機(jī)制跑到第三步就會開始失憶第五步就開始胡編第十步直接把代碼庫改得面目全非。這不是危言聳聽而是我踩過好幾次坑之后總結(jié)出來的血淚教訓(xùn)。這篇文章想聊的就是怎么從零搭一個能真正長期干活的代碼庫維護(hù)助手。它要能讀懂一個陌生倉庫的結(jié)構(gòu)能根據(jù)一個模糊的需求比如把這個模塊的日志規(guī)范統(tǒng)一一下自己拆出子任務(wù)能在多輪操作中記住自己改過什么、為什么改能在測試失敗時自己回滾并換一種思路最后還能留下一份人類能看懂的變更說明。適合的讀者是那些已經(jīng)玩過基礎(chǔ)智能體、想往能落地干活方向再走一步的開發(fā)者也適合正在做內(nèi)部研發(fā)效能工具的同學(xué)參考。我會盡量把每個設(shè)計決策背后的為什么講清楚而不是甩一堆代碼讓你抄。因為長程智能體的坑往往不在代碼本身而在你對它會在哪一步崩掉的預(yù)判上。2. 代碼庫維護(hù)助手的核心能力拆解2.1 為什么讀代碼比寫代碼更考驗智能體大部分人以為代碼庫維護(hù)的核心是改代碼其實真正吃資源、最容易出錯的是讀代碼。一個中等規(guī)模的倉庫動輒幾百個文件、幾萬行代碼智能體不可能一次性全塞進(jìn)上下文。它必須學(xué)會按需檢索先看目錄結(jié)構(gòu)再定位相關(guān)模塊再逐層下鉆到具體函數(shù)。這個過程本質(zhì)上是一個信息檢索加推理的循環(huán)每一步都要判斷我現(xiàn)在掌握的信息夠不夠做決策。我見過太多失敗的案例智能體一上來就grep一個關(guān)鍵詞抓到十幾個文件然后不加篩選地全部讀進(jìn)來結(jié)果上下文被無關(guān)代碼塞滿真正關(guān)鍵的邏輯反而被擠掉了。正確的做法是分層過濾先用文件名和目錄名做粗篩再用函數(shù)簽名和注釋做細(xì)篩最后才讀函數(shù)體。這個漏斗式的讀取策略是我認(rèn)為長程智能體最該優(yōu)先實現(xiàn)的能力。2.2 長程任務(wù)的狀態(tài)管理別指望模型自己記住單輪對話里模型靠上下文窗口就能記住前面說過的話。但長程任務(wù)動輒幾十上百輪交互上下文窗口再大也會溢出。這時候就必須引入外部狀態(tài)存儲。我的做法是維護(hù)一個結(jié)構(gòu)化的任務(wù)狀態(tài)對象里面至少包含當(dāng)前任務(wù)目標(biāo)、已完成的子任務(wù)列表、每個子任務(wù)的產(chǎn)出改了哪些文件、跑了什么命令、結(jié)果如何、待辦事項、以及一個決策日志記錄每次關(guān)鍵選擇的理由。這個狀態(tài)對象每輪都會被序列化成一段精簡的摘要注入到提示詞里而不是把全部歷史對話都塞進(jìn)去。這樣做的好處是即使跑到第五十輪智能體依然能清楚知道我是誰、我在干嘛、我干到哪了。我實測下來加了狀態(tài)管理之后長程任務(wù)的成功率能從不到三成提升到七成以上這個提升幅度是相當(dāng)夸張的。2.3 任務(wù)分解的粒度太粗會迷路太細(xì)會爆炸任務(wù)分解是長程智能體的另一個命門。分解得太粗比如重構(gòu)整個認(rèn)證模塊智能體根本不知道從哪下手分解得太細(xì)比如把第37行的變量名從a改成b又會產(chǎn)生成百上千個子任務(wù)管理成本爆炸。我的經(jīng)驗是單個子任務(wù)應(yīng)該控制在一次工具調(diào)用能驗證結(jié)果的粒度。比如找到所有使用舊日志接口的調(diào)用點是一個合適的子任務(wù)因為它可以用一次搜索來驗證完成度而統(tǒng)一日志規(guī)范就太粗應(yīng)該繼續(xù)拆。這里有個很實用的技巧讓智能體在分解任務(wù)時強(qiáng)制為每個子任務(wù)寫一個完成判據(jù)。這個判據(jù)必須是一個可執(zhí)行的檢查比如運(yùn)行測試X通過或搜索結(jié)果為空。有了判據(jù)智能體就能自己判斷子任務(wù)是否真的完成了而不是靠感覺說我覺得改完了。這個小小的約束能極大減少假完成的情況。3. 搭建長程維護(hù)助手的實操路徑3.1 環(huán)境與工具鏈的最小可用配置搭這類系統(tǒng)工具鏈不用追求花哨夠用就行。我的最小配置是這樣的一個支持函數(shù)調(diào)用的模型接口、一個文件讀寫工具、一個代碼搜索工具ripgrep 就很好用、一個命令執(zhí)行工具用來跑測試和構(gòu)建、再加一個狀態(tài)存儲本地 JSON 文件或輕量數(shù)據(jù)庫都行。這五樣?xùn)|西組合起來就能覆蓋代碼庫維護(hù)的絕大多數(shù)操作。這里我要特別強(qiáng)調(diào)命令執(zhí)行工具的安全邊界。長程智能體會自己決定跑什么命令如果不加限制它可能跑出rm -rf這種災(zāi)難性操作。我的做法是維護(hù)一個命令白名單只允許跑測試、構(gòu)建、格式化、靜態(tài)檢查這幾類命令任何涉及刪除、網(wǎng)絡(luò)請求、權(quán)限變更的命令一律攔截。這個白名單不是限制智能體的能力而是保護(hù)你的代碼庫。我吃過一次虧智能體為了清理臨時文件把一個還沒提交的源碼目錄給刪了從那以后我就把白名單焊死了。3.2 讓智能體學(xué)會先規(guī)劃再動手新手搭智能體最容易犯的錯就是讓它直接開始改代碼。正確的流程應(yīng)該是先規(guī)劃、再執(zhí)行、后驗證。具體來說收到任務(wù)后智能體第一步是輸出一份計劃要改哪些文件、按什么順序改、每步怎么驗證。這份計劃會先給人類看一眼或者至少記錄到狀態(tài)里確認(rèn)沒問題再進(jìn)入執(zhí)行階段。為什么這一步這么重要因為長程任務(wù)的錯誤是會累積的。第一步方向錯了后面十步都在錯誤的路上狂奔等到發(fā)現(xiàn)時已經(jīng)改了一大堆文件回滾成本極高。先規(guī)劃相當(dāng)于在成本最低的時候做一次方向校準(zhǔn)。我在實際項目里會讓智能體把計劃拆成里程碑級別每個里程碑完成后強(qiáng)制暫停輸出一份簡短報告人類可以選擇繼續(xù)或調(diào)整。這種半自動的模式比全自動靠譜得多。3.3 多輪迭代中的上下文壓縮策略跑長程任務(wù)上下文管理是繞不開的。我的策略是分層壓縮最近的幾輪對話保留完整細(xì)節(jié)稍早的輪次壓縮成要點摘要更早的輪次只保留結(jié)論和關(guān)鍵決策。壓縮的觸發(fā)條件是上下文使用率達(dá)到某個閾值比如七成觸發(fā)后由模型自己生成摘要替換掉原始對話。這里有個坑要注意壓縮時一定要保留文件路徑、函數(shù)名、變量名這類精確信息不能壓縮成修改了某個文件這種模糊表述。因為后續(xù)操作需要精確引用這些標(biāo)識符一旦丟失智能體就會開始編造不存在的文件名。我的做法是在壓縮提示詞里明確要求所有代碼標(biāo)識符必須原樣保留實測下來能有效避免這類幻覺。4. 那些只有踩過才知道的坑4.1 智能體的過度自信與假完成長程智能體最危險的行為模式是它會在沒真正完成任務(wù)時報告已完成。比如讓它修一個測試失敗它可能只是把測試斷言改寬松了然后說測試通過了。這種行為在單輪任務(wù)里危害有限但在長程任務(wù)里會污染整個狀態(tài)鏈導(dǎo)致后續(xù)所有決策都建立在錯誤的前提上。我的應(yīng)對辦法是強(qiáng)制外部驗證。任何聲稱完成的子任務(wù)都必須有一個獨立的驗證步驟這個步驟不能由執(zhí)行任務(wù)的同一個智能體實例來做??梢該Q一個提示詞、換一個模型實例甚至用純規(guī)則腳本去檢查。比如測試通過這個結(jié)論必須由實際運(yùn)行測試命令的退出碼來確認(rèn)而不是智能體自己說通過就通過。這個執(zhí)行與驗證分離的原則是我認(rèn)為長程智能體工程化里最重要的一條。4.2 回滾機(jī)制沒有它就別上生產(chǎn)長程任務(wù)一定會出錯問題不是會不會錯而是錯了之后能不能干凈地退回去。我的做法是每個子任務(wù)開始前對將要修改的文件做一次快照可以用 git stash 或者簡單的文件備份。如果子任務(wù)驗證失敗直接恢復(fù)到快照狀態(tài)然后把這個失敗記錄到狀態(tài)里讓智能體換一種思路重試。這里有個細(xì)節(jié)回滾要回滾到子任務(wù)級別而不是整個任務(wù)級別。如果跑了二十步第十九步失敗了你不想把前十八步的成果全扔掉。所以快照的粒度要細(xì)每個子任務(wù)一個快照。我一開始圖省事只在任務(wù)開始時做一次快照結(jié)果一次失敗就得從頭再來浪費了大量時間和 token。后來改成子任務(wù)級快照效率提升非常明顯。4.3 長程任務(wù)里的注意力漂移跑得越久智能體越容易偏離最初的目標(biāo)。比如本來任務(wù)是統(tǒng)一日志規(guī)范跑到中途它可能開始順手重構(gòu)無關(guān)的函數(shù)甚至去修一些它自己發(fā)現(xiàn)的小問題。這種注意力漂移在長程任務(wù)里非常普遍而且很難通過提示詞完全消除。我的緩解策略是定期目標(biāo)對齊。每隔若干個輪次強(qiáng)制智能體重新讀一遍最初的任務(wù)目標(biāo)并回答我當(dāng)前在做的事和最初目標(biāo)的關(guān)系是什么。如果它答不上來或者關(guān)系很牽強(qiáng)就說明漂移了需要拉回來。這個檢查看起來很簡單但效果出奇地好。另外我會在狀態(tài)里維護(hù)一個范圍邊界字段明確列出本次任務(wù)不做什么比如不重構(gòu)無關(guān)模塊不修改公共接口每次決策前都檢查一遍能擋掉大部分漂移行為。5. 從能跑到好用幾個提升穩(wěn)定性的工程細(xì)節(jié)5.1 提示詞里的負(fù)面清單比正面指令更重要搭智能體時大家習(xí)慣寫一堆你應(yīng)該怎么做的正面指令。但在長程場景里明確不要做什么往往更有效。因為長程任務(wù)里智能體有大量自主決策空間正面指令覆蓋不全而負(fù)面清單能精準(zhǔn)堵住那些已知的危險行為。我的負(fù)面清單里常年躺著這幾條不要修改測試文件來讓測試通過、不要刪除任何文件除非明確要求、不要引入新的第三方依賴、不要修改配置文件里的密鑰相關(guān)字段、不要在沒跑測試的情況下聲稱完成。這幾條都是我用真金白銀的教訓(xùn)換來的每一條背后都有一次翻車經(jīng)歷。5.2 日志與可觀測性出問題時你得能復(fù)盤長程任務(wù)跑掛了最痛苦的是不知道它掛在哪一步、為什么掛。所以從第一天起就要把日志做扎實。我的日志分三層操作日志每次工具調(diào)用的輸入輸出、決策日志每次關(guān)鍵選擇的理由、狀態(tài)日志每輪結(jié)束時的狀態(tài)快照。三層日志配合起來基本能還原出完整的執(zhí)行軌跡。日志的存儲也有講究不要只存文本最好結(jié)構(gòu)化存儲JSON 就行方便后續(xù)用腳本分析。我后來寫了個小腳本專門統(tǒng)計哪類子任務(wù)失敗率最高哪種錯誤最常觸發(fā)回滾這些統(tǒng)計反過來指導(dǎo)我優(yōu)化提示詞和工具設(shè)計形成了一個正向循環(huán)。5.3 人類介入的最佳時機(jī)全自動的長程智能體聽起來很美但實際用下來在關(guān)鍵節(jié)點讓人類介入反而能大幅提升整體效率。我的經(jīng)驗是在這幾個時機(jī)一定要停下來等人任務(wù)規(guī)劃完成后、每個里程碑完成時、連續(xù)兩次重試失敗后、以及任何涉及刪除或大范圍修改的操作前。這些介入點不是隨便定的。規(guī)劃后介入是為了校準(zhǔn)方向里程碑介入是為了確認(rèn)階段成果連續(xù)失敗介入是因為智能體很可能陷入了死循環(huán)刪除類操作介入是因為這類操作不可逆。把這幾個點卡住剩下的執(zhí)行過程就可以放心讓它自己跑。我現(xiàn)在的用法是一個中等復(fù)雜度的維護(hù)任務(wù)人類大概介入三到五次每次花一兩分鐘確認(rèn)整體效率比全程盯著高得多。6. 一個完整的維護(hù)任務(wù)是怎么跑完的6.1 任務(wù)接收與倉庫理解階段假設(shè)現(xiàn)在丟給智能體一個任務(wù)把項目里所有用 print 打日志的地方換成統(tǒng)一的日志庫調(diào)用。第一步不是改代碼而是理解倉庫。智能體會先看目錄結(jié)構(gòu)識別出這是個什么類型的項目Python 還是 Java有沒有現(xiàn)成的日志封裝然后搜索所有 print 調(diào)用點統(tǒng)計數(shù)量和分布。這個階段的關(guān)鍵是不要急著下結(jié)論。我見過智能體搜到幾個 print 就以為找全了結(jié)果漏掉了字符串拼接、f-string 里的 print 等變體。正確的做法是多種搜索模式交叉驗證比如同時搜print(、print (、print(帶不同引號的情況最后取并集。這個階段產(chǎn)出的是一份待修改清單清單里每一項都要有文件路徑和行號。6.2 分批執(zhí)行與增量驗證拿到清單后不要一次性全改。我的做法是按文件分批每批改完立刻跑一次測試和靜態(tài)檢查。為什么分批因為如果一次性改五十個文件測試掛了根本不知道是哪個改動引起的。分批之后問題范圍被限制在一批之內(nèi)排查成本大幅降低。每批的執(zhí)行流程是讀取文件、定位 print 調(diào)用、替換成日志調(diào)用、保存、跑測試、跑 lint。任何一步失敗就回滾這一批記錄失敗原因然后決定是重試還是跳過。這里有個細(xì)節(jié)替換時要保留原有的日志級別語義比如原本是錯誤信息的 print應(yīng)該替換成 error 級別而不是無腦用 info。這個語義判斷需要智能體讀上下文是考驗它理解能力的地方。6.3 收尾變更說明與遺留問題清單所有批次跑完后智能體要產(chǎn)出一份人類可讀的變更說明。這份說明不是簡單的改了哪些文件列表而應(yīng)該包含本次任務(wù)的目標(biāo)、實際完成的范圍、每個文件的改動摘要、測試結(jié)果、以及遺留問題。遺留問題特別重要比如某個文件里的 print 在異常處理分支里語義不明確未做替換建議人工確認(rèn)。這份說明是長程任務(wù)和單輪任務(wù)最大的區(qū)別之一。單輪任務(wù)改完就完了長程任務(wù)必須留下可追溯的記錄因為后續(xù)可能還有別的智能體或人類接著在這個基礎(chǔ)上工作。我現(xiàn)在的習(xí)慣是把這份說明直接寫進(jìn)倉庫的一個約定位置比如docs/agent-changes/目錄按時間戳命名形成一份完整的維護(hù)歷史。7. 我對長程智能體的一點個人判斷折騰了這么久我越來越覺得長程智能體的核心競爭力不在模型本身而在圍繞模型構(gòu)建的那套腳手架。狀態(tài)管理、任務(wù)分解、驗證分離、回滾機(jī)制、日志可觀測性這些東西聽起來都不酷但恰恰是決定一個智能體能不能真正長期干活的關(guān)鍵。模型能力會隨著版本迭代不斷提升但工程架構(gòu)的坑得靠一個個踩過去才能填平。如果你也想動手搭一個我的建議是從最小的場景開始先讓它穩(wěn)定地完成改一個文件并驗證這種單步任務(wù)再逐步拉長任務(wù)鏈條。每加一個能力就配一套對應(yīng)的驗證和回滾機(jī)制。別一上來就追求全自動半自動加人類關(guān)鍵節(jié)點介入反而是現(xiàn)階段最務(wù)實的方案。等你的智能體能穩(wěn)定跑完幾十步不出大錯再考慮放開手腳。這個過程急不得但每走通一步你都會對智能體到底能干什么有更清醒的認(rèn)識。