跳轉到內容

在 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 APIObservations 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。

yaml
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_urlhttps://cloud.langfuse.comLangfuse 主機。自託管伺服器使用自己的 URL
public_key、secret_key無項目的 API 金鑰
poll_interval30兩次輪詢之間的秒數
lookback_hours24第一次輪詢回溯多遠
settle_seconds30軌跡結束後,等待多少秒再取走該軌跡
max_trace_seconds3600超過該秒數後,未結束的軌跡也會被取走

輪詢器取走的軌跡

輪詢器在給定時間視窗內讀取 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 下。轉換檔案的命令如下。

bash
python -m potato.trace_converter \
  --input langfuse_traces.json \
  --input-format langfuse \
  --output data/traces.jsonl

該命令為每條軌跡寫出一行 JSON,--auto-detect 會根據首項帶有 type 的 observations 列表識別該格式。轉換器對各欄位的對映如下。

Langfuse 欄位Potato 欄位
軌跡 idid
軌跡 nameagent_name
軌跡 input.query、input.input、input.question,或字串形式的 inputtask_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 條目換成下面的內容。

yaml
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 金鑰檢查傳送情況。

bash
curl -H "X-API-Key: $KEY" http://localhost:8000/admin/api/trace_writeback

響應會統計待發送、已傳送、失敗和被擱置的分數數量,並給出每條被擱置軌跡正在等待的原因。

Potato 匯出中的結果

每條軌跡,無論是輪詢到的還是轉換得到的,其標註都儲存在 annotation_output/<user>/user_state.json 中。agent_eval 匯出會按軌跡彙總答案,為每個單選標註方案給出標籤分佈和多數結果,為每個數值標註方案給出平均值。

bash
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 的輪詢器讀取這些觀測,並把每個觀測轉為評審者可以標註的一個步驟。

延伸閱讀