多智能體系統一旦從順序執行走向并行,測試的需求就更嚴格了。單個智能體的輸出可能都是對的,但多個智能體并行決策、彼此影響時,集體行為可能違反系統級約束,而傳統的單元測試和輸出斷言對這類問題完全無能為力。
這篇文章聚焦的就是這個問題:如何測試并行多智能體系統的協調行為。以一個跨四個城市的網絡流量調度系統為例,從軌跡捕獲、行為不變量、回放回歸、黃金數據集到 CI/CD 集成,逐步搭建一套完整的協調測試框架。
被測系統
具體場景是骨干網光纖切斷事件的自動響應。當故障被檢測到時,一個 Coordinator Agent 分析影響范圍,將任務分發給四個并行的區域流量智能體,分別負責紐約、達拉斯、拉斯維加斯和舊金山。四個智能體并行工作,轉移流量負載,對非關鍵服務施加限速,目標是在級聯擁塞發生之前完成處置。
┌──────────────────────┐
│ Fiber Cut Detected │
│ (BGP flap alarm) │
└──────────┬───────────┘
│
▼
┌─────────────────────┐
│ Coordinator Agent │
│ (impact analysis + │
│ task delegation) │
└──────────┬──────────┘
│
┌────────────────────┼─────────────────────────────────────────┐
▼ ▼ ▼ ▼
┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐ ┌─────────────────┐
│ NYC Agent │ │ Dallas Agent │ │ Las Vegas Agent │ │ SF Agent │
│ Shift load to │ │ Absorb transit │ │ Rate-limit CDN │ │ Reroute peering │
│ NJ peer │ │ from cut path │ │ non-critical │ │ via Seattle │
└────────┬────────┘ └────────┬────────┘ └────────┬────────┘ └────────┬────────┘
└────────────────────┴─────────────────────┴────────────────────┘
│
▼
┌────────────────────────┐
│ Reducer + Validation │
│ Merge state, check │
│ invariants, confirm │
│ SLA preserved │
└────────────────────────┘
每個區域智能體是 LangGraph 狀態圖中一個由 LLM 驅動的節點,在一個 super-step 中并行執行。這是 LangGraph 的并行節點執行機制,它們各自的輸出在下一階段開始前由自定義 reducer 合并。
這不是簡單的流水線。這是協調式的并行推理,會引入順序系統壓根不存在的故障模式。
傳統測試為什么在這里失效
傳統軟件里測試四個并行 worker 是可控的。Mock 輸入,捕獲輸出,assert 正確性。Worker 是確定性的同樣的輸入永遠給出同樣的輸出。
但智能體不是確定性的。
達拉斯智能體可能這次決定吸收 40% 的重路由負載,下次變成 35%。從遙測數據來看都說得通因為都是合理的決策。但差異足以讓任何寫死精確值的斷言直接掛掉。比如硬編碼 assert result.load_shift_percent == 40,下一次模型升級或 prompt 調整就會讓它失敗。。
更麻煩的情況是四個智能體并行跑、決策互相糾纏的時候。紐約智能體把負載轉向某條通道,拉斯維加斯智能體卻在同一條通道的過時利用率數據上做決策。如果單獨看兩個智能體都沒做錯什么,但放到一起它們制造了一個認為的擁塞。
每個智能體的輸出看起來都是對的,但是協調出了問題。
智能體測試要捕獲的正是這類故障,不是單個智能體的正確性而是集體行為的完整性。
步驟一:捕獲執行軌跡
首先需要的是可觀測性。看到的不能只是最終狀態,還要包括完整的決策序列——每個并行智能體干了什么,reducer 怎么合并的輸出。
LangGraph 的 checkpoint 機制天然提供了這些:
from langgraph.checkpoint.sqlite import SqliteSaver
checkpointer = SqliteSaver.from_conn_string("incidents.db")
graph = fiber_cut_graph.compile(checkpointer=checkpointer)
result = graph.invoke(alarm_event, config={"configurable": {"thread_id": "incident-2024-0312"}})
從存儲的歷史記錄中提取執行軌跡:
START
→ coordinator_analysis [Impact: HIGH | Regions: NYC, DAL, LV, SF]
┌─ nyc_agent [Load shift: 30% → NJ peer | Rate-limit: OTT]
├─ dallas_agent [Transit absorption: 38% | Carrier notified]
├─ lv_agent [CDN rate-limit: applied | MPLS LSPs: held]
└─ sf_agent [BGP MED adjusted | Reroute: Seattle path]
→ reducer_merge [Total redistributed: 102% ?]
→ sla_validation [SLA breached: false | Margin: 4%]
END
注意 reducer 中那個 102%。這種問題只有軌跡捕獲才能暴露出來。四個智能體各自基于自己對網絡的局部視圖做判斷,集體的結果是容量過度承諾。沒有哪個智能體做錯了決定,但系統層面卻出了問題。
設計良好的 reducer 難道不能自動阻止這種情況發生嗎?
能,但是這恰恰就是重點。在 LangGraph 中自定義 reducer 就是協調約束的載體。通過條件邊可以在總承諾負載超過容量時,把執行路由到重新平衡步驟,在任何實際操作執行之前攔截。
但Reducer 不只是個合并函數,它是執行層。
在 LangGraph 的并行執行中reducer 承擔的角色相當于分布式事務邊界,或者說是唯一一個能在副作用發生之前基于所有智能體的組合意圖評估全局不變量的位置。
軌跡測試驗證的是這個執行層有沒有實際運行,不變量測試驗證的是它有沒有守住規則。
步驟二:行為不變量
不變量是結構性規則:無論底層跑的是哪個 LLM、選了什么路由、智能體之間怎么分工這些規則都必須成立,因為這些規則是由業務來定義的。
多智能體網絡系統不存在一套通用的不變量。金融客戶和 CDN 運營商的 SLA 承諾不同;處在維護窗口的區域和正常運行的區域約束也不同。不變量應該反映的是業務上下文不只是技術拓撲。
不過有些不變量是系統級的,對任何并行流量響應系統都值得顯式定義。
不變量 1:流量在轉移過程中必須守恒。
這是最底層的保障。流量從路徑 X 挪到路徑 Y 或路徑 Z,但進入網絡的總流量在重新分配前后必須相等,不能有流量被靜默丟棄,也不能被重復計算。
def invariant_traffic_conservation(pre_shift: dict, post_shift: dict) -> bool:
pre_total = sum(pre_shift["path_volumes"].values())
post_total = sum(post_shift["path_volumes"].values())
return abs(pre_total - post_total) <= pre_total * 0.01 # allow 1% drift
這個不變量一旦失敗,問題就不只是協調錯誤了,要么流量被黑洞吞掉了,要么智能體在不一致的狀態快照上做決策。
在智能體系統中確定性被有界變異性替代。目標不是凍結輸出而是定義變異的結構性邊界。
不變量 2:SLA 驗證必須在 reducer 合并之后運行,絕不能在之前。
每個區域智能體看到的只是網絡的局部。SLA 影響只有在四個決策合并為全局狀態之后才能真正評估。在部分狀態上跑 SLA 檢查,比不跑還糟——它給人一種虛假的安全感。
def invariant_sla_after_merge(trajectory: list[str]) -> bool:
return trajectory.index("sla_validation") > trajectory.index("reducer_merge")
不變量 3:不允許任何區域在沒有有效操作集的情況下行動。
如果某個區域智能體返回了空操作,比如所有備選路徑都在維護。這時系統不能靜默地繼續執行,必須升級給人來處理。靜默的部分執行是事后復盤里最讓人困惑的故障模式。
def invariant_no_silent_partial_execution(merged_state: dict, trajectory: list[str]) -> bool:
for region in ["nyc", "dal", "lv", "sf"]:
if not merged_state[region].get("actions"):
return "escalate_to_human" in trajectory
return True
不變量 4:承諾容量不能超過可用余量。
這條直接驗證 reducer 的條件邊邏輯。如果各區域轉移量之和超過骨干網可用容量,系統必須走重新平衡流程而不是執行已經超限的方案。
def invariant_no_capacity_overcommit(merged_state: dict) -> bool:
total_shifted = sum(merged_state[r]["load_shift_percent"] for r in ["nyc", "dal", "lv", "sf"])
return total_shifted <= merged_state["backbone_available_capacity_percent"]
每次執行后把四個不變量全跑一遍,就是在對整個協調層做行為健康檢查,而不是只看某個智能體的輸出。
![]()
步驟三:基于回放的回歸測試
上線半年后團隊升級到了新版 LLM。單個智能體的決策質量確實提升了,但并行執行時舊金山智能體變得更激進了,比如在負載轉移上承諾到更高的比例,把總利用率推過了 reducer 調優時設定的余量閾值。不變量本可以抓住這個問題,但如果升級完沒人跑回放測試,它就帶著隱患上了線。
LangGraph 的 checkpoint 機制讓每個真實事件都成了回歸測試資產。把原始輸入在更新后的圖上重新跑一遍,提取新軌跡然后驗證所有不變量是否依然成立。
更聰明的模型可能用更短的路徑達到同樣的結果。但在并行協調系統中無法解釋的漂移必須經過人工審查,特別是 reducer 和 SLA 驗證步驟前后的變化。
回放測試的價值不只是抓住已知的故障更是建立一種信心:模型和 prompt 的變化不會在所有輸出級測試的盲區之外悄悄改變系統的集體行為。
步驟四:來自真實事件的黃金數據集
真實事件里藏著你編都編不出來的邊界情況:維護窗口和光纖切斷同時發生了、BGP 重收斂在執行中途發生、遙測數據對一個區域返回過時結果而其他區域正常。
系統處理過的每一個真實事件都是黃金數據集的候選,這里有一個核心設計原則:不要記錄每個智能體說了什么,要記錄哪些屬性必須成立。
黃金數據集的一條記錄包含原始輸入(真實遙測快照、拓撲狀態、當時的策略約束)和預期行為屬性:哪些不變量必須成立、哪些軌跡節點必須出現、哪些不能出現。不記錄精確輸出值,不記錄具體路徑名。
這樣做的好處是數據集不會隨著系統演進而過時。模型換了、prompt 改了、圖邏輯重構了,數據集驗證的依然是真正重要的東西不會在合理變化的部分誤報。
步驟五:CI/CD 集成
智能體測試不自動化就沒有意義,每次 prompt 變更、工具 schema 更新、或者切換模型版本,流水線都應該回放黃金數據集,并且在任何變更上線之前完成不變量驗證。
不變量違規是硬性失敗流水線直接掛掉。軌跡漂移標記出來交給人審查但不自動阻斷,可接受的行為變化和真正的回歸之間的判斷,應該由人來做而不是流水線。
閾值問題值得提前想清楚:不是所有軌跡變化都是回歸,有時更智能的模型會走一條更高效的路徑。在凌晨 2:47 做部署回滾的時候再去定義什么叫"有意義的漂移"就太晚了。
換個角度想:升級路由協議之前你不會不跑回歸測試,升級多智能體協調圖里的 LLM 也不應例外。
步驟六:協調測試——并行系統真正出事的地方
多智能體系統里最難纏的故障不在任何單個智能體內部。它們存在于智能體之間的縫隙——并行系統比順序系統有多得多的這種縫隙。
三種故障模式反復出現:
┌─────────────────────────────────────────────────────────────┐
│ Pattern 1: Capacity Overcommit │
│ │
│ Each agent independently prefers the eastern backbone. │
│ Reducer sums all four shifts → 105% utilization. │
│ No single agent was wrong. The coordination was. │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Pattern 2: Stale State in Parallel Branches │
│ │
│ NYC Agent reads backbone_utilization = 72% │
│ DAL Agent reads backbone_utilization = 68% (4s stale) │
│ Both make shift decisions on different views of reality. │
│ Reducer aggregates inconsistent data as if it were valid. │
└─────────────────────────────────────────────────────────────┘
┌─────────────────────────────────────────────────────────────┐
│ Pattern 3: Reducer State Overwrite │
│ │
│ Multiple agents write to a shared metric field. │
│ Last writer wins → earlier, higher-accuracy data lost. │
│ Downstream agents make decisions on corrupted state. │
└─────────────────────────────────────────────────────────────┘
針對每種模式,要寫專門的協調測試:注入問題狀態,運行圖,斷言 reducer 和條件邊正確處理了沖突。測試的不是智能體聰不聰明,而是協調層能不能在智能體產出合理但相互沖突的結果時依然執行結構性規則。
這是大多數團隊在智能體測試中跳過的部分,也恰恰是造成生產事故最多的部分。
總結
至此已經搭建起了一套完整的基線:軌跡捕獲、行為不變量、回放回歸測試、黃金數據集、CI/CD 集成、協調測試。對于一個并行管理四座城市流量的系統來說,這是把它放到生產環境之前必須具備的測試紀律。
但這個框架還有幾個沒有覆蓋的問題,值得明確指出。
系統對每個決策有多大信心?達拉斯智能體選了 38% 的負載吸收率,這是基于清晰遙測數據的高置信度判斷,還是不確定條件下的最優猜測?如果能在決策粒度上引入置信度評分,就可以校準每一步需要多大程度的人工介入。這個在智能體操作實時骨干網流量的場景下,這一點非常關鍵。
混沌環境下會發生什么?遙測 API 返回過時數據,拓撲數據庫落后 90 秒,某個區域智能體的工具調用在執行中途超時而其他三個正常推進。當前的測試假設環境全程配合但是生產中不會,針對智能體系統的混沌測試目前還沒有成熟的標準化工具,所以只能手動進行。
傳統軟件里bug 是邏輯錯誤。代碼做了不該做的事;并行智能體系統里的 bug 往往以另一種形態出現:協調漂移。
四個智能體各自推理正確,各自的決策在隔離環境下都站得住腳,但組合在一起違反了一個系統級不變量。系統平穩運行了半年某次模型升級讓某個智能體的決策偏了那么一點,剛好夠造成 reducer 沒有攔住的容量過度承諾。
該問的問題不是"系統有沒有產出正確的輸出",而是"系統有沒有通過正確的協調達到這個輸出,而且在模型和 prompt 持續演進的過程中,它能不能一直做到"。這是兩個完全不同的問題,需要完全不同的工具。
https://avoid.overfit.cn/post/da0b85b778d24bd4b8aa8430026b37f6
作者:ravikiran veldanda
特別聲明:以上內容(如有圖片或視頻亦包括在內)為自媒體平臺“網易號”用戶上傳并發布,本平臺僅提供信息存儲服務。
Notice: The content above (including the pictures and videos if any) is uploaded and posted by a user of NetEase Hao, which is a social media platform and only provides information storage services.