Agent工程化 · 第五篇:Agent黑盒怎麼打開
整理版優先睇
要讓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等字段,零依賴,今日就能加。
簡單AgentTrace類
用context對象追蹤調用鏈,每次模型推理或工具調用記錄步驟,任務結束後寫入日誌。
Agent黑盒嘅調試困境
想像一個早晨:你嘅Agent尋晚跑咗300個任務,今朝打開睇,47個失敗咗。錯誤日誌得一句「Tool call failed: timeout」。你唔知邊個工具超時、超時前模型做咗咩、係偶發定系統性、同樣輸入有冇成功過。呢個就係冇可觀測性嘅Agent——佢行得鬱,但壞咗你完全睇唔到嘢。
- 非確定性執行:同一輸入,重跑十次可能只復現三次錯誤。
- 多步驟鏈路:理解輸入→規劃→調用工具A→處理返回→調用工具B→生成輸出,每一步都可能出錯,前一步影響後一步。
- 模型推理唔透明:工具調用失敗睇到,但「模型理解錯輸入」呢類問題冇系統報錯,只能靠最終輸出倒推。
- 上下文污染:第三輪奇怪行為可能源於第一輪工具返回嘅髒數據,冇完整追蹤就揾唔到源頭。
非確定性執行
多步驟鏈路
模型推理唔透明
上下文污染
可觀測性嘅三個層次
作者提出可觀測性唔係加幾行print就解決到,而係要分三個層次,缺一不可。由最基本嘅日誌,到揭示原因嘅Trace,再到持續監測嘅評測。
輸入輸出都要記,唔好淨係記錯誤
工具調用要記參數,唔好淨係記名
- 1 日誌層:記錄發生咗咩,最小集合包括任務ID、輸入/輸出、時間、狀態、模型版本、Token消耗、工具調用列表。
- 2 Trace層:解釋點樣走到呢一步,將每一步推理同工具調用串成有時序嘅鏈路,例如Task #47入面每一步嘅時間同結果。
- 3 評測層:持續監測表現係咪變好,每次改動都自動跑回歸測試,對比準確率、Token消耗、工具調用次數、失敗率等指標,跌過閾值就告警。
Trace層特別重要,文章用四個真實案例說明:模型繞路、上下文污染、工具選擇偏差、靜默失敗。冇Trace嘅時候你完全摸不着頭腦,有Trace之後一眼睇出問題所在。
模型繞路
上下文污染
工具選擇偏差
靜默失敗
今日就能用嘅最小方案
理論講完,作者提供一個立即可以落手嘅方案。先從結構化日誌開始,唔需要複雜追蹤系統,強制每條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)[: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寫入日誌。
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)[:500] if 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 三個必須設置嘅告警閾值
有咗日誌,下一步係設閾值——唔係所有失敗都值得你即刻處理,但有三類問題必須即刻知道:
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系統,你係在養一個黑盒寵物——佢表現好咗你開心,表現差咗你唯有抱怨,但你改變唔到任何嘢。
一、一個讓你崩潰的早晨
線上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)[:500] if 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 三個必須設置的告警閾值
有了日誌,下一步是設閾值——不是所有失敗都值得你立刻處理,但有三類問題必須立刻知道:
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系統,你是在養一個黑盒寵物——它表現好了你高興,表現差了你只能抱怨,但你改變不了任何事情。