CodeGym /課程 /ChatGPT Apps /指標與 SLO: p95, error‑rate, webhooks, availability

指標與 SLO: p95, error‑rate, webhooks, availability

ChatGPT Apps
等級 17 , 課堂 1
開放

1. 為什麼 ChatGPT App 需要指標與 SLO

想像團隊有兩種狀態。

第一種大家都按照「看起來能動」在過日子。只要使用者沒去投訴、也沒有在推特上抱怨——似乎就一切安好。 偶爾有人打開日誌,翻翻一長串文字,點點頭就關掉分頁。

第二種,團隊有幾個簡單的面板:

  • 主要 MCP 工具的 p95 延遲。
  • MCP 與 checkout 的 error‑rate
  • webhook 的可用性。
  • 漏斗中的付款轉換率。

並且有 3–5 個 SLO: 「工具 recommend_gifts 的 95% 呼叫——快於 2 秒」、 「MCP‑tools 的錯誤占比 < 1%」、 「checkout 可用性 ≥ 99.5%」、 「從小工具到成功付款的轉換率 ≥ 10%」。

在第二種狀態下,你可以:

  • 在出現抱怨之前就能快速知道應用出了點問題
  • 量化任何變更的效果(新推薦演算法、遷移 SDK、供應商新資費);
  • 等到走到 Store 與審核的模組時,能坦然說:「我們有品質標準,也知道自己符合到什麼程度」。

我們會討論一些很直觀的事情:什麼是 p95、它為什麼比平均值更好,如何計算 error‑rateavailability,對含 webhook 的電商部分有哪些重要指標,以及如何把這些拼成簡單但有用的 SLO。 我們將以教學應用 GiftGenius 為例——一個包含 MCP 推薦工具、checkout 與付款 webhook 的電商情境。

2. ChatGPT App 的基礎指標:到底要量什麼

延遲:用 p50/p95/p99,而不是「平均」

延遲是從操作開始到其邏輯結束的時間。在我們的堆疊中有幾類這樣的操作:

  • 呼叫 MCP 工具 recommend_gifts(挑選禮物);
  • 呼叫在 ACP 中建立 checkout intent 的工具;
  • 處理「付款成功」的 webhook。

重要的是,使用者在 ChatGPT 端看到的整體延遲我們只能部分控制。 有平臺的因素(模型推理、OpenAI 網路延遲),也有我們的部分——工具、後端、資料庫、支付服務商。 對於延遲的 SLI(Service Level Indicator——可量測的服務品質指標),通常量的就是我們這一段:從請求進入 App/MCP 到我們的伺服器給出回應。

平均回應時間在這裡很迷惑。若 90% 的請求在 100 毫秒內返回,而 10% 要花 5 秒,平均大約是半秒,圖表看起來「綠油油」。 但每十個使用者就有一個會遇到 5 秒的卡頓——UX 明顯受損。

因此業界習慣用百分位數:p50(中位數)、p95p99p95 是指有 95% 的請求延遲低於這個值。 SRE 指南強調,百分位數「不會讓離群值藏在平均數裡」,能讓那 510% 常常落在分佈尾端的使用者的實際體驗被看見。

最簡單的理解是:p50 反映「典型」使用者的體驗,而 p95/p99 告訴你那些耐心的人到底有多痛苦,因為他們總是在「卡卡的」狀態。

Error‑rate:不成功請求的占比

Error‑rate 是某段期間內不成功請求數量占總請求數量的比例。 主要的資料來源通常是結構化日誌,或帶有標籤的指標資料 status="success" | "error"

在我們的堆疊中,自然有幾種 error‑rate

  • MCP‑tools 層級:recommend_gifts 呼叫以錯誤結束的占比(例外、外部 API HTTP 5xx、逾時);
  • ACP/checkout 層級:結帳嘗試不成功的占比(API 錯誤、支付服務不可用);
  • webhook 處理層級:以失敗結束的 webhook 占比。

有個微妙之處:在 ChatGPT App 中會有「靜默」錯誤。 例如 MCP 工具回傳了錯誤,模型道歉後繼續對話,沒有把技術錯誤顯示在頂部。 形式上 ChatGPT 仍給使用者一個人類可讀的回應,但就可靠性而言,你的堆疊出了問題。 因此計算 error‑rate 時,重點是統計工程層面的狀態,而不是「GPT 的最終 UX 回覆」。

Availability:服務可用性

Availability 是某段期間內成功處理的請求百分比。 概念上它就是 error‑rate 的反向:看「成功了多少」,而不是「失敗了多少」。 典型公式:availability = successful_requests / total_requests * 100%。

在我們的堆疊中,可這樣套用:

  • MCP 伺服器可用性:最近一小時 ChatGPT 發起的 JSON‑RPC 呼叫中,有多少得到正確回應;
  • checkout‑API 可用性:對 /api/checkout 的請求中,HTTP 2xx 的占比;
  • webhook 端點可用性:支付服務商送來的 webhook 中,有多少得到我們的正確回應(通常 HTTP 200)。

若支付提供商文件中承諾的可用性是 99.9%,而你只有 97%,那多半問題不在他們,而在你。

電商指標:轉換率與漏斗

GiftGenius 是電商情境。不僅要看技術面(回應快、錯誤少),還要看商業結果——推薦究竟多常轉成已付款訂單。

這裡就輪到轉換漏斗上場:

  • 顯示了小工具的使用者(view);
  • 選擇了禮物的使用者(selection);
  • 點擊「開始結帳」的使用者(checkout started);
  • 訂單狀態變成「已付款」的使用者(paid)。

由此可定義「從小工具到付款」的轉換率,這也屬於 SLI——可量測的服務品質指標。 例如:「每日成功付款數 / 看見小工具的使用者 = 7%」。 如果延遲突然升高或 checkout 有時會逾時,漏斗就會在意料之外的地方變窄。

如果你使用 Instant Checkout

上述電商指標同樣適用於基於 Agentic Commerce Protocol 的 Instant Checkout。 此時你不是用自有的 /api/checkout,而是用標準化的 Agentic Checkout API: ChatGPT 會呼叫你的 REST 端點 POST /checkout_sessionsPOST /checkout_sessions/{id}POST /checkout_sessions/{id}/complete (可選的還有 cancelGET), 每次都從你這裡取得購物車與結帳的「真實」狀態。

對指標而言沒有任何改變:p95 延遲、error-rateavailability 不再針對任意的 /api/checkout 計算,而是針對這些標準 endpoint。 像「p95 checkout 延遲 < 3 秒、error-rate < 2%」的 SLO 直接套用到 checkout_sessions 呼叫,而不是自製 API。

另一個世界:webhook 指標

Webhook 是非同步事件(例如支付服務商的 payment_succeeded),推動訂單走完生命週期。 如果 webhook 處理不好,應用也許推薦得很漂亮,但訂單會停留在「等待付款」或某種不明確的狀態。

如果你不是直接整合 Stripe/支付,而是透過 Instant Checkout,那「支付的 webhook」這個角色就由 Agentic Checkout 的 order events 來承擔: 你的後端會把 order.createdorder.updated 之類的事件,發送到 OpenAI 指定的 webhook URL。 這是同一類型的實體:決定訂單最終狀態的非同步事件,只是接收方不是你的前端,而是 ChatGPT。

因此,同樣的指標——success rate、latency、error-rate——你將不是算 Stripe webhook,而是算你發往 OpenAI 的 order‑event webhooks。 在 SLO 中可以直接寫:「至少 99% 的 order.* 事件在 7 天內送達並成功處理,p95 處理延遲 < 500 毫秒」——這對 Instant Checkout 來說是正確的 SLI/SLO

