
高可用實驗結果的邊界與解讀高可用實驗最容易出現兩種誤讀一次壓測通過就認為系統能夠承受同等線上流量一次 Agent 循環失控又把所有問題歸因于模型不確定。前者忽略了請求分布、依賴和故障注入后者則掩蓋了本應由程序控制的重試、并發與取消邊界。實驗結論只覆蓋已記錄的條件。把變量、樣本、拓撲和復現步驟寫完整失敗結果才有資格進入架構決策。先把要驗證的假設寫成問題以 Agent 調用內部工具為例可以分別驗證參數缺失時系統是否在發出網絡請求前拒絕下游返回不可重試錯誤時任務是否立即結束或詢問用戶臨時錯誤發生時重試是否受次數和總時限約束用戶取消后排隊、模型請求和工具調用是否停止入口流量高于處理能力時隊列是否有上限并能恢復。這些問題不能用同一條“峰值 QPS”回答。實驗應先跑沒有故障的基線再一次注入一種失敗。輸入分布也要固定包括任務步驟數、工具類型、參數大小和返回時間。若所有樣本都只調用一個快速只讀接口結果不能外推到包含寫操作和長任務的工作流。流量逐級增加每一級等待系統進入相對穩定狀態觀察入口、Agent 執行器和下游隊列。負載生成器自身的 CPU、網絡與錯誤也要記錄否則測到的上限可能只是生成端先到瓶頸。先描述現象再解釋原因發現內部調用量明顯高于入口流量時先計算每個任務的工具調用分布和步驟數。Agent 正常完成多步驟任務本來就會產生放大只有調用數持續增長、同一任務無法結束或失敗后仍派生新步驟才說明控制流可能失去邊界。排查記錄把事實、假設和驗證分開。事實可以是某類錯誤增加、隊列不再回落、取消后仍有調用“模型陷入幻覺”只是解釋需要用調用軌跡證明。若發現同一參數連續失敗再查看失敗類別和重新規劃輸入確認它是否真的在做相同動作。不要補寫沒有來源的精確流量、Token 倍數或協程數量。實驗日志能支持到什么粒度就報告到什么粒度。證據不足時寫明缺口比給出一條完整但無法復驗的故事更有價值。Trace 記錄控制流不保存任務正文Agent Trace 可以在普通 HTTP Span 之外增加運行標識、步驟序號、工具名稱、參數校驗結果、錯誤類別、嘗試次數和剩余預算。敏感參數、模型 Prompt、工具返回正文不應默認進入 Span 屬性它們容易被導出到多個觀測后端。下面是一段脫敏后的示意結構。args_fingerprint只用于同一次運行內識別重復動作不能據此還原參數也不跨用戶做全局判斷。{ name: agent.tool.execute, attributes: { agent.run_id: run-example, agent.step: 3, tool.name: getUserDetail, tool.args_valid: false, tool.args_fingerprint: sha256:example, tool.attempt: 1, error.type: validation_error, retryable: false }, events: [ { name: agent.stopped, reason: missing_required_argument } ] }參數缺失屬于本地 Schema 校驗錯誤根本不應發往下游。系統可以讓用戶補充信息或者終止當前動作。把相同的 400 響應再次喂給模型只是消耗預算并不會創造缺失的業務數據。三層邊界分別解決不同問題第一層是動作協議。每個工具聲明參數 Schema、權限、是否有副作用、冪等要求和可重試錯誤。模型輸出通過校驗后還要由服務端填入當前用戶與租戶上下文。模型不能提供權限字段也不能直接決定目標主鍵。第二層是單次運行預算。最大步驟、總時限、模型調用、工具調用和并發任務都有上限。任一預算耗盡狀態機進入明確的停止或人工接管節點。運行預算必須隨取消信號一起傳遞到子任務不能只讓最外層請求返回。第三層是下游保護。連接池、并發許可、隊列和斷路器保護具體服務防止所有 Agent 同時重試。它與單個任務的循環檢測不同斷路器根據一段時間內的下游失敗決定是否暫時停止調用而循環檢測只判斷本次運行是否在重復沒有進展的動作。一個按運行隔離的執行示例下面的 Python 片段演示參數校驗之后的異步執行邊界。歷史只屬于一個RunGuard不會把不同用戶的相同查詢誤判為循環超時使用單調時鐘相同指紋只有在前一次失敗后才累計。import asyncio import hashlib import json import time from dataclasses import dataclass, field from typing import Any, Awaitable, Callable class ToolExecutionError(Exception): def __init__(self, message: str, *, retryable: bool) - None: super().__init__(message) self.retryable retryable dataclass class RunGuard: max_steps: int deadline: float repeated_failure_limit: int 2 steps: int 0 failures: dict[str, int] field(default_factorydict) def before_call(self, tool: str, args: dict[str, Any]) - str: if self.steps self.max_steps: raise ToolExecutionError(step budget exhausted, retryableFalse) if time.monotonic() self.deadline: raise ToolExecutionError(run deadline exceeded, retryableFalse) canonical json.dumps(args, sort_keysTrue, separators(,, :)) fingerprint hashlib.sha256(f{tool}:{canonical}.encode()).hexdigest() if self.failures.get(fingerprint, 0) self.repeated_failure_limit: raise ToolExecutionError(repeated failed action, retryableFalse) self.steps 1 return fingerprint def record_failure(self, fingerprint: str) - None: self.failures[fingerprint] self.failures.get(fingerprint, 0) 1 async def execute_tool( guard: RunGuard, tool_name: str, validated_args: dict[str, Any], call: Callable[..., Awaitable[Any]], step_timeout: float, ) - Any: fingerprint guard.before_call(tool_name, validated_args) try: return await asyncio.wait_for(call(**validated_args), timeoutstep_timeout) except ToolExecutionError as exc: guard.record_failure(fingerprint) if not exc.retryable: raise raise except asyncio.TimeoutError: guard.record_failure(fingerprint) raise ToolExecutionError(tool timeout, retryableTrue)示例沒有包含 Schema、鑒權、重試退避、冪等鍵和審計不能直接作為完整執行器。wait_for發出取消后底層客戶端還必須支持取消若請求已經到達有副作用的服務調用方應先查詢結果再決定重試。相同調用出現兩次也不必一律禁止。輪詢一個最終一致的只讀狀態可能是合理行為關鍵是協議必須允許并有等待間隔和總時限。循環規則應結合工具語義而不是把哈希次數寫成全局常量。降級路徑不能依賴失效的 Agent當模型或 Agent 執行器不可用時入口仍需根據普通服務狀態決定返回靜態信息、進入人工隊列還是拒絕任務。降級邏輯不能再次調用同一模型來判斷是否降級也不能繞過原有權限。高影響寫操作失敗時優先保留當前狀態與冪等標識不要自動換模型重放。只讀問答可以回到搜索結果或已有緩存但要說明結果來源發生變化。恢復后從少量任務開始放量確認隊列、錯誤和取消都回到預期。實驗結論按范圍交付報告最后應列出環境拓撲、版本、樣本分布、負載階梯、注入故障、指標口徑和觀測開銷。結論寫成條件句在當前步驟預算與隊列上限下某類失敗能夠收斂哪些工具和峰值尚未覆蓋則明確留空。失敗實驗可以產生三類資產能穩定回放的樣本、阻斷同類問題的自動測試以及故障時可執行的限流與接管說明。它們比一句“Agent 不適合高并發”更有用也比一張峰值流量圖更接近高可用設計真正需要的證據。