Skip to content
Guides8 min read

逐步錯誤定位:用軌跡評估找出智慧體究竟在哪一步出錯

用 Potato 的 trajectory_eval 方案做逐步錯誤定位:分層錯誤分類體系、嚴重程度評分,以及貫穿整條智慧體 trace 的連續評分。

Potato Team

問題所在:只知道智慧體失敗了,遠遠不夠

A hierarchical agent error taxonomy with four categories and a severity scaleA trajectory error taxonomy

你在某個基準上跑了自己的智慧體,任務完成率 63%。然後呢?

一個通過/失敗的數字只告訴你有 37% 的任務失敗了,別的什麼都說明不了。它不會告訴你 trace 中哪個位置出了問題、智慧體犯的是哪一類錯誤、錯得有多嚴重。是第 2 步上一個災難性的失誤,還是十五個小推理錯誤一路累積?智慧體是用錯了工具,還是從一個錯誤前提出發推理?

沒有逐步的錯誤定位,你既沒法診斷失敗模式,也沒法判斷該先修哪裡,更沒法為過程獎勵模型攢訓練資料。調超參數基本靠猜。

Potato 的 trajectory_eval 標註方案解決的正是這件事。標註者逐步走完一條 trace,並記錄:

  • 正確性:這一步是對是錯?
  • 錯誤類型:從你定義的分層分類體系中選取
  • 嚴重程度:輕微、嚴重或致命,分數權重可配置
  • 理由:對錯誤的自由文本說明(可選)
  • 連續評分:一個累計分數,按嚴重程度遞減,給出整條 trace 的品質曲線

本文覆蓋完整的搭建流程:定義錯誤分類體系、跑標註、分析收集到的資料。該方案的配置參考見源文件


trajectory_eval 方案概覽

trajectory_eval 方案是為按順序評估多步驟智慧體 trace 而設計的。它不給一個總體品質評分,而是為每一步生成一條結構化的錯誤標註,最終得到一張詳細的地圖,標出智慧體在哪裡、因為什麼失敗。

標註介面在每一步的流程是這樣的:

  1. 標註者看到當前步驟的內容(thought、action、observation、程式碼等)
  2. 把這一步標記為正確不正確
  3. 如果不正確,從分層分類體系中選出錯誤類型
  4. 指定嚴重程度(輕微、嚴重或致命)
  5. 可選地寫一段理由說明這個錯誤
  6. 介面頂部的連續評分自動更新

標註者一步一步走完整條 trace,逐漸構建出完整的錯誤畫像。

軌跡評估介面會在每一步旁邊顯示對應的分數:

Trajectory evaluation with running score trackerEach step gets a correctness rating, error type, and severity level with a running score that decrements based on severity


設計分層錯誤分類體系

分類體系是軌跡評估的價值所在。設計得好,你可以跨 trace 彙總錯誤、看出系統性的失敗模式;設計得差,標出來的標籤拼不出任何結論。下面是我會作為起點的一套體系,共四個頂層類別。

推理錯誤

即使智慧體看到的和做的都沒問題,推理本身也可能出錯。

錯誤類型說明示例
logical_error無效的邏輯推斷“A 蘊含 B,B 為真,所以 A 必為真”(肯定後件)
incorrect_assumption假定了證據並不支援的事情沒做檢查就假定某個檔案存在
over_generalization從有限證據得出過寬的結論“這個函式失敗了一次,所以整個 API 都壞了”
circular_reasoning把結論當作前提“答案是 X,因為 X 是正確的”
incorrect_calculation數學或邏輯計算錯誤推理迴圈邊界時差一錯誤

感知錯誤

智慧體讀錯、理解錯或漏掉了觀測中的資訊。

錯誤類型說明示例
missed_element沒注意到相關資訊忽略了終端輸出裡的報錯資訊
misidentified_element誤解了自己看到的東西把 404 錯誤當成成功響應
hallucinated_element引用了並不存在的東西引用了一個不存在的函式參數
outdated_reference沿用了前面步驟中已過時的資訊使用了一個已被覆蓋的變數值

動作錯誤

智慧體做了錯誤的動作,或者用錯誤的方式做了正確的動作。

錯誤類型說明示例
wrong_tool為任務選了不合適的工具該用 find 時用了 grep
wrong_arguments工具選對了但參數不對給編輯命令傳了錯誤的檔案路徑
premature_termination任務沒完成就停下只找到部分資訊就返回答案
unnecessary_action做了沒有任何價值的動作重複讀取剛剛讀過的檔案
destructive_action做了造成破壞的動作未備份就刪除檔案

溝通錯誤

這類錯誤出現在智慧體回覆使用者的內容裡,或者它對自己工作的敘述中。

錯誤類型說明示例
unclear_explanation解釋含混或有歧義描述了修復方案卻沒說原來哪裡壞了
missing_context回覆中漏掉了關鍵上下文報告成功卻不提附帶的限制
incorrect_summary總結與實際動作不符只改了 2 個檔案卻聲稱改了 3 個
overconfident_claim把不確定說成確定對未經測試的改動說“這肯定能解決問題”

嚴重程度與分數權重

每個錯誤都要指定嚴重程度。預設權重如下:

嚴重程度權重說明
minor-1不會讓整條 trace 跑偏的小問題(例如多餘動作、解釋不清)
major-5浪費工作量或產出部分錯誤結果的明顯錯誤(例如用錯工具、錯誤假設)
critical-10從根本上毀掉整條 trace 的錯誤(例如破壞性動作、給出錯誤答案後提前終止)

連續評分從 100 起算,每出現一個錯誤就按其嚴重程度權重扣分。最終停在 85 的 trace 只有幾個小問題;停在 40 的則出過好幾次嚴重失敗。

這些權重可以在配置中修改:

yaml
severity_levels:
  - name: minor
    weight: -1
    description: "Small issue, does not derail the overall trace"
  - name: major
    weight: -5
    description: "Significant error that wastes effort or produces wrong intermediate results"
  - name: critical
    weight: -10
    description: "Fundamental failure that breaks the trace or causes harm"

完整 YAML 配置

下面是一份用於軌跡評估的完整 config.yaml,包含完整的分類體系:

yaml
annotation_task_name: "Agent Trajectory Error Localization"
 
data_files:
  - "data/traces.jsonl"
 
item_properties:
  id_key: "trace_id"
  text_key: "task"
 
# Display agent traces with step-by-step rendering
display:
  type: "agent_trace"
  trace_key: "trace"
  step_display:
    thought: { label: "Thought", color: "#E8F0FE" }
    action: { label: "Action", color: "#FFF3E0" }
    observation: { label: "Observation", color: "#F1F8E9" }
    code: { label: "Code", color: "#F3E5F5" }
 
annotation_schemes:
  - annotation_type: "trajectory_eval"
 
    # Per-step correctness check
 
    # Hierarchical error taxonomy (shown when step is marked incorrect)
 
      - category: "perception"
        label: "Perception Error"
        types:
          - name: "missed_element"
            label: "Missed Element"
            description: "Fails to notice relevant information in observations"
          - name: "misidentified_element"
            label: "Misidentified Element"
            description: "Misinterprets what it observes"
          - name: "hallucinated_element"
            label: "Hallucinated Element"
            description: "Refers to something not present in the context"
          - name: "outdated_reference"
            label: "Outdated Reference"
            description: "Uses stale information from a previous step"
 
      - category: "action"
        label: "Action Error"
        types:
          - name: "wrong_tool"
            label: "Wrong Tool"
            description: "Selects an inappropriate tool for the task"
          - name: "wrong_arguments"
            label: "Wrong Arguments"
            description: "Correct tool but incorrect parameters"
          - name: "premature_termination"
            label: "Premature Termination"
            description: "Stops before the task is complete"
          - name: "unnecessary_action"
            label: "Unnecessary Action"
            description: "Takes an action that adds no value"
          - name: "destructive_action"
            label: "Destructive Action"
            description: "Takes an action that causes harm or data loss"
 
      - category: "communication"
        label: "Communication Error"
        types:
          - name: "unclear_explanation"
            label: "Unclear Explanation"
            description: "Explanation is confusing or ambiguous"
          - name: "missing_context"
            label: "Missing Context"
            description: "Omits critical context from the response"
          - name: "incorrect_summary"
            label: "Incorrect Summary"
            description: "Summary does not match the actual actions taken"
          - name: "overconfident_claim"
            label: "Overconfident Claim"
            description: "States uncertain outcomes as certainties"
 
    # Severity levels with score weights
 
    # Running score configuration
 
    # Optional rationale field
 
  # Overall trace quality (optional, shown at the end)
  - annotation_type: "radio"
 
# Annotator settings
annotator_config:
  allow_back_navigation: true
  require_all_steps: true
 
# Output settings
output:
  path: "output/"
  format: "jsonl"

分步搭建

1. 準備智慧體 trace

trace 資料用 JSONL 格式,一行一條。每條 trace 需要一個 ID、一段任務描述和一個步驟列表:

json
{
  "trace_id": "trace_042",
  "task": "Find the bug in the calculate_discount function and fix it",
  "trace": [
    {
      "type": "thought",
      "content": "I need to look at the calculate_discount function to find the bug. Let me search for it."
    },
    {
      "type": "action",
      "content": "search",
      "action_input": "def calculate_discount"
    },
    {
      "type": "observation",
      "content": "Found in pricing.py line 45:\ndef calculate_discount(price, discount_pct):\n    return price * discount_pct / 100"
    },
    {
      "type": "thought",
      "content": "The function calculates the discount amount but doesn't subtract it from the price. It should return price - (price * discount_pct / 100)."
    },
    {
      "type": "action",
      "content": "edit_file",
      "action_input": "pricing.py:45: return price - (price * discount_pct / 100)"
    },
    {
      "type": "observation",
      "content": "File edited successfully."
    },
    {
      "type": "action",
      "content": "finish",
      "action_input": "Fixed the calculate_discount function. It was returning the discount amount instead of the discounted price."
    }
  ]
}

如果你的 trace 是別的格式(OpenAI messages、LangChain 執行記錄、Claude 對話日誌),可以用 Potato 的 trace 轉換器:

bash
python -m potato.trace_converter \
  --input raw_traces/ \
  --output data/traces.jsonl \
  --input-format react

2. 配置分類體系

先用上面這套完整體系,再按自己的智慧體裁剪或擴充。比如針對編碼智慧體,可以加一個 code_quality 類別:

yaml
- category: "code_quality"
  label: "Code Quality Error"
  types:
    - name: "syntax_error"
      label: "Syntax Error"
      description: "Generated code has syntax errors"
    - name: "runtime_error"
      label: "Runtime Error"
      description: "Code runs but produces an error"
    - name: "logic_bug"
      label: "Logic Bug"
      description: "Code runs without errors but produces wrong output"
    - name: "style_violation"
      label: "Style Violation"
      description: "Code works but violates project conventions"

對於編碼智慧體的 trace,評估介面會在評分控制元件旁邊渲染 diff 和終端輸出:

Coding agent evaluation with diff renderingCodingTraceDisplay renders diffs, terminal blocks, and file reads alongside trajectory evaluation controls

3. 啟動標註伺服器

bash
potato start config.yaml -p 8000

在瀏覽器中開啟 http://localhost:8000,你會看到第一條 trace 以分步形式展示。

4. 寫標註指南

要給標註者清楚的說明。至少要寫明:

  • 什麼情況算不正確,什麼情況算正確但不是最優
  • 多個錯誤類別同時適用時怎麼選(取最具體的那個)
  • 各個嚴重程度分別在什麼情況下使用,並給出具體例子
  • 評估某一步時,是隻依據當時可得的資訊,還是可以事後回看

