Skip to content

fix(telemetry): emit a UsageEvent for failed non-chat requests - #650

Merged
jarvis9443 merged 2 commits into
mainfrom
fix/non-chat-error-usage-events
Jun 25, 2026
Merged

fix(telemetry): emit a UsageEvent for failed non-chat requests#650
jarvis9443 merged 2 commits into
mainfrom
fix/non-chat-error-usage-events

Conversation

@jarvis9443

@jarvis9443 jarvis9443 commented Jun 25, 2026

Copy link
Copy Markdown
Contributor

Problem

chat / messages / responses emit a zero-token UsageEvent on a failed attempt (#655), so failures appear in the dashboard Logs and the budget ledger. The single-attempt non-chat handlers — /v1/completions, /v1/embeddings, /v1/rerank, /v1/audio/*, /v1/images/generations — dropped the event entirely on the error path. A failed request to those endpoints was invisible in Logs and the ledger (only the access log + a metrics counter recorded it).

Fix

Add a shared usage_attr::emit_error_usage_event helper and call it from each handler's error arm: one zero-token event carrying status_code, a bounded error_class (ProxyError::kind), the requested model name, api_key, and client IP/UA. model_id is left empty (the resolved id isn't threaded out of dispatch on the error path) — requested_model + status_code + error_class are enough to surface the row. The 501 NotImplemented path still emits nothing (no upstream call), unchanged.

Tests

Two existing tests pinned the old "no event on 5xx" behavior (completions, rerank) — updated to assert the new zero-token error event (status, zero tokens, non-empty error_class, exactly one event). New equivalent tests added for embeddings, images, and audio/speech. Full aisix-proxy suite green (487), clippy + fmt clean.

Origin: cross-API consistency audit after AISIX-Cloud#867.

Summary by CodeRabbit

  • New Features
    • Failed requests across audio, completions, embeddings, images, and rerank now generate zero-token usage records for better visibility in logs and analytics.
  • Bug Fixes
    • Error responses are now consistently attributed with status codes, error classification, API key, and request context.
    • Upstream 5xx failures now surface as tracked events instead of being silently omitted.

chat / messages / responses emit a zero-token UsageEvent on a failed attempt
(#655), so failures show up in the dashboard Logs and the budget ledger. The
single-attempt non-chat handlers — /v1/completions, /v1/embeddings, /v1/rerank,
/v1/audio/* and /v1/images/generations — dropped the event entirely on the error
path, so a failed request was invisible: it appeared in neither Logs nor the
ledger, only in metrics + the access log.

Add a shared `usage_attr::emit_error_usage_event` helper and call it from each
handler's error arm: one zero-token event carrying status_code, a bounded
error_class (ProxyError::kind), the requested model name, api_key, and client
IP/UA. model_id is left empty (the resolved id isn't threaded out of dispatch
on the error path) — requested_model + status + error_class are enough to
surface the row. The 501 NotImplemented path still emits nothing (no upstream
call), unchanged.

Two existing tests pinned the old "no event on 5xx" behavior (completions,
rerank) — updated to assert the new zero-token error event. New equivalent
tests added for embeddings, images and audio. Full aisix-proxy suite green
(487), clippy + fmt clean.

Origin: cross-API consistency audit after AISIX-Cloud#867.
@coderabbitai

coderabbitai Bot commented Jun 25, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

The proxy now emits zero-token error UsageEvents for failed /v1/completions, /v1/embeddings, /v1/images/generations, /v1/rerank, and /v1/audio/* requests. Shared telemetry helpers in usage_attr.rs build and dispatch these events, and tests now assert the new emissions.

Changes

Error UsageEvent emission

Layer / File(s) Summary
Shared telemetry helpers
crates/aisix-proxy/src/usage_attr.rs
usage_attr.rs adds provider-key telemetry tag resolution, tag application, and emit_error_usage_event, which builds zero-token error UsageEvents and sends them to the usage sink and OTLP exporters.
Non-audio handler wiring
crates/aisix-proxy/src/completions.rs, crates/aisix-proxy/src/embeddings.rs, crates/aisix-proxy/src/images.rs, crates/aisix-proxy/src/rerank.rs
These handlers now emit error UsageEvents on upstream failures, and their 5xx tests assert one zero-token event with mapped status, request attribution, and a non-empty error class.
Audio handler wiring
crates/aisix-proxy/src/audio.rs
The transcription and translation failure paths pass an empty requested_model, speech uses the resolved model_name, and the speech 5xx test asserts the single zero-token event and its fields.

Sequence Diagram(s)

sequenceDiagram
  participant completions
  participant emit_error_usage_event
  participant ProxyState
  participant usage_sink
  participant exporters
  completions->>emit_error_usage_event: upstream failure metadata
  emit_error_usage_event->>usage_sink: emit zero-token UsageEvent
  emit_error_usage_event->>ProxyState: load current snapshot
  ProxyState-->>emit_error_usage_event: exporters
  emit_error_usage_event->>exporters: fan out UsageEvent
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~30 minutes

Possibly related PRs

  • api7/aisix#646: Updates /v1/responses telemetry attribution with provider-key tags, which uses the same usage_attr plumbing touched here.
  • api7/aisix#647: Modifies the shared non-chat usage telemetry flow in usage_attr.rs, closely matching this PR’s new error-path UsageEvent emission.
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
E2e Test Quality Review ⚠️ Warning Only in-process tokio/MockServer tests were added; no API→service→DB/external end-to-end coverage, so the E2E requirement is unmet. Add at least one true E2E test that exercises the public API through the full stack and verifies the emitted usage row/event in the real sink or ledger.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title accurately summarizes the main change: emitting UsageEvents for failed non-chat requests.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Security Check ✅ Passed No security issues found: the new error telemetry uses sanitized client context and bounded error_class, with no raw secrets/headers/body logged.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/non-chat-error-usage-events

Comment @coderabbitai help to get the list of available commands.

…age-events

# Conflicts:
#	crates/aisix-proxy/src/audio.rs

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🧹 Nitpick comments (1)
crates/aisix-proxy/src/usage_attr.rs (1)

66-95: 🚀 Performance & Scalability | 🔵 Trivial | ⚡ Quick win

Consider recording latency_ms on the error event.

Every caller already computes elapsed before invoking this helper (e.g. completions.rs Line 130, audio.rs Line 287), but the emitted error event leaves latency_ms at its Default of 0. For a feature whose purpose is surfacing failed requests in Logs, time-to-failure is useful signal (distinguishes a fast 4xx reject from a slow upstream timeout that mapped to 502). Threading an elapsed: Duration param through here and the six call sites is mechanical.

♻️ Sketch
 pub(crate) fn emit_error_usage_event(
     state: &ProxyState,
     label: &'static str,
     request_id: &str,
     requested_model: &str,
     api_key_id: &str,
     status_code: u16,
     error_class: &str,
+    elapsed: std::time::Duration,
     client: &ClientContext,
 ) {
     let event = UsageEvent {
         request_id: request_id.to_string(),
         occurred_at: chrono::Utc::now().to_rfc3339_opts(chrono::SecondsFormat::Secs, true),
         api_key_id: api_key_id.to_string(),
         requested_model: requested_model.to_string(),
         status_code,
+        latency_ms: elapsed.as_millis().min(u32::MAX as u128) as u32,
         inbound_protocol: "openai".to_string(),
         error_class: error_class.to_string(),
         client_source_ip: client.source_ip.clone(),
         client_user_agent: client.user_agent.clone(),
         ..Default::default()
     };
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@crates/aisix-proxy/src/usage_attr.rs` around lines 66 - 95, The error usage
event currently omits `latency_ms`, leaving failed requests recorded with the
default value even though callers already compute `elapsed`. Update
`emit_error_usage_event` in `usage_attr.rs` to accept the elapsed duration and
populate `latency_ms` on `UsageEvent`, then thread that new argument through
each caller that invokes this helper (such as the error paths in
`completions.rs` and `audio.rs`) so the emitted error event carries the actual
time-to-failure.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Nitpick comments:
In `@crates/aisix-proxy/src/usage_attr.rs`:
- Around line 66-95: The error usage event currently omits `latency_ms`, leaving
failed requests recorded with the default value even though callers already
compute `elapsed`. Update `emit_error_usage_event` in `usage_attr.rs` to accept
the elapsed duration and populate `latency_ms` on `UsageEvent`, then thread that
new argument through each caller that invokes this helper (such as the error
paths in `completions.rs` and `audio.rs`) so the emitted error event carries the
actual time-to-failure.

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: aa240605-4d41-4fb4-bc6f-70ff49a44d05

📥 Commits

Reviewing files that changed from the base of the PR and between 09e52c4 and b439038.

📒 Files selected for processing (6)
  • crates/aisix-proxy/src/audio.rs
  • crates/aisix-proxy/src/completions.rs
  • crates/aisix-proxy/src/embeddings.rs
  • crates/aisix-proxy/src/images.rs
  • crates/aisix-proxy/src/rerank.rs
  • crates/aisix-proxy/src/usage_attr.rs

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant