iT邦幫忙

2026 iThome 鐵人賽

DAY 25
0
Software Development

成為產品型工程師吧!從培養產品思維到 PostHog 數據實戰系列 第 25 篇

Day 25|AI 在 Production 發生了什麼?PostHog AI Observability

  • 分享至 

  • xImage
  •  

前一篇把 AI 功能的驗證拆成三件事:原本的 Test 確認程式有沒有正常執行;Eval 判斷 AI 有沒有把任務做好;Observability 則負責留下 Production 裡到底發生了什麼。

要把這個 Loop 真的做起來,第一步不是先寫一堆 Grader,而是讓 Production 裡的 AI Interaction 可以一路追到使用者和最後的 Product Outcome。

這篇就直接用 PostHog AI Observability 把這件事接起來。

https://ithelp.ithome.com.tw/upload/images/20260928/20102556UoML7jCBv5.png

我們繼續用一個查訂單的客服 Agent 當例子。使用者的目標不是「產生一次 Generation」,而是拿到正確的物流資訊,所以實作前可以先把幾個 Product Event 定下來:

  • order_tracking_started
  • tracking_status_viewed
  • support_ticket_created

tracking_status_viewed 可以當成成功 Outcome,support_ticket_created 則代表使用者最後仍然需要人工協助。

這些 Event 和 AI Event 最好使用同一個 distinct_id。後面我們才有辦法從「哪些使用者沒有成功」一路追到他當時的 AI Session、Trace 和 Generation。

接入 PostHog AI Observability

最快的方式是直接執行 Wizard:

npx @posthog/wizard ai-observability

PostHog 目前支援 OpenAI、Anthropic、Vercel AI SDK、OpenRouter、LangChain 等常見 Provider 與 Framework。

如果使用 OpenAI,以 Node.js 為例,可以改用 PostHog 提供的 Wrapper:

import { OpenAI } from '@posthog/ai/openai'
import { PostHog } from 'posthog-node'

const posthog = new PostHog(
    process.env.NEXT_PUBLIC_POSTHOG_KEY!,
    {
        host: process.env.NEXT_PUBLIC_POSTHOG_HOST
    }
)

const openai = new OpenAI({
    apiKey: process.env.OPENAI_API_KEY,
    posthog
})

原本呼叫 OpenAI 的方式幾乎不用改。比較重要的是把這次 Interaction 的 Context 一起帶進去:

const traceId = crypto.randomUUID()

const response = await openai.responses.create({
    model: 'gpt-5-mini',
    input: [
        {
            role: 'user',
            content: '我的訂單現在在哪?'
        }
    ],

    posthogDistinctId: user.id,
    posthogTraceId: traceId,

    posthogProperties: {
        $ai_session_id: conversation.id,
        feature: 'order_tracking',
        prompt_version: 'v3',
        environment: 'production'
    }
})

這裡幾個欄位後面都會用到:

  • posthogDistinctId:把 AI Event 接回同一個使用者。
  • posthogTraceId:把同一次 AI Interaction 裡的相關操作放在同一條 Trace。
  • $ai_session_id:把多個相關 Trace 分在同一個 Conversation、Thread 或 Workflow。
  • feature、prompt_version:之後比較不同功能或版本時可以直接 Filter。

Wrapper 會自動 Capture $ai_generation,包含 Input、Output、Model、Token、Latency、Cost 等資訊。這些同時也是一般的 PostHog Event,所以不需要為 AI 另外建立一套和 Product Analytics 完全分離的資料系統。

https://ithelp.ithome.com.tw/upload/images/20260928/20102556zVnQ84Qit4.png

AI Event 與 Product Outcome

接入 AI Observability 之後,我們會開始取得 Generation Count、Token Usage、Latency 等資料。這些資料可以用來了解 AI 功能的使用量、成本與執行狀況。

如果想知道 AI 是否真的解決了使用者的問題,仍然需要回到前面定義的 Product Outcome。

以查訂單為例,一個使用者可能只產生一次 Generation,就成功看到物流資訊;另一個使用者也可能因為答案不正確而反覆詢問五次,最後仍然建立 Support Ticket。只看 Generation Count,第二種情況的使用量反而更高。

所以如果目標是讓使用者取得物流資訊,可以建立 order_tracking_started → tracking_status_viewed 的 Funnel,觀察開始使用這項功能的使用者,有多少最後真的完成目標。

如果使用者沒有完成 tracking_status_viewed,或者後面又出現 support_ticket_created,就可以再進一步查看當時的 Session、Trace 與 Generation,了解問題發生在哪裡。

如果之後要比較不同 Prompt、Model 或 Experiment Variant,也需要讓這些版本資訊能和最後的 Product Outcome 關聯。否則即使在 $ai_generation 上記錄了 prompt_version,看到 Conversion 發生變化時,仍然很難直接判斷是哪一個版本造成的。

從 Product Outcome 回到 AI 的執行過程

Product Outcome 可以幫助我們找出沒有完成目標的使用者,但還不能說明問題實際發生在哪裡。

以查訂單為例,使用者最後沒有看到物流資訊,可能是 Agent 一開始就理解錯問題,也可能是查詢訂單的 Tool 失敗,或者前幾輪回答都正常,只是在後面的互動才開始出錯。

這些狀況需要看的範圍不同。要了解整段互動,可以看 Session;要確認其中一次互動發生了什麼,可以看 Trace;如果問題已經縮小到某一次模型呼叫,則可以再看 Generation。Tool、Retrieval 等中間操作的執行情況,則可以透過 Span 確認。

使用者到底有沒有在這段互動裡解決問題:看 Session

對聊天或 Agent 類產品來說,一次回答通常不是完整的使用者旅程。

例如,使用者先問「我的訂單在哪?」,接著追問「什麼時候會到?」,最後又說:「你確定嗎?物流頁不是這樣寫。」這幾輪訊息可能分屬不同 Trace,但仍然是同一段 Conversation。

PostHog 的 $ai_session_id 就是用來做這種分組。Session 的定義可以依產品決定,可能是 Conversation、Thread、Workflow,或其他你認為合理的 logical boundary。

如果使用者最後沒有成功,可以先看整個 Session:他問了幾輪、在哪一輪開始重複、總共花了多少 Cost,以及最後是不是轉去人工處理。

Session 特別適合回答「這個使用者整段體驗發生了什麼」,而不是 Debug 某一次 Model Call。

這一次回答為什麼變成這樣:看 Trace

找到可疑的一輪之後,再往下看 Trace。

Trace 會把同一次 AI Interaction 裡相關的 AI Event 放在一起。以「我的訂單現在在哪?」這次 Request 為例,裡面可能包含:

  • Generation:判斷需要查訂單。
  • Span:執行 get_order()。
  • Span:執行 get_shipping_status()。
  • Generation:整理 Tool Result 並回覆使用者。

Trace 很適合回答:

  • 這一次 Request 經過哪些步驟?
  • 時間花在哪裡?
  • 哪一個 Tool Error?
  • 最後答案是根據什麼資料產生的?

所以 $ai_trace_id 的價值不是多一個 ID,而是讓後面所有 AI Event 都可以被還原成同一次 Interaction。

問題是不是出在某一次 Model Call:看 Generation

如果 Trace 的流程看起來正常,接著就可以打開其中某個 Generation。

Generation 是一次 LLM Call,可以看到:

  • Input / Output
  • Model
  • Token Usage
  • Latency / Cost
  • Tool Call

假設最後回答突然多出「退款期限是 90 天」,可以直接看這次 Generation 收到的 Input 裡到底有沒有這個資訊。

如果 Input 本身就是錯的,問題可能在前面的 Retrieval 或 Tool;如果 Input 沒有,則更可能是 Model 自己產生了不存在的內容。

PostHog 也會從支援的 LLM Response 格式自動擷取 Tool Call,包括 OpenAI、Anthropic、OpenAI Agents SDK、Vercel AI SDK 等,因此不需要為了知道「模型要求呼叫哪個 Tool」再額外建立 Span。

Tool 真的執行了什麼:看 Span

「Model 決定呼叫 get_order」和「get_order() 實際執行成功」是兩件事。

如果想知道 Tool Execution、Vector Search、Retrieval 或其他中間工作實際花多久、輸入輸出是什麼、最後有沒有 Error,可以另外 Capture $ai_span。

例如:

posthog.capture({
    distinctId: user.id,
    event: '$ai_span',
    properties: {
        $ai_trace_id: traceId,
        $ai_session_id: conversation.id,
        $ai_span_id: crypto.randomUUID(),
        $ai_span_name: 'get_order',
        $ai_input_state: { orderId },
        $ai_output_state: result,
        $ai_latency: elapsed
    }
})

這樣 Trace 裡就不只知道 Model 想呼叫哪個 Tool,也能看到 Tool 真正執行的結果。

另外,$ai_session_id 和 Session Replay 使用的 $session_id 是不同概念。前者是我們替 AI Interaction 定義的分組;後者是 PostHog 一般網站 Session。

https://ithelp.ithome.com.tw/upload/images/20260928/20102556f6zTuRNXi0.png

Sentiment:使用者在對話中的情緒

Production 一多,不可能每個 Session 都人工閱讀。這時可以先利用 Sentiment 找出可能需要注意的對話。

這裡的 Sentiment 指的是使用者訊息呈現出的情緒或態度,不是在判斷 AI 的回答正不正確。PostHog 的 Sentiment classification 會針對使用者送出的訊息分類成 Positive、Neutral 或 Negative。

可以先用很直觀的方式理解:

  • Positive:使用者的訊息偏正面,例如「謝謝,這就是我要找的」。
  • Neutral:沒有明顯正面或負面情緒,例如「幫我查一下這筆訂單現在在哪」。
  • Negative:使用者表達不滿、困惑或挫折。

它不能直接當成 Success Metric。使用者一開始說「我的包裹不見了」本來就可能是 Negative,即使 Agent 最後成功找到包裹;反過來,使用者用很平靜的語氣詢問,也不代表 AI 給出的答案一定正確。

所以 Sentiment 比較適合回答:

哪些對話可能不順利,值得優先打開?

而不是:

這次 AI Interaction 成功了嗎?

使用者回饋也是一種信號

除了從行為和 Sentiment 推測,PostHog 也可以透過 Survey Feedback 把 Thumbs up / down 和 Follow-up Question 直接接到 $ai_trace_id。

這樣看到 Thumbs down 時,可以直接回到當次 Trace 看 Input、Output、Tool 和整個執行過程。

Feedback 比 Sentiment 更直接,因為它是使用者自己對這次結果的評價。

它一樣不能取代 Outcome。很多成功的使用者不會按任何按鈕;也有人可能對語氣很好的錯誤答案按下 Thumbs up。

所以比較好的做法仍然是把它當成另一個 Signal,而不是唯一的品質指標。

從 Product Outcome 一路往下查

把這些資料放在一起後,可以依照問題所在的範圍逐步縮小:

  1. 從 Product Outcome 找到沒有完成任務,或最後轉人工的使用者。
  2. 查看 Session,確認整段互動是否出現重複詢問、卡住或 Sentiment 惡化。
  3. 查看 Trace,找出是哪一次 Interaction 出問題。
  4. 再到 Generation 或 Span,確認問題出在 Model、Context、Tool 還是 Retrieval。

這個順序也可以接回前一篇談的 Eval。Production 裡真的發生過的 Failure,在找出原因並修正後,可以再整理成 Eval Task 加入 Regression Suite。等 Prompt、Model 或 Tool 再修改時,就能用 Regression Test 確認同一種錯誤是否再次出現。

Input / Output 本身就是資料

AI Observability 還多了一種一般 API Log 很少直接保存的東西:使用者實際問了什麼,以及產品實際回答了什麼。

這些資料本身就有產品價值。Input 可以幫我們發現真實 Use Case;Output 和 Trace 可以找出反覆出現的 Failure,後面也能整理成 Dataset 與 Eval。

同一個特性也代表不能把它當普通 Metadata 處理。使用者可能在 Input 裡貼上 Email、電話、訂單資訊,Context 也可能包含公司內部文件。

如果不需要保存 Prompt 與 Completion,可以使用 PostHog 的 Privacy Mode。在 SDK 呼叫中設定 posthogPrivacyMode: true,就會排除 $ai_input 和 $ai_output_choices。

如果需要保留這些內容來做 Debug、Clustering 或 Eval,就應該依產品自己的資料政策決定哪些資料可以 Capture、哪些要先移除。

到這裡,我們已經能從 Product Outcome 找到沒有成功的使用者,再一路往下追到 Session、Trace、Generation 與 Span。下一篇會繼續處理另一個更現實的問題:即使我們看得到 AI 怎麼失敗,它還是一定會犯錯,產品本身要怎麼把 Failure 控制在可以接受的範圍內?


如果你願意花 30 秒留下回饋,我會用這些意見來調整後續文章:分享你的意見


上一篇
Day 24|AI 沒有報錯,為什麼答案還是錯的?LLM 開發的測試與 Eval
下一篇
Day 26|Production 裡的 AI 都在做什麼?用 Clustering 從大量 Trace 找出模式
系列文
成為產品型工程師吧!從培養產品思維到 PostHog 數據實戰 共 29 篇
圖片
  熱門推薦
圖片
{{ item.channelVendor }} | {{ item.webinarstarted }} |
{{ formatDate(item.duration) }}
直播中

尚未有邦友留言

立即登入留言