Skip to content

fix(messages): stop forwarding Anthropic-only fields to OpenAI-compatible upstreams - #710

Merged
jarvis9443 merged 1 commit into
mainfrom
fix/953-anthropic-extras-cross-provider
Jul 3, 2026
Merged

fix(messages): stop forwarding Anthropic-only fields to OpenAI-compatible upstreams#710
jarvis9443 merged 1 commit into
mainfrom
fix/953-anthropic-extras-cross-provider

Conversation

@jarvis9443

@jarvis9443 jarvis9443 commented Jul 3, 2026

Copy link
Copy Markdown
Contributor

Problem

When an Anthropic SDK / Claude Code client calls /v1/messages targeting a model whose upstream is OpenAI/Azure OpenAI, Anthropic-only top-level request fields leak into the upstream request body and the upstream rejects them:

API Error: 400 {"type":"error","error":{"type":"invalid_request_error","message":"Unknown parameter: 'context_management'."}}

The chain: parse_inbound_request() copies every non-whitelisted top-level key into ChatFormat.extra; cross_provider_dispatch() only translates tools/tool_choice; OpenAiRequest.extra is #[serde(flatten)], so context_management (and thinking, top_k, mcp_servers, …) land verbatim on the OpenAI wire.

Fix

Replace the tools/tool_choice-only fixup in cross_provider_dispatch with a whitelist translation, translate_extras_to_openai_shape() in the anthropic provider crate:

  • tools / tool_choice → existing OpenAI-shape translation (bug: /v1/messages silently drops tools and tool_choice when upstream is OpenAI-compatible #236 behavior unchanged)
  • stop_sequencesstop
  • metadata.user_iduser (LiteLLM parity)
  • thinkingreasoning_effort (LiteLLM-aligned buckets: budget_tokens ≥4096 high / ≥2048 medium / ≥1024 low / below minimal; adaptive → medium; disabled → omitted)
  • everything else (context_management, top_k, mcp_servers, container, service_tier, betas, …) is dropped (debug-logged), so future Anthropic SDK fields cannot re-trigger the 400

This mirrors the whitelist-and-drop policy the /v1/responses bridge (#825) already applies in the opposite direction. Both streaming and non-streaming go through the same ChatFormat build, so one fix covers both.

Behavior changes / compatibility

  • Anthropic-native /v1/messages passthrough (Anthropic upstream) is untouched — bodies still forward verbatim, context_management keeps working there.
  • Bedrock/Vertex bridges only ever read tools/tool_choice from extras, so dropping the rest changes nothing for them.
  • LiteLLM baseline (HEAD 88e03e5): its Anthropic→OpenAI adapter is also whitelist-based and maps metadata.user_iduser and thinkingreasoning_effort; it translates context_management into OpenAI Responses-API compaction (or polyfills it in-gateway on the chat path) rather than dropping — that fuller treatment is deferred to a follow-up issue. stop_sequencesstop goes beyond LiteLLM (which silently drops it) since chat/completions supports stop natively.

Tests

  • Unit tests for translate_extras_to_openai_shape (drop set, mappings, thinking buckets).
  • New E2E messages-cross-provider-extras-e2e.test.ts (non-streaming + streaming): asserts Anthropic-only fields never reach the mock OpenAI upstream while translated fields survive. Fails before this fix (context_management leaked), passes after.

Fixes api7/AISIX-Cloud#953

Summary by CodeRabbit

  • New Features

    • Expanded cross-provider request translation so Anthropic message extras are converted into an OpenAI-compatible shape more consistently.
    • Added support for mapping additional fields such as stop sequences, user metadata, and thinking-related settings.
  • Bug Fixes

    • Removed Anthropic-only fields from upstream requests to prevent unsupported parameters from being forwarded.
    • Improved handling for both streaming and non-streaming message requests across providers.
  • Tests

    • Added end-to-end coverage for cross-provider extras translation and field preservation/removal behavior.

…ible upstreams

The /v1/messages cross-provider path preserved every unrecognized
Anthropic top-level field in ChatFormat.extra, and the OpenAI bridge
flattens extras into the upstream request body. Anthropic-only fields
(context_management, thinking, top_k, mcp_servers, ...) reached
OpenAI/Azure upstreams verbatim, which reject them:
400 "Unknown parameter: 'context_management'".

Replace the tools/tool_choice-only fixup with a whitelist translation
(translate_extras_to_openai_shape): tools/tool_choice keep their
existing translation, stop_sequences -> stop, metadata.user_id -> user,
thinking -> reasoning_effort (LiteLLM-aligned budget buckets); every
other Anthropic-only field is dropped, so future Anthropic SDK fields
can't re-trigger the 400. Mirrors the whitelist-and-drop policy the
/v1/responses bridge (#825) already applies in the opposite direction.

Fixes api7/AISIX-Cloud#953
@coderabbitai

coderabbitai Bot commented Jul 3, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds a translate_extras_to_openai_shape function in the Anthropic provider crate that whitelists and translates Anthropic-specific request extras (tools, tool_choice, stop_sequences, metadata.user_id, thinking) into OpenAI-compatible fields, dropping unrecognized Anthropic-only fields. It replaces prior manual tools/tool_choice translation in cross_provider_dispatch with a single call to this helper, and adds unit and e2e tests validating the behavior.

Changes

Anthropic-to-OpenAI extras translation

Layer / File(s) Summary
Translation helper and re-export
crates/aisix-provider-anthropic/src/wire.rs, crates/aisix-provider-anthropic/src/lib.rs
Adds translate_extras_to_openai_shape (whitelisting tools/tool_choice/stop_sequences/metadata.user_id/thinking, dropping other fields with a debug log) and reasoning_effort_from_thinking (bucketing thinking config into reasoning_effort levels); re-exports the new function from the crate root.
Unit tests for translation
crates/aisix-provider-anthropic/src/wire.rs
Adds tests covering dropping of Anthropic-only fields, correct translation of whitelisted fields, metadata without user_id, and thinking bucketing across enabled/adaptive/disabled variants.
Cross-provider dispatch wiring
crates/aisix-proxy/src/messages.rs
Replaces manual tools/tool_choice extraction and translation logic in cross_provider_dispatch with a single call to translate_extras_to_openai_shape(&mut chat.extra).
E2E coverage
tests/e2e/src/cases/messages-cross-provider-extras-e2e.test.ts
Adds a new e2e test suite with shared Anthropic-only field definitions and helper assertions, plus non-streaming and streaming tests verifying Anthropic-only fields are dropped and translatable fields correctly propagate to an OpenAI-compatible upstream mock.

Estimated code review effort: 3 (Moderate) | ~25 minutes

Sequence Diagram(s)

sequenceDiagram
  participant Client
  participant Dispatch as cross_provider_dispatch
  participant Translator as translate_extras_to_openai_shape
  participant Upstream as OpenAI-compatible upstream

  Client->>Dispatch: POST /v1/messages (Anthropic body with extras)
  Dispatch->>Translator: translate_extras_to_openai_shape(chat.extra)
  Translator->>Translator: map tools/tool_choice, stop_sequences, metadata.user_id, thinking
  Translator->>Translator: drop unrecognized Anthropic-only fields
  Translator-->>Dispatch: translated extras
  Dispatch->>Upstream: POST /v1/chat/completions (translated body)
  Upstream-->>Dispatch: response
  Dispatch-->>Client: response
Loading

Possibly related issues

  • api7/AISIX-Cloud#953: Directly addresses the reported bug where Anthropic-only fields like context_management leaked to OpenAI-compatible upstreams, causing 400 errors.

Possibly related PRs

  • api7/aisix#254: Prior PR implemented the explicit tools/tool_choice translation logic that this PR consolidates into translate_extras_to_openai_shape.
🚥 Pre-merge checks | ✅ 5 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
E2e Test Quality Review ⚠️ Warning The new E2E suite uses a permissive OpenAI mock, so it won’t catch the thinkingreasoning_effort regression on non-reasoning models. Add an E2E case that exercises a non-reasoning OpenAI fixture and asserts reasoning_effort is omitted (or the request still succeeds) for both streaming and non-streaming.
✅ Passed checks (5 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title is concise and accurately describes the main change: preventing Anthropic-only fields from reaching OpenAI-compatible upstreams.
Linked Issues check ✅ Passed The changes whitelist/translate extras, dropping Anthropic-only fields on the OpenAI path and preserving Anthropic-native behavior, which matches #953.
Out of Scope Changes check ✅ Passed The helper, proxy wiring, and E2E tests are all directly related to the same cross-provider request-shaping fix.
Security Check ✅ Passed No new secret/PII leakage or auth/ownership issues found; the only new log emits dropped field names, not values.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch fix/953-anthropic-extras-cross-provider

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🧹 Nitpick comments (1)
tests/e2e/src/cases/messages-cross-provider-extras-e2e.test.ts (1)

27-40: 📐 Maintainability & Code Quality | 🔵 Trivial | ⚡ Quick win

Move thinking out of the drop-only set. It maps to reasoning_effort, so this case should assert the translated field instead of treating it as Anthropic-only.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@tests/e2e/src/cases/messages-cross-provider-extras-e2e.test.ts` around lines
27 - 40, Move thinking out of ANTHROPIC_ONLY_FIELDS in
messages-cross-provider-extras-e2e.test.ts, because it translates to
reasoning_effort rather than being Anthropic-only. Update the test case that
uses ANTHROPIC_ONLY_FIELDS to assert the translated reasoning_effort field
instead, and keep only true drop-only extras in the Anthropic-only set. Use the
ANTHROPIC_ONLY_FIELDS constant and the related cross-provider extras assertions
to locate the test logic.

Source: Coding guidelines

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@crates/aisix-provider-anthropic/src/wire.rs`:
- Around line 487-491: The current `thinking` handling in `wire.rs` always maps
to `reasoning_effort`, which can break non-reasoning upstreams like `gpt-4o`.
Update the `thinking` branch in the request translation logic to only emit
`reasoning_effort` when the target model/provider supports it, using the
existing `reasoning_effort_from_thinking` helper plus a capability/model gate.
Keep the `extra.insert("reasoning_effort", ...)` path behind that check so
Anthropic `thinking` is ignored or dropped for unsupported OpenAI models rather
than forwarded as an invalid field.

---

Nitpick comments:
In `@tests/e2e/src/cases/messages-cross-provider-extras-e2e.test.ts`:
- Around line 27-40: Move thinking out of ANTHROPIC_ONLY_FIELDS in
messages-cross-provider-extras-e2e.test.ts, because it translates to
reasoning_effort rather than being Anthropic-only. Update the test case that
uses ANTHROPIC_ONLY_FIELDS to assert the translated reasoning_effort field
instead, and keep only true drop-only extras in the Anthropic-only set. Use the
ANTHROPIC_ONLY_FIELDS constant and the related cross-provider extras assertions
to locate the test logic.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 488f4ddf-0133-42d9-96e8-92bbea4eaf46

📥 Commits

Reviewing files that changed from the base of the PR and between bba94b5 and eb60efa.

📒 Files selected for processing (4)
  • crates/aisix-provider-anthropic/src/lib.rs
  • crates/aisix-provider-anthropic/src/wire.rs
  • crates/aisix-proxy/src/messages.rs
  • tests/e2e/src/cases/messages-cross-provider-extras-e2e.test.ts

Comment thread crates/aisix-provider-anthropic/src/wire.rs
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant