Skip to content

fix(gateway): keep reasoning on unpinned routing - #2791

Merged
steebchen merged 3 commits into
mainfrom
bedrock-claude-reasoning
Jun 22, 2026
Merged

steebchen merged 3 commits into
mainfrom
bedrock-claude-reasoning

Conversation

@steebchen

@steebchen steebchen commented Jun 22, 2026 •

Copy link
Copy Markdown
Member

Problem

Reasoning was silently dropped for Claude models on AWS Bedrock when the model was requested without a provider prefix.

Requested model Routed to Reasoning before
aws-bedrock/claude-opus-4-6 (pinned) aws-bedrock ✅ yes
claude-opus-4-6 (unpinned) aws-bedrock ❌ none (reasoning_tokens: 0)

Reported by users via RisuAI: reasoning_effort: high on claude-opus-4-6 / claude-opus-4-8 produced no <Thoughts> / delta.reasoning / reasoning tokens, but the same request pinned to aws-bedrock/... worked.

Root cause

useExpandedRoutingProviders is true only when a provider is explicitly requested. For unpinned routing, modelInfo.providers keeps only the region-agnostic root mapping (region: undefined) — region expansion (expandProviderRegions) is never applied to it. Meanwhile routing still resolves usedRegion to a concrete value (AWS Bedrock's global, pinDefaultRegion: true).

The capability lookup in chat.ts matched on exact region equality:

modelInfo.providers.find(p => p.providerId === usedProvider && p.region === usedRegion)

'global' !== undefined → no match → supportsReasoning = false (and splitTaggedReasoning / healStreamingJsonOutput) → prepareRequestBody skipped the entire thinking block → Bedrock Claude returned no reasoning.

Fix

Resolve the mapping with a region-agnostic fallback — prefer the exact (providerId, region) mapping, otherwise fall back to the provider-level mapping. Capability flags (reasoning, reasoningMode, splitTaggedReasoning, healStreamingJsonOutput) cannot be overridden per region (ProviderRegion only permits pricing/context/output/streaming overrides), so the base mapping carries identical flags to every regional variant and the fallback is lossless. Extracted into a pure selectProviderMapping helper in @llmgateway/actions with unit tests.

Verification

Live against AWS Bedrock (gateway run locally with real Bedrock key):

Case Before After
unpinned claude-opus-4-6 → bedrock 0 reasoning tokens 187
unpinned claude-opus-4-8 → bedrock 0 reasoning tokens 160
pinned aws-bedrock/claude-opus-4-6:global reasons reasons (no regression)
anthropic/claude-opus-4-6 reasons reasons (no regression)
unpinned claude-haiku-4-5 (no reasoning support) 400 400 (not over-enabled)
  • New unit test fails before the fix and passes after.
  • pnpm format and full pnpm build pass; existing prepare-request-body reasoning specs unchanged.
  • Adversarially reviewed for sibling bugs: the other (providerId, region) lookups in the rate-limit/uptime fallback and dev-plan paths are gated by requestedProvider (the pinned path, where providers are already region-expanded), so they are not affected.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • Bug Fixes

    • Improved provider capability detection (including reasoning support) by resolving the correct provider/region mapping with reliable region-agnostic fallbacks when an exact region entry isn’t present.
  • Tests

    • Added a new test suite covering provider mapping resolution scenarios: exact region matches, region-agnostic fallback selection, order independence, no-match behavior, and handling of undefined regions.

When a Claude model is requested without a provider prefix (e.g.
`claude-opus-4-6` rather than `aws-bedrock/claude-opus-4-6`), the gateway
left `modelInfo.providers` un-expanded — only the region-agnostic root
mapping (`region: undefined`) survives, because region expansion is
applied only when a provider is explicitly requested. Routing still
resolves `usedRegion` to a concrete value (AWS Bedrock's `global`), so
the capability lookup `p.region === usedRegion` matched nothing
(`'global' !== undefined`). `supportsReasoning` collapsed to false and
`prepareRequestBody` skipped the entire thinking block, so Bedrock Claude
returned no reasoning. Pinned routing was unaffected.

Resolve the mapping with a region-agnostic fallback: prefer the exact
`(providerId, region)` mapping, otherwise fall back to the provider-level
mapping. Capability flags (reasoning, splitTaggedReasoning,
healStreamingJsonOutput, reasoningMode) cannot be overridden per region
(`ProviderRegion` only allows pricing/context/output/streaming
overrides), so the fallback is lossless. Extracted into a pure
`selectProviderMapping` helper with unit tests.

Verified live against AWS Bedrock: unpinned `claude-opus-4-6` /
`claude-opus-4-8` now emit reasoning tokens; pinned and native Anthropic
routes unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented Jun 22, 2026 •

Copy link
Copy Markdown
Contributor

Review Change Stack

No actionable comments were generated in the recent review. 🎉

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 750b3053-4194-4432-9acb-cc139d9719ef

📥 Commits

Reviewing files that changed from the base of the PR and between bb836b5 and bcba97e.

📒 Files selected for processing (1)
  • apps/gateway/src/chat/tools/resolve-provider-context.ts

Walkthrough

A new selectProviderMapping function is added to packages/actions that resolves a ProviderModelMapping by first attempting an exact (providerId, region) match and falling back to a region-agnostic match. It is exported from the package index and used in the gateway chat and provider context resolution modules to replace direct providers.find(...) calls, ensuring capability flags like supportsReasoning remain accurate when provider mappings are not region-expanded.

Changes

selectProviderMapping helper and gateway integration

Layer / File(s) Summary
selectProviderMapping helper implementation and tests
packages/actions/src/select-provider-mapping.ts, packages/actions/src/select-provider-mapping.spec.ts
Implements selectProviderMapping with exact (providerId, region) lookup and region-agnostic fallback, documented with a comment explaining the resolution order and why the fallback is needed. Five Vitest cases cover: root-only fallback, exact region preference, order-independent behavior, no-match undefined return, and region-less mapping selection.
Package export and gateway integration
packages/actions/src/index.ts, apps/gateway/src/chat/chat.ts, apps/gateway/src/chat/tools/resolve-provider-context.ts
Re-exports selectProviderMapping from the actions package index; the gateway chat and provider context resolution modules import and call it in place of direct modelInfo.providers.find(...) lookups for provider mapping resolution and supportsReasoning detection.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

  • theopenco/llmgateway#2033: Both PRs modify apps/gateway/src/chat/chat.ts to ensure usedRegion is accurate—main PR relies on usedRegion to resolve provider mappings via selectProviderMapping, while the retrieved PR fixes usedRegion being stale in the low-uptime fallback path.
  • theopenco/llmgateway#2224: Both PRs adjust how the selected provider model mapping is resolved with fallback logic to prevent capability flags like supportsReasoning from being dropped when provider/region mapping mismatches occur.

Suggested reviewers

  • smakosh
🚥 Pre-merge checks | ✅ 5
✅ 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 'fix(gateway): keep reasoning on unpinned routing' directly and concisely summarizes the main change: fixing a bug where reasoning capability was dropped during unpinned routing to AWS Bedrock.
Docstring Coverage ✅ Passed Docstring coverage is 100.00% which is sufficient. The required threshold is 80.00%.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch bedrock-claude-reasoning

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

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

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 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 `@packages/actions/src/select-provider-mapping.ts`:
- Around line 26-28: The fallback logic in the provider find chain does not
explicitly target the region-agnostic root mapping as described in the function
contract. The current second find returns any first match with the providerId,
but it should specifically look for a provider with region set to undefined
before falling back to any generic provider lookup. Modify the fallback chain to
add an explicit intermediate check that searches for a provider with the
matching providerId and region explicitly set to undefined, ensuring the
fallback behavior follows the documented contract and is not order-dependent on
provider array ordering.
🪄 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: Repository UI

Review profile: CHILL

Plan: Pro

Run ID: 54856e33-ac23-4774-9191-74c58bdf6742

📥 Commits

Reviewing files that changed from the base of the PR and between dc7aba7 and f78b06c.

📒 Files selected for processing (4)
  • apps/gateway/src/chat/chat.ts
  • packages/actions/src/index.ts
  • packages/actions/src/select-provider-mapping.spec.ts
  • packages/actions/src/select-provider-mapping.ts

Comment thread packages/actions/src/select-provider-mapping.ts Outdated
@steebchen
steebchen added this pull request to the merge queue Jun 22, 2026
@steebchen
steebchen removed this pull request from the merge queue due to a manual request Jun 22, 2026
Address PR review: the region-agnostic fallback in selectProviderMapping
returned the first provider entry by array order, which contradicted the
documented "region-agnostic root" contract and was order-dependent.
Resolve explicitly: exact (providerId, region) -> root (region: undefined)
-> any provider mapping. No behavioral change for the reasoning fix
(capability flags are region-invariant), but the lookup is now
order-independent and matches its docstring. Adds an order-independence
regression test.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@steebchen
steebchen enabled auto-merge June 22, 2026 17:13

@chatgpt-codex-connector chatgpt-codex-connector 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.

💡 Codex Review

Here are some automated review suggestions for this pull request.

Reviewed commit: bb836b53ff

ℹ️ About Codex in GitHub

Your team has set up Codex to review pull requests in this repo. Reviews are triggered when you

  • Open a pull request for review
  • Mark a draft as ready
  • Comment "@codex review".

If Codex has suggestions, it will comment; otherwise it will react with 👍.

Codex can also answer questions or update the PR. Try commenting "@codex address that feedback".

Comment on lines +5018 to 5022
const selectedProviderMapping = selectProviderMapping(
modelInfo.providers,
usedProvider,
usedRegion,
);

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

P2 Badge Reuse region fallback for retry context

This fixes the initial request, but retry/fallback requests still rebuild the provider context through resolveProviderContext, which exact-matches modelInfo.providers on (providerId, region) at apps/gateway/src/chat/tools/resolve-provider-context.ts:378 and derives supportsReasoning from that result at line 415. For unpinned AWS Bedrock routing, modelInfo.providers can still contain only the root mapping while usedRegion is global, so any retry after a transient upstream/key failure will send the retried request without reasoning even though the first attempt now includes it. Use the same fallback helper in the retry context path as well.

Useful? React with 👍 / 👎.

@steebchen
steebchen disabled auto-merge June 22, 2026 17:22
Address PR review (Codex): resolveProviderContext — used to rebuild the
provider context on retries and alternate-key/provider fallback — did the
same exact (providerId, region) lookup as the initial path and derived
supportsReasoning from it. For unpinned AWS Bedrock routing, modelInfo is
un-expanded (root mapping, region: undefined) while the retry candidate's
region is the concrete resolved usedRegion ('global'), so the lookup
missed and any retry (e.g. after a transient upstream/key failure) sent
the request without reasoning even though the first attempt included it.

Resolve via the shared selectProviderMapping fallback helper so the retry
path matches the initial-request fix.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@steebchen
steebchen enabled auto-merge June 22, 2026 17:27
@steebchen
steebchen added this pull request to the merge queue Jun 22, 2026
Merged via the queue into main with commit 66f1045 Jun 22, 2026
17 checks passed
@steebchen
steebchen deleted the bedrock-claude-reasoning branch June 22, 2026 17:46
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