iT邦幫忙

2025 iThome 鐵人賽

DAY 25
0
AI & Data

30 天從 0 至 1 建立一個自已的 AI 學習工具人系列 第 25

30-25: [知識] AI Application 的 Evaluation ( 1 ) 之評估方式與 Langfuse 篇

  • 分享至 

  • xImage
  •  

https://ithelp.ithome.com.tw/upload/images/20251009/20089358akyciLC4or.png

接下來我們將要討論 AI Observability 的其中一個很重要的主軸,那就是 Evaluation,有好的方法,我們才能讓我們的 AI Application 回應的質量進行穩定,並且也才能優化它。

🚀 Evaluation 是什麼與目前實作的方式

Evaluation 主要做的就是評估你 AI Application 回應的好與不好

目前就我所知,判斷 AI 回應的好與不好的評分方式有以下三種 :

  • LLM-as-a-Judge : 這個就是叫 LLM 來評分你的 AI Application 回應。
  • Human Annotation : 這個就是透過工人智慧,來判斷 AI Application 回應。
  • 透過 API/SDK 完全自訂的評分 : 這個比較接近就是根據回應,然後自已手寫個評分程式碼的概念。

然後這三個東西目前在很多的 AI Platform 例如 Langsmith、Langfuse 都有支援,然後這系統我們會以 Langfuse 為主,因為免費

然後這個評分方式理解完後,我們才能進行評分指標,但這個就後面幾篇。

🚀 Langfuse 的架構

Langfuse 是一個開源的 LLM Engineering Platform,把「可觀測性(tracing/metrics)」、「評估(evals)」、「提示詞管理(prompt management)」、「標註(annotations)」整合在同一套系統,協助團隊開發、監控、評估、除錯 AI Applcation。

整個 Langfuse 就是可以提供 AI Observability 的平台

架起來後就長的像這樣,之後在介紹它的其它功能,這篇我們要專注說 evaluation。

https://ithelp.ithome.com.tw/upload/images/20251010/20089358Mfk10HtBZh.png

🤔 它的架構

然後當你將 Langfuse 建立起來後,它的整個架構如下圖 :

https://ithelp.ithome.com.tw/upload/images/20251009/20089358UXrGfIi7R3.png

這裡簡單說一下每個在幹啥用的 :

  • Web Server : 就是你上面看到黑黑畫面的那個。
  • Async Worker : 就是會非同步的處理一些工具。
  • Postgres : 就 OLTP 資料庫,這個很常用看到應該不用說。
  • Clickhouse : 這個我自已覺得就比較少見,它是 OLAP 服務,這種服務大部份是處理大量讀取分析的應用,可用性的東西會比較少考慮 ( 例如強一致性或啥的 )。
  • S3 : Langfuse 也可以使用 GCP 的 GCS 之類的,但它文件是以他為主,而且很可笑的是環境變數也寫死叫 S3,但 GCP 還是可以用,至於它的地位下面會提到。
  • Redis : 就 Cache + Queue 服務。

以上兩個我自已覺得還好,我自已是對這個架構比較感興趣的是以下四個 :

然後我們先來看看,當我們一個事件進來後整個流程,如下圖 :

我們先來看看事件進來的流程。

備註: 編號和圖不一樣,下面只是順序。

首先第一階段是送事件給 Langfuse 然後應用收到 OK。

  1. 一個事件透過 Langfuse SDK 進來後,會打個請求到 Langfuse Web Server。
  2. 接下來它透過 Redis 來先檢查 API Key 有沒有 Cache ( 為了性能 )
  3. 然後再將事件儲放到 S3。
  4. 再將事件 reference ( 想成 c++ reference 就好,它不是整個事件內容),儲放到 Redis。
  5. 然後和 AI 應用說 ok。

再來是第二階段它這裡會將事件實際上的儲到資料庫中。

  1. 首先 worker 會從 queue 中取出事件 reference。
  2. 再來從這資訊,去 S3 取得到完整的事件資料。
  3. 接下來就是進行資料處理。
  4. 將 metadata 與 releational data 儲放到 postgres。
  5. 同時將追蹤/觀測/評分等資料放到 Clickhouse。
  6. 到了這,這一階段就算完成。

最後第三階段,這裡就是我們實際上在 Langfuse 的 Web 進行操作,基本上就是去這些資料庫來取得到資料,這個應該就不用說太多。

https://ithelp.ithome.com.tw/upload/images/20251009/20089358pkXCO8MsEV.png

🤔 為什麼這個東西,會需要用到 4 個儲存類的服務呢 ~

Redis 應該是沒有毛病,他就是用來 Cache + Queue 使用,而 OLTP 是用來儲放一些需要放在關聯性資料庫且保證資料一致性的資料,而 OLAP 就比較是放一些不會變動的資料,例如 trace 之類的,因為這些之後要大量讀取與分析用。

至於它在這裡的定位,我自已覺得比較是為了在進行非同步時,作為第一層持久安全而存在的,也就是說以這個想法來看,那進入第二層持久 ( OLAP/OLTP ) 後應該是可以砍掉,但不確定,文件沒有說明很清楚。

🤔 Langfuse 有進行一些性能與可靠性的優化部份,我覺得思路可以學學

https://langfuse.com/self-hosting#optimized-for-performance-reliability-and-uptime

接下來我們針對上面三種 Evaluation 方式,來簡單看一下是如何用。

  • LLM-as-a-Judge
  • Human Annotation
  • 透過 API/SDK 完全自訂的評分

🚀 Langfuse 的 Local 測試環境建立 ( GCP Bucket )

我這裡是用 docker-compose 來建立的實驗用的,參考官網這份文件。

https://github.com/langfuse/langfuse/blob/main/docker-compose.yml

備註: 正式環境一定不是這樣 ( 給新手 )

然後因為我是用 GCP ,所以我自已改成如下,而且上面官網的,很多東西不需要 :

version: "3.9"

services:
  langfuse-worker:
    image: docker.io/langfuse/langfuse-worker:3
    restart: always
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      clickhouse:
        condition: service_healthy
    env_file: .env
    ports:
      - 127.0.0.1:3030:3030
    volumes:
       - ./secrets:/secrets:ro
  langfuse-web:
    image: docker.io/langfuse/langfuse:3
    restart: always
    depends_on:
      postgres:
        condition: service_healthy
      redis:
        condition: service_healthy
      clickhouse:
        condition: service_healthy
    ports:
      - 3000:3000
    volumes:
      - ./secrets:/secrets:ro
    env_file: .env
  postgres:
    image: postgres:15
    environment:
      POSTGRES_DB: langfuse
      POSTGRES_DATABASE: langfuse
      POSTGRES_USER: langfuse
      POSTGRES_PASSWORD: test
    volumes:
      - ./docker-data/postgres:/var/lib/postgresql/data
    healthcheck:
      test: ["CMD-SHELL", "pg_isready -U langfuse -d langfuse"]
      interval: 10s
      timeout: 5s
      retries: 10
    restart: unless-stopped

  clickhouse:
    image: clickhouse/clickhouse-server:24.8
    environment:
      CLICKHOUSE_DB: langfuse
      CLICKHOUSE_USER: langfuse
      CLICKHOUSE_PASSWORD: test
    ulimits:
      nofile:
        soft: 262144
        hard: 262144
    volumes:
      - ./docker-data/clickhouse:/var/lib/clickhouse
    ports:
      - "8123:8123"
      - "9000:9000"
    restart: always
    healthcheck:
      test: wget --no-verbose --tries=1 --spider http://localhost:8123/ping || exit 1
      interval: 5s
      timeout: 5s
      retries: 10
      start_period: 1s
  redis:
    image: redis:7
    command: ["redis-server", "--requirepass", "test", "--appendonly", "yes"]
    volumes:
      - ./docker-data/redis:/data
    ports:
      - "6379:6379"
    healthcheck:
      test: ["CMD", "redis-cli", "ping"]
      interval: 3s
      timeout: 10s
      retries: 10
    restart: always

然後要用這份的話,有幾個東西要放對 :

  1. .env 放在與 docker-compose.yaml 同一個層級的資料夾裡。
  2. 在 ./secrets/ 放 gcp credentials json。
  3. gcp bucket 你要自已建好。
# === 基本 ===
NEXTAUTH_URL=http://127.0.0.1:3000
NEXTAUTH_SECRET=abc
ENCRYPTION_KEY=0000000000000000000000000000000000000000000000000000000000000000
SALT=changeme
LANGFUSE_TELEMETRY_DISABLED=true
LANGFUSE_ENABLE_EXPERIMENTAL_FEATURES=true

# === Postgres ===
POSTGRES_PASSWORD=test
DATABASE_URL=postgresql://langfuse:test@postgres:5432/langfuse

# === ClickHouse(v3 需要) ===
CLICKHOUSE_CLUSTER_ENABLED=false
CLICKHOUSE_USER=langfuse
CLICKHOUSE_PASSWORD=test
CLICKHOUSE_URL=http://clickhouse:8123
CLICKHOUSE_MIGRATION_URL:clickhouse://test:test@clickhouse:9000/langfuse 

# === Redis ===
REDIS_PASSWORD=test
REDIS_HOST=redis
REDIS_PORT=6379
REDIS_AUTH=test

# === GCP Bucket ===
LANGFUSE_USE_GOOGLE_CLOUD_STORAGE=true
LANGFUSE_S3_EVENT_UPLOAD_BUCKET=langfuse-mark
LANGFUSE_GOOGLE_CLOUD_STORAGE_CREDENTIALS=/secrets/credentials.json
LANGFUSE_S3_EVENT_UPLOAD_PREFIX=events/

然後就執行 docker-compose up 然後再等入到 127.0.0.1:3000 有看到登入畫面就對了。

🚀 Langfuse 的 Evaluation 方式 1 : LLM-as-a-Judge

https://langfuse.com/docs/evaluation/evaluation-methods/llm-as-a-judge

你的 AI 輸出 → 送給評審 LLM → 給分數/評價

🤔 Step 1. 建立 Evaluator,就是評估的機器人+評分方式

如果我們要用,那就先預設 LLM Model 如下圖,我們設定是 gpt-5-mini :

https://ithelp.ithome.com.tw/upload/images/20251009/20089358pXRzoSS4T6.png

接下來我們有兩種選項可以用 Langfuse 提供的 evaluator 或是我們自訂的 evaluator ( 就是 prompt 我們自已想 )。

然後下圖那些看不太懂的單字,就是 Langfuse 提供的,那些單字下篇會說,它就是評估指標,我們這裡範例就先用自訂的。

https://ithelp.ithome.com.tw/upload/images/20251010/20089358Wm1MuFG8Sw.png

https://ithelp.ithome.com.tw/upload/images/20251010/20089358Xg0PsQ5awe.png

🤔 Step 2. 設定那些條件的 trace 會自動執行 ( 沒設好你就炸了 )

下面這個就是設定的地方,其中 filter 就是用來判斷那些要過這個 evaluator,這個在另一篇會說,因為這和指標與情境有關。

然後這裡先以 label 為範例,就是如果 trace 有這個 label ai-judge的,則進行 eval。

https://ithelp.ithome.com.tw/upload/images/20251009/20089358e9L3S6gEcy.png

🤔 Step 3. 送個有 ai-judge 的 trace

import Langfuse from "langfuse";
import OpenAI from "openai";

process.env.LANGFUSE_SECRET_KEY = "sk-lf-55578317-fd2d-47de-9a09-ed2b3ace927a";
process.env.LANGFUSE_PUBLIC_KEY = "pk-lf-91fc647a-b184-44cd-a3c6-d293fdd7a346";
process.env.LANGFUSE_BASE_URL = "http://127.0.0.1:3000";

const langfuse = new Langfuse({
  publicKey: process.env.LANGFUSE_PUBLIC_KEY,
  secretKey: process.env.LANGFUSE_SECRET_KEY,
  baseUrl: process.env.LANGFUSE_BASE_URL,
});

// ============================================
// 1. LLM-as-a-Judge 評分
// ============================================
async function llmAsJudgeScoring() {
  // 步驟 1: 記錄你的 AI 應用的對話
  const userQuery = "如何重設密碼?";
  const aiResponse =
    "請到設定頁面點選「忘記密碼」,系統會發送重設連結到您的信箱。";

  const trace = langfuse.trace({
    name: "這個是要叫 AI 評分的-3",
    userId: "mark",
    input: {
      role: "user",
      content: userQuery,
    },
    output: {
      role: "assistant",
      content: aiResponse,
    },
    tags: ["ai-judge"],
  });

  const generation = trace.generation({
    name: "ai-response",
    model: "gpt-5-mini",
    input: [{ role: "user", content: userQuery }],
    output: aiResponse,
  });

  await langfuse.flushAsync();
  console.log("✅ LLM-as-a-Judge 評分完成");
}

(async () => {
  await llmAsJudgeScoring();
})();

然後結果如下,LLM 真是嚴格。

https://ithelp.ithome.com.tw/upload/images/20251010/20089358EaegOgeJdQ.png

🚀 Langfuse 的 Evaluation 方式 2 : Human Annotation ( 人類評份 )

AI 輸出 → 顯示在 Langfuse UI → 人類評審者打分

下面這一段就只是產生 Trace 送到 Langfuse。

import { Langfuse } from "langfuse";

// 2. Human Annotation (人工標註)
// ============================================
async function humanAnnotationSetup() {
  const langfuse = new Langfuse({
    publicKey: process.env.LANGFUSE_PUBLIC_KEY,
    secretKey: process.env.LANGFUSE_SECRET_KEY,
    baseUrl: process.env.LANGFUSE_BASE_URL,
  });

  // 步驟 1: 記錄需要人工評審的對話
  const trace = langfuse.trace({
    name: "需要人工標註的對話",
    userId: "mark",
    metadata: {
      needsReview: true, // 標記需要人工審核
      priority: "high",
    },
    input: {
        role: "user",
        content: "我的帳單有誤,想要退款並取消訂閱",
    },
    output: {
        role: "assistant",
        content: "我了解您的困擾。關於退款,我們需要先確認您的訂單編號。取消訂閱可以在帳戶設定中操作。",
    },
  });

  // 這個是假設你的LLM回答
  trace.generation({
    name: "ai-response",
    model: "gpt-5-mini",
  });

  await langfuse.flushAsync();

  // 注意: 人工評分是在 Langfuse UI 中進行,不需要程式碼
  // 評分後,可以透過 API 查詢評分結果
}

(async () => {
  await humanAnnotationSetup();
})();

其中有個地方可以注意一下 langfuse.trace 與 trace.generation 這兩個分別執行建立後的結果就會如下圖畫線的地方,綠字的是 langfuse.trace 的內容,而紅字則為 trace.generation :

https://ithelp.ithome.com.tw/upload/images/20251009/20089358TJQgoCzyNy.png

然後接下來我們就來人工評份

我們會將剛剛產生的 trace 送到這個待評份的 Queue 中 ( 需要馬克大人判斷是否正確 ),順到說一下這個 Queue 也先手動建立。

https://ithelp.ithome.com.tw/upload/images/20251009/20089358cAQXejqijS.png

然後在進 Queue 中看到這個 trace 就可以進行評份了。

https://ithelp.ithome.com.tw/upload/images/20251009/200893584DMOdUc9Yx.png

🚀 Langfuse 的 Evaluation 方式 3: 透過 API/SDK 完全自訂的評分

AI 輸出 -> 你在你的程式碼寫個 Score ( 評分方法 -> 在同步至 Langfuse

https://langfuse.com/docs/evaluation/evaluation-methods/custom-scores

下面為我們的範例程式碼,我們就是用一些方法來判斷 AI 的回應,例如我們下面就直接判斷他有沒有包含價格/元之類的關鍵字 ( 這是範例 ),然後再將它同到 Langfuse。

import { Langfuse } from "langfuse";

// ============================================
// 3. 透過 API/SDK 自訂評分
// ============================================
async function customAPIScoring() {
  const langfuse = new Langfuse({
    publicKey: process.env.LANGFUSE_PUBLIC_KEY,
    secretKey: process.env.LANGFUSE_SECRET_KEY,
    baseUrl: process.env.LANGFUSE_BASE_URL,
  });

  const userQuery = "推薦我一台適合打遊戲的筆電";
  const aiResponse = `我推薦以下筆電:
1. ASUS ROG Strix G15 - NT$45,000
2. MSI Katana 15 - NT$38,000
配備 RTX 4060 顯卡,16GB RAM`;

  const trace = langfuse.trace({
    name: "product-recommendation",
    userId: "mark",
    input: {
      role: "user",
      content: userQuery,
    },
    output: {
      role: "assistant",
      content: aiResponse,
    },
  });

  const generation = trace.generation({
    name: "recommendation",
    model: "gpt-5-mini",
    input: [{ role: "user", content: userQuery }],
    output: aiResponse,
  });

  // 步驟 2: 自訂評分邏輯 - 檢查是否包含價格資訊
  function customEvaluate(response: string): boolean {
    return /NT\$|元|價格/.test(response);
  }

  const score = customEvaluate(aiResponse);

  // 步驟 3: 記錄評分到 Langfuse
  langfuse.score({
    traceId: trace.id,
    name: "has-pricing",
    value: score ? 1 : 0,
    dataType: "BOOLEAN",
    comment: "檢查是否包含價格資訊",
  });

  await langfuse.flushAsync();
  console.log("✅ 自訂評分完成,分數:", score);
}

(async () => {
  await customAPIScoring();
})();

在 Langfuse 看到的結果如下圖。

https://ithelp.ithome.com.tw/upload/images/20251009/20089358tYqwBXvtso.png

注意,它只提到以下三種 dataType

  • BOOLEAN : 就是對與錯
  • NUMERIC : 就是分數
  • CATEGORICAL : 就是分類對不對

🚀 小總結

這篇文章我們學會了如何評估 AI Application 的方式,然後有以下三種 :

  • LLM-as-a-Judge
  • Human Annotation
  • 透過 API/SDK 完全自訂的評分

並且也知道如何透過 langfuse 來實現它,接下來我們將會來說明評估指標的東西。

🚀 參考資料


上一篇
30-24: [實作-13] 用字幕檔實作 AI 課程問答功能 - 升級 Advanced RAG ( Post-Retrieval 的 Context-Compressing 篇 )
系列文
30 天從 0 至 1 建立一個自已的 AI 學習工具人25
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言