戰(zhàn):遺留系統(tǒng)AI Agent落地的七條反直覺(jué)法則)
1. 棕地 Agent 工程到底在解決什么問(wèn)題1.1 從“白地”到“棕地”一個(gè)被忽視的戰(zhàn)場(chǎng)過(guò)去兩年大部分關(guān)于 AI Agent 的討論都集中在一個(gè)理想化的場(chǎng)景里你有一個(gè)全新的項(xiàng)目干凈的代碼庫(kù)清晰的架構(gòu)然后你從零開(kāi)始搭建一個(gè) Agent 系統(tǒng)。這種場(chǎng)景我稱(chēng)之為“白地 Agent 工程”——綠地項(xiàng)目沒(méi)有歷史包袱想怎么設(shè)計(jì)就怎么設(shè)計(jì)。但現(xiàn)實(shí)是絕大多數(shù)開(kāi)發(fā)者面對(duì)的并不是白地。你手里有一個(gè)跑了五年的 Spring Boot 單體應(yīng)用或者一個(gè) Django 項(xiàng)目里面塞滿(mǎn)了各種歷史遺留的 service 層、工具類(lèi)、定時(shí)任務(wù)測(cè)試覆蓋率不到 30%文檔停留在三年前。這時(shí)候老板跟你說(shuō)“給咱們系統(tǒng)加個(gè) AI Agent 吧讓用戶(hù)能用自然語(yǔ)言查數(shù)據(jù)、觸發(fā)流程?!边@就是“棕地 Agent 工程”要解決的問(wèn)題。棕地Brownfield這個(gè)詞來(lái)自城市規(guī)劃指的是那些已經(jīng)被開(kāi)發(fā)過(guò)、可能存在污染或基礎(chǔ)設(shè)施老化的地塊。對(duì)應(yīng)到軟件領(lǐng)域就是那些已經(jīng)存在、有技術(shù)債務(wù)、但仍在承載核心業(yè)務(wù)的代碼庫(kù)。你不可能推倒重來(lái)只能在現(xiàn)有基礎(chǔ)上做增量改造。我過(guò)去一年在三個(gè)不同規(guī)模的遺留系統(tǒng)上落地過(guò) AI Agent踩過(guò)的坑比想象中多得多。這篇文章不講那些“從零搭建 Agent”的教程而是聚焦一個(gè)更現(xiàn)實(shí)的問(wèn)題當(dāng)你面對(duì)一個(gè)幾十萬(wàn)行代碼、依賴(lài)關(guān)系錯(cuò)綜復(fù)雜的遺留系統(tǒng)時(shí)怎么讓 AI Agent 真正跑起來(lái)而不是變成一個(gè)演示完就沒(méi)人用的玩具。1.2 棕地 Agent 工程的三個(gè)核心約束在動(dòng)手之前你必須先認(rèn)清棕地場(chǎng)景的三個(gè)硬約束這決定了你后續(xù)所有技術(shù)選型和架構(gòu)決策。約束一不能大改現(xiàn)有代碼結(jié)構(gòu)。遺留系統(tǒng)的代碼可能很爛但它能跑而且在承載真實(shí)業(yè)務(wù)。你沒(méi)有權(quán)限也沒(méi)有時(shí)間去做大規(guī)模重構(gòu)。Agent 必須作為一個(gè)“外掛層”存在通過(guò)接口調(diào)用現(xiàn)有能力而不是侵入式地修改核心邏輯。約束二現(xiàn)有接口的語(yǔ)義不清晰。白地項(xiàng)目里你設(shè)計(jì)接口時(shí)會(huì)考慮語(yǔ)義化和可組合性。但遺留系統(tǒng)里的接口往往是“歷史堆積”的產(chǎn)物——一個(gè)processData()方法可能做了七八件事參數(shù)是一個(gè)巨大的 DTO返回值是一個(gè) Map。Agent 要調(diào)用這些接口首先得理解它們到底在干什么。約束三沒(méi)有完整的測(cè)試覆蓋。這是最要命的一點(diǎn)。你想改任何東西都擔(dān)心會(huì)不會(huì)把某個(gè)隱藏的業(yè)務(wù)邏輯搞崩。特征測(cè)試Characterization Test在這里不是可選項(xiàng)而是必選項(xiàng)。你得先給現(xiàn)有系統(tǒng)“拍照”記錄它當(dāng)前的行為才能在后續(xù)改造中有底氣。理解了這三個(gè)約束你就能明白為什么棕地 Agent 工程需要一套完全不同的方法論。下面我拆成七個(gè)反直覺(jué)的法則來(lái)講每一條都是我在實(shí)際項(xiàng)目中驗(yàn)證過(guò)的。2. 法則一先寫(xiě)特征測(cè)試再碰 Agent 代碼2.1 為什么特征測(cè)試是棕地 Agent 的地基很多人拿到一個(gè)遺留系統(tǒng)第一反應(yīng)是“我先搭個(gè) Agent 框架跑通一個(gè) demo 再說(shuō)”。這個(gè)思路在白地項(xiàng)目里沒(méi)問(wèn)題但在棕地場(chǎng)景里是致命的。原因很簡(jiǎn)單Agent 要調(diào)用現(xiàn)有系統(tǒng)的接口而你對(duì)這些接口的實(shí)際行為可能并不完全了解。文檔寫(xiě)的是 A代碼實(shí)現(xiàn)是 B線(xiàn)上跑出來(lái)的結(jié)果是 C。如果你不先把現(xiàn)有行為固化下來(lái)后面 Agent 出了 bug你根本分不清是 Agent 的邏輯問(wèn)題還是底層接口本來(lái)就有坑。特征測(cè)試的核心思路是不關(guān)心代碼“應(yīng)該”怎么跑只記錄它“實(shí)際”怎么跑。你給一個(gè)輸入記錄輸出把這個(gè)輸入輸出對(duì)作為測(cè)試用例。這些測(cè)試不驗(yàn)證業(yè)務(wù)正確性只驗(yàn)證行為一致性。后續(xù)你改任何東西只要這些測(cè)試還過(guò)就說(shuō)明你沒(méi)有破壞現(xiàn)有行為。2.2 特征測(cè)試的實(shí)操步驟具體怎么做我以一個(gè)有十年歷史的 Java 后端系統(tǒng)為例。第一步找出 Agent 將要調(diào)用的核心接口。不要貪多先圈定 Agent 需要用到的那 10 到 20 個(gè)方法。這些方法通常集中在幾個(gè) service 類(lèi)里比如訂單查詢(xún)、用戶(hù)信息獲取、庫(kù)存檢查等。第二步為每個(gè)方法構(gòu)造典型輸入。這里有個(gè)技巧從生產(chǎn)日志里撈真實(shí)請(qǐng)求。把過(guò)去一周的接口調(diào)用日志導(dǎo)出來(lái)按方法名分組每個(gè)方法取 50 到 100 個(gè)真實(shí)輸入樣本。這比你自己拍腦袋構(gòu)造的輸入靠譜得多。第三步記錄輸出并生成測(cè)試。寫(xiě)一個(gè)簡(jiǎn)單的腳本遍歷這些輸入調(diào)用方法把輸入和輸出序列化成 JSON然后自動(dòng)生成 JUnit 測(cè)試。下面是一個(gè)簡(jiǎn)化的示例// 自動(dòng)生成的特征測(cè)試骨架 Test public void characterize_queryOrder() { // 輸入來(lái)自生產(chǎn)日志樣本 OrderQueryRequest request new OrderQueryRequest(); request.setOrderId(ORD-2023-001); request.setUserId(12345L); // 調(diào)用實(shí)際方法 OrderResult result orderService.queryOrder(request); // 斷言實(shí)際輸出首次運(yùn)行時(shí)從實(shí)際結(jié)果復(fù)制 assertEquals(PAID, result.getStatus()); assertEquals(3, result.getItems().size()); assertEquals(new BigDecimal(299.00), result.getTotalAmount()); }第四步跑通所有特征測(cè)試確保全綠。這時(shí)候你就有了一個(gè)“行為基線(xiàn)”。后續(xù)任何改動(dòng)只要這些測(cè)試還過(guò)就說(shuō)明底層行為沒(méi)變。注意特征測(cè)試不是單元測(cè)試不要試圖去 mock 依賴(lài)。讓它跑真實(shí)邏輯哪怕慢一點(diǎn)。你追求的是行為快照的準(zhǔn)確性不是測(cè)試執(zhí)行速度。2.3 特征測(cè)試的常見(jiàn)坑第一個(gè)坑是數(shù)據(jù)依賴(lài)。遺留系統(tǒng)的接口往往依賴(lài)數(shù)據(jù)庫(kù)里的特定數(shù)據(jù)狀態(tài)。你今天跑測(cè)試是綠的明天數(shù)據(jù)庫(kù)被人改了一條記錄測(cè)試就紅了。解決辦法是給測(cè)試準(zhǔn)備獨(dú)立的數(shù)據(jù)集或者在測(cè)試前用腳本重置數(shù)據(jù)。第二個(gè)坑是時(shí)間依賴(lài)。很多接口的行為跟當(dāng)前時(shí)間有關(guān)比如“查詢(xún)最近 30 天訂單”。這種測(cè)試你沒(méi)法直接斷言輸出需要把時(shí)間參數(shù)化或者用固定時(shí)鐘注入。第三個(gè)坑是外部服務(wù)依賴(lài)。如果接口調(diào)用了第三方支付、短信等服務(wù)特征測(cè)試會(huì)變得不穩(wěn)定。我的做法是先用擋板Stub把外部依賴(lài)隔離掉只測(cè)試本地邏輯的行為。3. 法則二Agent 不是替代者是翻譯層3.1 重新理解 Agent 在遺留系統(tǒng)中的角色很多團(tuán)隊(duì)在引入 Agent 時(shí)潛意識(shí)里把它當(dāng)成一個(gè)“更聰明的接口”。用戶(hù)說(shuō)一句話(huà)Agent 理解意圖然后調(diào)用對(duì)應(yīng)接口返回結(jié)果。這個(gè)理解不算錯(cuò)但太淺了。在棕地場(chǎng)景里Agent 的真正價(jià)值是翻譯層。它翻譯的不是語(yǔ)言而是語(yǔ)義鴻溝。遺留系統(tǒng)的接口是給程序員用的參數(shù)是技術(shù)化的返回值是結(jié)構(gòu)化的。但用戶(hù)的需求是業(yè)務(wù)化的、模糊的、帶有上下文依賴(lài)的。Agent 要做的是把“幫我看看上個(gè)月那個(gè)大單子到哪了”翻譯成queryOrder(orderId..., userId...)這樣的技術(shù)調(diào)用。這個(gè)翻譯過(guò)程涉及三個(gè)層次的理解意圖識(shí)別、參數(shù)映射、結(jié)果解釋。意圖識(shí)別是基礎(chǔ)參數(shù)映射是難點(diǎn)結(jié)果解釋是加分項(xiàng)。3.2 參數(shù)映射棕地 Agent 最臟最累的活參數(shù)映射為什么難因?yàn)檫z留系統(tǒng)的接口參數(shù)往往不是為自然語(yǔ)言設(shè)計(jì)的。我見(jiàn)過(guò)一個(gè)查詢(xún)接口需要傳一個(gè)sceneCode參數(shù)取值是01、02、03分別代表不同的業(yè)務(wù)場(chǎng)景。用戶(hù)說(shuō)“查一下我的訂單”Agent 怎么知道該傳哪個(gè) sceneCode解決辦法是建一個(gè)語(yǔ)義映射表。把接口參數(shù)的業(yè)務(wù)含義顯式地寫(xiě)出來(lái)作為 Agent 的知識(shí)庫(kù)。這個(gè)表不需要很復(fù)雜一個(gè) YAML 文件就夠了queryOrder: description: 查詢(xún)訂單詳情 parameters: sceneCode: type: enum values: 01: 普通商城訂單 02: 團(tuán)購(gòu)訂單 03: 預(yù)售訂單 default: 01 orderId: type: string description: 訂單編號(hào)通常以 ORD- 開(kāi)頭 userId: type: long description: 用戶(hù)ID從當(dāng)前登錄態(tài)獲取有了這個(gè)映射表Agent 在解析用戶(hù)意圖時(shí)就有了依據(jù)。用戶(hù)說(shuō)“查一下我的團(tuán)購(gòu)訂單”Agent 就能推斷出 sceneCode 應(yīng)該是 02。3.3 結(jié)果解釋讓 Agent 說(shuō)人話(huà)遺留系統(tǒng)的返回值往往是給程序看的不是給人看的。一個(gè)訂單查詢(xún)可能返回幾十個(gè)字段包含各種狀態(tài)碼、時(shí)間戳、內(nèi)部標(biāo)識(shí)。用戶(hù)只想知道“我的訂單到哪了”。Agent 的結(jié)果解釋層要做的是從結(jié)構(gòu)化數(shù)據(jù)中提取關(guān)鍵信息用自然語(yǔ)言組織成用戶(hù)能理解的回答。這里有個(gè)原則寧可少說(shuō)不要亂說(shuō)。如果某個(gè)字段的含義不確定寧可不提也不要瞎猜。我通常會(huì)在映射表里加一個(gè)responseTemplate字段定義每種意圖的返回格式queryOrder: responseTemplate: | 您的訂單 {{orderId}} 當(dāng)前狀態(tài)是{{statusDesc}}。 下單時(shí)間{{createTime}} 訂單金額{{totalAmount}} 元 包含 {{itemCount}} 件商品。這樣 Agent 只需要做字段填充不需要自己組織語(yǔ)言既保證了準(zhǔn)確性又降低了幻覺(jué)風(fēng)險(xiǎn)。4. 法則三遷移盲區(qū)比技術(shù)債務(wù)更危險(xiǎn)4.1 什么是遷移盲區(qū)遷移盲區(qū)Migration Blind Spot是我自己造的一個(gè)詞指的是那些在遺留系統(tǒng)中“看起來(lái)能用但實(shí)際上已經(jīng)腐爛”的部分。它們不在你的技術(shù)債務(wù)清單上因?yàn)闆](méi)人覺(jué)得它們是問(wèn)題直到 Agent 調(diào)用它們時(shí)炸了。舉個(gè)例子。你有一個(gè)用戶(hù)信息查詢(xún)接口返回用戶(hù)的基本信息。這個(gè)接口跑了五年一直沒(méi)問(wèn)題。但當(dāng)你讓 Agent 去調(diào)用它時(shí)發(fā)現(xiàn)返回的userLevel字段有時(shí)候是數(shù)字有時(shí)候是字符串有時(shí)候是 null。為什么因?yàn)槲迥昵坝袀€(gè)實(shí)習(xí)生寫(xiě)了一段代碼在某些分支下直接返回了原始數(shù)據(jù)庫(kù)值沒(méi)有做類(lèi)型轉(zhuǎn)換。這個(gè) bug 一直存在但因?yàn)榍岸俗隽思嫒萏幚頉](méi)人發(fā)現(xiàn)?,F(xiàn)在 Agent 直接消費(fèi)這個(gè)接口就踩坑了。4.2 如何發(fā)現(xiàn)遷移盲區(qū)發(fā)現(xiàn)遷移盲區(qū)的方法論是用 Agent 的調(diào)用方式去壓測(cè)現(xiàn)有接口。具體來(lái)說(shuō)做三件事。第一邊界值測(cè)試。把每個(gè)參數(shù)推到極端值——空字符串、超長(zhǎng)字符串、負(fù)數(shù)、零、最大整數(shù)——看接口怎么反應(yīng)。很多遺留接口在邊界條件下會(huì)返回意料之外的結(jié)果。第二并發(fā)測(cè)試。Agent 的調(diào)用模式跟人類(lèi)用戶(hù)不同它可能在短時(shí)間內(nèi)發(fā)起大量請(qǐng)求。遺留系統(tǒng)里那些依賴(lài)單例狀態(tài)、靜態(tài)變量、線(xiàn)程不安全集合的代碼在并發(fā)場(chǎng)景下會(huì)暴露問(wèn)題。第三時(shí)序測(cè)試。Agent 可能會(huì)在非工作時(shí)間調(diào)用接口或者以人類(lèi)不會(huì)采用的順序調(diào)用接口。比如先查訂單再查用戶(hù)而正常流程是先查用戶(hù)再查訂單。這種時(shí)序變化可能觸發(fā)隱藏的狀態(tài)依賴(lài)問(wèn)題。4.3 遷移盲區(qū)的處理策略發(fā)現(xiàn)遷移盲區(qū)后你有三個(gè)選擇修復(fù)、繞過(guò)、或者隔離。修復(fù)是最徹底的但成本最高。如果這個(gè)盲區(qū)影響面大而且修復(fù)方案清晰那就修。但更多時(shí)候我建議繞過(guò)。在 Agent 層加一個(gè)適配器把臟數(shù)據(jù)洗干凈再返回給 Agent。這樣既不影響現(xiàn)有系統(tǒng)又能讓 Agent 正常工作。隔離是最保守的策略。如果某個(gè)接口的盲區(qū)太多修不動(dòng)也繞不過(guò)那就干脆不讓 Agent 調(diào)用它。在映射表里把這個(gè)接口標(biāo)記為deprecated讓 Agent 走別的路徑。我的經(jīng)驗(yàn)是遷移盲區(qū)的處理優(yōu)先級(jí)應(yīng)該是“繞過(guò) 隔離 修復(fù)”。因?yàn)槟愕氖滓繕?biāo)是讓 Agent 跑起來(lái)而不是借這個(gè)機(jī)會(huì)重構(gòu)遺留系統(tǒng)。重構(gòu)的事等 Agent 穩(wěn)定運(yùn)行三個(gè)月后再考慮。5. 法則四Agent 的并發(fā)能力取決于最慢的那個(gè)接口5.1 為什么 Agent 并發(fā)是個(gè)偽命題“AI Agent 怎么扛并發(fā)”是最近被問(wèn)得最多的問(wèn)題之一。很多人的思路是Agent 框架本身要支持高并發(fā)要用異步、要用協(xié)程、要用消息隊(duì)列。這個(gè)思路在白地項(xiàng)目里成立但在棕地場(chǎng)景里Agent 的并發(fā)能力根本不取決于 Agent 框架而取決于它調(diào)用的最慢的那個(gè)遺留接口。我做過(guò)一個(gè)測(cè)試Agent 框架本身用異步 IO單機(jī)能扛 5000 QPS。但它調(diào)用的訂單查詢(xún)接口底層是一個(gè)沒(méi)有索引的數(shù)據(jù)庫(kù)查詢(xún)平均響應(yīng)時(shí)間 800ms并發(fā)超過(guò) 50 就開(kāi)始排隊(duì)。結(jié)果整個(gè) Agent 系統(tǒng)的實(shí)際吞吐量被卡在 50 QPS 左右。5.2 棕地 Agent 的并發(fā)優(yōu)化策略既然瓶頸在遺留接口優(yōu)化就要從接口層入手。但你不能直接去改數(shù)據(jù)庫(kù)索引或者重寫(xiě)查詢(xún)邏輯那超出了 Agent 項(xiàng)目的范圍。你能做的是在 Agent 層做請(qǐng)求合并和結(jié)果緩存。請(qǐng)求合并的思路是如果多個(gè) Agent 請(qǐng)求在短時(shí)間內(nèi)查詢(xún)同一個(gè)數(shù)據(jù)把它們合并成一個(gè)底層調(diào)用。比如用戶(hù) A 和用戶(hù) B 同時(shí)查同一個(gè)訂單Agent 層只發(fā)一次查詢(xún)請(qǐng)求然后把結(jié)果分發(fā)給兩個(gè)請(qǐng)求。結(jié)果緩存的思路更直接對(duì)于讀多寫(xiě)少的數(shù)據(jù)在 Agent 層加一層緩存。緩存的有效期不需要很長(zhǎng)5 到 10 秒就夠了。這能擋住大部分重復(fù)請(qǐng)求。下面是一個(gè)簡(jiǎn)單的請(qǐng)求合并實(shí)現(xiàn)思路class RequestCoalescer: def __init__(self): self.pending {} async def query(self, key, fetch_func): if key in self.pending: return await self.pending[key] future asyncio.ensure_future(fetch_func()) self.pending[key] future try: result await future return result finally: del self.pending[key]這段代碼的核心邏輯是如果同一個(gè) key 的請(qǐng)求已經(jīng)在處理中就復(fù)用那個(gè) future而不是發(fā)起新的調(diào)用。5.3 并發(fā)場(chǎng)景下的降級(jí)策略即使做了合并和緩存遺留接口在高峰期仍然可能扛不住。這時(shí)候你需要一個(gè)降級(jí)策略當(dāng)?shù)讓咏涌陧憫?yīng)時(shí)間超過(guò)閾值時(shí)Agent 自動(dòng)切換到“簡(jiǎn)化模式”。簡(jiǎn)化模式的意思是不查完整數(shù)據(jù)只返回最核心的信息。比如訂單查詢(xún)正常模式返回訂單詳情、商品列表、物流信息簡(jiǎn)化模式只返回訂單狀態(tài)。這樣底層接口的負(fù)載能降低一個(gè)數(shù)量級(jí)。降級(jí)策略的觸發(fā)條件需要根據(jù)實(shí)際壓測(cè)結(jié)果來(lái)定。我的經(jīng)驗(yàn)值是當(dāng) P99 響應(yīng)時(shí)間超過(guò) 2 秒或者錯(cuò)誤率超過(guò) 5%就觸發(fā)降級(jí)。6. 法則五Agent 的提示詞要寫(xiě)進(jìn)代碼倉(cāng)庫(kù)6.1 提示詞不是配置是代碼很多團(tuán)隊(duì)把 Agent 的提示詞當(dāng)成配置文件放在數(shù)據(jù)庫(kù)里或者配置中心運(yùn)行時(shí)動(dòng)態(tài)加載。這個(gè)做法在白地項(xiàng)目里可以但在棕地場(chǎng)景里會(huì)帶來(lái)嚴(yán)重問(wèn)題。原因很簡(jiǎn)單棕地 Agent 的提示詞跟遺留系統(tǒng)的接口語(yǔ)義強(qiáng)耦合。接口改了提示詞必須跟著改。如果提示詞在配置中心代碼在 Git 倉(cāng)庫(kù)兩者的版本就對(duì)不上了。你改了一個(gè)接口的參數(shù)名忘了同步更新配置中心的提示詞Agent 就開(kāi)始胡言亂語(yǔ)。我的做法是提示詞必須跟代碼在同一個(gè)倉(cāng)庫(kù)同一個(gè)分支同一個(gè)提交里。提示詞文件用 Markdown 或者 YAML 格式放在代碼目錄下跟調(diào)用它的代碼放在一起。6.2 提示詞的版本管理策略提示詞進(jìn)倉(cāng)庫(kù)后版本管理就變得很重要。我通常采用三層結(jié)構(gòu)第一層是基礎(chǔ)提示詞定義 Agent 的角色、能力邊界、輸出格式。這部分相對(duì)穩(wěn)定變更頻率低。第二層是接口提示詞每個(gè)遺留接口對(duì)應(yīng)一段提示詞描述這個(gè)接口的功能、參數(shù)、返回值。這部分跟接口代碼強(qiáng)綁定接口改了就改它。第三層是場(chǎng)景提示詞針對(duì)特定業(yè)務(wù)場(chǎng)景的補(bǔ)充說(shuō)明。比如“查詢(xún)訂單時(shí)如果用戶(hù)沒(méi)有指定訂單號(hào)優(yōu)先查詢(xún)最近一筆訂單”。這部分最靈活可以頻繁調(diào)整。三層提示詞在運(yùn)行時(shí)拼接成完整的 prompt。這樣既保證了穩(wěn)定性又保留了靈活性。6.3 提示詞的測(cè)試與回歸提示詞進(jìn)倉(cāng)庫(kù)的另一個(gè)好處是可以做回歸測(cè)試。你可以像寫(xiě)單元測(cè)試一樣為提示詞寫(xiě)測(cè)試用例給定一個(gè)用戶(hù)輸入斷言 Agent 的輸出包含某些關(guān)鍵信息。def test_query_order_prompt(): user_input 幫我查一下上個(gè)月那個(gè)大單子 context {userId: 12345} result agent.process(user_input, context) assert 訂單 in result assert ORD- in result # 應(yīng)該包含訂單號(hào) assert 元 in result # 應(yīng)該包含金額這些測(cè)試跑在 CI 里每次改提示詞都會(huì)觸發(fā)。如果某個(gè)改動(dòng)導(dǎo)致測(cè)試失敗你立刻就知道有問(wèn)題。7. 法則六不要追求全自動(dòng)人機(jī)協(xié)同才是終點(diǎn)7.1 全自動(dòng) Agent 的幻覺(jué)陷阱很多團(tuán)隊(duì)對(duì) Agent 的期望是“全自動(dòng)”——用戶(hù)說(shuō)一句話(huà)Agent 從頭到尾搞定不需要人工介入。這個(gè)目標(biāo)在演示環(huán)境里很容易實(shí)現(xiàn)但在生產(chǎn)環(huán)境里尤其是棕地場(chǎng)景里幾乎不可能。原因在于遺留系統(tǒng)的不確定性。接口可能返回臟數(shù)據(jù)業(yè)務(wù)規(guī)則可能有例外情況用戶(hù)輸入可能模糊不清。Agent 在這些情況下如果強(qiáng)行“自動(dòng)處理”結(jié)果往往是災(zāi)難性的。我見(jiàn)過(guò)一個(gè)案例Agent 自動(dòng)幫用戶(hù)提交了一個(gè)退款申請(qǐng)因?yàn)橛脩?hù)說(shuō)“這個(gè)訂單我不想要了”。但用戶(hù)的實(shí)際意思是“我想修改訂單地址”只是表達(dá)得比較隨意。結(jié)果退款流程走了一半用戶(hù)收到退款通知才發(fā)現(xiàn)問(wèn)題。7.2 人機(jī)協(xié)同的三種模式在棕地 Agent 工程里我推薦三種人機(jī)協(xié)同模式根據(jù)場(chǎng)景風(fēng)險(xiǎn)等級(jí)選擇。模式一確認(rèn)式協(xié)同。Agent 完成意圖理解和參數(shù)映射后把結(jié)果展示給用戶(hù)確認(rèn)用戶(hù)點(diǎn)“確認(rèn)”后才執(zhí)行。這種模式適合高風(fēng)險(xiǎn)操作比如退款、刪除、修改關(guān)鍵數(shù)據(jù)。模式二建議式協(xié)同。Agent 給出建議但由人工決定是否采納。比如 Agent 說(shuō)“我建議查詢(xún) sceneCode02 的團(tuán)購(gòu)訂單是否正確”用戶(hù)可以選擇“是”或者手動(dòng)指定其他值。這種模式適合中等風(fēng)險(xiǎn)操作。模式三靜默式協(xié)同。Agent 自動(dòng)執(zhí)行但記錄完整日志人工可以事后審計(jì)。這種模式適合低風(fēng)險(xiǎn)操作比如查詢(xún)類(lèi)請(qǐng)求。7.3 協(xié)同模式的選擇標(biāo)準(zhǔn)怎么判斷一個(gè)操作該用哪種模式我通??慈齻€(gè)維度可逆性、影響范圍、用戶(hù)預(yù)期??赡嫘圆僮髂懿荒艹蜂N(xiāo)查詢(xún)可以退款不行。影響范圍操作影響一個(gè)用戶(hù)還是所有用戶(hù)影響越大越需要確認(rèn)。用戶(hù)預(yù)期用戶(hù)是否期望這個(gè)操作自動(dòng)完成如果用戶(hù)說(shuō)“幫我查一下”他期望立刻看到結(jié)果如果用戶(hù)說(shuō)“幫我處理一下”他可能期望有人工介入。把這三個(gè)維度畫(huà)成一個(gè)矩陣就能快速判斷該用哪種協(xié)同模式。8. 法則七Agent 的日志比 Agent 本身更重要8.1 為什么棕地 Agent 需要超詳細(xì)日志在白地項(xiàng)目里Agent 出問(wèn)題了你可以看代碼、看測(cè)試、看監(jiān)控。但在棕地場(chǎng)景里Agent 出問(wèn)題往往是因?yàn)榈讓舆z留系統(tǒng)的某個(gè)隱藏行為。如果你沒(méi)有詳細(xì)的日志根本無(wú)從排查。我要求棕地 Agent 的日志必須包含以下信息用戶(hù)原始輸入、Agent 解析后的意圖、映射到的接口和參數(shù)、接口的實(shí)際調(diào)用時(shí)間和返回值、Agent 生成的最終回復(fù)。這五段信息缺一不可。8.2 日志的結(jié)構(gòu)化與可查詢(xún)?nèi)罩静荒苁羌兾谋颈仨毷墙Y(jié)構(gòu)化的 JSON。每個(gè)字段都有明確的含義方便后續(xù)查詢(xún)和分析。{ traceId: abc-123, timestamp: 2024-01-15T10:30:00Z, userInput: 幫我查一下上個(gè)月那個(gè)大單子, parsedIntent: queryOrder, mappedParams: { sceneCode: 01, userId: 12345, timeRange: last_month }, apiCall: { endpoint: /api/order/query, durationMs: 850, responseCode: 200 }, agentResponse: 您的訂單 ORD-2023-1234 當(dāng)前狀態(tài)是已發(fā)貨... }有了這樣的日志排查問(wèn)題就簡(jiǎn)單了。用戶(hù)說(shuō)“Agent 回答錯(cuò)了”你拿 traceId 一查立刻能看到是意圖解析錯(cuò)了還是參數(shù)映射錯(cuò)了還是接口返回了臟數(shù)據(jù)。8.3 日志驅(qū)動(dòng)的持續(xù)優(yōu)化日志不僅是排查工具還是優(yōu)化依據(jù)。我每周會(huì)做一次日志分析看三件事第一意圖解析失敗率。哪些用戶(hù)輸入 Agent 無(wú)法正確理解把這些輸入收集起來(lái)補(bǔ)充到提示詞的示例里。第二參數(shù)映射錯(cuò)誤率。哪些參數(shù)經(jīng)常映射錯(cuò)檢查映射表是不是寫(xiě)得不清楚或者接口本身有歧義。第三接口異常率。哪些接口經(jīng)常返回錯(cuò)誤或超時(shí)這些接口就是遷移盲區(qū)的候選需要重點(diǎn)處理。這個(gè)日志驅(qū)動(dòng)的優(yōu)化循環(huán)是棕地 Agent 工程能持續(xù)迭代的關(guān)鍵。沒(méi)有日志你就是在盲人摸象。9. 棕地 Agent 工程的工具選型與團(tuán)隊(duì)配置9.1 工具選型的核心原則棕地 Agent 工程的工具選型跟白地項(xiàng)目完全不同。白地項(xiàng)目可以追求最新最酷的框架棕地項(xiàng)目必須追求穩(wěn)定、可觀測(cè)、易集成。Agent 框架方面我傾向于選擇那些對(duì)遺留系統(tǒng)友好的方案。比如 LangChain 和 LangGraph 提供了豐富的工具集成能力可以很方便地包裝現(xiàn)有 HTTP 接口。Spring AI 對(duì)于 Java 技術(shù)棧的團(tuán)隊(duì)來(lái)說(shuō)是個(gè)不錯(cuò)的選擇它能直接復(fù)用 Spring 生態(tài)的依賴(lài)注入和配置管理。如果你追求極致的性能和并發(fā)Rust 生態(tài)的 Agent 框架也值得考慮但前提是你的團(tuán)隊(duì)有 Rust 經(jīng)驗(yàn)。否則學(xué)習(xí)成本會(huì)拖慢整個(gè)項(xiàng)目。9.2 團(tuán)隊(duì)配置建議棕地 Agent 工程不需要很大的團(tuán)隊(duì)但需要角色搭配合理。我的建議配置是一個(gè)架構(gòu)師負(fù)責(zé)整體方案設(shè)計(jì)和遷移盲區(qū)的判斷。這個(gè)人必須對(duì)遺留系統(tǒng)有深入了解知道哪些地方能碰、哪些地方不能碰。一個(gè)后端工程師負(fù)責(zé) Agent 層的開(kāi)發(fā)和接口適配。這個(gè)人要熟悉 Agent 框架同時(shí)也要能讀懂遺留代碼。一個(gè)測(cè)試工程師負(fù)責(zé)特征測(cè)試和回歸測(cè)試。這個(gè)人要有耐心愿意寫(xiě)大量的行為快照測(cè)試。如果條件允許再加一個(gè)業(yè)務(wù)分析師負(fù)責(zé)梳理接口語(yǔ)義和編寫(xiě)映射表。這個(gè)角色往往被忽視但在棕地場(chǎng)景里非常重要。9.3 項(xiàng)目推進(jìn)的節(jié)奏棕地 Agent 工程不能搞“大爆炸”式上線(xiàn)。我的建議是分三個(gè)階段推進(jìn)。第一階段是單點(diǎn)驗(yàn)證選一個(gè)最簡(jiǎn)單的查詢(xún)場(chǎng)景把 Agent 跑通。這個(gè)階段的目標(biāo)不是功能完整而是驗(yàn)證技術(shù)路線(xiàn)可行。第二階段是場(chǎng)景擴(kuò)展把 Agent 的能力擴(kuò)展到 5 到 10 個(gè)核心場(chǎng)景。這個(gè)階段會(huì)暴露大量的遷移盲區(qū)和接口問(wèn)題是工作量最大的階段。第三階段是穩(wěn)定運(yùn)行重點(diǎn)做日志分析、性能優(yōu)化、降級(jí)策略。這個(gè)階段的目標(biāo)是讓 Agent 在生產(chǎn)環(huán)境里穩(wěn)定跑三個(gè)月以上。每個(gè)階段之間留出至少兩周的緩沖期用來(lái)處理意料之外的問(wèn)題。棕地項(xiàng)目的問(wèn)題總是比預(yù)想的多。10. 一些踩坑之后的個(gè)人體會(huì)棕地 Agent 工程最反直覺(jué)的一點(diǎn)是技術(shù)不是最大的障礙對(duì)遺留系統(tǒng)的理解才是。我見(jiàn)過(guò)太多團(tuán)隊(duì)Agent 框架玩得很溜但一碰到遺留系統(tǒng)的臟數(shù)據(jù)、隱藏邏輯、 undocumented 行為就束手無(wú)策。我的建議是在寫(xiě)第一行 Agent 代碼之前先花兩周時(shí)間做三件事讀遺留系統(tǒng)的核心代碼跑特征測(cè)試跟業(yè)務(wù)方聊接口的實(shí)際使用場(chǎng)景。這三件事做完你對(duì)系統(tǒng)的理解會(huì)超過(guò)過(guò)去半年的總和。另一個(gè)體會(huì)是不要試圖用 Agent 去解決遺留系統(tǒng)的所有問(wèn)題。Agent 是一個(gè)增量能力不是重構(gòu)工具。它的價(jià)值在于讓用戶(hù)用更自然的方式使用現(xiàn)有系統(tǒng)而不是讓現(xiàn)有系統(tǒng)變得更好。把這兩個(gè)目標(biāo)混在一起項(xiàng)目一定會(huì)失控。最后分享一個(gè)實(shí)用技巧在 Agent 的映射表里給每個(gè)接口加一個(gè)confidence字段表示你對(duì)這個(gè)接口語(yǔ)義理解的置信度。置信度低的接口Agent 在調(diào)用時(shí)自動(dòng)觸發(fā)人工確認(rèn)。這個(gè)簡(jiǎn)單的機(jī)制能擋住大部分因?yàn)榻涌诶斫忮e(cuò)誤導(dǎo)致的問(wèn)題。棕地 Agent 工程沒(méi)有標(biāo)準(zhǔn)答案每個(gè)遺留系統(tǒng)都是獨(dú)特的。但上面這七條法則是我在三個(gè)項(xiàng)目里反復(fù)驗(yàn)證過(guò)的。它們不一定全對(duì)但至少能讓你少走一些彎路。