Agent工程化 · 第五篇:Agent黑盒怎麼打開

作者:努力撞蘑菇AI
日期:2026年6月5日 上午7:29
來源:WeChat 原文

整理版優先睇

速讀 5 個重點 高亮

要讓Agent可理解、可信任,必須從結構化日誌、Trace追蹤到評測嵌入,建立三層可觀測性。

整理版摘要

呢篇文章係「Agent工程化」系列嘅第五篇,作者係專注AI Agent深度實踐嘅「努力撞蘑菇AI」。佢從一個具體場景入手:線上Agent跑300個任務,47個失敗,但日誌得一句「Tool call failed: timeout」,你完全唔知發生咩事。呢個就係冇可觀測性嘅Agent:佢識行,但壞咗你乜都睇唔到。

作者認為,Agent比普通程序更難調試,因為佢嘅執行路徑係動態、非確定性嘅,多步驟鏈路互相影響,模型推理唔透明,而且上下文污染令問題根源可以隱藏喺好早嘅步驟。為咗解決呢個問題,佢提出可觀測性要分三個層次:日誌層(記錄發生咗咩)、Trace層(顯示點樣走到呢一步)、評測層(持續監測表現係咪變好)。

整體結論係:加日誌、做Trace、設告警只係手段,終極目標係令Agent嘅行為對你嚟講係可理解嘅。一個你能理解嘅Agent,先至係一個你能信任嘅Agent。文章仲提供咗一個今日就能用嘅最小方案,包括結構化日誌模板、三個告警閾值同簡單Trace類,同埋提醒兩個經常被忽視嘅盲區:模型版本漂移同工具返回內容嘅質量。

  • 建立三層可觀測性(日誌、Trace、評測)先至可以真正掌控Agent行為,30分鐘內定位根因。
  • Agent嘅非確定性同動態路徑令調試遠比傳統程序困難,唔可以簡單重現問題。
  • 日誌層必須記錄完整信息(輸入、輸出、工具參數等),唔好只記結果;Trace層串聯每一步推理同工具調用,揭示真正原因
  • 最小方案包含20行結構化日誌模板、三個告警閾值(工具調用失敗率>10%、Token消耗>預期3倍、任務失敗率>5%)同簡易Trace類。
  • 兩個常見盲區:模型版本漂移(日誌要記精確版本號)同工具返回內容質量(加採樣檢查),後者比工具失敗更危險。
值得記低
流程

結構化日誌模板

20行JSON日誌模板,強制記錄task_id、model、input、tool_calls、output、error等字段,零依賴,今日就能加。

Skill

簡單AgentTrace類

用context對象追蹤調用鏈,每次模型推理或工具調用記錄步驟,任務結束後寫入日誌。

整理重點

Agent黑盒嘅調試困境

想像一個早晨:你嘅Agent尋晚跑咗300個任務,今朝打開睇,47個失敗咗。錯誤日誌得一句「Tool call failed: timeout」。你唔知邊個工具超時、超時前模型做咗咩、係偶發定系統性、同樣輸入有冇成功過。呢個就係冇可觀測性嘅Agent——佢行得鬱,但壞咗你完全睇唔到嘢。

  • 非確定性執行:同一輸入,重跑十次可能只復現三次錯誤。
  • 多步驟鏈路:理解輸入→規劃→調用工具A→處理返回→調用工具B→生成輸出,每一步都可能出錯,前一步影響後一步。
  • 模型推理唔透明:工具調用失敗睇到,但「模型理解錯輸入」呢類問題冇系統報錯,只能靠最終輸出倒推。
  • 上下文污染:第三輪奇怪行為可能源於第一輪工具返回嘅髒數據,冇完整追蹤就揾唔到源頭。

非確定性執行

多步驟鏈路

模型推理唔透明

上下文污染

整理重點

可觀測性嘅三個層次

作者提出可觀測性唔係加幾行print就解決到,而係要分三個層次,缺一不可。由最基本嘅日誌,到揭示原因嘅Trace,再到持續監測嘅評測。

輸入輸出都要記,唔好淨係記錯誤

工具調用要記參數,唔好淨係記名

  1. 1 日誌層:記錄發生咗咩,最小集合包括任務ID、輸入/輸出、時間、狀態、模型版本、Token消耗、工具調用列表。
  2. 2 Trace層:解釋點樣走到呢一步,將每一步推理同工具調用串成有時序嘅鏈路,例如Task #47入面每一步嘅時間同結果。
  3. 3 評測層:持續監測表現係咪變好,每次改動都自動跑回歸測試,對比準確率、Token消耗、工具調用次數、失敗率等指標,跌過閾值就告警。

Trace層特別重要,文章用四個真實案例說明:模型繞路、上下文污染、工具選擇偏差、靜默失敗。冇Trace嘅時候你完全摸不着頭腦,有Trace之後一眼睇出問題所在。

模型繞路

上下文污染

工具選擇偏差

靜默失敗

整理重點

今日就能用嘅最小方案

理論講完,作者提供一個立即可以落手嘅方案。先從結構化日誌開始,唔需要複雜追蹤系統,強制每條Agent執行寫一條JSON日誌。

結構化日誌模板 python
def log_agent_run(task_id, model, input_text, tool_calls, output, error, start_time):
 log_entry = {
 "ts": datetime.utcnow().isoformat(),
 "task_id": task_id,
 "model": model,
 "input": input_text[:500],
 "output": str(output)[:500] if output else None,
 "tools": [
 {
 "name": t["name"],
 "args": str(t["args"])[:200],
 "status": t["status"],
 "duration_ms": t["duration_ms"]
 }
 for t in tool_calls
 ],
 "tokens": {
 "input": getattr(usage, "input_tokens", 0),
 "output": getattr(usage, "output_tokens", 0)
 },
 "duration_ms": int((time.time() - start_time) * 1000),
 "status": "error" if error else "success",
 "error": str(error) if error else None
 }
 logger.info(json.dumps(log_entry, ensure_ascii=False))

20行,零依賴

  • 三個必須設嘅告警閾值:工具調用失敗率>10%(工具層問題)、單任務Token消耗>預期3倍(模型繞路)、任務整體失敗率>5%(系統性問題)。
  • Trace最簡實現:用一個AgentTrace類,每次模型推理或工具調用add_step,任務結束後dump寫入日誌。
簡單Trace類 python
class AgentTrace:
 def __init__(self, task_id):
 self.task_id = task_id
 self.steps = []
 self.start_time = time.time()

 def add_step(self, step_type, content, status="ok", duration_ms=0):
 self.steps.append({
 "seq": len(self.steps) + 1,
 "type": step_type, # "model_call" / "tool_call" / "decision"
 "content": str(content)[:300],
 "status": status,
 "elapsed_ms": int((time.time() - self.start_time) * 1000),
 "duration_ms": duration_ms
 })

 def dump(self):
 return {
 "task_id": self.task_id,
 "total_steps": len(self.steps),
 "total_duration_ms": int((time.time() - self.start_time) * 1000),
 "steps": self.steps
 }

建設優先級:第1步結構化日誌(1天)→第2步失敗率監控+告警(2天)→第3步Trace鏈路追蹤(1周)→第4步評測層嵌入迭代流程(持續)。每一步獨立有價值,唔使等曬先上線。

優先級:先睇到發生咩,再感知邊度壞,然後理解點解壞,最後預防會唔會壞

整理重點

盲區同終極目標

模型版本漂移

工具返回內容嘅質量

兩個成日被忽略嘅盲區:第一,模型版本漂移——好多團隊日誌冇記模型版本,API提供商悄悄升級模型,行為變咗都唔知。解決方法:每條日誌強制寫精確版本號。第二,工具返回內容嘅質量——大部份人淨係記工具調用成功與否,但「成功返回咗髒數據」比失敗更危險,因為唔會觸發報錯,只會靜靜污染後續推理。解決方法:對關鍵工具加採樣檢查,每100次抽5次人工睇。