對於 webhooks,通常會看:

  • webhook success rate——包含重試在內,最終業務邏輯成功的 webhook 占比;
  • webhook latency——從 webhook 進入到處理結束的時間;
  • webhook error rate——處理 webhook 以錯誤結束的占比(驗證失敗、資料庫不可用、逾時等)。

例如,可以設定 SLO: 「至少 99% 的 webhook 在 7 天內成功處理」 以及「webhook 處理延遲的 p95 < 500 毫秒」。

3. 在 GiftGenius 中如何技術性地量測指標

從理論走向程式。我們需要弄清楚在哪裡加上量測,以便後續彙總成 p95error‑rate 等指標。

為了不把特定的 Prometheus 或 Datadog 拉進講座,先假設我們有個簡單的 logMetric 函式,或是記錄 JSON 事件。任何觀測系統都能在此之上建出所需的圖表。

量測 MCP 工具的延遲

假設我們有一個用 TypeScript 寫的 GiftGenius MCP 伺服器,以及工具 recommend_gifts。 用計時器把商業邏輯包起來:

// mcp/tools/recommendGifts.ts
import { logMetric } from "../observability/metrics"; // 假想的 helper

export async function recommendGiftsTool(input: RecommendInput) {
  const startedAt = performance.now(); // 開始時間
  try {
    const result = await recommendGifts(input); // 商業邏輯
    const duration = performance.now() - startedAt;

    logMetric("tool_latency_ms", duration, {
      tool: "recommend_gifts",
      status: "success",
    });

    return result;
  } catch (error) {
    const duration = performance.now() - startedAt;

    logMetric("tool_latency_ms", duration, {
      tool: "recommend_gifts",
      status: "error",
      error_type: "exception",
    });

    throw error;
  }
}

這裡 logMetric 可以只是把 JSON 日誌寫到 stdout:

// observability/metrics.ts
export function logMetric(
  name: string,
  value: number,
  labels: Record<string, string | number>
) {
  // 實際上這裡會是 Prometheus/DataDog 用戶端
  console.log(
    JSON.stringify({
      type: "metric",
      name,
      value,
      labels,
      timestamp: new Date().toISOString(),
    })
  );
}

用這種方式你會得到一串 tool_latency_ms 事件流,藉由 toolstatus 這些 label,就能只針對成功呼叫計算 p95,或反過來觀察以錯誤結束的請求通常要跑多久。

計算 error‑rate

用相似方式可以記錄獨立的錯誤指標:

// 在同一個工具處理器裡
logMetric("tool_error_total", 1, {
  tool: "recommend_gifts",
  error_type: "external_api_timeout",
});

對於成功的請求:

logMetric("tool_success_total", 1, {
  tool: "recommend_gifts",
});

接著在指標系統中建立 error_rate = tool_error_total / (tool_error_total + tool_success_total) 在某一期間的比值。 這部分由指標系統聚合;在應用中重點是穩定地丟出事件。

如果想要更極簡,也可以只用日誌而不單獨記 error_total。 這樣 error‑rate 就由 status 欄位來計算。

Checkout/ACP 的指標

對於 Next.js 的 checkout 端點,邏輯相同:用計時器包住處理器並統計狀態。

// app/api/checkout/route.ts
import { NextRequest, NextResponse } from "next/server";
import { logMetric } from "@/observability/metrics";

export async function POST(req: NextRequest) {
  const startedAt = performance.now();

  try {
    const body = await req.json();
    const result = await createCheckoutSession(body); // 呼叫 ACP/Stripe
    const duration = performance.now() - startedAt;

    logMetric("checkout_latency_ms", duration, { status: "success" });
    logMetric("checkout_total", 1, { status: "success" });

    return NextResponse.json(result, { status: 200 });
  } catch (error) {
    const duration = performance.now() - startedAt;

    logMetric("checkout_latency_ms", duration, { status: "error" });
    logMetric("checkout_total", 1, { status: "error" });

    return NextResponse.json(
      { error: "Checkout failed" },
      { status: 500 }
    );
  }
}

現在可以查看 checkout_latency_msp95,以及 checkout_totalerror‑rate。 基於這些 SLI,很容易設定 SLO,例如「p95 < 3 秒,error‑rate < 2%」。

Webhook 的指標

當然還有 webhooks。前面(第 2 節)說了哪些指標很重要,現在看看在程式裡要如何量。 這裡不只時間重要,還要記錄是否成功處理:否則使用者付了款,訂單卻沒有轉為「paid」。

// app/api/webhooks/payment/route.ts
import { NextRequest, NextResponse } from "next/server";
import { logMetric } from "@/observability/metrics";

export async function POST(req: NextRequest) {
  const startedAt = performance.now();

  try {
    const payload = await req.text(); // 原始資料
    const sig = req.headers.get("stripe-signature") || "";
    const event = verifyStripeSignature(payload, sig); // 驗證

    await handlePaymentEvent(event); // 更新訂單

    const duration = performance.now() - startedAt;

    logMetric("webhook_latency_ms", duration, {
      type: event.type,
      status: "success",
    });

    logMetric("webhook_total", 1, {
      type: event.type,
      status: "success",
    });

    return new NextResponse("ok", { status: 200 });
  } catch (error) {
    const duration = performance.now() - startedAt;

    logMetric("webhook_latency_ms", duration, {
      type: "unknown",
      status: "error",
    });

    logMetric("webhook_total", 1, {
      type: "unknown",
      status: "error",
    });

    return new NextResponse("error", { status: 500 });
  }
}

基於這些事件,可以定義 SLI

  • webhook success rate = success / (success + error);
  • payment_succeededwebhook_latency_ms p95

然後為它們設定 SLO,例如「99% 的 webhooks 在 7 天內成功處理,p95 < 500 毫秒」。

4. 百分位數統計:再深入一點

我們已多次用到 p50/p95/p99,現在稍微更正式地看看它們的定義。 實務上百分位數會由指標系統來算,但理解背後在做什麼仍然很有幫助。

百分位數 pX 是指有 X% 的量測值位於該值之下。 若你把延遲的陣列由小到大排序,p95 會在「尾端」,接近最大值。 在程式中(例如在 Node 上,如果你想在本地對一組數值算 p95)大概可以這麼寫:

// 簡單的百分位數計算函式
export function percentile(values: number[], p: number): number {
  if (values.length === 0) return 0;
  const sorted = [...values].sort((a, b) => a - b);
  const index = Math.ceil((p / 100) * sorted.length) - 1;
  return sorted[Math.max(0, Math.min(index, sorted.length - 1))];
}

這種函式可用於測試或小腳本,玩玩資料、看看 p95 和平均值究竟差多少。 在生產環境,這件事交給 Prometheus、Datadog、New Relic 等成熟系統就好。

5. SLI、SLO 與 SLA:白話版

三個看起來可怕的縮寫,其實很簡單。

SLI — Service Level Indicator

SLI 是具體可量測的指標與公式。例如:

  • 過去 24 小時內,工具 recommend_gifts 的延遲 p95
  • 過去 7 天內,MCP 工具的 error‑rate
  • 小工具 → 成功付款的週轉換率。

SLI 不是目標也不是承諾,它只是「溫度計」。

SLO — Service Level Objective

SLO 是針對 SLI 設定的目標,本質上是服務「健康條件」。例如:

  • p95(latency recommend_gifts) < 2 秒,視窗為 7 天」;
  • 「所有 MCP‑tools 的 error‑rate < 1%,視窗為 30 天」;
  • 「checkout API 可用性 ≥ 99.5%,視窗為一季」;
  • 「從小工具到成功付款的轉換率 ≥ 15%,視窗為一個月」。

好做法是先量出當前水位,再把 SLO 設得比現狀稍嚴一些,不然要嘛成了「不可能任務」,要嘛是「反正已經夠好」。

SLA — Service Level Agreement

SLA 不是內部目標,而是對使用者或合作方的外部協議。 在 SLA 中會寫清楚義務(例如「99.9% uptime」)與違反後果(補償、罰金、延長訂閱等)。 SLO 通常比 SLA 更嚴,以留出「錯誤預算」的空間。

在教學用的 GiftGenius 中你多半不需要 SLA,但要理解這個階梯:

SLI → SLO → SLA
指標 → 目標 → 對外承諾

Error budget(錯誤預算)

Error budget 簡而言之,就是可接受的「不完美」容量。 若你的 SLO 是「30 天內可用性 99.9%」,那 0.1% 就是錯誤預算,可以「花」在:

  • 有停機的計劃性釋出;
  • 實驗;
  • 不可預期的事故。

當預算「燒完」時(例如實際上該月可用性只有 99.5%),就該暫緩新功能、優先修穩定性。

6. GiftGenius 的特殊指標:電商 + webhooks

把所有東西放在一起,從整體產品角度看 GiftGenius。

漏斗與轉換率

想像一個簡單的使用者旅程:

flowchart TD
  A[打開含有 GiftGenius 小工具的 App] --> B[收到推薦]
  B --> C[選擇禮物]
  C --> D[點擊「前往付款」]
  D --> E[付款成功]

對每個步驟,我們可以記錄事件:

logMetric("funnel_step_total", 1, {
  step: "widget_view",
});

logMetric("funnel_step_total", 1, {
  step: "gift_selected",
});

logMetric("funnel_step_total", 1, {
  step: "checkout_started",
});

logMetric("funnel_step_total", 1, {
  step: "checkout_paid",
});

接著,知道 widget_viewcheckout_paid 的事件數,就能計算轉換率: paid / view * 100%。 並設定 SLO:「轉換率 ≥ 10%」。 若轉換率突然掉到 3%,而技術 SLIlatency/error‑rate)都正常,那可能是 UX、模型指示或商品 feed 品質的問題,而不是基礎設施。

把 webhook 指標納入轉換

我們已經談過 webhook 的技術指標與實作。現在要把它們和轉換串起來:在電商情境中,webhook 是關鍵。 使用者看到「付款完成」,但訂單最終狀態取決於你的後端多快接收並處理 payment_succeeded

SLI(webhooks):

  • webhook success rate;
  • webhook latency 的 p95
  • 粗略的 webhook_endpoint_availability 可用性。

如果 webhook 的 success rate 下降,你就在實打實地流失訂單。

7. 基於 SLO 的簡易告警

完整的告警系統更偏運維主題,但現在可以先畫出基本面。

重點是針對SLO 的違反發送告警,而不是每一個單獨錯誤。 這常被稱為 symptom‑based alerts(基於症狀的告警)——聚焦在服務劣化的症狀:你關心的不是「發生一個錯誤」,而是「服務開始系統性地低於承諾」。

常見例子:

  • 最近 5 分鐘 MCP‑tools 的 error‑rate > 2%,而 SLO 是 < 1%;
  • 最近 10 分鐘 p95(latency recommend_gifts) > 3 秒,而 SLO 是 2 秒;
  • 最近 15 分鐘沒有任何成功的 checkout_paid——懷疑整合掛了;
  • 最近 10 分鐘 webhook success rate < 95%。

告警內容應該是人話,而不是「Alert: metric 123 > 456」。例如 GiftGenius 的 Slack 通知:

[ALERT][GiftGenius] High error rate on MCP tools:
error_rate = 3.2% (>1% SLO) for last 5 minutes.

Impact: part of users cannot receive gift recommendations.
Actions: check MCP logs and external gift API health.

這種訊息和 SLO 綁在一起,能幫 on‑call 開發者迅速判斷嚴重程度。

8. 小練習:替 GiftGenius 擬定 SLO

試著為我們的應用擬一組 SLO。即使沒有完整的指標系統,也可以先在紙上完成。

