定性實戰(zhàn):DNS 故障、工具調(diào)用重試與狀態(tài)管理)
1. 從“停訓(xùn)”說起AI Agent 到底在哪些環(huán)節(jié)容易失控1.1 一個被忽略的事實Agent 的失敗往往不是模型本身很多人第一次接觸 AI Agent腦子里想的都是“模型夠不夠聰明”。但真正在一線搭過 Agent 系統(tǒng)的人會告訴你模型能力只是整個鏈條里的一環(huán)而且往往不是最先崩的那一環(huán)。OpenAI 在三個月內(nèi)兩次暫停訓(xùn)練任務(wù)外界看到的新聞標題是“安全審查”“能力評估”但如果你自己動手搭過 Agent就會明白一個 Agent 從接收指令到完成任務(wù)中間要經(jīng)過工具調(diào)用、網(wǎng)絡(luò)請求、文件讀寫、狀態(tài)同步、錯誤重試等十幾個環(huán)節(jié)任何一個環(huán)節(jié)的配置失誤都可能讓整個任務(wù)鏈斷裂。我自己的經(jīng)驗是Agent 失控通常分三類第一類是工具調(diào)用失控Agent 反復(fù)調(diào)用同一個工具卻拿不到有效結(jié)果陷入死循環(huán)第二類是環(huán)境依賴失控比如 DNS 解析失敗導(dǎo)致所有外部請求超時Agent 卻不知道如何降級處理第三類是狀態(tài)管理失控多輪對話后上下文膨脹Agent 開始“忘記”最初的目標。這三類問題里DNS 相關(guān)的環(huán)境依賴問題最隱蔽也最容易被忽視因為它在傳統(tǒng)軟件開發(fā)里幾乎不會成為瓶頸但在 Agent 場景下會被無限放大。1.2 為什么 DNS 會成為 Agent 的“隱形殺手”DNS 解析在普通應(yīng)用里是一個毫秒級的操作用戶根本感知不到。但 Agent 的工作模式完全不同它可能在一次任務(wù)中發(fā)起幾十次甚至上百次外部請求每次請求都要走一遍 DNS 解析。如果 DNS 配置有問題比如解析超時、返回了錯誤的 IP、或者在某些網(wǎng)絡(luò)環(huán)境下被劫持Agent 就會陷入“請求發(fā)出去了但永遠等不到響應(yīng)”的狀態(tài)。更麻煩的是很多 Agent 框架默認不處理 DNS 層面的異常。它們假設(shè)網(wǎng)絡(luò)是可靠的DNS 是永遠可用的。一旦這個假設(shè)被打破Agent 要么卡死要么開始瘋狂重試要么直接拋出“agent execution terminated due to error”這樣的錯誤然后終止。我在實際項目中遇到過一種情況Agent 在調(diào)用某個外部 API 時DNS 解析偶爾會失敗但框架的重試邏輯只針對 HTTP 狀態(tài)碼不針對網(wǎng)絡(luò)層異常結(jié)果就是 Agent 在“解析失敗-重試-再解析失敗”之間循環(huán)了十幾次最后超時退出而日志里只留下一行模糊的錯誤信息。1.3 這篇文章適合誰看如果你正在從零搭建 AI Agent或者已經(jīng)在維護一個 Agent 系統(tǒng)但經(jīng)常被各種“莫名其妙”的失敗困擾這篇文章就是寫給你的。我不會只講概念而是會把 DNS 配置、工具調(diào)用重試、狀態(tài)管理這些具體環(huán)節(jié)拆開告訴你每一步該怎么做、為什么這么做、以及我踩過哪些坑。即使你用的是 Cline、Codex 或者其他 Agent 框架底層的思路是相通的。2. 核心細節(jié)解析Agent 環(huán)境依賴的三大雷區(qū)2.1 DNS 配置從“能上網(wǎng)”到“Agent 能穩(wěn)定上網(wǎng)”很多人覺得 DNS 配置很簡單不就是填個 8.8.8.8 或者運營商的 DNS 地址嗎但在 Agent 場景下DNS 配置要考慮的問題遠不止“能不能解析”。我整理了一個對比表把普通應(yīng)用和 Agent 場景對 DNS 的要求列出來你一看就明白差距在哪。維度普通應(yīng)用Agent 場景解析頻率低通常只在啟動時解析一次高每次工具調(diào)用都可能觸發(fā)解析超時容忍度較高用戶可等待極低超時會直接導(dǎo)致任務(wù)失敗失敗處理通常有降級方案多數(shù)框架無降級直接報錯緩存策略系統(tǒng)級緩存足夠需要應(yīng)用層緩存避免重復(fù)解析多環(huán)境切換不頻繁開發(fā)、測試、生產(chǎn)環(huán)境 DNS 可能不同從表里可以看出Agent 對 DNS 的要求是“高頻、低延遲、高可靠”。如果你在 Linux 上修改了 DNS 配置重啟網(wǎng)絡(luò)后配置還原了那 Agent 在運行過程中就會突然失去解析能力。這種情況在容器化環(huán)境里尤其常見因為容器的 DNS 配置往往由宿主機或編排平臺管理手動修改很容易被覆蓋。我的建議是不要依賴系統(tǒng)級 DNS 配置而是在 Agent 應(yīng)用層做 DNS 緩存和降級。具體做法是在 Agent 初始化時解析所有可能用到的域名把 IP 緩存到內(nèi)存里并設(shè)置一個合理的 TTL。當 DNS 解析失敗時優(yōu)先使用緩存中的 IP而不是直接報錯。如果緩存也沒有再走降級邏輯比如切換到備用 DNS 服務(wù)器或者直接返回一個可處理的錯誤讓 Agent 決定下一步。2.2 工具調(diào)用重試別讓 Agent 陷入“死循環(huán)”Agent 的工具調(diào)用重試機制是一個容易被忽視的細節(jié)。很多框架默認的重試策略是“失敗就重試重試 N 次后放棄”但這里的“失敗”定義很關(guān)鍵。如果只把 HTTP 5xx 狀態(tài)碼定義為失敗那 DNS 解析失敗、連接超時、SSL 握手失敗這些網(wǎng)絡(luò)層異常就不會觸發(fā)重試Agent 會直接收到一個異常然后終止。我在一個項目里做過統(tǒng)計Agent 任務(wù)失敗的原因中網(wǎng)絡(luò)層異常占了將近四成其中 DNS 相關(guān)問題又占了網(wǎng)絡(luò)層異常的一半以上。這個比例在跨地域部署的 Agent 系統(tǒng)里會更高因為不同地區(qū)的 DNS 解析質(zhì)量和延遲差異很大。正確的重試策略應(yīng)該分層第一層網(wǎng)絡(luò)層重試。針對 DNS 解析失敗、連接超時、連接重置等異常立即重試但重試間隔要短比如 100ms、200ms、400ms 這樣指數(shù)退避。第二層應(yīng)用層重試。針對 HTTP 4xx、5xx 狀態(tài)碼根據(jù)具體狀態(tài)碼決定是否重試。比如 429 限流可以重試401 未授權(quán)就不應(yīng)該重試。第三層任務(wù)層重試。如果整個工具調(diào)用鏈都失敗了Agent 應(yīng)該有能力重新規(guī)劃任務(wù)而不是直接終止。注意重試次數(shù)不是越多越好。我見過一個 Agent 配置了 10 次重試結(jié)果一個簡單的 API 調(diào)用失敗后Agent 花了將近一分鐘在重試上最后用戶等不及直接關(guān)掉了頁面。重試次數(shù)建議控制在 3 到 5 次并且要有總超時限制。2.3 狀態(tài)管理上下文膨脹比你想的更致命Agent 的狀態(tài)管理是一個老生常談的話題但很多人只關(guān)注“上下文長度夠不夠”忽略了“上下文質(zhì)量高不高”。一個 Agent 在運行過程中會產(chǎn)生大量的中間狀態(tài)工具調(diào)用的輸入輸出、錯誤信息、重試記錄、臨時文件路徑等等。如果這些狀態(tài)全部塞進上下文很快就會把 token 預(yù)算耗盡而且會讓模型難以聚焦在核心任務(wù)上。我的做法是分層管理狀態(tài)核心狀態(tài)任務(wù)目標、當前步驟、關(guān)鍵決策這些必須保留在上下文里。中間狀態(tài)工具調(diào)用的詳細輸入輸出只保留最近幾次更早的可以摘要化或者存到外部存儲。調(diào)試狀態(tài)完整的日志、錯誤堆棧寫到文件或日志系統(tǒng)不進入上下文。這樣做的好處是Agent 的上下文始終保持在可控范圍內(nèi)模型不會被無關(guān)信息干擾。實測下來同樣的任務(wù)分層管理狀態(tài)后Agent 的完成率能提升兩成以上而且響應(yīng)速度也更快。3. 實操過程從零搭建一個抗 DNS 故障的 Agent3.1 環(huán)境準備與基礎(chǔ)配置假設(shè)你現(xiàn)在要從零搭建一個 Agent并且希望它在 DNS 不穩(wěn)定的環(huán)境下也能正常工作。我以 Python 技術(shù)棧為例把關(guān)鍵步驟拆開講。如果你用的是其他語言思路是一樣的。首先你需要一個 DNS 解析庫不要直接用系統(tǒng)默認的 socket 解析。我推薦用dnspython因為它支持自定義 DNS 服務(wù)器、超時設(shè)置和緩存。安裝命令很簡單pip install dnspython然后在 Agent 初始化的時候創(chuàng)建一個 DNS 解析器實例配置多個備用 DNS 服務(wù)器。這里要注意DNS 服務(wù)器的選擇很關(guān)鍵。國內(nèi)環(huán)境建議用運營商提供的 DNS 加上一個公共 DNS 作為備用比如 114.114.114.114 和 223.5.5.5。不要只配一個因為單點故障在 Agent 場景下是不可接受的。import dns.resolver resolver dns.resolver.Resolver() resolver.nameservers [114.114.114.114, 223.5.5.5] resolver.timeout 2.0 resolver.lifetime 5.0timeout是單次查詢的超時時間lifetime是總超時時間。這兩個參數(shù)要根據(jù)你的網(wǎng)絡(luò)環(huán)境調(diào)整。如果網(wǎng)絡(luò)延遲高可以適當放寬但不要超過 5 秒否則 Agent 的響應(yīng)會變得很慢。3.2 實現(xiàn)帶緩存的 DNS 解析模塊接下來寫一個帶緩存的 DNS 解析函數(shù)。這個函數(shù)的作用是先查緩存緩存命中就直接返回緩存未命中就發(fā)起 DNS 查詢查詢成功就更新緩存查詢失敗就返回緩存中的舊值如果有的話并記錄一條警告日志。import time import dns.resolver class DNSCache: def __init__(self, ttl300): self.cache {} self.ttl ttl self.resolver dns.resolver.Resolver() self.resolver.nameservers [114.114.114.114, 223.5.5.5] self.resolver.timeout 2.0 self.resolver.lifetime 5.0 def resolve(self, domain): now time.time() if domain in self.cache: ip, expire_at self.cache[domain] if now expire_at: return ip try: answers self.resolver.resolve(domain, A) ip answers[0].to_text() self.cache[domain] (ip, now self.ttl) return ip except Exception as e: if domain in self.cache: ip, _ self.cache[domain] print(fDNS 解析失敗使用緩存 IP: {ip}, 錯誤: {e}) return ip raise這個模塊的關(guān)鍵點是緩存不是簡單的字典而是帶過期時間的緩存。TTL 設(shè)置成 300 秒是一個折中值太短會導(dǎo)致頻繁解析太長會導(dǎo)致 IP 變更后 Agent 還在用舊 IP。你可以根據(jù)實際需求調(diào)整。3.3 把 DNS 緩存接入 Agent 的工具調(diào)用鏈有了 DNS 緩存模塊下一步是把它接入 Agent 的工具調(diào)用鏈。大多數(shù) Agent 框架都允許你自定義 HTTP 客戶端你可以在客戶端里用 DNS 緩存替換默認的解析邏輯。以requests庫為例你可以自定義一個HTTPAdapter在發(fā)送請求前先解析域名然后把 IP 直接傳給連接池。這樣做的好處是DNS 解析和 HTTP 請求解耦解析失敗不會直接導(dǎo)致請求失敗而是走緩存降級。import requests from requests.adapters import HTTPAdapter from urllib3.util.connection import create_connection class CachedDNSAdapter(HTTPAdapter): def __init__(self, dns_cache, *args, **kwargs): self.dns_cache dns_cache super().__init__(*args, **kwargs) def send(self, request, **kwargs): from urllib.parse import urlparse parsed urlparse(request.url) if parsed.hostname: try: ip self.dns_cache.resolve(parsed.hostname) request.url request.url.replace(parsed.hostname, ip, 1) request.headers[Host] parsed.hostname except Exception as e: print(fDNS 解析完全失敗: {e}) return super().send(request, **kwargs)這段代碼的邏輯是在發(fā)送請求前把 URL 里的域名替換成緩存中的 IP同時保留Host頭這樣服務(wù)端仍然能正確識別請求。如果 DNS 解析完全失敗請求會帶著原始域名繼續(xù)發(fā)送由底層庫決定是否報錯。提示替換 URL 里的域名時要注意只替換 hostname 部分不要影響路徑和查詢參數(shù)。另外HTTPS 請求需要 SNI 支持直接用 IP 替換域名可能會導(dǎo)致 SSL 握手失敗。這種情況下更好的做法是在連接層做 DNS 緩存而不是在 URL 層替換。3.4 配置 Agent 的重試與降級策略工具調(diào)用鏈準備好了接下來配置重試和降級策略。我在前面提到過重試要分層。具體實現(xiàn)上可以用一個裝飾器來包裝工具調(diào)用函數(shù)根據(jù)異常類型決定重試行為。import time import functools def retry_on_network_error(max_retries3, base_delay0.1): def decorator(func): functools.wraps(func) def wrapper(*args, **kwargs): last_exception None for attempt in range(max_retries): try: return func(*args, **kwargs) except (ConnectionError, TimeoutError) as e: last_exception e delay base_delay * (2 ** attempt) print(f網(wǎng)絡(luò)異常第 {attempt 1} 次重試等待 {delay:.2f}s) time.sleep(delay) except Exception as e: raise e raise last_exception return wrapper return decorator這個裝飾器只對網(wǎng)絡(luò)層異常重試其他異常直接拋出。重試間隔用指數(shù)退避避免短時間內(nèi)大量重試壓垮網(wǎng)絡(luò)。max_retries設(shè)置成 3 是一個比較穩(wěn)妥的值超過 3 次還沒成功說明問題不是暫時的繼續(xù)重試意義不大。3.5 實測記錄一次 DNS 故障的完整排查過程上個月我在一個測試環(huán)境里模擬了 DNS 故障把 DNS 服務(wù)器地址改成一個不可達的 IP然后觀察 Agent 的行為。第一次測試時Agent 在調(diào)用外部 API 時直接卡住了日志里只有一行“agent execution terminated due to error”沒有任何有用的信息。排查過程是這樣的先看 Agent 框架的日志級別發(fā)現(xiàn)默認是 INFO網(wǎng)絡(luò)層的異常沒有打出來。把日志級別調(diào)到 DEBUG 后看到了 DNS 解析超時的記錄。然后檢查 DNS 配置發(fā)現(xiàn)框架用的是系統(tǒng)默認的 DNS而系統(tǒng) DNS 在容器環(huán)境里指向了一個已經(jīng)下線的地址。修復(fù)方案分兩步第一步在 Agent 配置里顯式指定 DNS 服務(wù)器不依賴系統(tǒng)默認第二步加上前面說的 DNS 緩存和重試邏輯。改完之后重新測試同樣的 DNS 故障場景下Agent 雖然解析失敗了但因為緩存里有舊的 IP請求仍然成功發(fā)出任務(wù)沒有中斷。這個案例說明一個問題Agent 的穩(wěn)定性不是靠單一措施保證的而是靠多層防護。DNS 緩存是一層重試是另一層降級是第三層。任何一層單獨拿出來都不夠但組合起來就能大幅提升 Agent 的可用性。4. 常見問題與排查技巧實錄4.1 Agent 報“agent execution terminated due to error”怎么查這個錯誤信息非常籠統(tǒng)幾乎等于沒說。我的排查順序是這樣的先看日志級別。把 Agent 框架和底層 HTTP 庫的日志都調(diào)到 DEBUG重新跑一次任務(wù)看錯誤發(fā)生前最后幾條日志是什么。檢查網(wǎng)絡(luò)連通性。用ping和curl測試 Agent 需要訪問的域名確認是 DNS 問題還是網(wǎng)絡(luò)問題。檢查 DNS 配置。在 Linux 上用cat /etc/resolv.conf看當前 DNS 服務(wù)器在 Windows 上用ipconfig /all看 DNS 配置。如果 DNS 服務(wù)器地址不對或者不可達那就是根因。檢查 Agent 的超時設(shè)置。有些框架默認超時很短網(wǎng)絡(luò)稍微慢一點就報錯。把超時時間適當調(diào)大看問題是否消失。檢查工具調(diào)用的輸入?yún)?shù)。有時候錯誤不是網(wǎng)絡(luò)問題而是 Agent 傳了錯誤的參數(shù)導(dǎo)致工具調(diào)用直接失敗。我整理了一個速查表你可以對照著排查現(xiàn)象可能原因排查方法解決方案Agent 卡住無響應(yīng)DNS 解析超時查看 DEBUG 日志中的 DNS 記錄配置備用 DNS加緩存任務(wù)突然終止網(wǎng)絡(luò)層異常未捕獲檢查異常處理邏輯分層重試捕獲網(wǎng)絡(luò)異常工具調(diào)用返回空DNS 返回錯誤 IP用nslookup驗證解析結(jié)果更換 DNS 服務(wù)器重試多次后失敗重試策略不合理查看重試日志和間隔調(diào)整重試次數(shù)和退避策略上下文溢出狀態(tài)管理不當檢查 token 使用量分層管理狀態(tài)摘要化中間結(jié)果4.2 Windows 和 Linux 下 DNS 配置的差異Windows 和 Linux 在 DNS 配置上有一些差異這些差異在 Agent 開發(fā)中會帶來意想不到的問題。Windows 的 DNS 配置通常在網(wǎng)卡屬性里修改后立即生效但重啟網(wǎng)絡(luò)后可能會還原。Linux 的 DNS 配置在/etc/resolv.conf但很多發(fā)行版會用systemd-resolved或者NetworkManager來管理直接改文件可能被覆蓋。我在 Windows 上遇到過一個典型問題主機能上網(wǎng)但虛擬機里的 Agent 只能手動設(shè)置 DNS 才能解析域名。排查后發(fā)現(xiàn)是虛擬機的網(wǎng)絡(luò)模式問題NAT 模式下虛擬機的 DNS 請求沒有正確轉(zhuǎn)發(fā)到宿主機。解決方案是把虛擬機網(wǎng)絡(luò)模式改成橋接或者在虛擬機里手動指定 DNS 服務(wù)器。Linux 下更常見的問題是修改 DNS 后重啟網(wǎng)絡(luò)配置還原。如果你用systemd-resolved應(yīng)該通過resolvectl命令來配置而不是直接改/etc/resolv.conf。如果你用NetworkManager應(yīng)該在連接配置里設(shè)置 DNS而不是改文件。這些細節(jié)看起來瑣碎但在 Agent 長時間運行的過程中任何一次 DNS 配置還原都可能導(dǎo)致任務(wù)失敗。4.3 Agent 框架選型對穩(wěn)定性的影響不同的 Agent 框架對網(wǎng)絡(luò)異常的處理能力差異很大。有些框架把網(wǎng)絡(luò)請求封裝得很好自帶重試和降級有些框架則完全依賴底層庫網(wǎng)絡(luò)一有問題就崩。我在選型時會重點看幾個方面是否支持自定義 HTTP 客戶端。如果框架不允許你替換 HTTP 客戶端那你就沒法接入自己的 DNS 緩存和重試邏輯。是否有完善的錯誤處理機制。看框架的異常體系是否區(qū)分網(wǎng)絡(luò)異常、業(yè)務(wù)異常、系統(tǒng)異常。是否支持任務(wù)級重試。工具調(diào)用失敗后Agent 能否重新規(guī)劃任務(wù)而不是直接終止。日志是否足夠詳細。出問題時能不能快速定位到具體環(huán)節(jié)。Cline 和 Codex 這類工具在 Agent 開發(fā)中比較常見它們的配置方式不同但核心思路是一樣的把網(wǎng)絡(luò)層的穩(wěn)定性交給應(yīng)用層來保證不要依賴框架的默認行為。4.4 幾個我踩過的坑和對應(yīng)的解法第一個坑是DNS 緩存沒有設(shè)置過期時間。早期我圖省事把解析結(jié)果永久緩存結(jié)果某個服務(wù)的 IP 變更后Agent 還在用舊 IP請求全部失敗。后來加了 TTL問題解決。第二個坑是重試邏輯沒有區(qū)分異常類型。一開始所有異常都重試結(jié)果遇到 401 未授權(quán)也重試白白浪費了時間和配額。后來改成只對網(wǎng)絡(luò)異常和 5xx 重試效率提升明顯。第三個坑是日志里沒有記錄 DNS 解析結(jié)果。出問題時只能看到“請求失敗”看不到具體是哪個域名解析失敗、解析到了什么 IP。后來在 DNS 模塊里加了詳細日志排查效率大幅提升。第四個坑是沒有做 DNS 解析的并發(fā)控制。Agent 同時發(fā)起多個請求時每個請求都觸發(fā)一次 DNS 解析導(dǎo)致 DNS 服務(wù)器壓力過大解析成功率下降。后來加了并發(fā)限制和請求合并同樣的問題再沒出現(xiàn)過。5. 從 DNS 逃逸看 Agent 系統(tǒng)的整體穩(wěn)定性設(shè)計5.1 單點故障是 Agent 系統(tǒng)的最大敵人DNS 只是 Agent 系統(tǒng)中的一個單點類似的單點還有很多API 網(wǎng)關(guān)、認證服務(wù)、消息隊列、數(shù)據(jù)庫連接池等等。任何一個單點出問題都可能導(dǎo)致整個 Agent 任務(wù)鏈斷裂。我在設(shè)計 Agent 系統(tǒng)時會先把所有外部依賴列出來然后逐個評估這個依賴掛了Agent 還能不能繼續(xù)工作如果不能有沒有備用方案以 DNS 為例備用方案就是緩存加多 DNS 服務(wù)器。以 API 網(wǎng)關(guān)為例備用方案就是多地域部署加自動切換。以認證服務(wù)為例備用方案就是 token 緩存加離線驗證。這些方案不一定都要實現(xiàn)但至少要有預(yù)案知道出問題時該怎么處理。5.2 可觀測性比事后排查更重要Agent 系統(tǒng)出問題是常態(tài)關(guān)鍵是要能快速定位和恢復(fù)。我在項目里會重點建設(shè)三個可觀測性能力結(jié)構(gòu)化日志。每條日志都帶任務(wù) ID、步驟 ID、時間戳、異常類型方便過濾和關(guān)聯(lián)。關(guān)鍵指標監(jiān)控。DNS 解析成功率、工具調(diào)用成功率、任務(wù)完成率、平均響應(yīng)時間這些指標要實時監(jiān)控異常時自動告警。鏈路追蹤。一個 Agent 任務(wù)可能涉及多個工具調(diào)用和外部請求鏈路追蹤能把整個調(diào)用鏈串起來快速定位瓶頸和故障點。有了這些能力DNS 故障發(fā)生時你能在幾分鐘內(nèi)知道是哪個域名解析失敗、影響范圍有多大、有沒有緩存可用而不是對著“agent execution terminated due to error”發(fā)呆。5.3 給 Agent 開發(fā)者的幾條實用建議第一不要假設(shè)網(wǎng)絡(luò)是可靠的。任何外部請求都要有超時、重試和降級。第二不要假設(shè) DNS 是永遠可用的。緩存、多 DNS 服務(wù)器、應(yīng)用層解析這些都要有。第三不要假設(shè)框架會幫你處理一切??蚣艿哪J行為往往是最簡單的行為生產(chǎn)環(huán)境需要你自己加固。第四不要忽視日志和監(jiān)控。出問題時詳細的日志能幫你節(jié)省大量時間。第五定期做故障演練。主動模擬 DNS 故障、網(wǎng)絡(luò)延遲、服務(wù)不可用等場景驗證你的防護措施是否有效。我在實際項目中的體會是Agent 系統(tǒng)的穩(wěn)定性不是靠某個神奇的工具或框架保證的而是靠一層一層的防護和一次又一次的故障演練積累出來的。DNS 逃逸只是冰山一角水面下的東西才是真正決定 Agent 能不能穩(wěn)定運行的關(guān)鍵。