Skip to content

fix(proxy): normalise upstream error.type to DP taxonomy, keep code/param verbatim (#327) - #328

Merged
moonming merged 1 commit into
mainfrom
fix/error-type-normalize-327
May 18, 2026
Merged

fix(proxy): normalise upstream error.type to DP taxonomy, keep code/param verbatim (#327)#328
moonming merged 1 commit into
mainfrom
fix/error-type-normalize-327

Conversation

@moonming

@moonming moonming commented May 18, 2026

Copy link
Copy Markdown
Collaborator

Summary

Fixes #327. PR #323 over-corrected by preserving upstream's error.type verbatim — this broke the stable "upstream_error" contract that dp-upstream-error-live.spec.ts:152 already pins, and leaked upstream-private taxonomy (upstream_test_fixture, AccessDeniedException, authentication_error, etc.) to customers.

Contract correction

Field Source Why
error.type DP taxonomy: "upstream_error" Stable, customer-SDK-actionable, hides upstream zoo
error.message upstream verbatim (4xx) / canned (5xx) Human-readable, already correct
error.code upstream verbatim or derived per-wire SDK retry branches on this — preservation from #322 stays
error.param upstream verbatim Tells client which field caused the error — preservation from #322 stays

Customer SDKs branch on error.type == "upstream_error" for upstream-class detection and on error.code for granular retry routing.

Implementation

  • error_translate::render_openai_envelope: hardcodes kind to a new UPSTREAM_ERROR_TYPE constant ("upstream_error").
  • Translation tables collapse from (String, Option<String>) to Option<String> — the per-wire OpenAI type half is now uniformly "upstream_error", so the tables only carry the derived OpenAI string code.
  • 5xx, UpstreamWire::Unknown, and generic-envelope paths already emit "upstream_error" — this commit just makes the 4xx path consistent.

Reference impl divergence (CLAUDE.md §7)

The established gateway impls in this space normalise upstream taxonomy too — LiteLLM maps to a closed set of Python exception classes, Portkey emits its own stable type enum. Choosing a single DP token ("upstream_error") rather than a closed OpenAI-vocabulary set (invalid_request_error, rate_limit_exceeded, server_error) is more conservative — it sidesteps the question of how Bedrock's AccessDeniedException should map onto OpenAI's invalid_request_error vs authentication_error, since the customer just needs "upstream broke; check code for granularity."

Test plan

  • cargo check --workspace --all-targets clean
  • cargo clippy --workspace --all-targets clean
  • cargo fmt --check clean
  • cargo test --workspace — all tests green
  • 22 error_translate::tests updated: body.kind always "upstream_error", code assertions unchanged
  • 3 affected integration tests in aisix-proxy::tests updated and renamed to reflect the corrected contract:
    • upstream_openai_4xx_forwards_code_and_param_but_normalises_type_per_issue_327 (was ..._forwards_full_envelope_per_issue_322)
    • upstream_anthropic_400_normalises_type_to_upstream_error (was ..._passes_through_with_openai_envelope)
    • upstream_anthropic_rate_limit_derives_openai_rate_limit_exceeded_code (was ..._translates_to_openai_rate_limit_exceeded)
  • dp-upstream-error-live.spec.ts:152 should go back to green once :dev image is rebuilt with this PR merged.
  • AISIX-Cloud matrix spec tightening (adapter-openai-overrides-live.spec.ts:634 from upstream_test_fixture to upstream_error) is the issue author's follow-up.

Closes #327

Summary by CodeRabbit

  • Bug Fixes
    • Standardized upstream service error responses to use consistent error type formatting across all provider services, preventing provider-specific error taxonomy from being exposed to client applications.
    • Enhanced error code mapping to improve SDK compatibility and ensure proper retry routing behavior across integrated providers.

Review Change Stack

…/param verbatim (#327)

#323 over-corrected by preserving upstream's `error.type` verbatim,
which:
- broke `dp-upstream-error-live.spec.ts:152` (asserting the stable
  `"upstream_error"` token) on every PR's e2e-playwright run from
  the merge forward
- leaked upstream-private taxonomy to customers (mock-llm's
  `upstream_test_fixture`, Bedrock's `AccessDeniedException`,
  Anthropic's `authentication_error` etc.)
- defeated the gateway's role as a normalising layer that hides
  the upstream zoo behind a stable, customer-SDK-actionable
  taxonomy

This commit restores the DP-stable `error.type = "upstream_error"`
contract for upstream errors, while keeping the #322/#323
preservation of `error.code` and `error.param` — those are still
SDK-actionable and customer-facing-positive.

Final wire contract for `ProxyError::Bridge(UpstreamStatus { .. })`:

| Field          | Source                          |
|----------------|---------------------------------|
| `error.type`   | DP taxonomy: `"upstream_error"` |
| `error.message`| upstream verbatim (4xx) / canned `"upstream returned N"` (5xx) |
| `error.code`   | upstream verbatim or derived per-wire (Anthropic/Bedrock/Vertex/Azure) |
| `error.param`  | upstream verbatim                |

Customer SDKs branch on `error.type == "upstream_error"` for
upstream-class detection and on `error.code` for granular retry
routing (`rate_limit_exceeded` vs `insufficient_quota` vs
`model_not_found` etc.).

Implementation:

* `error_translate::render_openai_envelope` hardcodes `kind` to the
  new `UPSTREAM_ERROR_TYPE` constant. The translation tables
  (Anthropic / Bedrock / Vertex / Azure) collapse from returning
  `(String, Option<String>)` to just `Option<String>` — the type
  half is now uniformly `"upstream_error"`, so the per-wire tables
  only carry the derived OpenAI string code.

* Generic envelope (missing parsed view) also emits
  `"upstream_error"` — unchanged from prior behaviour.

* 5xx and `UpstreamWire::Unknown` paths in
  `render_bridge_upstream_envelope` already emit `"upstream_error"`;
  this commit makes the 4xx path consistent.

Tests:

* All 22 `error_translate::tests` updated: `body.kind` assertions
  pinned to `"upstream_error"`; code assertions unchanged.

* 3 integration tests in `aisix-proxy::tests` updated and renamed:
  - `upstream_openai_4xx_forwards_full_envelope_per_issue_322` →
    `upstream_openai_4xx_forwards_code_and_param_but_normalises_type_per_issue_327`
    (asserts `upstream_test_fixture` does NOT leak)
  - `upstream_anthropic_400_passes_through_with_openai_envelope` →
    `upstream_anthropic_400_normalises_type_to_upstream_error`
  - `upstream_anthropic_rate_limit_translates_to_openai_rate_limit_exceeded`
    →
    `upstream_anthropic_rate_limit_derives_openai_rate_limit_exceeded_code`
    (the rate-limit `code` derivation is the customer-facing value;
    the `type` is normalised)

Closes #327

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
Copilot AI review requested due to automatic review settings May 18, 2026 05:19
@coderabbitai

coderabbitai Bot commented May 18, 2026

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

Configuration used: Organization UI

Review profile: CHILL

Plan: Free

Run ID: 443cdddd-c528-4f52-95ac-29328f6b5e15

📥 Commits

Reviewing files that changed from the base of the PR and between 1a744ee and 0fe40fc.

📒 Files selected for processing (2)
  • crates/aisix-proxy/src/error_translate.rs
  • crates/aisix-proxy/src/lib.rs

📝 Walkthrough

Walkthrough

This PR normalizes upstream error type handling in the proxy: error.type is always set to the DP-stable "upstream_error" token for upstream-originated 4xx errors, while error.code is intelligently derived via provider-specific helpers that map upstream taxonomies to OpenAI-compatible strings, with fallback preservation of upstream codes when derivation is unavailable.

Changes

Error type normalization and code derivation

Layer / File(s) Summary
Documentation and envelope rendering refactoring
crates/aisix-proxy/src/error_translate.rs
Module documentation updated to describe DP-stable error type normalization. The render_openai_envelope function refactored to always set kind to fixed "upstream_error" token and compute derived OpenAI code via per-upstream match logic that prefers derived codes for non-OpenAI wires while preserving upstream codes for OpenAI same-wire.
Error code derivation helpers
crates/aisix-proxy/src/error_translate.rs
Introduced UPSTREAM_ERROR_TYPE constant, updated generic envelope builder, and new derive_anthropic_code, derive_bedrock_code, derive_vertex_code, and derive_azure_code functions returning Option<String> for provider-specific OpenAI-compatible code mappings; Azure falls back to upstream code when derivation returns None.
Anthropic and Bedrock error handling tests
crates/aisix-proxy/src/error_translate.rs
Updated and added unit tests for Anthropic error kinds and Bedrock throttling to assert kind == "upstream_error" while validating derived code values (including None for unmapped kinds).
Bedrock, Vertex, and Azure error mapping tests
crates/aisix-proxy/src/error_translate.rs
Expanded tests for Bedrock quota/validation/access errors, Vertex gRPC status-code mappings, and Azure DeploymentNotFound to verify normalized kind and provider-specific derived code values.
Azure/OpenAI compatibility and same-wire preservation tests
crates/aisix-proxy/src/error_translate.rs
Tests for Azure content-policy violation derivation, Azure OpenAI-compatible code fallback behavior, OpenAI same-wire preservation of both code and param with normalized kind, and missing-parsed-message derived code assertion.
Cross-provider contract tests in lib.rs
crates/aisix-proxy/src/lib.rs
Integration-level tests verifying that OpenAI upstream 4xx and Anthropic 400/rate-limit errors enforce normalized error.type == "upstream_error" at the proxy boundary while preserving granular error.code and error.param for SDK retry branching and Anthropic→OpenAI code derivation.

🎯 3 (Moderate) | ⏱️ ~25 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.

Copilot AI 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.

Pull request overview

This PR corrects an over-correction from #323: while error.code and error.param are still preserved verbatim from upstream (preserving #322's SDK retry-routing fix), error.type is now normalized to the DP-stable "upstream_error" token across all wires/paths, so upstream-private taxonomies (upstream_test_fixture, AccessDeniedException, authentication_error, etc.) no longer leak to customers. The translation tables in error_translate.rs are collapsed from (type, code) pairs to a single derived code.

Changes:

  • Hardcode kind = "upstream_error" in render_openai_envelope; introduce UPSTREAM_ERROR_TYPE constant.
  • Rename translate_*derive_*_code and return Option<String> only (the OpenAI-shape code).
  • Update unit and integration tests + their docstrings/names to assert the corrected contract.

Reviewed changes

Copilot reviewed 2 out of 2 changed files in this pull request and generated no comments.

File Description
crates/aisix-proxy/src/error_translate.rs Drop per-wire type derivation; emit DP-stable upstream_error and only derive OpenAI code. Update module doc and tests.
crates/aisix-proxy/src/lib.rs Rename/retighten 3 integration tests to assert type == "upstream_error" while keeping code/param preservation assertions.

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

@moonming

moonming commented May 18, 2026

Copy link
Copy Markdown
Collaborator Author

Audit completed — verdict: merge, no HIGH/MEDIUM findings.

Two LOW findings:

Per CLAUDE.md §8 merge gate: ready when CI passes.

@moonming

Copy link
Copy Markdown
Collaborator Author

Follow-up on the audit's LOW-1: traced the path, not a real bug. BridgeError::UpstreamStatus is only constructed pre-stream (via map_http_error), and the stream itself only yields Transport/UpstreamDecode whose Display impls don't carry upstream body content. Closed #329 with the trace.

@moonming
moonming merged commit 30f70a2 into main May 18, 2026
12 checks passed
@moonming
moonming deleted the fix/error-type-normalize-327 branch May 18, 2026 05:30
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.

bug(proxy): #322 over-corrected — error.type should normalize to DP taxonomy, not verbatim from upstream

2 participants