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

@moonmingmoonming 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:

ProviderField 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:

PathEmit?Reason
200 with recognisable usage fieldyesupstream-reported
200 without recognisable usagenoavoid noise rows (MEDIUM-1 precedent)
4xx / 5xxnonegative 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

coderabbitaiBot 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 freeto 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

@moonming
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Add copy buttons to all
 blocks\n(function() {\n function addCopyButtons() {\n document.querySelectorAll('pre code').forEach(function(codeBlock) {\n if (codeBlock.parentElement.hasAttribute('data-copy-added')) return;\n codeBlock.parentElement.setAttribute('data-copy-added', 'true');\n \n var btn = document.createElement('button');\n btn.textContent = 'Copy';\n btn.style.cssText = 'position:absolute;top:4px;right:4px;padding:2px 8px;font-size:11px;background:#4ecdc4;border:none;border-radius:4px;color:#1a1a2e;cursor:pointer;opacity:0.7;transition:opacity 0.2s;';\n btn.onmouseover = function() { this.style.opacity = '1'; };\n btn.onmouseout = function() { this.style.opacity = '0.7'; };\n btn.onclick = function() {\n navigator.clipboard.writeText(codeBlock.textContent).then(function() {\n btn.textContent = 'Copied!';\n setTimeout(function() { btn.textContent = 'Copy'; }, 1500);\n });\n };\n codeBlock.parentElement.style.position = 'relative';\n codeBlock.parentElement.appendChild(btn);\n });\n }\n \n addCopyButtons();\n \n // Re-run on dynamic content\n var observer = new MutationObserver(addCopyButtons);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Add Copy Buttons to Code Blocks");
}
} catch(__e) { console.warn('[Userscript:Add Copy Buttons to Code Blocks]', __e); }
})();
(function(){
try {
var __m = "github.com";
var __re = new RegExp('^' + "github\\.com" + '
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

@moonmingmoonming 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:

ProviderField 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:

PathEmit?Reason
200 with recognisable usage fieldyesupstream-reported
200 without recognisable usagenoavoid noise rows (MEDIUM-1 precedent)
4xx / 5xxnonegative 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

coderabbitaiBot 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 freeto 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

@moonming
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Force GitHub README to respect dark mode\n(function() {\n var style = document.createElement('style');\n style.textContent = '\n .markdown-body {\n color-scheme: dark light;\n }\n .markdown-body pre { background: #161b22 !important; }\n .markdown-body code { background: rgba(110, 118, 129, 0.4) !important; }\n .markdown-body table th, .markdown-body table td { border-color: #30363d !important; }\n .markdown-body img { background: #0d1117; }\n .markdown-body blockquote { border-left-color: #8b949e; }\n .markdown-body hr { border-color: #30363d; }\n ';\n document.head.appendChild(style);\n})();", "GitHub Dark Mode README Fix"); } } catch(__e) { console.warn('[Userscript:GitHub Dark Mode README Fix]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@moonmingmoonming 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:

ProviderField 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:

PathEmit?Reason
200 with recognisable usage fieldyesupstream-reported
200 without recognisable usagenoavoid noise rows (MEDIUM-1 precedent)
4xx / 5xxnonegative 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

coderabbitaiBot 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 freeto 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

@moonming
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Highlight search terms from Google/DuckDuckGo/Bing referrer\n(function() {\n var ref = document.referrer;\n var terms = [];\n \n if (ref.includes('google.com') || ref.includes('duckduckgo.com') || ref.includes('bing.com')) {\n var url = new URL(ref);\n var q = url.searchParams.get('q') || url.searchParams.get('p');\n if (q) {\n terms = q.split(/\\s+/).filter(function(t) { return t.length > 2; });\n }\n }\n \n if (terms.length === 0) return;\n \n var style = document.createElement('style');\n style.textContent = '.userscript-highlight { background: #fbbf24; color: #1a1a2e; padding: 1px 3px; border-radius: 2px; }';\n document.head.appendChild(style);\n \n function highlight(node) {\n if (node.nodeType === 3) { // text node\n var text = node.textContent;\n var found = false;\n terms.forEach(function(term) {\n var regex = new RegExp('(' + term.replace(/[.*+?^${}()|[\\]\\\\]/g, '\\\\') + ')', 'gi');\n if (regex.test(text)) {\n found = true;\n var frag = document.createDocumentFragment();\n var parts = text.split(regex);\n parts.forEach(function(part, i) {\n if (i % 2 === 0) {\n frag.appendChild(document.createTextNode(part));\n } else {\n var span = document.createElement('span');\n span.className = 'userscript-highlight';\n span.textContent = part;\n frag.appendChild(span);\n }\n });\n node.parentNode.replaceChild(frag, node);\n }\n });\n } else if (node.nodeType === 1 && node.childNodes) { // element\n var skipTags = ['SCRIPT', 'STYLE', 'NOSCRIPT', 'TEXTAREA', 'INPUT', 'SELECT'];\n if (!skipTags.includes(node.tagName)) {\n Array.from(node.childNodes).forEach(highlight);\n }\n }\n }\n \n highlight(document.body);\n \n // Re-highlight on dynamic content\n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1 || node.nodeType === 3) highlight(node);\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Highlight Search Terms"); } } catch(__e) { console.warn('[Userscript:Highlight Search Terms]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@moonmingmoonming 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:

ProviderField 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:

PathEmit?Reason
200 with recognisable usage fieldyesupstream-reported
200 without recognisable usagenoavoid noise rows (MEDIUM-1 precedent)
4xx / 5xxnonegative 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

coderabbitaiBot 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 freeto 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

@moonming
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Strip utm_, fbclid, gclid, etc. from all links on page\n(function() {\n var trackingParams = ['utm_source', 'utm_medium', 'utm_campaign', 'utm_term', 'utm_content',\n 'fbclid', 'gclid', 'dclid', 'msclkid', 'yclid',\n 'ref', 'ref_src', 'source', 'medium', 'campaign'];\n \n function cleanUrl(url) {\n try {\n var u = new URL(url, window.location.origin);\n var changed = false;\n trackingParams.forEach(function(p) {\n if (u.searchParams.has(p)) {\n u.searchParams.delete(p);\n changed = true;\n }\n });\n return changed ? u.toString() : url;\n } catch (e) {\n return url;\n }\n }\n \n function cleanLinks() {\n document.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n \n cleanLinks();\n \n var observer = new MutationObserver(function(mutations) {\n mutations.forEach(function(m) {\n m.addedNodes.forEach(function(node) {\n if (node.nodeType === 1) {\n if (node.tagName === 'A') cleanLinks();\n node.querySelectorAll('a[href]').forEach(function(a) {\n var clean = cleanUrl(a.href);\n if (clean !== a.href) a.href = clean;\n });\n }\n });\n });\n });\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "Remove Tracking Parameters from Links"); } } catch(__e) { console.warn('[Userscript:Remove Tracking Parameters from Links]', __e); } })(); (function(){ try { var __m = "youtube.com"; var __re = new RegExp('^' + "youtube\\.com" + '
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

@moonmingmoonming 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:

ProviderField 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:

PathEmit?Reason
200 with recognisable usage fieldyesupstream-reported
200 without recognisable usagenoavoid noise rows (MEDIUM-1 precedent)
4xx / 5xxnonegative 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

coderabbitaiBot 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 freeto 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

@moonming
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Auto-enable theater mode on YouTube\n(function() {\n function tryTheater() {\n var btn = document.querySelector('button[aria-label=\"Theater mode\"], ytd-player #player button[title=\"Theater mode\"]');\n if (btn && !btn.classList.contains('activated')) {\n btn.click();\n }\n }\n \n // Try immediately\n tryTheater();\n \n // Try after navigation (SPA)\n var lastUrl = location.href;\n setInterval(function() {\n if (location.href !== lastUrl) {\n lastUrl = location.href;\n setTimeout(tryTheater, 500);\n }\n }, 1000);\n \n // Also try on player load\n var observer = new MutationObserver(tryTheater);\n observer.observe(document.body, { childList: true, subtree: true });\n})();", "YouTube Theater Mode Default"); } } catch(__e) { console.warn('[Userscript:YouTube Theater Mode Default]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@moonmingmoonming 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:

ProviderField 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:

PathEmit?Reason
200 with recognisable usage fieldyesupstream-reported
200 without recognisable usagenoavoid noise rows (MEDIUM-1 precedent)
4xx / 5xxnonegative 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

coderabbitaiBot 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 freeto 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

@moonming
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Remove or un-stick sticky/fixed headers that block content\n(function() {\n function unstick() {\n document.querySelectorAll('header, nav, [role=\"banner\"], .header, .navbar, .sticky, .fixed-top, [style*=\"position: fixed\"], [style*=\"position:sticky\"]').forEach(function(el) {\n if (el.style.position === 'fixed' || el.style.position === 'sticky' || \n getComputedStyle(el).position === 'fixed' || getComputedStyle(el).position === 'sticky') {\n el.style.position = 'static';\n el.style.top = 'auto';\n el.style.zIndex = 'auto';\n }\n });\n }\n \n unstick();\n \n var observer = new MutationObserver(unstick);\n observer.observe(document.body, { childList: true, subtree: true, attributes: true, attributeFilter: ['style', 'class'] });\n})();", "Kill Sticky Headers"); } } catch(__e) { console.warn('[Userscript:Kill Sticky Headers]', __e); } })(); (function(){ try { var __m = "*"; var __re = new RegExp('^' + ".*" + '
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

@moonmingmoonming 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:

ProviderField 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:

PathEmit?Reason
200 with recognisable usage fieldyesupstream-reported
200 without recognisable usagenoavoid noise rows (MEDIUM-1 precedent)
4xx / 5xxnonegative 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

coderabbitaiBot 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 freeto 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

@moonming
, 'i'); if (__m === '*' || __re.test(location.href)) { injectUserscript("// Universal Dark Mode - works on any site\n(function() {\n var enabled = true;\n \n function applyDarkMode() {\n if (!enabled) return;\n \n // Create style element if it doesn't exist\n var style = document.getElementById('universal-dark-mode-style');\n if (!style) {\n style = document.createElement('style');\n style.id = 'universal-dark-mode-style';\n document.head.appendChild(style);\n }\n \n // Dark mode CSS - inverts colors but preserves images/video\n style.textContent = '\n /* Invert everything except media */\n html {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #1a1a2e !important;\n }\n \n /* Restore images, videos, iframes, canvas */\n img, video, iframe, canvas, svg, picture, [style*=\"background-image\"] {\n filter: invert(1) hue-rotate(180deg) !important;\n }\n \n /* Preserve specific elements that should not be inverted */\n .no-dark-mode, .no-dark-mode *,\n [data-theme=\"light\"], [data-theme=\"light\"],\n .ace_editor, .ace_editor *,\n .CodeMirror, .CodeMirror *,\n .monaco-editor, .monaco-editor *,\n .markdown-body pre, .markdown-body pre *,\n .highlight, .highlight *,\n pre code, pre code * {\n filter: none !important;\n }\n \n /* Fix common UI elements */\n .modal, .popup, .dropdown-menu, .tooltip, .popover {\n filter: invert(1) hue-rotate(180deg) !important;\n background: #2d2d44 !important;\n border-color: #444 !important;\n }\n \n /* Scrollbars */\n ::-webkit-scrollbar { background: #1a1a2e !important; }\n ::-webkit-scrollbar-thumb { background: #444 !important; }\n ::-webkit-scrollbar-thumb:hover { background: #555 !important; }\n \n /* Selection */\n ::selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ::-moz-selection { background: #4ecdc4 !important; color: #1a1a2e !important; }\n ';\n }\n \n function removeDarkMode() {\n var style = document.getElementById('universal-dark-mode-style');\n if (style) style.remove();\n }\n \n // Toggle with Alt+Shift+D\n document.addEventListener('keydown', function(e) {\n if (e.altKey && e.shiftKey && e.key === 'D') {\n e.preventDefault();\n enabled = !enabled;\n if (enabled) {\n applyDarkMode();\n console.log('[Universal Dark Mode] Enabled');\n } else {\n removeDarkMode();\n console.log('[Universal Dark Mode] Disabled');\n }\n }\n });\n \n // Apply on load\n applyDarkMode();\n \n // Re-apply on dynamic content\n var observer = new MutationObserver(function(mutations) {\n if (enabled && !document.getElementById('universal-dark-mode-style')) {\n applyDarkMode();\n }\n });\n observer.observe(document.head, { childList: true });\n \n console.log('[Universal Dark Mode] Loaded - Press Alt+Shift+D to toggle');\n})();", "Universal Dark Mode"); } } catch(__e) { console.warn('[Userscript:Universal Dark Mode]', __e); } })(); })();
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

@moonmingmoonming 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:

ProviderField 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:

PathEmit?Reason
200 with recognisable usage fieldyesupstream-reported
200 without recognisable usagenoavoid noise rows (MEDIUM-1 precedent)
4xx / 5xxnonegative 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

coderabbitaiBot 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 freeto 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

@moonming