標註流程

標註者開啟一條 trace 時,任務描述在最上方,第一步緊隨其後。右上角的連續評分顯示 100

對於每一步,標註者要:

  1. 結合前面的步驟讀懂當前步驟的內容
  2. 判定正確性,點選“Correct”或“Incorrect”
  3. 如果不正確,先選錯誤類別(例如“Reasoning Error”),再選具體類型(例如“Incorrect Assumption”)
  4. 指定嚴重程度:輕微、嚴重或致命
  5. 寫理由(如果啟用):“智慧體沒做檢查就假定檔案在當前目錄下,但搜尋結果顯示它在 src/utils/ 裡”
  6. 進入下一步,點選“Next Step”或按右方向鍵

每出現一個錯誤,連續評分就更新一次。把第 3 步標為嚴重錯誤(-5),分數從 100 降到 95;把第 7 步標為致命錯誤(-10),再降到 85。

走完整條 trace 後,標註者給出總體的成功/部分成功/失敗評價並提交。


分析結果

載入標註資料

python
import json
import pandas as pd
from collections import Counter
from pathlib import Path
 
# Load all annotation files
annotations = []
output_dir = Path("output/")
for f in output_dir.glob("*.jsonl"):
    with open(f) as fh:
        for line in fh:
            annotations.append(json.loads(line))
 
print(f"Loaded {len(annotations)} annotated traces")

錯誤分佈分析

python
# Extract all errors across all traces
errors = []
for ann in annotations:
    for step_ann in ann.get("error_localization", []):
        if step_ann["correctness"] == "incorrect":
            errors.append({
                "trace_id": ann["trace_id"],
                "step_index": step_ann["step_index"],
                "category": step_ann["error_category"],
                "error_type": step_ann["error_type"],
                "severity": step_ann["severity"],
                "rationale": step_ann.get("rationale", ""),
            })
 
error_df = pd.DataFrame(errors)
print(f"Total errors found: {len(error_df)}")
print()
 
# Error distribution by category
print("Errors by category:")
print(error_df["category"].value_counts())
print()
 
# Most common specific error types
print("Top 10 error types:")
print(error_df["error_type"].value_counts().head(10))
print()
 
# Severity distribution
print("Severity distribution:")
print(error_df["severity"].value_counts())

錯誤位置分析

看看錯誤在 trace 中落在什麼位置,常常能暴露出系統性的規律:

python
import matplotlib.pyplot as plt
import numpy as np
 
# Normalize step positions to [0, 1] range
for ann in annotations:
    trace_length = len(ann.get("error_localization", []))
    for step_ann in ann["error_localization"]:
        if step_ann["correctness"] == "incorrect":
            step_ann["normalized_position"] = step_ann["step_index"] / max(trace_length - 1, 1)
 
# Collect normalized positions
positions = [
    step_ann["normalized_position"]
    for ann in annotations
    for step_ann in ann.get("error_localization", [])
    if step_ann["correctness"] == "incorrect"
    and "normalized_position" in step_ann
]
 
plt.figure(figsize=(10, 4))
plt.hist(positions, bins=20, edgecolor="black", alpha=0.7)
plt.xlabel("Normalized Position in Trace (0 = start, 1 = end)")
plt.ylabel("Error Count")
plt.title("Where Do Agent Errors Occur?")
plt.tight_layout()
plt.savefig("error_position_distribution.png", dpi=150)
print("Saved error_position_distribution.png")

連續評分的分佈

python
# Extract final running scores
final_scores = []
for ann in annotations:
    score = 100
    severity_weights = {"minor": -1, "major": -5, "critical": -10}
    for step_ann in ann.get("error_localization", []):
        if step_ann["correctness"] == "incorrect":
            score += severity_weights.get(step_ann["severity"], 0)
    score = max(score, 0)
    final_scores.append({
        "trace_id": ann["trace_id"],
        "final_score": score,
        "overall_success": ann.get("overall_success", "unknown"),
    })
 
score_df = pd.DataFrame(final_scores)
 
print("Score statistics:")
print(score_df["final_score"].describe())
print()
 
# Score distribution by overall success
for label in ["success", "partial", "failure"]:
    subset = score_df[score_df["overall_success"] == label]
    if len(subset) > 0:
        print(f"{label}: mean={subset['final_score'].mean():.1f}, "
              f"median={subset['final_score'].median():.1f}, "
              f"n={len(subset)}")

最常見的失敗模式

python
# Group errors by category + type for a failure mode analysis
failure_modes = (
    error_df.groupby(["category", "error_type"])
    .agg(
        count=("severity", "size"),
        avg_severity_weight=("severity", lambda x: x.map(
            {"minor": 1, "major": 5, "critical": 10}
        ).mean()),
    )
    .sort_values("count", ascending=False)
)
 
print("Top failure modes (by frequency):")
print(failure_modes.head(15).to_string())
print()
 
# Impact-weighted failure modes (frequency x average severity)
failure_modes["impact"] = failure_modes["count"] * failure_modes["avg_severity_weight"]
print("Top failure modes (by impact):")
print(failure_modes.sort_values("impact", ascending=False).head(10).to_string())

研究背景

逐步錯誤定位與近年智慧體評估的幾條研究線索是對得上的:

TRAIL(Patronus AI,2025)用一套超過 20 種錯誤類型的分類體系,標註了取自 GAIA 與 SWE-bench Lite 的 148 條智慧體蹤跡,合計 841 處錯誤。值得記住的結果是定位有多難:他們測過的最好的長上下文推理模型,在錯誤類別加位置上的聯合準確率只有 11%。這正是 trajectory_eval 交給人類標註員的活兒,也是這些標籤值得花錢的原因。

AgentRewardBench(McGill NLP,2025)盯的則是裁判本身。它收集了跨五個基準的 1302 條 Web 智慧體軌跡,讓專家逐條從成功、副作用和重複三方面複核,再拿這些複核去給十二個 LLM 裁判打分。沒有哪個裁判在所有基準上都領先,而基準自帶的規則式評估還低報了智慧體實際成功的次數。如果你打算用模型自動化這套分類體系的一部分,你需要的就是這種形式的核對。

trajectory_eval 產出的步驟級正確性與嚴重度標籤,也能直接餵給過程獎勵模型的訓練:每個標註過的步驟都是一個帶有真值品質訊號的訓練樣本。

Anthropic 的 Demystifying evals for AI agents 給出了同一主張的操作版本:不要只評結果,也要評整段記錄;對於智慧體如何呼叫工具、如何與使用者對話,使用帶明確評分細則的模型評分器。它同時提醒不要照著預設的步驟序列打分,因為智慧體總能找到評測設計者沒料到的有效路徑。套用這套分類體系時請記住這一點:某一步算錯,是因為它做錯了,而不是因為它出乎意料。

按嚴重程度加權的連續評分也能對應到 RLHF 中使用的獎勵訊號。一條 20 步的 trace,如果分數曲線在第 5 步陡然下跌,就直接指明瞭智慧體需要改進的地方,這比整條 trace 結束時給一個總獎勵要有用得多。


小結

trajectory_eval 方案把智慧體評估從一次通過/失敗的檢查變成了一次診斷。有了分層分類體系、嚴重程度評分和連續評分,你能看清是哪一步出了錯、錯的類型是什麼、有多嚴重,以及錯誤在不同 trace 中傾向於聚集在哪些位置。這些步驟級標籤也可以直接用作過程獎勵模型的訓練資料。

先用本文給出的完整分類體系起步,再根據你的智慧體和實際觀察到的錯誤模式去打磨。最好的分類體系,是那個能指向你真正改得動的修復方向的體系。