Skip to content

feat(rerank): emit UsageEvent on /v1/rerank 200 (#405) - #428

Merged
moonming merged 2 commits into
mainfrom
feat/issue-405-rerank-usage-emit
May 27, 2026
Merged

feat(rerank): emit UsageEvent on /v1/rerank 200 (#405)#428
moonming merged 2 commits into
mainfrom
feat/issue-405-rerank-usage-emit

Conversation

@moonming

@moonming moonming commented May 27, 2026

Copy link
Copy Markdown
Member

Summary

Fixes #405. Sibling of PR #402 (embeddings), PR #425 (responses), PR #426 (completions).

Pre-fix, `/v1/rerank` dropped the `UsageEvent` entirely. Customers using Cohere / Voyage rerank had spend invisible to cp-api's budget ledger and customer-facing /logs analytics. This PR closes the gap for all three supported upstreams (OpenAI-compat, Cohere, Jina).

Wire-shape coverage

The three rerank-supporting providers each surface tokens differently. The extractor handles all three:

Provider Field path
OpenAI-compat `usage.prompt_tokens` (or `usage.input_tokens`)
Jina `usage.total_tokens`
Cohere `meta.billed_units.input_tokens`

All three surface as `UsageEvent.prompt_tokens` — rerank has no completion side, and cp-api's `dpmgr_usage_events` table has no rerank-specific columns. The single counter is what gets multiplied by the model's per-token price for billing.

Architecture note

Dispatch now parses the upstream body bytes once for usage extraction, then forwards the raw bytes verbatim downstream. This preserves provider-specific fields (Cohere `meta.api_version`, Jina extras) that a JSON round-trip would reformat.

Emit semantics

Lessons baked in from PR #425 audit MEDIUM-1 / MEDIUM-2:

Path Emit? Reason
200 with recognisable usage field yes upstream-reported
200 without recognisable usage no avoid noise rows (MEDIUM-1 precedent)
4xx / 5xx no negative pinning (MEDIUM-2 precedent)

Test plan

  • `emits_usage_event_on_200_openai_compat_issue_405` — OpenAI/Jina `usage.prompt_tokens` shape
  • `emits_usage_event_on_cohere_wire_shape_issue_405` — Cohere `meta.billed_units.input_tokens` shape
  • `skips_usage_event_when_upstream_lacks_usage_fields` — no recognisable shape → no emit
  • `upstream_5xx_does_not_emit_usage_event` — negative pinning
  • All 7 pre-existing `rerank::tests` still pass
  • `cargo clippy -p aisix-proxy -- -D warnings` clean

References

Summary by CodeRabbit

  • New Features
    • Rerank endpoint now emits usage metrics for improved observability across supported providers.

Review Change Stack

Pre-#405, /v1/rerank dropped the UsageEvent entirely. Customers
using Cohere or Voyage rerank had spend invisible to cp-api's
budget ledger and customer-facing /logs analytics.

This PR mirrors PR #402 (embeddings) — rerank, like embeddings,
has no completion side, no streaming, no reasoning tokens; the
extractor handles the three known wire shapes:

- OpenAI-compat: `usage.prompt_tokens` (or `usage.input_tokens`)
- Jina: `usage.total_tokens`
- Cohere: `meta.billed_units.input_tokens`

All three end up surfaced as `UsageEvent.prompt_tokens` because
cp-api's `dpmgr_usage_events` has no rerank-specific column; the
value is what gets multiplied by the model's per-token price.

Architecture: dispatch now parses the upstream body bytes once
for usage extraction, then forwards the raw bytes verbatim
downstream so any provider-specific fields (Cohere `meta`, Jina
extras) round-trip without re-formatting.

Emit semantics (lessons baked in from #425 audit MEDIUM-1/-2):
- 200 with recognisable usage field → emit
- 200 without recognisable usage field → no emit (avoids
  zero-everything noise rows)
- 4xx / 5xx → no emit (negative pinning test)

Tests (4 new):
- `emits_usage_event_on_200_openai_compat_issue_405` — OpenAI/Jina
  `usage.prompt_tokens` shape
- `emits_usage_event_on_cohere_wire_shape_issue_405` — Cohere
  `meta.billed_units.input_tokens` shape
- `skips_usage_event_when_upstream_lacks_usage_fields` — no
  recognisable shape → no emit
- `upstream_5xx_does_not_emit_usage_event` — negative pinning

References:
- Parent: #226
- Sibling MVPs: #402 (embeddings), #425 (responses), #426 (completions)
- Cohere spec: <https://docs.cohere.com/reference/rerank>
- Jina spec: <https://api.jina.ai/v1/rerank>
@coderabbitai

coderabbitai Bot commented May 27, 2026

Copy link
Copy Markdown
ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Free

Run ID: 69938c5c-38d4-43a1-8a44-5e7e1f01dfae

📥 Commits

Reviewing files that changed from the base of the PR and between ba47e37 and f26744b.

📒 Files selected for processing (1)
  • crates/aisix-proxy/src/rerank.rs

📝 Walkthrough

Walkthrough

The /v1/rerank handler is refactored to emit UsageEvent telemetry on successful responses. The internal dispatch function returns a success bundle containing the upstream response and extracted usage data. Upstream response bodies are parsed as JSON to extract provider-specific token counts (OpenAI, Jina, Cohere formats), then conditionally emitted as telemetry events.

Changes

Rerank Usage Event Emission

Layer / File(s) Summary
Imports and dispatch success types
crates/aisix-proxy/src/rerank.rs
UsageEvent import added; RerankDispatchSuccess struct introduced to carry upstream Response, provider/model identifiers, and optional extracted prompt_tokens from provider-specific response shapes.
Dispatch signature and rerank handler integration
crates/aisix-proxy/src/rerank.rs
dispatch function signature updated to return Result<RerankDispatchSuccess, ProxyError>; top-level rerank handler consumes the new struct, records access logs/metrics using dispatched provider info, and conditionally emits UsageEvent when usage extraction succeeds.
Usage extraction and emission
crates/aisix-proxy/src/rerank.rs
Upstream response body bytes parsed as JSON before building downstream response; extract_rerank_usage implements provider-specific precedence chain (OpenAI-compat usage.*, Jina usage.total_tokens, Cohere meta.billed_units.input_tokens); emit_usage_event constructs and publishes UsageEvent with inbound_protocol = "openai" and prompt_tokens populated.
Test coverage for usage event emission
crates/aisix-proxy/src/rerank.rs
Test suite extended with cases verifying UsageEvent emission on 200 responses for OpenAI-compat, Jina, and Cohere formats; verifies skipped emission when usage fields absent; verifies no emission on upstream 5xx errors; OpenAiBridge added to test imports.

Sequence Diagram

sequenceDiagram
  participant Client
  participant RerankHandler
  participant UpstreamProvider
  participant UsageSink
  Client->>RerankHandler: POST /v1/rerank
  RerankHandler->>UpstreamProvider: dispatch request
  UpstreamProvider-->>RerankHandler: 200 response with usage
  RerankHandler->>RerankHandler: parse response body as JSON
  RerankHandler->>RerankHandler: extract_rerank_usage (provider-specific)
  RerankHandler->>UsageSink: emit_usage_event (prompt_tokens)
  RerankHandler-->>Client: rerank response
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45 minutes


Note

🎁 Summarized by CodeRabbit Free

Your organization has reached its limit of developer seats under the Pro Plan. For new users, CodeRabbit will generate a high-level summary and a walkthrough for each pull request. For a comprehensive line-by-line review, please add seats to your subscription by visiting https://app.coderabbit.ai/login.If you believe this is a mistake and have available seats, please assign one to the pull request author through the subscription management page using the link above.

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

PR #428 audit raised 1 HIGH + 1 MEDIUM. Both addressed:

HIGH — Silent parse failure dropped billing. The previous code did
`serde_json::from_slice(&bytes).ok().and_then(...)` which made an
upstream that returned 200 + malformed body produce zero billing
with zero visibility. Operators couldn't see this in dashboards
because the failure surfaced only as missing UsageEvents (no log
line, no metric). Fixed: log a `tracing::warn!` with the
request_id, model name, and parse error so the failure is
operator-visible.

MEDIUM — Jina's wire shape uses `usage.total_tokens` only (no
`prompt_tokens` or `input_tokens` field). The extractor's
precedence chain has the right fallback, but no test exercised
the Jina-only path with a real emit assertion — the existing
`jina_provider_dispatches_to_upstream_with_bearer_auth` test
doesn't wire `usage_sink`, so a refactor breaking the
`total_tokens` arm would silently zero every Jina-backed billing
row. Added `emits_usage_event_on_jina_total_tokens_only_shape_audit_m1`
asserting `event.prompt_tokens == 19` for the Jina-only payload.
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.

UsageEvent emission missing on /v1/rerank (#226 follow-up)

1 participant