在 Potato 中審閱 Langfuse 軌跡
通過輪詢 Langfuse 項目或轉換匯出檔案,把 Langfuse 軌跡拉進 Potato,讓多名評審者審閱每條軌跡,並把答案作為分數寫回。
Potato 以兩種方式讀取 Langfuse 軌跡:輪詢 Langfuse 項目中已完成的軌跡,或用 python -m potato.trace_converter --input-format langfuse 轉換軌跡檔案。被輪詢到的軌跡,其評審者合併後的答案會作為分數寫回 Langfuse,掛在該軌跡和每個被評判的觀測(observation)上;轉換得到的軌跡,其結果保留在 Potato 的匯出檔案中。
Langfuse 是一個開源、可自託管的 AI 工程平臺,用於除錯和評估 LLM 與智慧體應用,Langfuse 倉庫在 ee 資料夾之外採用 MIT 許可。在 Langfuse 資料模型中,一條軌跡(trace)代表一次請求或操作。觀測是軌跡內的一個步驟,例如一次 LLM 呼叫、一次工具呼叫或一個檢索步驟,觀測之間相互巢狀,以反映應用的結構。會話(session)把同一次使用者互動產生的多條軌跡歸在一起,分數則是附加在軌跡、觀測或會話上的一個值。
在 Langfuse 自帶的標註佇列中,評審者依據分數配置為軌跡、觀測和會話打分。Potato 在此基礎上增加了每條軌跡由多名盲評審者審閱、評審者之間的一致性計算,以及對分歧的裁決,然後把結果寫回 Langfuse 中的同一條軌跡。LLM 與智慧體軌跡的人工稽核佇列對比了 Langfuse、LangSmith、Braintrust、Arize 和 Potato 的審閱功能。
輪詢與匯出檔案的比較
下表比較了兩條路徑。
輪詢(trace_ingestion) | 匯出檔案(trace_converter) | |
|---|---|---|
| 軌跡 | 項目中在回溯視窗內開始的每條軌跡,之後每條新軌跡在結束時各取一次 | 檔案中的軌跡 |
| Langfuse API | Observations API v2,舊版 traces API 作為後備 | 無,因為檔案由你自己生成 |
| 顯示讀取的條目欄位 | steps,每個觀測一項 | conversation,每個思考、動作或結果一輪 |
| 保留的觀測類型 | 所有類型 | GENERATION、SPAN 和 EVENT |
| 會話 ID | 儲存在 metadata.session_id 下 | 不保留 |
| 向 Langfuse 傳送分數 | 是 | 否 |
輪詢適合希望在 Langfuse 中把評審後的分數放在每條軌跡旁邊的團隊。匯出檔案適合結果可以留在 Potato 中的固定樣本,為論文或保留評估集挑選的軌跡就是典型的固定樣本。
輪詢 Langfuse 項目
下面的配置每分鐘輪詢一次 Langfuse Cloud,並把每條軌跡交給兩名評審者。第一次輪詢會取回過去七天內開始的所有軌跡。兩名評審者都作答後,Potato 把每個合併後的答案作為名為 human.resolved、human.helpfulness 或 human.step_check 的分數發回該軌跡。該配置是完整的,可以通過 python -m potato.validate_cli --strict。
annotation_task_name: Langfuse trace review
task_dir: .
output_annotation_dir: annotation_output/
data_files:
- data/traces.jsonl
item_properties:
id_key: id
text_key: task_description
user_config:
allow_all_users: true
users: []
# Pull every trace that started in the last 7 days, then poll once a minute.
# Keys for write-back come from LANGFUSE_PUBLIC_KEY and LANGFUSE_SECRET_KEY.
trace_ingestion:
enabled: true
api_key: change-me
sources:
- type: langfuse
api_url: https://cloud.langfuse.com
public_key: pk-lf-...
secret_key: sk-lf-...
poll_interval: 60
lookback_hours: 168
writeback:
trigger: fully_annotated
min_annotators: 2
score_prefix: human
langfuse:
score_configs: true
num_annotators_per_item: 2
instance_display:
fields:
- key: steps
type: agent_trace
label: Langfuse trace
display_options:
collapse_observations: true
annotation_schemes:
- annotation_type: radio
name: resolved
description: Did the agent answer the user's question correctly?
labels: [correct, partly_correct, wrong]
- annotation_type: likert
name: helpfulness
description: How helpful was the final answer?
size: 5
min_label: Not helpful
max_label: Very helpful
- annotation_type: process_reward
name: step_check
description: Mark each step correct or incorrect.
steps_key: steps
mode: per_step用 potato start config.yaml -p 8000 啟動伺服器。Potato 在啟動時需要一個數據檔案,data/traces.jsonl 中已有的軌跡會與輪詢到的軌跡一同進入佇列。檔案中的軌跡只有帶有 steps 列表時,才會出現在上面的顯示中。
trace_ingestion.sources 下每個 type: langfuse 的條目會啟動一個輪詢器。輪詢器讀取以下鍵:
| 鍵 | 預設值 | 含義 |
|---|---|---|
api_url | https://cloud.langfuse.com | Langfuse 主機。自託管伺服器使用自己的 URL |
public_key、secret_key | 無 | 項目的 API 金鑰 |
poll_interval | 30 | 兩次輪詢之間的秒數 |
lookback_hours | 24 | 第一次輪詢回溯多遠 |
settle_seconds | 30 | 軌跡結束後,等待多少秒再取走該軌跡 |
max_trace_seconds | 3600 | 超過該秒數後,未結束的軌跡也會被取走 |
輪詢器取走的軌跡
輪詢器在給定時間視窗內讀取 GET /api/public/v2/observations,並按軌跡 ID 對各行分組。沒有該端點的伺服器會改用舊版 GET /api/public/traces API。Langfuse 遷移指南指出,Langfuse Cloud 會提供舊版讀取端點直到 2026 年 11 月 16 日,輪詢器無需為切換做任何更改。當根觀測有了結束時間且已過 settle_seconds 秒,這條軌跡就算已結束。根觀測始終不結束的軌跡會在 max_trace_seconds 之後被取走,並把 metadata.complete 設為 false。Potato 會跳過條目庫中已有的軌跡,因此重啟不會重複新增任何軌跡。
每條軌跡成為一個條目,ID 為 langfuse_<trace id>。條目的 steps 列表按開始順序儲存子觀測,每個步驟保留該觀測的輸入、輸出、類型、名稱、模型和觀測 ID。軌跡名稱成為 task_description,沒有名稱的軌跡則使用軌跡輸入。會話 ID、使用者 ID、標籤、輸入和輸出放在 metadata 下。輪詢器會取回視窗內的每條軌跡,不按名稱、標籤或環境過濾。自動化規則和分診佇列決定評審者先看到哪些被輪詢到的軌跡。
轉換 Langfuse 匯出檔案
langfuse 轉換器讀取一個軌跡物件,或由軌跡物件組成的列表,格式為 JSON 或 JSONL。每條軌跡都需要一個由完整觀測物件組成的 observations 列表。舊版 GET /api/public/traces/{traceId} 端點返回的軌跡就是這種形狀,而列表端點 GET /api/public/traces 返回的是觀測 ID,不是物件。來自 v2 Observations API 的各行,在轉換前必須按 traceId 分組成每條軌跡一個物件,觀測放在 observations 下。轉換檔案的命令如下。
python -m potato.trace_converter \
--input langfuse_traces.json \
--input-format langfuse \
--output data/traces.jsonl該命令為每條軌跡寫出一行 JSON,--auto-detect 會根據首項帶有 type 的 observations 列表識別該格式。轉換器對各欄位的對映如下。
| Langfuse 欄位 | Potato 欄位 |
|---|---|
軌跡 id | id |
軌跡 name | agent_name |
軌跡 input.query、input.input、input.question,或字串形式的 input | task_description |
GENERATION 觀測的輸出(content、text 或 completion) | conversation 中的一個 Agent (Thought) 輪次 |
SPAN 觀測的名稱和輸入 | 一個 Agent (Action) 輪次,寫作 name(key=value, ...) |
SPAN 觀測的輸出(result、output 或整個物件) | 一個 Environment 輪次 |
EVENT 觀測的輸出,沒有輸出時取輸入 | 一個 System (<name>) 輪次 |
| 觀測數量、軌跡名稱和模型 | metadata_table 中的行 |
轉換器按檔案順序保留觀測,並丟棄其他所有觀測類型。Langfuse 的觀測類型還包括 agent、tool、chain、retriever、evaluator、embedding 和 guardrail,使用這些類型埋點的軌跡在轉換時會丟失相應步驟。軌跡輸出、後設資料、會話 ID 和觀測級別同樣不會被帶過來。當軌跡依賴這些類型或欄位時,請改為輪詢項目。
對於轉換得到的檔案,請讓顯示和任何步驟級標註方案指向 conversation。在輪詢配置中,把 instance_display 塊以及 annotation_schemes 下的 step_check 條目換成下面的內容。
instance_display:
fields:
- key: task_description
type: text
label: User request
- key: conversation
type: agent_trace
label: Langfuse trace
annotation_schemes:
- annotation_type: process_reward
name: step_check
description: Click the first step that went wrong.
steps_key: conversation
mode: first_error用於 Langfuse 軌跡的顯示與標註方案
agent_trace 顯示把每個步驟呈現為帶類型徽標的卡片,既能讀取輪詢器產生的 steps 列表,也能讀取轉換器產生的 conversation 列表。三窗格 eval_trace 顯示會把同樣的步驟拆分為推理、函式呼叫和最終答案。請根據軌跡的來源路徑,把顯示的 key 設為 steps 或 conversation。
寫回會發送來自 radio、likert、slider、number、select 和 multiselect 標註方案的軌跡級答案。步驟級問題使用 process_reward、trajectory_eval、tool_call_review、failure_attribution 或 gui_trajectory。對被輪詢到的軌跡,凡是有 min_annotators 名評審者給出的步驟標籤,都會成為該步驟自身觀測上的一個分數。文本框記錄的理由只進入 Potato 的匯出,不會被髮送。對於涉及整個 Langfuse 會話的問題,設定 enabled: true 的 sessions 塊會按 metadata.session_id 對被輪詢到的軌跡分組,帶有 session_level: true 的標註方案在 /sessions 頁面上每個會話只回答一次(見會話級評分)。
MAST 故障分類法和 τ-bench 智慧體評估設計為智慧體故障提供了現成的軌跡級標註方案。
Langfuse 中的分數
一旦有 min_annotators 名評審者作答,寫回就會為每個標註方案發送一個分數。設定 trigger: adjudicated 時,Potato 改為等待裁決者的決定,而且無論哪種觸發方式,裁決得到的值都會取代投票結果。每個分數命名為 <score_prefix>.<scheme name>,所帶的值見下表。
| 標註方案 | 分數的值 | Langfuse 資料類型 |
|---|---|---|
radio、select、帶 labels 的 likert | 多數評審者選擇的標籤 | 分類 |
multiselect | 超過半數評審者選擇的標籤,以逗號分隔 | 分類 |
不帶 labels 的 likert | 中位數分值 | 數值 |
slider、number | 平均值 | 數值 |
| 步驟級標註方案 | 多數評審者給該步驟的標籤,傳送到該步驟的觀測上 | 分類 |
平票時不會發送任何內容,直到裁決者做出決定或另一名評審者打破平局。評論說明該值是如何得出的,例如 Potato: 2 of 2 annotator(s) chose correct。後設資料包含評審者人數、各答案的計數,以及評審者兩兩之間意見一致的比例。不會發送任何使用者名稱。設定 langfuse.score_configs: true 後,Potato 在第一次傳送某個分數時,會建立一個以該分數命名的 Langfuse 分數配置。隨後 Langfuse 會在篩選器中列出 resolved 的各個類別以及 1 到 5 的 helpfulness 範圍。
當 LANGFUSE_PUBLIC_KEY 和 LANGFUSE_SECRET_KEY 兩個變數都已設定時,寫回從這兩個變數讀取 Langfuse 金鑰,否則從 langfuse 來源讀取。Potato 為每條軌跡和每個標註方案的分數賦予固定的 ID。只有當 ID、名稱和日期都一致時,Langfuse 才會替換分數(見防止重複分數)。因此,在之後某一天發生變化的答案,會新增一個帶當天日期的第二個分數。可以用管理員 API 金鑰檢查傳送情況。
curl -H "X-API-Key: $KEY" http://localhost:8000/admin/api/trace_writeback響應會統計待發送、已傳送、失敗和被擱置的分數數量,並給出每條被擱置軌跡正在等待的原因。
Potato 匯出中的結果
每條軌跡,無論是輪詢到的還是轉換得到的,其標註都儲存在 annotation_output/<user>/user_state.json 中。agent_eval 匯出會按軌跡彙總答案,為每個單選標註方案給出標籤分佈和多數結果,為每個數值標註方案給出平均值。
python -m potato.export --config config.yaml --format agent_eval --output results/匯出格式列出了 jsonl、parquet、huggingface 和其他格式。會話級答案寫入 annotation_output/session_annotations.jsonl。/admin/iaa 儀表板報告每個標註方案下評審者之間的一致性,標註者間一致性詳解介紹瞭如何選擇係數。資料集頁面通過 Import ingested traces 從被輪詢到的軌跡構建帶版本的評估集。修正後的軌跡經由軌跡編輯成為 SFT 和 DPO 記錄。
限制
- 寫回只覆蓋被輪詢到的軌跡。 來自資料檔案的軌跡,無論是否經過轉換,其結果都保留在 Potato 中。
- 文本答案留在 Potato 中。 寫回不傳送任何自由文本答案,會話級答案同樣不會發送。
- 輪詢器取走整個項目。 軌跡到達之後,取樣和優先順序由自動化規則和分診佇列決定。
常見問題
Langfuse 是什麼?
Langfuse 是一個開源的 AI 工程平臺,用於追蹤 LLM 和智慧體應用、評估這些軌跡並管理提示詞。該倉庫在 ee 資料夾之外採用 MIT 許可,Langfuse 可以作為 Langfuse Cloud 使用,也可以執行在你自己的伺服器上。Potato 連線 Langfuse,讓多名人工評審者審閱每條軌跡,並把他們合併後的答案作為分數發回 Langfuse。
Langfuse 為智慧體可觀測性記錄了什麼?
Langfuse 把每次智慧體執行記錄為一條軌跡,把執行中的每個步驟記錄為巢狀在該軌跡下的觀測。一個步驟可以是一次 LLM 呼叫、一次工具呼叫、一個檢索步驟,或執行中的其他操作。觀測有十種類型,包括 generation、agent、tool、retriever 和 guardrail,會話則把同一次使用者互動的多條軌跡歸在一起。當一條軌跡包含 span、event、generation 之外的觀測類型時,Langfuse 會根據這些觀測的時間和巢狀關係繪製一張智慧體圖。Potato 的輪詢器讀取這些觀測,並把每個觀測轉為評審者可以標註的一個步驟。
延伸閱讀
- LLM 與智慧體軌跡的人工稽核佇列和AI 智慧體評估工具對比
- 智慧體標註,介紹其他 14 種軌跡轉換器
- 如何衡量 LLM 評判者與人工標註者之間的一致性,用於比較 Langfuse 的 LLM 評判分數與評審者標籤
- Potato 與 LangSmith 和 Langfuse 的智慧體評估對比
- 軌跡分數寫回,位於 Potato 的原始碼文件中