iT邦幫忙

2026 iThome 鐵人賽

DAY 14
0
AI Engineering

AI Agent 系統開發 30 天系列 第 14 篇

高風險 Tool 的 Human-in-the-Loop 人工審核

  • 分享至 

  • xImage
  •  

在前面幾篇的探討中,我們確立了高可靠 Agent 系統的核心觀念:「職責分離(AI 做 vs. 程式做)」——讓 AI 專注於非結構化語意理解,而由確定性的程式(Pydantic、State、Router)負責資料驗證與邊界守門。但當 Agent 的職責從「唯讀診斷」跨入具備副作用的「系統處置」時,就必須引入架構中的第三個關鍵維度 —— 「人做(Human-in-the-Loop,人工介入與審核)」。

直接讓 AI 拿著變更權限去碰正式環境(Production)是絕對的安全紅線。但在測試與 Staging 環境,系統常因測試殘留卡死,最適合用來安全驗證處置流程。維護時若直接重啟伺服器往往過於粗暴,容易中斷測試,實務上更傾向採用副作用較低的處置手段(例如強制清除閒置連線、隔離異常節點流量)。然而,即使在非正式環境,依然不能放任模型「全自動」處置;一旦判斷錯誤誤砍關鍵連線,同樣會打亂團隊步調,因此高風險操作必須由程式中斷並交由工程師裁決。

我們延續上一篇的微服務事故排查情境:Staging 環境的結帳服務 checkout 出現大量 500 錯誤,顧客在付款步驟逾時。當時 Agent 查出資料庫連線池滿載的根因後,輸出分析報告即結案;本篇則進一步賦予它執行處置的能力,並在提議清理連線時觸發人工審核,展示從診斷、安全攔截到授權修復的完整流程。

這套處置流程透過終端機互動腳本(uv run hitl-demo)展示:Agent 收到通報自主排查日誌與指標,當它鎖定問題並提議執行高風險工具 terminate_idle_connections 時,中介軟體立即攔截該動作並暫停,終端機會停下來等待工程師在 CLI 輸入裁決:

⏸️  【觸發安全中斷】中介軟體攔截到高風險系統處置操作!
   處置動作:terminate_idle_connections
   提議參數:{'service': 'database', 'idle_seconds': 600, 'max_terminate': 50}
   安全政策:高風險處置必須由現場工程師裁決

👤 【請在 CLI 選擇處理方式】
   [1] approve - 核准:依照 AI 提議的參數直接執行
   [2] reject  - 拒絕:禁止執行該動作,並傳送指示給 AI
   [3] exit    - 放棄並結束

請輸入選項 (1-3) [預設: 1]: 

整個審核流程包含 5 個階段:

  1. 收到通報與自主排查:Agent 讀取通報後,自主呼叫 search_logs 檢索日誌,發現資料庫連線池已滿(100/100),其中有 50 筆閒置逾時的連線未釋放。
  2. 提議高風險處置:Agent 主動提議呼叫處置工具 terminate_idle_connections(service="database", idle_seconds=600, max_terminate=50) 來清理連線。
  3. 安全攔截與中斷:因為該工具涉及強制中斷連線,中介軟體依政策強制暫停流程,保存目前狀態,並在 CLI 提示工程師選擇處理方式。
  4. 人工審核放行:工程師在終端機輸入選項進行裁決(核准或拒絕)。
  5. 喚醒執行與自主驗證:Agent 被喚醒並執行處置,接著自主呼叫 check_service_health 驗證資料庫連線池是否恢復正常,確認環境可用後輸出結案報告。

第一步:定義排查與處置工具

在 src/langgraph_human_in_the_loop/tools.py 中定義排查與處置工具:

from langchain_core.tools import tool


@tool
def search_logs(service: str, query: str) -> str:
    """唯讀工具:檢索指定微服務的日誌記錄。安全操作。"""
    if service == "checkout" or "database" in query:
        return (
            "【日誌檢索結果 - checkout】\n"
            "- 10:02:15 ERROR: DB connection pool exhausted (active=100/100, idle=0)\n"
            "- 10:02:18 ERROR: checkout request timed out waiting for db connection\n"
            "- 診斷發現:有 50 筆來自 batch-job 的閒置連線已超時未釋放。"
        )
    return f"【日誌檢索結果 - {service}】未發現明顯異常日誌。"


@tool
def check_service_health(service: str) -> str:
    """唯讀工具:檢查指定微服務或資料庫的即時健康度與指標。安全操作。"""
    if service == "database":
        return "【健康狀態 - database】連線池使用率 98%,延遲 4500ms(嚴重異常)。"
    return f"【健康狀態 - {service}】狀態正常(HTTP 200,延遲 15ms)。"


@tool
def terminate_idle_connections(
    service: str,
    idle_seconds: int,
    max_terminate: int,
) -> str:
    """高風險處置工具:強制中斷資料庫閒置連線以釋放連線池。涉及線上連線中斷風險。"""
    return (
        f"【處置成功】已強制中斷 {service} 中閒置超過 {idle_seconds} 秒的連線,"
        f"共釋放 {max_terminate} 筆連線。連線池已恢復可用狀態。"
    )


@tool
def isolate_problematic_pod(pod_name: str) -> str:
    """高風險處置工具:將異常 Pod 隔離並摘除流量(Cordon & Drain)。

    保留現場供後續排查。涉及節點容量縮減。
    """
    return f"【處置成功】已將 Pod {pod_name} 流量摘除並完成節點隔離(Cordon)。"


@tool
def scale_up_replicas(service: str, count: int) -> str:
    """高風險處置工具:水平擴展(Scale out)副本數量以分擔線上負載。

    涉及資源配額與成本。
    """
    return f"【處置成功】服務 {service} 副本數已增加 {count} 個節點。"


@tool
def toggle_feature_flag(feature: str, enabled: bool) -> str:
    """高風險處置工具:緊急切換功能旗標以進行功能降級或恢復。

    涉及業務功能可用性。
    """
    state_str = "啟用" if enabled else "關閉(降級)"
    return f"【處置成功】功能旗標 {feature} 已切換為 {state_str}。"


@tool
def ask_engineer(question: str) -> str:
    """向現場工程師提問以獲取確認(由人類輸入直接作為工具結果)。"""
    return ""

第二步:使用 HumanInTheLoopMiddleware 配置審核政策與組裝 Agent

在 agent.py 中,我們用 HumanInTheLoopMiddleware 設定審核規則。

原則很清楚:查日誌、看指標等唯讀操作直接放行,讓 Agent 自主排查;只有會改動系統狀態的處置動作,或是需要向工程師提問時,才停下來等待人工介入。

因為中介軟體預設會放行未列出的工具,所以我們在 interrupt_on 把要攔截的工具加上:

middleware = [
    HumanInTheLoopMiddleware(
        interrupt_on={
            # 處置工具:強制中斷審核,僅允許核准或拒絕
            "terminate_idle_connections": {
                "allowed_decisions": ["approve", "reject"],
            },
            # 提問工具:暫停並等待工程師直接代答
            "ask_engineer": {
                "allowed_decisions": ["respond"],
            },
            # 唯讀工具(search_logs、check_service_health)沒列出,預設自動放行
        },
        description_prefix="高風險處置操作待工程師核准",
    )
]

定義好中介軟體後,使用 create_agent 將模型、工具清單、中介軟體與 Checkpointer 組裝在一起。

Human-in-the-Loop 機制在觸發 interrupt 時,必須將當前圖的節點狀態、尚未執行的 Tool Call 與上下文完整持久化;工程師審核完成後,才能依據同一個 Session(thread_id)從中斷點安全喚醒並繼續執行。因此,組裝 Agent 時必須掛載 Checkpointer(範例使用記憶體型的 InMemorySaver,實務上可替換為 PostgreSQL 等持久化儲存):

def build_agent(
    model: Any,
    checkpointer: BaseCheckpointSaver[Any] | None = None,
) -> CompiledStateGraph[Any]:
    if checkpointer is None:
        checkpointer = InMemorySaver()

    return create_agent(
        model=model,
        tools=tools,
        middleware=middleware,
        checkpointer=checkpointer,
    )

第三步:處理中斷與喚醒

定義好 Agent 後,應用程式(例如維運 API、Slack 機器人或本範例的 CLI 互動腳本)必須負責偵測中斷點、收集工程師決策,並通知 LangGraph 恢復執行。

這段生命週期在 demo.py 的 run_interactive_demo 函式中實作:

config = {"configurable": {"thread_id": "sre-thread-interactive"}}

# 1. 啟動 Agent 進行自主排查
result = agent.invoke(
    {"messages": [{"role": "user", "content": prompt}]},
    config=config,
    version="v2",
)

# 2. 檢查是否觸發高風險處置中斷
if result.interrupts:
    action = result.interrupts[0].value["action_requests"][0]

    # 3. 取得工程師在 CLI 輸入的選項(choice = input(...)),組裝對應的 Command
    if choice == "1":    # 核准
        resume_cmd = Command(resume={"decisions": [{"type": "approve"}]})
    elif choice == "2":  # 拒絕
        resume_cmd = Command(resume={"decisions": [{"type": "reject", "message": ...}]})

    # 4. 帶入相同的 thread_id 喚醒 Agent 繼續執行
    final_result = agent.invoke(resume_cmd, config=config, version="v2")

3 種決策在底層執行的行為

當應用程式呼叫 agent.invoke(resume_cmd, config=config) 時,LangGraph 會依 thread_id 從 Checkpointer 載入先前凍結的狀態,並依據決策類型執行相應邏輯:

  1. approve(原樣核准):
    中介軟體確認決策為 approve,正式呼叫底層的 terminate_idle_connections 清理連線,並將成功訊息包裝成 ToolMessage 寫入歷史狀態。Agent 甦醒後自主呼叫 check_service_health 驗證資料庫連線池恢復正常,輸出結案報告。
  2. reject(拒絕處置):
    中介軟體不會呼叫底層處置工具,而是將工程師填寫的拒絕原因(例如「該節點正在進行關鍵測試,嚴禁清理連線」)合成 ToolMessage 傳回給 Agent。Agent 讀取反饋後調整策略,改採其他處置方案。
  3. respond(人類直接代答):
    當 Agent 呼叫 ask_engineer 詢問特定批次排程時,工程師在終端機輸入的回覆直接作為工具結果返回給 Agent,Agent 讀取後繼續推論。

讓 AI 執行系統處置的防護與責任邊界

雖然 Human-in-the-Loop 機制為高風險操作加上了人工審核的防線,但賦予 Agent 系統處置能力時,依然必須保持謹慎。人類的審核放行不是萬靈丹,要避免系統在處置過程中失控,設計上仍需守住幾個核心考量:

  • 工具能力必須設定物理防護邊界:不要給予 Agent 影響範圍無上限的處置工具。每個工具在程式實作時都該有硬性防護,例如清除連線工具必須限制單次最大清理筆數、擴充節點工具必須設定最大副本數量上限。即使模型在參數中填入過大數值,底層程式也絕不允許突破安全極限。
  • 避免審核疲勞導致無效把關:如果系統把太多瑣碎或低風險的操作都送交審核,維運人員在面對大量中斷彈窗時容易麻木,最後往往未經細看就隨手點擊核准。只有真正具備不可逆破壞性的動作才觸發中斷;審核介面也必須清楚列出 AI 先前查到的日誌證據與處置理由,讓工程師能在數秒內核對事實,而不是憑空猜測 AI 的意圖。
  • 處置後必須由程式驅動驗證,不能盲目重試:處置執行完畢不代表故障已經排除。Agent 恢復執行後,必須自動重新檢查即時健康指標;若指標依然未改善,代表處置無效,流程應立即停止並通知工程師接手處理,絕不能放任模型反覆發起相同或更激進的處置操作。
  • 以非正式環境為起點,逐步累積信任度:直接在生產環境開放處置工具門檻過高,但將其落地於測試、預發布(Staging)或開發環境,能立即產生實質價值。非正式環境常因壓測或連線洩漏而卡死,讓 Agent 自主排查並重置環境,既解決了維護痛點,又能在無營運損失的沙盒中觀察模型提議參數的準確度與審核互動,為日後評估是否進一步推展建立數據基礎。
  • 最終責任永遠在於人類:AI 的專長在於快速消化複雜日誌並提出處置建議,但在系統按下執行的那一刻,承擔可用性責任的始終是審核的人類工程師。讓 AI 負責提議與草擬參數、程式負責阻斷與邊界驗證、人類負責最後裁決,才是維持系統高可靠的平衡點。

上一篇
用 State 控制 Agent Loop 邊界
下一篇
用 Prompt Caching 降低成本與延遲
系列文
AI Agent 系統開發 30 天 共 21 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言