冇可觀測性,你唔係營運緊一個AI系統,而係養緊一個黑盒寵物——佢表現好你開心,表現差你淨係得抱怨,但改變唔到任何嘢。呢個就係作者最想傳達嘅訊息。


一、一個令你崩潰嘅朝早

線上Agent尋晚跑咗300個任務,朝早你打開睇結果——其中47個失敗咗。

錯誤日誌得一行:Tool call failed: timeout

你唔知道:

  • 係邊個工具超時咗?
  • 超時之前模型做咗啲乜?
  • 係偶發定係系統性㗎?
  • 同樣嘅輸入,有冇成功過?

你唯有逐個逐個重跑,然後眼定定睇住屏幕估。

這就是冇可觀測性嘅Agent:識行,但行壞咗你乜都睇唔到。

圖片


二、點解Agent比普通程式更難調試

傳統程式出咗問題,你打開日誌,睇堆棧,定位到第幾行,基本上揾到原因。

Agent唔同,佢嘅執行路徑係動態嘅、非確定性嘅

非確定性執行同樣嘅輸入,唔同時間跑出嚟嘅結果可能唔一樣。唔係bug,係模型本身嘅特性。呢個意味住你冇辦法簡單噉"重現問題"——你重跑十次,可能得三次復現。

多步驟鏈路一個Agent任務可能包含:理解輸入 → 規劃步驟 → 調用工具A → 處理返回 → 調用工具B → 生成輸出。每一步都可能出錯,而且前一步嘅輸出直接影響後一步嘅行為。鏈路越長,定位越難。

模型推理唔透明工具調用失敗,你睇到。但"模型理解錯咗輸入"呢種問題,冇任何系統級嘅報錯,你只能夠從最終輸出倒推——而呢個往往需要你將成個推理過程重新行一次。

上下文污染多輪對話場景裏便,第三輪嘅奇怪行為可能根源係第一輪某個工具返回咗污糟數據。冇完整嘅上下文追蹤,你永遠揾唔到真正嘅源頭。


三、可觀測性嘅三個層次

Agent嘅可觀測性唔係"加幾行print"搞得掂嘅,佢有三個層次,缺一不可。

3.1 日誌層:發生咗啲乜

最基礎嘅層次,記錄Agent做咗啲乜。

必須記錄嘅最小集合:

任務ID
輸入內容(截斷到合理長度)
開始時間 / 結束時間
執行狀態(成功/失敗/超時)
使用的模型版本
Token消耗(輸入/輸出分開記)
工具調用列表(名稱 + 參數摘要 + 返回狀態)
最終輸出(截斷到合理長度)
錯誤信息(如有)

注意兩個細節:

  • 輸入輸出都要記,唔可以淨係記錯誤。好多奇怪嘅失敗,原因係輸入本身,唔係執行過程。
  • 工具調用要記參數,唔可以淨係記"調用咗tool_A"。同一個工具,參數唔同,結果天差地別。

一個常見嘅反面教材:

# 錯誤示範:只記結果
logger.info(f"Task {task_id}{'success' if result else 'failed'}")

# 正確示範:記完整鏈路
logger.info({
    "task_id": task_id,
    "model": model_version,
    "input_preview": input_text[:200],
    "tool_calls": [{"name": t.name, "args_preview": str(t.args)[:100], "status": t.status} for t in tool_calls],
    "tokens": {"input": usage.input_tokens, "output": usage.output_tokens},
    "duration_ms": elapsed,
    "status""success"if result else"failed",
    "error": str(error) if error elseNone
})

3.2 Trace層:點樣行到呢一步嘅

日誌話畀你知發生咗啲乜,Trace話畀你知點解會行到呢一步

Trace嘅核心係將Agent嘅每一步推理同工具調用,串成一條有時序關係嘅鏈路。

Task #47
├── [00:00] 接收輸入:"分析Q2銷售數據,找出異常"
├── [00:01] 模型規劃:決定調用data_query工具
├── [00:02] 調用 data_query(table="sales", period="Q2") → 返回3000行數據
├── [00:05] 模型分析:發現第847行數據異常,決定調用verify工具
├── [00:06] 調用 verify(row_id=847) → TIMEOUT(30s後)
├── [00:36] 重試 verify(row_id=847) → TIMEOUT
└── [00:66] 任務失敗:工具連續超時

呢個結構令你一眼睇出:唔係模型嘅問題,係verify工具喺特定參數下會超時。你需要去查verify嘅實現,而唔係改Prompt。

四個真實case,Trace幫你定位咗啲乜:

Case 1:模型"繞路"問題表現:任務耗時比預期長3倍,Token消耗異常高。 冇Trace時:以為係模型變慢咗,或者換咗更貴嘅模型。 有Trace後發現:模型喺第二步調用工具之後,對返回結果唔滿意,自己又調咗兩次額外嘅驗證工具,呢個唔係預期行為。 根因:工具返回格式唔穩定,模型產生咗不確定性,觸發咗"自我驗證"行為。 解決:規範工具返回格式,加置信度字段,模型就唔再重複驗證啦。

Case 2:上下文污染問題表現:多輪對話場景,第5輪開始輸出質量急劇下降。 冇Trace時:以為係第5輪嘅輸入有問題,反覆改Prompt冇效。 有Trace後發現:第3輪某個工具返回咗一段包含錯誤假設嘅文本,模型將佢當成事實納入咗上下文,後續所有推理都基於呢個錯誤假設展開。 根因:工具返回嘅內容冇做格式校驗,混入咗描述性語言。 解決:工具返回加嚴格嘅Schema校驗,描述性內容同結構化數據分開字段。

Case 3:工具選擇偏差問題表現:同一類任務,30%嘅概率調用咗錯誤嘅工具,導致結果質量低。 冇Trace時:只能夠從最終輸出睇出"有時候結果唔啱",完全摸唔着頭腦。 有Trace後發現:模型喺兩個功能相近嘅工具之間選擇時,遇到邊界case就會揀錯。 根因:兩個工具嘅描述文本語義重疊,模型無法區分。 解決:重寫工具描述,加"使用場景"同"不適用場景"兩個字段,選擇準確率從70%提升到97%。

Case 4:靜默失敗問題表現:任務顯示成功,但輸出係空嘅。 冇Trace時:完全唔知邊度出咗問題,日誌顯示"success"。 有Trace後發現:工具調用返回咗空列表(正常情況,表示無數據),但模型將"空列表"解讀為"查詢成功但冇結果",冇觸發任何錯誤處理,直接輸出咗空內容。 根因:空結果同錯誤結果喺工具返回裏邊冇區分,模型處理邏輯唔一致。 解決:空結果返回專門嘅狀態碼,Prompt裏邊明確指定空結果時嘅處理行為。


3.3 評測層:表現係咪在變好

日誌同Trace係事後嘅,評測層係持續嘅健康監測

唔係一次性跑評測,而係將評測內嵌到每次迭代裏邊:

每次改動Prompt或工具 → 自動跑回歸測試集
對比本次 vs 上次的:
  - 準確率變化
  - 平均Token消耗變化
  - 工具調用次數變化
  - 超時/失敗率變化
有任何指標下降超過閾值 → 自動告警

呢個係將第一篇(評測論)同第三篇(版本論)串起嚟嘅關鍵——你嘅版本記錄裏邊除咗"改咗啲乜",仲要有"改完之後各項指標係幾多",呢個先係真正嘅版本管理。


四、最小可觀測性實現方案

理論講完咗,畀一個今日就用得嘅最小方案。

4.1 結構化日誌模板

唔需要引入複雜嘅追蹤系統,先從結構化日誌開始。每次Agent執行,強制寫一條JSON日誌:

def log_agent_run(task_id, model, input_text, tool_calls, output, error, start_time):
    log_entry = {
        "ts": datetime.utcnow().isoformat(),
        "task_id": task_id,
        "model": model,
        "input": input_text[:500],          # 截斷,避免日誌爆炸
        "output": str(output)[:500if output elseNone,
        "tools": [
            {
                "name": t["name"],
                "args": str(t["args"])[:200],
                "status": t["status"],
                "duration_ms": t["duration_ms"]
            }
            for t in tool_calls
        ],
        "tokens": {
            "input": getattr(usage, "input_tokens"0),
            "output": getattr(usage, "output_tokens"0)
        },
        "duration_ms": int((time.time() - start_time) * 1000),
        "status""error"if error else"success",
        "error": str(error) if error elseNone
    }
    logger.info(json.dumps(log_entry, ensure_ascii=False))

呢個模板20行,零依賴,今日就加得入去。

4.2 三個必須設置嘅告警閾值

有咗日誌,下一步係設閾值——唔係所有失敗都值得你即刻處理,但有三類問題必須即刻知道:

指標
告警閾值
說明
工具調用失敗率
>10%
工具層問題,同模型無關
單任務Token消耗
>預期3倍
模型喺"繞路",通常係上下文問題
任務整體失敗率
>5%
系統性問題,需要即刻排查

4.3 Trace嘅最簡實現

唔想引入OpenTelemetry呢類完整方案嘅話,用一個簡單嘅context對象追蹤調用鏈就夠啦:

class AgentTrace:
    def __init__(self, task_id):
        self.task_id = task_id
        self.steps = []
        self.start_time = time.time()

    def add_step(self, step_type, content, status="ok", duration_ms=0):
        self.steps.append({
            "seq": len(self.steps) + 1,
            "type": step_type,      # "model_call" / "tool_call" / "decision"
            "content": str(content)[:300],
            "status": status,
            "elapsed_ms": int((time.time() - self.start_time) * 1000),
            "duration_ms": duration_ms
        })

    def dump(self):
        return {
            "task_id": self.task_id,
            "total_steps": len(self.steps),
            "total_duration_ms": int((time.time() - self.start_time) * 1000),
            "steps": self.steps
        }

用法:每次模型推理、每次工具調用,調一次trace.add_step(),任務結束後將trace.dump()寫入日誌。出問題時,你睇到完整嘅執行序列,唔使再估。

4.4 可觀測性建設嘅優先級

如果你從零開始,按呢個順序推進,唔好一嚟就想做全套:

第1步:結構化日誌(1天)
  → 先能看到"發生了什麼"

第2步:失敗率監控 + 告警(2天)
  → 先能感知"哪裏壞了"

第3步:Trace鏈路追蹤(1周)
  → 再能理解"為什麼壞了"

第4步:評測層嵌入迭代流程(持續)
  → 最後能預防"會不會壞"

每一步都獨立帶嚟價值,唔好等"全做曬"先上線。


五、兩個成日俾人忽視嘅可觀測性盲區

5.1 模型版本漂移

你嘅日誌裏邊,有冇記錄每次調用係用邊個模型版本?

好多團隊冇。然後有一日發現Agent行為突然變咗,揾咗兩日先意識到——API提供商靜靜雞將默認模型從gpt-5.5升級到咗gpt-5.5-turbo,行為有細微差異。

修復好簡單:每條日誌強制寫model_version字段,用精確版本號,唔好用別名。

5.2 工具返回內容嘅質量

大多數日誌淨係記錄工具調用是否成功,唔記錄返回內容嘅質量。

但"工具成功返回咗污糟數據"呢種情況,比"工具調用失敗"更危險——因為佢唔會觸發任何報錯,只會靜靜雞污染後續推理。

修復方案:對關鍵工具嘅返回內容加採樣檢查,例如每100次調用抽5次,人工review返回內容是否符合預期。呢個成本好低,但捕捉到大量"成功但有問題"嘅案例。


六、可觀測性嘅終極目標

加日誌、做Trace、設告警——呢啲都係手段,終極目標得一個:

令Agent嘅行為對你嚟講係可理解嘅。

唔係話Agent要變成確定性程序,佢本來就唔係。而係話:當佢出咗問題,你能夠喺30分鐘內定位根因;當佢表現變好或變差,你能夠講清楚係咩改動導致嘅。

一個你理解到嘅Agent,先至係一個你信得過嘅Agent。

冇可觀測性,你唔係在營運一個AI系統,你係在養一個黑盒寵物——佢表現好咗你開心,表現差咗你唯有抱怨,但你改變唔到任何嘢。


我係專注 AI Agent深度實踐嘅努力撞蘑菇AI,致力於分享真實可用嘅 AI 使用經驗與工作流探索,歡迎你同我一齊將 AI 玩得明明白白,如果內容對你有幫助,歡迎關注、點讚、轉發。


一、一個讓你崩潰的早晨

線上Agent昨晚跑了300個任務,早上你打開看結果——其中47個失敗了。

錯誤日誌只有一行:Tool call failed: timeout

你不知道:

  • 是哪個工具超時了?
  • 超時之前模型做了什麼?
  • 是偶發的還是系統性的?
  • 同樣的輸入,有沒有成功過?

你只能一個個重跑,然後盯着屏幕猜。

這就是沒有可觀測性的Agent:能跑,但跑壞了你什麼都看不見。

圖片


二、為什麼Agent比普通程序更難調試

傳統程序出了問題,你打開日誌,看堆棧,定位到第幾行,基本能找到原因。

Agent不一樣,它的執行路徑是動態的、非確定性的

非確定性執行同樣的輸入,不同時刻跑出來的結果可能不一樣。不是bug,是模型本身的特性。這意味着你沒辦法簡單地"重現問題"——你重跑十次,可能只有三次復現。

多步驟鏈路一個Agent任務可能包含:理解輸入 → 規劃步驟 → 調用工具A → 處理返回 → 調用工具B → 生成輸出。每一步都可能出錯,而且前一步的輸出直接影響後一步的行為。鏈路越長,定位越難。

模型推理不透明工具調用失敗,你能看到。但"模型理解錯了輸入"這種問題,沒有任何系統級的報錯,你只能從最終輸出倒推——而這往往需要你把整個推理過程重新走一遍。

上下文污染多輪對話場景裏,第三輪的奇怪行為可能根源在第一輪某個工具返回了髒數據。沒有完整的上下文追蹤,你永遠找不到真正的源頭。


三、可觀測性的三個層次

Agent的可觀測性不是"加幾行print"能解決的,它有三個層次,缺一不可。

3.1 日誌層:發生了什麼

最基礎的層次,記錄Agent做了什麼。

必須記錄的最小集合:

任務ID
輸入內容(截斷到合理長度)
開始時間 / 結束時間
執行狀態(成功/失敗/超時)
使用的模型版本
Token消耗(輸入/輸出分開記)
工具調用列表(名稱 + 參數摘要 + 返回狀態)
最終輸出(截斷到合理長度)
錯誤信息(如有)

注意兩個細節:

  • 輸入輸出都要記,不能只記錯誤。很多詭異的失敗,原因在輸入本身,不在執行過程。
  • 工具調用要記參數,不能只記"調用了tool_A"。同一個工具,參數不同,結果天差地別。

一個常見的反面教材:

# 錯誤示範:只記結果
logger.info(f"Task {task_id}{'success' if result else 'failed'}")

# 正確示範:記完整鏈路
logger.info({
    "task_id": task_id,
    "model": model_version,
    "input_preview": input_text[:200],
    "tool_calls": [{"name": t.name, "args_preview": str(t.args)[:100], "status": t.status} for t in tool_calls],
    "tokens": {"input": usage.input_tokens, "output": usage.output_tokens},
    "duration_ms": elapsed,
    "status""success"if result else"failed",
    "error": str(error) if error elseNone
})

3.2 Trace層:怎麼走到這一步的

日誌告訴你發生了什麼,Trace告訴你為什麼會走到這一步

Trace的核心是把Agent的每一步推理和工具調用,串成一條有時序關係的鏈路。

Task #47
├── [00:00] 接收輸入:"分析Q2銷售數據,找出異常"
├── [00:01] 模型規劃:決定調用data_query工具
├── [00:02] 調用 data_query(table="sales", period="Q2") → 返回3000行數據
├── [00:05] 模型分析:發現第847行數據異常,決定調用verify工具
├── [00:06] 調用 verify(row_id=847) → TIMEOUT(30s後)
├── [00:36] 重試 verify(row_id=847) → TIMEOUT
└── [00:66] 任務失敗:工具連續超時

這個結構讓你一眼看出:不是模型的問題,是verify工具在特定參數下會超時。你需要去查verify的實現,而不是改Prompt。

四個真實case,Trace幫你定位了什麼:

Case 1:模型"繞路"問題表現:任務耗時比預期長3倍,Token消耗異常高。 沒有Trace時:以為是模型變慢了,或者換了更貴的模型。 有Trace後發現:模型在第二步調用工具後,對返回結果不滿意,自己又調了兩次額外的驗證工具,這不是預期行為。 根因:工具返回格式不穩定,模型產生了不確定性,觸發了"自我驗證"行為。 解決:規範工具返回格式,加置信度字段,模型就不再重複驗證了。

Case 2:上下文污染問題表現:多輪對話場景,第5輪開始輸出質量急劇下降。 沒有Trace時:以為是第5輪的輸入有問題,反覆改Prompt無效。 有Trace後發現:第3輪某個工具返回了一段包含錯誤假設的文本,模型把它當成事實納入了上下文,後續所有推理都基於這個錯誤假設展開。 根因:工具返回的內容沒有做格式校驗,混入了描述性語言。 解決:工具返回加嚴格的Schema校驗,描述性內容和結構化數據分開字段。

Case 3:工具選擇偏差問題表現:同一類任務,30%的概率調用了錯誤的工具,導致結果質量低。 沒有Trace時:只能從最終輸出看出"有時候結果不對",完全摸不着頭腦。 有Trace後發現:模型在兩個功能相近的工具之間選擇時,遇到邊界case就會選錯。 根因:兩個工具的描述文本語義重疊,模型無法區分。 解決:重寫工具描述,加"使用場景"和"不適用場景"兩個字段,選擇準確率從70%提升到97%。

Case 4:靜默失敗問題表現:任務顯示成功,但輸出是空的。 沒有Trace時:完全不知道哪裏出了問題,日誌顯示"success"。 有Trace後發現:工具調用返回了空列表(正常情況,表示無數據),但模型把"空列表"解讀為"查詢成功但沒有結果",沒有觸發任何錯誤處理,直接輸出了空內容。 根因:空結果和錯誤結果在工具返回裏沒有區分,模型處理邏輯不一致。 解決:空結果返回專門的狀態碼,Prompt裏明確指定空結果時的處理行為。


3.3 評測層:表現是否在變好

日誌和Trace是事後的,評測層是持續的健康監測

不是一次性跑評測,而是把評測內嵌到每次迭代裏:

每次改動Prompt或工具 → 自動跑回歸測試集
對比本次 vs 上次的:
  - 準確率變化
  - 平均Token消耗變化
  - 工具調用次數變化
  - 超時/失敗率變化
有任何指標下降超過閾值 → 自動告警

這是把第一篇(評測論)和第三篇(版本論)串起來的關鍵——你的版本記錄裏除了"改了什麼",還要有"改完之後各項指標是多少",這才是真正的版本管理。


四、最小可觀測性實現方案

理論說完了,給一個今天就能用的最小方案。

4.1 結構化日誌模板

不需要引入複雜的追蹤系統,先從結構化日誌開始。每次Agent執行,強制寫一條JSON日誌:

def log_agent_run(task_id, model, input_text, tool_calls, output, error, start_time):
    log_entry = {
        "ts": datetime.utcnow().isoformat(),
        "task_id": task_id,
        "model": model,
        "input": input_text[:500],          # 截斷,避免日誌爆炸
        "output": str(output)[:500if output elseNone,
        "tools": [
            {
                "name": t["name"],
                "args": str(t["args"])[:200],
                "status": t["status"],
                "duration_ms": t["duration_ms"]
            }
            for t in tool_calls
        ],
        "tokens": {
            "input": getattr(usage, "input_tokens"0),
            "output": getattr(usage, "output_tokens"0)
        },
        "duration_ms": int((time.time() - start_time) * 1000),
        "status""error"if error else"success",
        "error": str(error) if error elseNone
    }
    logger.info(json.dumps(log_entry, ensure_ascii=False))

這個模板20行,零依賴,今天就能加進去。

4.2 三個必須設置的告警閾值

有了日誌,下一步是設閾值——不是所有失敗都值得你立刻處理,但有三類問題必須立刻知道:

指標
告警閾值
說明
工具調用失敗率
>10%
工具層問題,和模型無關
單任務Token消耗
>預期3倍
模型在"繞路",通常是上下文問題
任務整體失敗率
>5%
系統性問題,需要立刻排查

4.3 Trace的最簡實現

不想引入OpenTelemetry這類完整方案的話,用一個簡單的context對象追蹤調用鏈就夠了:

class AgentTrace:
    def __init__(self, task_id):
        self.task_id = task_id
        self.steps = []
        self.start_time = time.time()

    def add_step(self, step_type, content, status="ok", duration_ms=0):
        self.steps.append({
            "seq": len(self.steps) + 1,
            "type": step_type,      # "model_call" / "tool_call" / "decision"
            "content": str(content)[:300],
            "status": status,
            "elapsed_ms": int((time.time() - self.start_time) * 1000),
            "duration_ms": duration_ms
        })

    def dump(self):
        return {
            "task_id": self.task_id,
            "total_steps": len(self.steps),
            "total_duration_ms": int((time.time() - self.start_time) * 1000),
            "steps": self.steps
        }

用法:每次模型推理、每次工具調用,調一次trace.add_step(),任務結束後把trace.dump()寫進日誌。出問題時,你能看到完整的執行序列,不用再猜。

4.4 可觀測性建設的優先級

如果你從零開始,按這個順序推進,不要一上來就想做全套:

第1步:結構化日誌(1天)
  → 先能看到"發生了什麼"

第2步:失敗率監控 + 告警(2天)
  → 先能感知"哪裏壞了"

第3步:Trace鏈路追蹤(1周)
  → 再能理解"為什麼壞了"

第4步:評測層嵌入迭代流程(持續)
  → 最後能預防"會不會壞"

每一步都能獨立帶來價值,不要等"全做完了"才上線。


五、兩個經常被忽視的可觀測性盲區

5.1 模型版本漂移

你的日誌裏,有沒有記錄每次調用用的是哪個模型版本?

很多團隊沒有。然後某天發現Agent行為突然變了,找了兩天才意識到——API提供商悄悄把默認模型從gpt-5.5升級到了gpt-5.5-turbo,行為有細微差異。

修復很簡單:每條日誌強制寫model_version字段,用精確版本號,不用別名。

5.2 工具返回內容的質量

大多數日誌只記錄工具調用是否成功,不記錄返回內容的質量。

但"工具成功返回了髒數據"這種情況,比"工具調用失敗"更危險——因為它不會觸發任何報錯,只會悄悄污染後續推理。

修復方案:對關鍵工具的返回內容加採樣檢查,比如每100次調用抽5次,人工review返回內容是否符合預期。這個成本很低,但能捕捉到大量"成功但有問題"的案例。


六、可觀測性的終極目標

加日誌、做Trace、設告警——這些都是手段,終極目標只有一個:

讓Agent的行為對你來說是可理解的。

不是說Agent要變成確定性程序,它本來就不是。而是說:當它出了問題,你能在30分鐘內定位根因;當它表現變好或變差,你能說清楚是什麼改動導致的。

一個你能理解的Agent,才是一個你能信任的Agent。

沒有可觀測性,你不是在運營一個AI系統,你是在養一個黑盒寵物——它表現好了你高興,表現差了你只能抱怨,但你改變不了任何事情。


我是專注 AI Agent深度實踐的努力撞蘑菇AI,致力於分享真實可用的 AI 使用經驗與工作流探索,歡迎你和我一起把 AI 玩明白,如果內容對你有幫助,歡迎關注、點贊、轉發。