「起步」範例:

  1. 對主要 MCP 工具 recommend_gifts
    • SLI:過去 7 天成功呼叫的延遲 p95
    • SLOp95(latency) < 2 秒。
  2. 對 MCP 工具的錯誤:
    • SLIerror‑rate = errors / (errors + successes),視窗 30 天。
    • SLOerror‑rate < 1%。
  3. 對 checkout API:
    • SLIavailability = 所有請求中 HTTP 2xx 的占比。
    • SLOavailability99.5%,視窗一個月。
  4. 對付款 webhooks:
    • SLI:webhook success rate。
    • SLO:7 天內至少 99% 成功處理 webhooks;webhook 處理延遲的 p95 < 500 毫秒。
  5. 對商業成果:
    • SLI:月度小工具 → 已付款訂單的轉換率。
    • SLO:轉換率 ≥ 1015%(視品類而定)。

就算只有這麼一小組,也足以提供決策的「骨架」: 如果你上線新模型、修改 prompt 或推薦演算法,看到 p95error‑rate 仍在 SLO 範圍內,而轉換率上升——大概就算是成功的實驗。

如果你不是用自有的 checkout 端點,而是使用 Instant Checkout,那「checkout API」其實就是 Agentic Checkout 的呼叫(/checkout_sessions create/update/complete),而「付款 webhooks」就是你發送給 OpenAI 的 order events(order.createdorder.updated)。指標與 SLO 的表述不變,只是協定不同。

9. 使用指標與 SLO 的常見錯誤

錯誤 №1:只量平均回應時間。
平均值方便但危險。它很容易掩蓋慢請求的尾巴。 在 ChatGPT App 中這點尤其致命:就算大部分使用者很快拿到結果,對那小部分人來說,固定的 5–10 秒延遲會被感知為「這 App 總是在卡」。 因此,延遲請至少用 p50p95,不要只看 mean。

錯誤 №2:在腦中與文件裡混淆 SLI、SLO 與 SLA。
有時開發者會寫「我們的 SLA——p95 < 2 秒」,但其實這既未對外承諾,也沒有任何配套。 事實上那是 SLOSLA 是與客戶簽的協議,包含違規後果。 如果把一切混在一起,就沒人說得清到底你對誰承諾了什麼。

錯誤 №3:告警與 SLO 沒有關聯。
典型陷阱是「每個錯誤都告警」、每個逾時都吵、任何 0.01 的偏差都通知。 結果 on‑call 工程師活在通知地獄,最後乾脆不看了。 更好的做法是基於 SLO 的違反來告警:當 error‑rate 顯著超出目標、或 p95 超過界線時,再去打擾人。

錯誤 №4:忽視 webhook 與非同步部分的指標。
雖然單獨提出,但這問題我們已見過——忽略 webhook 與非同步部分的指標。 許多團隊只看主要 API 的 HTTP 回應,不關注 webhooks 與背景處理。 在電商情境,最討厭的 bug 常躲在那裡:付款完成了,webhook 沒被處理,訂單「卡住」。 沒有 webhook 的 success rate 與 latency 指標,你可能會長期以為「一切正常」,直到帳務數字與資料庫嚴重對不上。

錯誤 №5:不切實際的 SLO,或完全沒有。
有時 SLO 會被寫成「100% uptime」或「完全沒有錯誤」。 現實做不到,會讓團隊挫折。 另一個極端是完全不訂 SLO,一直活在「應該還行」。 黃金中道是:先量當前的 SLI,設定稍嚴但可達成的目標,隨著系統成熟逐步收緊。

錯誤 №6:只為了「有指標」而有指標,與產品脫節。
也常見這樣的情況:指標系統裡滿是圖表、p95p99、數十個儀表板,但沒人能回答一個簡單問題: 「這條線如果跳起來——會影響什麼?使用者?收入?名譽?」 在 LLM 產品中特別要把技術指標(latencyerror‑rate)和商業指標(轉換率、成功付款)串起來。 否則,可觀測性就只是漂亮但無用的電視機。

留言
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION