Omi 真实流量 Journey 可观测性运行手册:封闭指标契约、客户端分段与 critical 级 SLI 分页告警
【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend
本文基于仓库内运维手册 backend/docs/runbooks/real-traffic-journeys.md 展开,并结合后端指标定义、标签契约、观测实现与 Grafana 告警规则源码进行纵深解析。本文适用于 Omi(本仓库代号 Friend)后端团队在 dev/beta 与生产环境维护实时转写、会话推送、语音捕获终结化等核心链路的可观测性体系,读者读完可以掌握:如何用「仅统计真实用户流量」的封闭指标契约区分用户故障与空闲系统、如何基于客户端分段 Journey 定位端到端失败归因、以及如何校准并部署分页级(critical)SLI 告警而避免在安静时段误报。
设计总纲:只度量真实用户流量,且绝不携带用户身份
omi_journey_*与omi_live_stt_*两个指标家族在整个仓库的可观测性体系中承担一个特殊职责:它们只统计用户发起的真实流量。这意味着这些计数器不会包含测试账号、合成金丝雀(synthetic canary)或脚本生成的请求。与此同时,dev/beta 与生产环境被刻意隔离——每个 Prometheus 实例只评估自己抓取的流量,指标上不携带任何跨环境比较标签,避免把不同环境的语义混在一起得出错误结论。
这个"真实性 + 隔离性"的设计直接决定了后续所有告警的表达方式:没有真实流量时,计数保持为零,而不是制造一个虚假的成功率分母或 0% 成功率的假象。下文所有指标定义、告警门槛与 noDataState 语义都建立在这一前提之上。
封闭指标契约:omi_journey_*与omi_live_stt_*家族
指标清单与含义
原文档给出了 9 个核心指标的完整契约,全部定义在 backend/utils/metrics.py 中:
| Metric | Labels | Meaning(含义) |
|---|---|---|
omi_journey_accepted_total | journey | 生产边界接受了工作。 |
omi_journey_terminal_total | journey,outcome | 已接受工作的一次性终结结果(one-shot terminal outcome)。 |
omi_journey_latency_seconds | journey,outcome | 从接受(acceptance)到终结(terminal)的时延。 |
omi_live_stt_accepted_total | provider,client_platform,deployment_environment | listener 接受了第一帧非平凡(nontrivial)的实时 STT 音频帧。 |
omi_live_stt_terminal_total | provider,outcome,client_platform,deployment_environment,phase | 已接受的实时 STT 尝试的一次性终结结果。 |
omi_live_stt_terminal_failures_total | provider,outcome,client_platform,deployment_environment,phase | 与实时 STT 终结事件关联的 provider 失败明细。 |
omi_capture_finalization_reconciliations_total | outcome | 过期(stale)的持久化捕获任务被重新入队(requeued),或其重新入队交接失败(enqueue_failed)。 |
omi_capture_finalization_failures_total | reason | 持久化终结器失败,且 reason 只能是以下隐私安全取值之一:memory_fence、memory_config、provider、stale、processing。 |
listen_finalization_oldest_nonterminal_age_seconds | 无 | 最老的已排队/已租约(leased)/被 BYOK 阻塞的捕获终结化任务的年龄。 |
listen_finalization_durable_jobs | state | 权威的 Firestore 任务投影:accepted、success、failure、stale、nonterminal、blocked_byok或terminal_unknown。 |
其中journey的封闭取值恰好为chat_response、pusher_session、capture_finalization三种;通用 journey 的outcome恰好为success、failure、cancelled、stale四种;reconciliation 的outcome恰好为requeued或enqueue_failed;实时 STT 终结outcome恰好为success、failure、cancelled;其终结phase恰好为transcript_delivery、initialization、connection、send、teardown。provider与client_platform沿用各自已有的封闭词表,deployment_environment是prod、dev、local、offline、unknown之一。整个体系不携带任何 user、conversation、request、error-text、revision、image、provider-model 或 content 标签——这是该体系的隐私安全底线。
源码级佐证:封闭标签如何在运行时被强制
代码层面对"封闭"的强制体现在两处:
指标定义处的封闭词表校验。在 backend/utils/observability/journeys.py 中,
_JOURNEYS与_OUTCOMES是两个 frozenset,_journey()与_outcome()两个辅助函数会在写入指标前校验标签值,非法的 journey 名或 outcome 直接抛出ValueError(如unknown journey: user-123)。测试 backend/tests/unit/test_journey_observability.py 明确断言了这一点:record_journey_accepted('user-123')必须抛错,record_journey_terminal('chat_response', 'raw exception text', 1.0)也必须抛错——原始异常文本永远不会成为标签。零初始化导出。在 backend/utils/metrics.py 中,进程启动时会遍历完整封闭组合,预先为每个 journey × outcome 组合创建零值标签子序列。这样健康但空闲的 exporter 导出的是一组明确的零,而不是一个"缺失的序列"——运维可以把"没有流量"与"抓取目标丢失"区分开(详见下文"抓取健康"小节)。
记录函数与语义
JourneyAttempt是一个进程内的一次性终结模型(backend/utils/observability/journeys.py):构造时调用record_journey_accepted自增接受计数,finish()只允许调用一次(_finished标志守卫),且同时写入OMI_JOURNEY_TERMINAL_TOTAL计数与OMI_JOURNEY_LATENCY_SECONDS直方图观测值。对于跨进程完成的持久化工作,如捕获终结化,record_capture_finalization_terminal(backend/utils/observability/journeys.py)使用持久化的接受时间(created_at)而非进程内monotonic()计算时延;对没有created_at的遗留任务,则只记录 outcome、不伪造时延值。对应的单元测试test_capture_terminal_uses_persisted_acceptance_time验证了"持久化接受时间 +5 秒"会得到 4~6 秒的观测值。
客户端分段 Journey:omi_client_journey_*家族
为什么需要独立家族
原文档强调了一个关键约束:既有的omi_journey_*家族刻意保持不动。它是一个封闭的、有历史stale语义、并且已经接了告警的契约。为了给客户分段(client segmentation)腾出空间而又不悄悄改写旧契约,新语义 Journey 使用独立的指标家族。这避免了两种风险:历史告警语义被漂移破坏,以及把"客户端分段"这个新维度硬塞进旧标签集造成基数爆炸。
指标清单
| Metric | Labels | Meaning(含义) |
|---|---|---|
omi_client_journey_accepted_total | journey,client_kind | 接受了一个语义 Journey。 |
omi_client_journey_terminal_total | journey,client_kind,outcome | 其一次性粗略终结结果。 |
omi_client_journey_issues_total | journey,client_kind,issue_class | 失败/降级/未知终结的一个有界明细。 |
omi_client_journey_duration_seconds | journey,outcome | 从接受到终结的持续时间。 |
outcome是success、failure、degraded、cancelled,或强制收敛哨兵unknown。degraded表示请求完成了、但没有交付所请求的主产品结果。canned_fallback与quota_capped必须保持为独立的issue_class值,禁止从粗略 outcome 反推;失败/降级明细是独立计数器。特别注意:client_kind被有意从直方图中剔除——直方图按journey+outcome分桶,避免把每个直方图桶再乘上客户端种类数量。
从源码看,这些封闭词表定义在 backend/utils/journey_metrics_contract.py 中:9 种ClientJourneyName(含desktop_chat、mobile_chat、live_transcription、conversation_finalization、memory_retrieval、desktop_proactivity、app_webhook_delivery、realtime_voice、unknown)、5 种ClientJourneyOutcome、12 种ClientJourneyIssueClass(upstream_rejected、upstream_timeout、provider_error、empty_answer、invalid_response、dependency_unavailable、quota_capped、rollout_disabled、canned_fallback、incomplete_attempt、incomplete_stream、unknown)以及 10 种ClientKind。bounded_*系列函数会对任何未经允许的输入做归一化、截断(默认 128 字符)并收敛到unknown,测试 backend/tests/unit/test_client_journey_foundation.py 验证了 1 万个字符的用户输入最终只产生 128 字符标签并收敛为unknown——用户输入永远不会直接变成标签。
client_kind解析:优先X-App-Platform,User-Agent 只是兜底
resolve_client_kind(backend/utils/journey_metrics_contract.py)的解析优先级如下:
- 存在非空的
X-App-Platform头时,按封闭映射解析:android→mobile_android、ios→mobile_ios、macos→desktop_macos、windows→desktop_windows、linux→desktop_linux、desktop→desktop_unknown_os、mobile→dart_mobile_unknown_os、web→web;未识别的新平台收敛为unknown,且不会回退去猜 User-Agent(避免自相矛盾)。 - 无头时只识别已知的 User-Agent 家族:
CFNetwork/Darwin→desktop_macos;Electron+Windows→desktop_windows;无头 Dart →dart_mobile_unknown_os。 - 两个 pi-mono 扩展目前发送相同的
OpenAI/JS 6.26.0User-Agent 且不带平台头,两者都会被解析为pi_mono_unknown_os。服务器在客户端发送X-App-Platform之前无法区分它们,也不得猜测操作系统——把一个无法归因的群体硬编成误导性遥测,比诚实的unknown更糟。
对应的参数化测试(backend/tests/unit/test_client_journey_foundation.py)覆盖了上述全部分支,包括"同一OpenAI/JS 6.26.0UA 在带/不带平台头时分别解析为desktop_macos/desktop_windows与pi_mono_unknown_os"。
零初始化、系列上限与"零也有意义"
该家族是零初始化的:只有下节列出的已接线 Journey 会产出真实流量,且目前尚不存在任何基于客户端分段的告警。这里有一个微妙的语义:零只有在与所属抓取作业/版本一起选择时才有意义,因为一个无关的健康 exporter 也会导出这些有界零子序列。这也是为什么零初始化在 backend/utils/metrics.py 中按完整笛卡尔积完成——测试test_client_journey_metrics_zero_initialize_the_complete_bounded_contract用_sample_label_sets断言 accepted/terminal/issues/duration 的子序列数量分别等于len(CLIENT_JOURNEYS)×len(CLIENT_KINDS)等精确乘积。
原文档给出了一个硬性容量结论:在启用固定客户端_created系列的情况下,该封闭笛卡尔积每个进程被限制在 3,915 个 Prometheus 系列——180 个 accepted、900 个 terminal、1,980 个 issue、855 个直方图系列。任何接入点如果试图超出这个封闭乘积,都会被bounded_*收敛机制拉回unknown,从而保护基数不失控。
流式调用的语义契约:observe_stream与错误帧优先
流式调用方必须把数据源通过ClientJourneyAttempt.observe_stream传递,并同时提供两个语义谓词(backend/utils/observability/journeys.py):
success_when:判定某个帧是成功候选。一个[DONE]帧只是成功候选——还需要流的干净耗尽(clean exhaustion)才最终成立;failure_when:必须识别每一个带内错误帧并给出有界的failure_class。检测到错误帧会立即终结(terminalize),且不能被后面的[DONE]或干净的流耗尽覆盖。
这正是"HTTP 200 响应头已提交之后才到达的错误"能被检测出来的机制:observe_stream在async for中先对每个 item 执行failure_when,失败立即fail(failure_class);成功则置success_observed标记但不终结;循环正常结束且未终结时,若见过成功帧则succeed(),否则fail(missing_success_class);若抛asyncio.CancelledError/ClientDisconnect/OSError则abandon_stream()以cancelled终结。_ObservedClientJourneyStream还负责在流未被耗尽(包括垃圾回收析构)时关闭被放弃的 attempt,防止泄漏未终结的 Journey。__exit__的失败闭合语义同样值得注意:正常退出上下文却未显式终结的 attempt 会被记为incomplete_attempt失败。
桌面端调用边界
desktop_chat:覆盖/v2/chat/completions,含托管网关分支与直连 Anthropic 分支。流式成功要求至少一个非空 content/tool delta、一个[DONE]帧以及干净耗尽;非流式成功要求非空 assistant content 或 tool call。带内error帧、空回答、provider 错误与不完整流都不算成功。desktop_proactivity:覆盖严格的/v1/desktop/proactivity/completions门面与经由遗留桌面 Gemini 代理的生成调用。严格门面只有在请求的 JSON schema 校验通过后才算成功;遗留 Gemini 流需要非空候选文本加终结finishReason,普通响应需要非空候选文本。按用户的 Redis 限额记为degraded(quota_capped),而 provider 错误与非法响应形状记为failure。
写指标是 fail-open 的
客户端 Journey 的指标写入是fail-open的:collector 或 registry 异常在可观测性边界被吞掉,绝不能改变产品响应。实现上,backend/utils/observability/journeys.py 的_record_fail_open捕获所有异常、静默返回给调用方,但会以 60 秒为上限节流打印client_journey_metric_record_failed警告——可观测性既不能破坏它所观测的对象,也不能安静地死掉(一个悄悄停止写入的 recorder 与"健康但无事可报"无法区分,而这恰恰是这个指标家族要消灭的失败模式)。
事件率 vs 在途量:禁止用减法推断积压
原文档特别警示:accepted 与 terminal 计数器是事件速率,不是 in-flight gauge,永远不要用两者之差去推断跨进程或重启的在途工作。如果一个 Journey 在进程 A 被接受、在进程 B 完成,必须持久化接受时间用于时延、两侧导出到同一遥测后端,并用持久化生命周期投影/队列 gauge 表示未完成工作——capture_finalization的listen_finalization_durable_jobs就是现成的范式(它在 backend/utils/metrics.py 中定义为"权威 Firestore 终结化任务按封闭持久化生命周期状态的全局计数",且注释明确要求用max()聚合、绝不能用sum(),否则会按副本数放大真实值)。
已接线的四个客户端边界
live_transcription:在首个非平凡音频帧后随既有实时 STT attempt 一起被接受;首个非空 transcript 发出后成功;provider/实时会话终结时失败;其余情况取消。既有的omi_live_stt_*契约保持权威且不变。realtime_voice:语音消息 WebSocket 被准入后接受;非空 transcript 发出后成功;provider 建立/发送或空终结回答时失败;客户端断开则取消。conversation_finalization:仅当 Firestore outbox 创建新任务时被接受;仅当持久化终结化完整完成时成功;任务 dead-letter 时失败;生命周期 fencing 使任务过期时取消。既有capture_finalizationJourney 保持不变。app_webhook_delivery:每个符合条件的 app/developer webhook 一次尝试;仅 2xx 响应算成功,拒绝、超时、目标无效、依赖熔断开启、传输/provider 错误都算失败。入队或调度发送不算成功。
跨进程终结的一个实现细节:record_conversation_finalization_client_terminal(backend/utils/observability/journeys.py)只有在能够解析出客户端的场景才终结客户端 Journey——从任务记录的client_platform字段解析client_kind,并用任务的created_at计算时延;缺失平台字段或时间戳时直接跳过而不是伪造。
边界语义:每个 Journey 的接受与终结定义
chat_response:在/v2/messages持久化人类消息并准备好其 SSE 响应后被接受。服务器产出终结done:帧时记录success;服务端流抛出异常为failure;流在终结帧前结束(消费者断开或取消)为cancelled。这无法证明客户端在服务器产出响应之后真正渲染了它。pusher_session:仅在/v1/trigger/listen完成 WebSocket accept 后被接受。关闭码1000与1001为success(除非服务器已识别出应用层失败);1011或更强的应用层失败为failure;其余传输/客户端结束方式为cancelled,不是产品失败。- 实时 STT:
/v4/listen收到第一个非平凡音频帧时被接受。只有在服务器已向该 WebSocket 发送第一个非空 transcript 载荷后才记录success(同样无法证明客户端渲染了载荷)。上游/实时会话失败或意外的 listen worker 失败为failure;transcript 发送之前的其余所有结束方式为cancelled。Provider 失败在有界标签(provider outcome 与 phase)下保留在omi_live_stt_terminal_failures_total;对应的已接受尝试在omi_live_stt_terminal_total中有一个failure终结。 capture_finalization:只在 Firestore 终结化 outbox 创建新的持久化任务时接受一次。成功完成为success,dead-letter 为failure,生命周期被 fencing 的持久化任务为stale。既有的任务重新派发不会增加接受计数;缺少有界字段的历史终结行是terminal_unknown;blocked_byok刻意保持非终结(nonterminal)。这个 Firestore 投影就是捕获任务的分母:PromQL 先跨 listener pod 取max,再用clamp_min(delta(...), 0)计算移动量,绝不求和复制后的全局 gauge。一个"死发射"(dead-emission)条件是:有界的新accepted移动、而success/failure/stale移动全为零。
对应的实现细节可以在 backend/tests/unit/test_journey_observability.py 中看到:_publish_job_metrics测试断言LISTEN_FINALIZATION_DURABLE_JOBS恰好以accepted/success/failure/stale/nonterminal/blocked_byok/terminal_unknown七种状态写出,而test_terminal_finalization_failure_records_once_after_dead_letter验证 dead-letter 后record_capture_finalization_terminal('failure', accepted_at)恰好调用一次。
仪表盘、告警与抓取健康
Resilience / Fallbacks 仪表盘
Resilience / Fallbacks仪表盘保留chat_response、pusher_session、capture_finalization的通用 Journey 终结成功率面板,并新增独立的实时 STT 失败/已接受尝试速率面板。通用速率刻意只使用终结success与failure两类 outcome:cancelled的客户端/传输结束与stale的 fenced 捕获任务保持可见,但不会伪装成应用失败。
终端成功率面板在某个 Journey 出现 success-or-failure 终结之前保持空(N/A)——它永远不会把空闲流量展示成 0% 成功率。对应测试断言了这些面板标题与 PromQL 表达式(如omi_journey_terminal_total{outcome=~"success|failure"}与and on (journey)、max by (state) (listen_finalization_durable_jobs)),见 backend/tests/unit/test_journey_observability.py。
告警门槛(warning 级)
- 实时 STT 产品失败告警:30 分钟内至少 20 个已接受的 listener 尝试,且有界的实时 STT 失败终结超过这些尝试的 10%,持续 10 分钟才告警。
- 其他产品失败告警:30 分钟内至少 20 个终结 success-or-failure 结果,且终结失败占比高于 10% 持续 10 分钟。
- 没有流量 → 接受计数为零 → 不触发寻呼。
- 独立的抓取源告警要求两个 Prometheus 作业
backend-listen-metrics与pusher-metrics都存在且每个目标 up——它区分的是"指标源缺失"与"健康但空闲的产品"。Grafana 查询错误保持为错误,而不是产品结果告警。
上述告警规则实体化在 backend/charts/monitoring/alerts/resilience.json 中,测试 backend/tests/unit/test_journey_observability.py 对每个产品规则断言了$A >= 20 && $B > 0.10结构、实时转写规则noDataState == OK、其余noDataState == NoData、抓取缺失规则noDataState == Alerting且表达式含backend-listen-metrics|pusher-metrics。实际表达式示例(来自该文件):sum(increase(omi_journey_terminal_total{journey="chat_response",outcome="failure"}[30m])) / clamp_min(sum(increase(omi_journey_terminal_total{journey="chat_response",outcome=~"success|failure"}[30m])), 1)。
抓取目标与零初始化
dev/beta 与生产环境预期的经过认证的/metrics抓取目标是:
backend-listen-metrics:chat 加上 Cloud Tasks 捕获终结化 worker;pusher-metrics:pusher 会话加上内联捕获终结化。
指标子序列在进程启动时初始化,因此被抓取的空闲目标导出零;缺失的系列应作为抓取或部署健康问题调查,而不是读成"零流量"。
Pusher 发布流程的实时 Grafana 契约验证
两个 Pusher 发布工作流在任何镜像发布或提升前都会验证实时 Grafana 供给契约。这要求受保护的MONITOR_GRAFANA_TOKEN密钥(monitor.omiapi.com使用 dest repo 密钥、monitor.omi.me使用 prod 环境密钥;绝不使用 TV 的GRAFANA_TOKEN),并证明:memory-admission 与 capture-outcome 规则的身份与表达式完全一致、规则未暂停、Prometheus 数据源健康、Pusher/backend-listen 抓取目标当前健康、Telegram 联系点路由存在。发布后的证明还额外要求两个作业的所有三个终结化指标家族都存在;发布前阶段刻意不要求候选引入的指标家族存在,避免引导死锁(bootstrap deadlock)。缺失凭据、发布后指标家族缺失、或任何 live/source 不匹配都会阻塞发布。
实时转写失败告警使用 GrafananoDataState: OK:在该流量出现之前,空结果是被预期的,不是故障。
Page-class Journey SLI 告警:对用户结果本身寻呼
原文档用一次真实事故说明了 warning 级规则为何不够:2026-08-30 的捕获终结化故障期间,capture_finalization成功率为 0% 持续数小时(失败约 360/h、stale 约 290/h、accepted 约 750/h),而 listen pods、WebSocket、LB 流量与 5xx 速率全部保持绿色。warning 级规则只报告降级,没有寻呼。以下severity: critical规则直接对用户结果寻呼,全部位于Resilience组并链接对应的Omi Core Features面板:
| Rule | Fires when(触发条件) | Traffic gate(流量门槛) | noDataState |
|---|---|---|---|
omi-journey-capture-success-critical | 捕获成功占比 < 0.90 持续 10m | 30m 内 ≥ 20 个 accepted | NoData |
omi-journey-pusher-success-critical | pusher 成功占比 < 0.90 持续 10m | 30m 内 ≥ 20 个 accepted | NoData |
omi-journey-chat-success-critical | chat 成功占比 < 0.90 持续 10m | 30m 内 ≥ 20 个 accepted | NoData |
omi-journey-capture-settle-gap | accepted − settled > 50 / 1h 持续 15m | 1h 内 ≥ 100 个 accepted | NoData |
omi-capture-oldest-nonterminal | 最老非终结任务年龄 > 3600s 持续 15m | 无(队列年龄与流量无关) | Alerting |
omi-capture-dead-letter-surge | dead letters > 50 / 1h 持续 15m | 无(每一个都是一次丢失的捕获) | Alerting |
omi-capture-finalization-memory-fence | 5m 内出现任何memory_fence或memory_config失败 | 无(首次 admitted-runtime 违规即可操作) | Alerting |
这些规则在 backend/charts/monitoring/alert-rules.json 中都有完整实体。以omi-journey-capture-success-critical为例,其表达式为sum(increase(omi_journey_terminal_total{journey="capture_finalization",outcome="success"}[30m])) / clamp_min(sum(increase(omi_journey_terminal_total{journey="capture_finalization",outcome=~"success|failure"}[30m])), 1),数学条件为$A >= 20 && $B < 0.90,for: 10m,且标注了user_impact("语音录音被接受但没有变成会话,用户完全丢失捕获")与safe_next_action(先确认成功磁贴与 accepted-vs-settled 面板再介入)。
分子分母同源与安静时段防护
成功占比的分子与分母都读取同一个 emitter 上同一journey的omi_journey_terminal_total,并以该 journey 的omi_journey_accepted_total作为流量门槛。这样两个目标同时达成:安静时段(quiet hours)不会误报;部分 emitter 故障无法伪造一个健康的比例。而"有真实 accepted 流量但终结完全停滞"会通过clamp_min(..., 1)得到 0 占比并同样在此寻呼。
校准依据(源自 2026-08-30 事故指纹)
原文档给出了各阈值的初始校准,并注明在规则导入时应针对生产历史进行回放验证:
- 0.90 寻呼下限:Core Features 磁贴展示 95/99 阈值;真实流量下持续 90% 下限就是明确的用户伤害。事故期间成功率为 0% 达数小时。
- 30m 内 ≥ 20 accepted:事故约 375 accepted/30m;20 与既有 warning 规则对终结结果使用的下限一致。
- 1h 内 gap > 50 且 ≥ 100 accepted:事故每小时遗留约 100 个"被接受但从未结算"的 Journey,积压老化到 5.9 天;正常在途工作在数分钟内结算,因此 1h 窗口的 gap 应接近零。
- 最老任务 > 3600s:健康终结化在数分钟内排空;事故最老任务约 5.9 天。
- 1h 内 dead letters > 50:事故每天约 2.5k dead letters(约 104/h);每个 dead letter 都是一次耗尽所有重试的捕获。
- 任何 memory admission 失败:一个接受持久化终结化工作但无法通过 canonical-memory fence 的主机,违反了其声明的能力。操作指引是:停止提升(halt promotion)、保留持久重试队列、对比服务镜像与 PodTemplate 收据与合格版本;不要为了诊断而记录或检查 transcript 内容。这条规则在 alert-rules.json 中的
safe_next_action注解里被完整复述("Halt promotion, preserve the durable retry queue, and restore a capability-admitted image plus config bundle before replay")。 - Chat 存活门槛(
omi-journey-signal-dead的 chat 臂):omi:auto:chat-agent通道在 7 个生产日内测得 min 9、p01 15、p05 23 请求/小时;> 20/1h只在高于 p05 的 chat 需求下要求存活。
两条队列规则的noDataState: Alerting
两条队列规则(omi-capture-oldest-nonterminal与omi-capture-dead-letter-surge)使用noDataState: Alerting,因为它们对应的系列无标签且零初始化:每个被抓取的 backend-listen 副本都以零导出它们,因此"缺失"意味着 emitter 或其抓取消失——在真实流量期间这本身就值得寻呼。
已知盲区:这套体系的诚实边界
原文档最后明确列出了一组无法消除的盲区:
- 服务器只能观察到它控制的 SSE/WS 边界,无法观察客户端渲染——
chat_response的done:帧与实时 STT 的 transcript 载荷都不能证明客户端真正渲染了内容。 - 进程重启可能推迟终结指标,直到持久化 worker/reconciler 恢复——重启期间的终结事件不是立即可见的。
- 仍处于有界 reconcile 延迟内的持久化任务被刻意视为非终结(nonterminal)而不是立即失败——这避免了把正常重试窗口误报为失败,但也意味着"延迟"与"卡死"之间需要靠年龄与 gap 类规则(而非单条终结率)来区分。
如何在本仓库继续深入
- 指标定义与零初始化:见 backend/utils/metrics.py,重点看
OMI_JOURNEY_*(第 57-90 行)、OMI_CLIENT_JOURNEY_*(第 210-256 行)、LISTEN_FINALIZATION_*(第 262-335 行)与OMI_LIVE_STT_*(第 481-526 行)。 - 封闭标签契约与
client_kind解析:见 backend/utils/journey_metrics_contract.py。 - Journey 记录实现(
JourneyAttempt、ClientJourneyAttempt、observe_stream、fail-open 写入):见 backend/utils/observability/journeys.py。 - 契约级单元测试:见 backend/tests/unit/test_journey_observability.py 与 backend/tests/unit/test_client_journey_foundation.py。
- 告警规则实体:warning 级见 backend/charts/monitoring/alerts/resilience.json,critical 级见 backend/charts/monitoring/alert-rules.json。
- 相关姊妹篇文档:backend/docs/runbooks/silent-failure-detection.md 与 backend/docs/runbooks/cloud-run-metrics-ingestion.md 分别覆盖静默失败检测与指标摄取管线,可与本文形成完整闭环。
综上,Omi 的真实流量 Journey 体系回答了可观测性领域一个核心问题:当所有基础设施指标都健康时,如何证明"用户旅程"本身没有在静默地失败。它的答案是:只度量真实流量、把每个 Journey 的接受与终结边界精确到协议层事件、用封闭词表杜绝用户身份进入标签、用零初始化和noDataState区分"空闲"与"缺失"、最后用分子分母同源的 critical 规则直接对用户结果寻呼——这套组合既保证了隐私安全与基数可控,又保证了在下次"0% 成功率但一切绿"的事故来临时,寻呼一定会响起。
【免费下载链接】FriendAI that sees your screen, listens to your conversations and tells you what to do项目地址: https://gitcode.com/GitHub_Trending/fr/Friend
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考