CodeGym /课程 /ChatGPT Apps /指标与 SLO:p95、error‑rate、webhooks、可用性

指标与 SLO:p95、error‑rate、webhooks、可用性

ChatGPT Apps
第 17 级 , 课程 1
可用

1. 为什么在 ChatGPT App 中需要指标和 SLO

设想两种团队状态。

第一种,大家秉持“看起来没问题”的原则。只要用户没有给客服留言、没有在社交媒体上抱怨——就当“一切正常”。 偶尔有人去翻一眼日志,扫几页“一长串”文本,点点头,关掉标签页。

第二种,团队有几块简单的看板:

  • 核心 MCP 工具的 p95 延迟。
  • MCP 和 checkout 的 error‑rate
  • webhooks 的可用性。
  • 支付转化漏斗。

并且定义了 3–5 个 SLO: “95% 对 recommend_gifts 工具的调用——不超过 2 秒”, “MCP‑tools 的错误占比 < 1%”, “checkout 可用性 ≥ 99.5%”, “从小部件到成功支付的转化 ≥ 10%”。

在第二种情况下,你会:

  • 即使在用户抱怨之前,也能很快意识到应用不太对劲
  • 能够度量任何改动的效果(新的推荐算法、SDK 迁移、供应商新套餐等);
  • 当你走到 Store 和审核模块时,可以诚实地说:“我们有质量标准,也知道目前达成的程度”。

我们会讨论一些非常直观的内容:p95 是什么、它为何优于平均值、如何计算 error‑rateavailability、commerce 场景中与 webhooks 相关的关键指标,以及如何把这些指标凝练为简单而有用的 SLO。 我们将以我们的教学应用 GiftGenius 为例进行拆解——一个包含推荐类 MCP 工具、checkout 和支付 webhooks 的 commerce 场景。

2. ChatGPT App 的基础指标:到底测什么

延迟:用 p50/p95/p99 代替“平均值”

延迟(latency)是从操作开始到其逻辑结束的时间。在我们的技术栈里,这样的操作有几种:

  • 调用 MCP 工具 recommend_gifts(选礼物);
  • 调用在 ACP 中创建 checkout intent 的工具;
  • 处理支付成功的 webhook。

要理解的是,用户在 ChatGPT 里感知到的整体时延,我们只能部分控制。 平台本身有开销(模型推理、OpenAI 的网络时延),而我们的部分——工具、后端、数据库、支付——也有开销。 对于延迟相关的 SLI(Service Level Indicator——可度量的服务质量指标),通常只度量我们的这部分:从请求进入 App/MCP 到我们的服务器产出响应为止。

平均响应时间是很“迷惑”的。假如 90% 的请求在 100 ms 内完成,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%,那么很可能问题不在他们,而在你这边。

Commerce 指标:转化与漏斗

GiftGenius 是一个 commerce 场景。这里不仅要关注技术层面(响应快、错误少),也要关注业务结果——推荐有多大概率变成已支付的订单。

这时就轮到转化漏斗登场了:

  • 看到小部件的用户(view);
  • 选择了礼物的用户(selection);
  • 点击“去下单”的用户(checkout started);
  • 订单状态变为“已支付”的用户(paid)。

由此可以定义“从小部件到支付”的转化率,这同样是一个 SLI——可度量的服务质量指标。 例如,“日维度:成功支付 / 看到小部件的用户 = 7%”。 如果延迟突然升高,或 checkout 偶尔超时失败,漏斗会在某些意料之外的环节开始“收缩”。

如果你使用 Instant Checkout

以上这些 commerce 指标同样适用于基于 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”切换为对这些标准端点的统计。 类似“p95 checkout 延迟 < 3 秒,error-rate < 2%”这样的 SLO,直接应用在 checkout_sessions 的调用上即可。

另一个世界:webhooks 指标

Webhooks 是异步事件(例如来自支付网关的 payment_succeeded),它们推动订单在生命周期中前进。 如果 webhook 处理得不好,应用也许能漂亮地做推荐,但订单会“卡”在“等待支付”或某个不确定的状态里。

如果你不是直接对接 Stripe/支付网关,而是通过 Instant Checkout,那么“支付网关的 webhooks”这件事会由 Agentic Checkout 的 order events 承担: 你的后端会把诸如 order.createdorder.updated 这样的事件推送到一个专用的 webhook‑URL(OpenAI 侧)。 这是同一类实体:决定订单最终状态的异步事件,只是接收方不是你的前端,而是 ChatGPT。

因此,同样的指标——success rate、latency、error-rate——不再是统计 Stripe 的 webhook,而是统计你发往 OpenAI 的 order‑event webhooks。 在 SLO 里可以直接写“99% 以上的 order.* 事件在 7 天内成功送达并被处理,处理延迟的 p95 < 500 ms”——对 Instant Checkout 来说,这是合理的 SLI/SLO

针对 webhooks 通常会看:

  • webhook success rate——包含重试在内,业务逻辑最终成功的 webhook 占比;
  • webhook latency——从 webhook 进入到处理完成的时间;
  • webhook error rate——处理以错误结束的占比(校验失败、数据库不可用、超时等)。

例如,可以设定 SLO: “7 天内至少 99% 的 webhooks 被成功处理” 以及“webhook 处理延迟的 p95 < 500 ms”。

3. 在 GiftGenius 中如何技术性地度量指标

从理论走向代码。我们需要弄清应该把度量嵌到哪里,才能在后续把数据聚合成 p95error‑rate 等指标。

为了不把具体的 Prometheus 或 Datadog 引进课堂,我们假设有一个简单函数 logMetric 或者记录 JSON 事件的日志。在这个基础上,任何可观测性系统都能画出需要的图表。

度量 MCP 工具的延迟

假设我们有一个用 TypeScript 写的 GiftGenius MCP 服务器,以及工具 recommend_gifts。 把业务逻辑用计时器包起来:

// mcp/tools/recommendGifts.ts
import { logMetric } from "../observability/metrics"; // 简化的辅助函数

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 这些标签只统计成功调用的 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%”。

Webhooks 的指标

当然,还有 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_msp95

随后为它们设定 SLO,例如“7 天内 99% 的 webhooks 被成功处理,p95 < 500 ms”。

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 是具体可度量的指标/公式。比如:

  • recommend_gifts 工具过去 24 小时的 p95(latency);
  • 过去 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 的特别指标:commerce + 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、模型提示或商品数据质量,而不是基础设施。

把 webhooks 的指标纳入转化

我们已经谈过 webhooks 的技术指标及其实现。现在要把它们和转化联动起来:对于 commerce 场景,webhooks 至关重要。 用户看到“支付成功”,但订单的最终状态取决于你的后端多快接收并处理 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%;
  • p95(latency recommend_gifts) 最近 10 分钟 > 3 秒,而 SLO 是 2 秒;
  • 最近 15 分钟没有任何成功的 checkout_paid——怀疑集成挂了;
  • webhook success rate 最近 10 分钟 < 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 绑定,能帮助值班工程师快速判断影响范围。

8. 小练习:为 GiftGenius 设计 SLO

试着为我们的应用拟定一组 SLO。这件事甚至可以在纸上完成,不需要完整的指标系统。

“起步版”示例:

  1. 针对核心 MCP 工具 recommend_gifts
    • SLI:过去 7 天成功调用的 p95(latency)。
    • SLOp95(latency) < 2 秒。
  2. 针对 MCP 工具的错误:
    • SLIerror‑rate = errors / (errors + successes)(30 天窗口)。
    • SLOerror‑rate < 1%。
  3. 针对 checkout API:
    • SLIavailability = 所有请求中 HTTP 2xx 的占比。
    • SLO:月维度 availability99.5%。
  4. 针对支付 webhooks:
    • SLI:webhook success rate。
    • SLO:7 天内 ≥ 99% 的 webhooks 被成功处理;处理延迟的 p95 < 500 ms。
  5. 针对业务结果:
    • SLI:小部件 → 已支付订单 的月转化率。
    • SLO:转化 ≥ 1015%(视品类而定)。

即便只有这样一小套,也已经为决策提供了“骨架”: 如果你上线了新模型版本、修改了提示或推荐算法,并且看到 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 秒的卡顿,他们会感到“应用总是很慢”。 因此对延迟请至少使用 p50p95,而不是只看 mean。

错误 2:在脑海和文档中混淆 SLI、SLO 与 SLA。
有时开发者会写“我们的 SLA 是 p95 < 2 秒”,但它既没有对外约定,也没有任何违约处置。 其实这应该是 SLOSLA 是与客户的外部协议,包含违反时的后果。 混在一起只会让“到底承诺了什么”变得不清不楚。

错误 3:告警与 SLO 脱节。
典型陷阱——为每个错误、每次超时、任何 0.01 的指标波动都发告警。 最终值班工程师会被通知“轰炸”,并开始忽视它们。 更有用的是围绕 SLO 违背来构建告警:当 error‑rate 明显超出目标水平,或 p95“越界”,这才值得叫醒大家。

错误 4:忽视 webhooks 和异步部分的指标。
这是我们已经提过、但值得单独强调的问题——忽略 webhooks 和异步流程的指标。 许多团队只盯主要 API 的 HTTP 响应,而不关注 webhooks 和后台处理。 在 commerce 场景里,最棘手的 bug 往往就藏在这里:支付成功了,webhook 没处理,订单“卡住了”。 没有针对 webhooks 的 success rate 与 latency 指标,你可能会长期以为“一切正常”,直到账务和数据库的统计开始严重不一致。

错误 5:不切实际的 SLO,或干脆没有 SLO。
有时 SLO 会被写成“100% uptime”或“绝不出错”。 现实中做不到,这会打击团队士气。 另一极端则是根本不设 SLO,一直“凭感觉”。 折中之道是:先测当前的 SLI,设一个略严格但可达的目标,随着系统成熟逐步收紧。

错误 6:为“凑指标”而度量,却与产品脱节。
也会出现这种情况:系统里满是图表、p95p99 和几十个仪表盘,但没人能回答一个简单问题: “这条线如果抬升——会伤害什么?用户?收入?品牌?” 在 LLM 产品中尤其要把技术指标(latencyerror‑rate)与业务指标(转化、成功支付)关联起来。 否则,可观测性会沦为漂亮却无用的“电视机”。

评论
TO VIEW ALL COMMENTS OR TO MAKE A COMMENT,
GO TO FULL VERSION