Skip to content

fix: send model-specific OpenAI reasoning formats for Bedrock Converse - #19420

Merged
gr2m merged 4 commits into
mainfrom
bugfix-19403-20260824214839509188
Aug 25, 2026
Merged

gr2m merged 4 commits into
mainfrom
bugfix-19403-20260824214839509188

Conversation

@ai-sdk-factory

@ai-sdk-factory ai-sdk-factory Bot commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

Background

CRIS-prefixed OpenAI Converse requests mapped reasoning effort to Nova-style reasoningConfig, causing Bedrock to reject the request with HTTP 400 unknown_parameter.

Root Cause

Two model-family assumptions caused the failure:

  • OpenAI detection used modelId.startsWith('openai.'), which excluded us. and global. CRIS profile IDs.
  • The OpenAI branch assumed every OpenAI Bedrock model used flat reasoning_effort, but GPT-5.x requires nested reasoning.effort while gpt-oss requires the flat field.

Summary

  • Recognize direct OpenAI model IDs and IDs with one CRIS profile prefix without broadly matching arbitrary custom IDs.
  • Send nested reasoning.effort for GPT-5.x models.
  • Preserve flat reasoning_effort for gpt-oss models.
  • Add a patch changeset describing the model-specific serialization fix.

Testing

  • Added request-body regression coverage for us.openai.gpt-5.6-luna and global.openai.gpt-5.6-luna, asserting nested reasoning.effort and the absence of both rejected fields.
  • Preserved the existing gpt-oss flat-field regression coverage.
  • Added coverage ensuring arbitrary custom model IDs containing openai. are not reclassified.
  • pnpm --filter @ai-sdk/amazon-bedrock test — 19 Node and 19 Edge test files passed, 474 tests in each runtime.
  • pnpm --filter @ai-sdk/amazon-bedrock type-check
  • pnpm check
  • pnpm type-check:full

Service Validation

Live Bedrock validation reported in #19403 confirmed that reasoning.effort succeeds for the us. and global. GPT-5.6 inference profiles, while flat reasoning_effort is rejected. The gpt-oss family continues to honor the flat field.

Related Issues

Fixes #19403

Closes #19410

ai-sdk-factory and others added 3 commits August 24, 2026 21:54
Co-authored-by: mmurilo <43329254+mmurilo@users.noreply.github.com>
Co-authored-by: mmurilo <43329254+mmurilo@users.noreply.github.com>
@mmurilo

mmurilo commented Aug 24, 2026

Copy link
Copy Markdown
Contributor

I applied this PR verbatim and tested it against live Bedrock. It fixes the classification but not the failure — the reported request still returns HTTP 400. The unit tests pass because the mocked server accepts any body once reasoningConfig is gone, so the suite cannot observe the constraint that actually breaks these models.

What this PR changes vs. what the service accepts

With this PR applied, I captured the exact additionalModelRequestFields the provider emits (capturing fetch, maxReasoningEffort: 'high'):

{
  "us.openai.gpt-5.6-luna":     { "reasoning_effort": "high" },
  "global.openai.gpt-5.6-luna": { "reasoning_effort": "high" },
  "openai.gpt-oss-120b-1:0":    { "reasoning_effort": "high" }
}

Replaying those exact bodies through Converse on bedrock-runtime in us-west-2:

Model Body sent by this PR Result
us.openai.gpt-5.6-luna {"reasoning_effort":"high"} 400 unknown_parameter: 'reasoning_effort'
global.openai.gpt-5.6-luna {"reasoning_effort":"high"} 400 unknown_parameter: 'reasoning_effort'
openai.gpt-oss-120b-1:0 {"reasoning_effort":"high"} 200 (reasoningContent, text)

And the shape the GPT-5.x family does accept:

Model Body Result
us.openai.gpt-5.6-luna {"reasoning":{"effort":"high"}} 200
global.openai.gpt-5.6-luna {"reasoning":{"effort":"high"}} 200

So after this PR the error message changes from unknown_parameter: 'reasoningConfig' to unknown_parameter: 'reasoning_effort', and the user-visible failure remains. This matches the observation already recorded during reproduction of #19403 — that manually sending reasoning_effort was also rejected by the live service.

Why the two families must diverge

The flat form cannot simply be replaced, because gpt-oss genuinely wants it. gpt-oss also silently ignores unknown fields ({"totally_made_up_param":"zzz"} returns 200), so absence of an error proves nothing there; testing which form is actually honored (temperature 0, 3 runs, output-token medians):

Model {"reasoning_effort": ...} {"reasoning":{"effort": ...}}
openai.gpt-oss-120b-1:0 low 200 / high 330 → honored low 279 / high 254 → not honored (bogus-field baseline 279)
global.openai.gpt-5.6-luna 400 rejected honoredreasoningContent appears only at high/max (8 / 7 / 41 / 58 output tokens for none / low / high / max)

By contrast GPT-5.6 strictly validates the map: an unknown key returns 400, and {"reasoning":{"effort":"ultra"}} returns invalid_value ... Supported values are: 'none', 'low', 'medium', 'high', 'xhigh', and 'max'.

Suggested amendment

Keep the classification change in this PR and add the shape selection:

       } else if (isOpenAIModel) {
-        // OpenAI models on Bedrock expect `reasoning_effort` as a flat value
-        amazonBedrockOptions.additionalModelRequestFields = {
-          ...amazonBedrockOptions.additionalModelRequestFields,
-          reasoning_effort: maxReasoningEffort,
-        };
+        // gpt-oss models on Bedrock expect `reasoning_effort` as a flat value,
+        // while the GPT-5.x family expects a nested `reasoning.effort` object
+        // and rejects the flat form with `unknown_parameter`.
+        amazonBedrockOptions.additionalModelRequestFields =
+          this.modelId.includes('gpt-oss')
+            ? {
+                ...amazonBedrockOptions.additionalModelRequestFields,
+                reasoning_effort: maxReasoningEffort,
+              }
+            : {
+                ...amazonBedrockOptions.additionalModelRequestFields,
+                reasoning: {
+                  ...amazonBedrockOptions.additionalModelRequestFields
+                    ?.reasoning,
+                  effort: maxReasoningEffort,
+                },
+              };
       } else {

The new CRIS test should then assert nested reasoning.effort and the absence of reasoning_effort, otherwise it locks in a request the service rejects:

       expect(requestBody).toMatchObject({
         additionalModelRequestFields: {
-          reasoning_effort: 'medium',
+          reasoning: { effort: 'medium' },
         },
       });
+      expect(
+        requestBody.additionalModelRequestFields?.reasoning_effort,
+      ).toBeUndefined();
       expect(
         requestBody.additionalModelRequestFields?.reasoningConfig,
       ).toBeUndefined();

Verification of the amendment

  • Current main: the nested assertion fails with "reasoningConfig": {"maxReasoningEffort": "high"} received — the originally reported defect.
  • This PR as-is: the nested assertion still fails, now receiving "reasoning_effort": "high".
  • With the amendment: Test Files 19 passed (19), Tests 473 passed (473) in packages/amazon-bedrock, including the existing openai.gpt-oss-120b-1:0 flat reasoning_effort tests, which the gpt-oss branch preserves. Live Converse calls for us. and global. GPT-5.6 return 200.

Since the mocked test server accepts anything, it may be worth having the CRIS mock reject flat reasoning_effort for GPT-5.x, so this class of mismatch fails in CI rather than only in production.

Happy to push the amendment to this branch or open a follow-up PR, whichever you prefer.

References

All calls were made in us-west-2 against the us./global. inference profiles; error strings are the service's own wording.

@mmurilo mmurilo 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.

Suggested changes to make this PR fix the reported failure. As tested against live Bedrock (details in my earlier comment), the classification change here is correct and necessary, but the emitted field still has to differ per OpenAI family:

  • gpt-oss → flat reasoning_effort (honored; must be preserved)
  • GPT-5.x → nested reasoning.effort (flat form returns 400 unknown_parameter: 'reasoning_effort')

The inline suggestions below cover the test assertions. The source change is not in this diff's hunks, so it cannot be offered as an applyable suggestion — it is included here for convenience:

       } else if (isOpenAIModel) {
-        // OpenAI models on Bedrock expect `reasoning_effort` as a flat value
-        amazonBedrockOptions.additionalModelRequestFields = {
-          ...amazonBedrockOptions.additionalModelRequestFields,
-          reasoning_effort: maxReasoningEffort,
-        };
+        // gpt-oss models on Bedrock expect `reasoning_effort` as a flat value,
+        // while the GPT-5.x family expects a nested `reasoning.effort` object
+        // and rejects the flat form with `unknown_parameter`.
+        amazonBedrockOptions.additionalModelRequestFields =
+          this.modelId.includes('gpt-oss')
+            ? {
+                ...amazonBedrockOptions.additionalModelRequestFields,
+                reasoning_effort: maxReasoningEffort,
+              }
+            : {
+                ...amazonBedrockOptions.additionalModelRequestFields,
+                reasoning: {
+                  ...amazonBedrockOptions.additionalModelRequestFields
+                    ?.reasoning,
+                  effort: maxReasoningEffort,
+                },
+              };
       } else {

(in packages/amazon-bedrock/src/amazon-bedrock-chat-language-model.ts, replacing the existing isOpenAIModel branch around line 331)

With both the source change and these test changes applied: Test Files 19 passed (19), Tests 473 passed (473) in packages/amazon-bedrock, and live Converse calls for us.openai.gpt-5.6-luna / global.openai.gpt-5.6-luna return 200. Without the source change, the suggested assertions fail with reasoning_effort: 'high' received — which is what the live service rejects.

Comment thread packages/amazon-bedrock/src/amazon-bedrock-chat-language-model.test.ts Outdated
Comment thread packages/amazon-bedrock/src/amazon-bedrock-chat-language-model.test.ts Outdated
@ai-sdk-factory

Copy link
Copy Markdown
Contributor Author

Bugfix review

Outcome: changes-required

Reproduction replay

Status: no-longer-reproduces

The exact original reproduction completed successfully and the original bug signal did not appear.

Fixes issue

Status: partially-addresses

The PR fixes CRIS OpenAI classification, but the resulting request still fails: the OpenAI branch emits flat reasoning_effort, while the issue's corrected live-service evidence shows GPT-5.6 requires nested reasoning.effort and rejects the flat field.

Concerns:

  • For us.openai.gpt-5.6-luna and global.openai.gpt-5.6-luna, the user-visible HTTP 400 remains; only the rejected parameter changes from reasoningConfig to reasoning_effort.
  • The implementation must distinguish GPT-5.x from gpt-oss, because existing gpt-oss models require the currently supported flat reasoning_effort field.

Side effects

Risk: low

Existing non-prefixed gpt-oss behavior is preserved, but includes('openai.') also reclassifies arbitrary custom or profile IDs containing that substring.

Concerns:

  • The broad substring check may classify custom model identifiers containing openai. as OpenAI even when they require another request format.

Performance

Risk: none

Replacing startsWith with includes adds only a negligible linear substring scan and no allocations or retained state of consequence.

Backwards compatibility

Risk: none

The changed code only constructs transient Converse request bodies and does not read, write, migrate, or reinterpret stored data.

Breaking changes

Risk: none

No public exports, APIs, types, accepted provider options, return shapes, defaults, configuration schemas, or persisted formats are removed or narrowed.

Architecture

Risk: medium

The change remains localized within the Bedrock provider, but the existing provider-wide isOpenAIModel abstraction is too coarse to select a reasoning wire format because gpt-oss and GPT-5.x require different shapes.

Concerns:

  • Reasoning serialization should use a localized model-family capability distinction: preserve reasoning_effort for gpt-oss and emit reasoning.effort for GPT-5.x.
  • The new regression tests reinforce the overly broad assumption that every OpenAI Bedrock model uses one reasoning request shape.

Change scope

Status: minimal

The production hunk, related regression tests, and patch changeset are all directly scoped to the reported Bedrock reasoning bug, with no unrelated files in the merge-base diff.

Security

Risk: none

The PR changes only model-ID classification and request serialization; it introduces no credential, URL, parsing, authorization, or data-exposure path.

Testing

Status: needs-more

The new tests pass but assert the wrong flat request shape and use a mock that cannot detect the live GPT-5.6 rejection.

Concerns:

  • The CRIS tests should require additionalModelRequestFields.reasoning.effort and assert that both reasoning_effort and reasoningConfig are absent.
  • Coverage should explicitly preserve flat reasoning_effort for gpt-oss while validating the distinct GPT-5.x shape.
  • The mock or fixture should reject flat reasoning_effort for GPT-5.6 rather than treating every request without reasoningConfig as successful.

Verification

Inspected the complete origin/main...HEAD diff and the shared request-construction path used by generation and streaming. The focused CRIS tests passed, all 473 Amazon Bedrock Node tests passed, package type checking passed, formatting/lint checks passed, and git diff checks were clean. Source inspection confirmed that the PR serializes reasoning_effort for the new GPT-5.6 cases, matching its tests but not the corrected service contract.

Relevant Documentation

@gr2m

gr2m commented Aug 24, 2026

Copy link
Copy Markdown
Collaborator

Thanks @mmurilo for help getting this fixed correctly! I'm on it but probably won't be able to finish it up today

Co-authored-by: mmurilo <43329254+mmurilo@users.noreply.github.com>
@gr2m gr2m changed the title fix: send the OpenAI reasoning effort format for CRIS-prefixed Bedrock model IDs fix: send model-specific OpenAI reasoning formats for Bedrock Converse Aug 25, 2026
@mmurilo

mmurilo commented Aug 25, 2026

Copy link
Copy Markdown
Contributor

Revalidated the current head (2a2625cebd8b551f154542ab48f663b6fb680878). This now fully fixes #19403.

  • GPT-5.6 CRIS (us.openai.gpt-5.6-luna, global.openai.gpt-5.6-luna) emits additionalModelRequestFields: { reasoning: { effort: "high" } }.
  • Live bedrock-runtime in us-west-2: both Converse calls return 200.
  • Live ConverseStream for global.openai.gpt-5.6-luna also returns 200.
  • openai.gpt-oss-120b-1:0 continues to emit flat reasoning_effort and returns 200.
  • The constrained model-ID matcher avoids the earlier broad includes("openai.") concern; the new custom-ID regression test covers that behavior.
  • Independently ran the full package suite: 19 files, 474 tests passed. oxfmt --check passes on both changed files.

No remaining findings. Thanks for incorporating the family-specific mapping.

@gr2m
gr2m merged commit dee4c16 into main Aug 25, 2026
52 checks passed
@gr2m
gr2m deleted the bugfix-19403-20260824214839509188 branch August 25, 2026 20:42
gr2m pushed a commit that referenced this pull request Aug 25, 2026
…Converse (#19624)

## Background

CRIS-prefixed OpenAI Converse requests mapped reasoning effort to
Nova-style `reasoningConfig`, causing Bedrock to reject the request with
HTTP 400 `unknown_parameter`.

## Root Cause

Two model-family assumptions caused the failure:

- OpenAI detection used `modelId.startsWith('openai.')`, which excluded
`us.` and `global.` CRIS profile IDs.
- The OpenAI branch assumed every OpenAI Bedrock model used flat
`reasoning_effort`, but GPT-5.x requires nested `reasoning.effort` while
gpt-oss requires the flat field.

## Summary

- Recognize direct OpenAI model IDs and IDs with one CRIS profile prefix
without broadly matching arbitrary custom IDs.
- Send nested `reasoning.effort` for GPT-5.x models.
- Preserve flat `reasoning_effort` for gpt-oss models.
- Add a patch changeset describing the model-specific serialization fix.

## Testing

- Added request-body regression coverage for `us.openai.gpt-5.6-luna`
and `global.openai.gpt-5.6-luna`, asserting nested `reasoning.effort`
and the absence of both rejected fields.
- Preserved the existing gpt-oss flat-field regression coverage.
- Added coverage ensuring arbitrary custom model IDs containing
`openai.` are not reclassified.
- `pnpm --filter @ai-sdk/amazon-bedrock test` — 19 Node and 19 Edge test
files passed, 474 tests in each runtime.
- `pnpm --filter @ai-sdk/amazon-bedrock type-check`
- `pnpm check`
- `pnpm type-check:full`

## Service Validation

Live Bedrock validation reported in #19403 confirmed that
`reasoning.effort` succeeds for the `us.` and `global.` GPT-5.6
inference profiles, while flat `reasoning_effort` is rejected. The
gpt-oss family continues to honor the flat field.

## Related Issues

Fixes #19403

Closes #19621

Backport of #19420

---------

Co-authored-by: ai-sdk-factory[bot] <305873210+ai-sdk-factory[bot]@users.noreply.github.com>
Co-authored-by: ai-sdk-factory <308175966+ai-sdk-factory@users.noreply.github.com>
gr2m added a commit that referenced this pull request Aug 26, 2026
…Converse (#19626)

## Background

CRIS-prefixed OpenAI Converse requests mapped reasoning effort to
Nova-style `reasoningConfig`, causing Bedrock to reject the request with
HTTP 400 `unknown_parameter`.

## Root Cause

Two model-family assumptions caused the failure:

- OpenAI detection used `modelId.startsWith('openai.')`, which excluded
`us.` and `global.` CRIS profile IDs.
- The OpenAI branch assumed every OpenAI Bedrock model used flat
`reasoning_effort`, but GPT-5.x requires nested `reasoning.effort` while
gpt-oss requires the flat field.

## Summary

- Recognize direct OpenAI model IDs and IDs with one CRIS profile prefix
without broadly matching arbitrary custom IDs.
- Send nested `reasoning.effort` for GPT-5.x models.
- Preserve flat `reasoning_effort` for gpt-oss models.
- Add a patch changeset describing the model-specific serialization fix.

## Testing

- Added request-body regression coverage for `us.openai.gpt-5.6-luna`
and `global.openai.gpt-5.6-luna`, asserting nested `reasoning.effort`
and the absence of both rejected fields.
- Preserved the existing gpt-oss flat-field regression coverage.
- Added coverage ensuring arbitrary custom model IDs containing
`openai.` are not reclassified.
- `pnpm --filter @ai-sdk/amazon-bedrock test` — 19 Node and 19 Edge test
files passed, 474 tests in each runtime.
- `pnpm --filter @ai-sdk/amazon-bedrock type-check`
- `pnpm check`
- `pnpm type-check:full`

## Service Validation

Live Bedrock validation reported in #19403 confirmed that
`reasoning.effort` succeeds for the `us.` and `global.` GPT-5.6
inference profiles, while flat `reasoning_effort` is rejected. The
gpt-oss family continues to honor the flat field.

## Related Issues

Fixes #19403

Closes #19622

Backport of #19420

---------

Co-authored-by: ai-sdk-factory[bot] <305873210+ai-sdk-factory[bot]@users.noreply.github.com>
Co-authored-by: ai-sdk-factory <308175966+ai-sdk-factory@users.noreply.github.com>
Co-authored-by: Gregor Martynus <39992+gr2m@users.noreply.github.com>
kaeluka added a commit to kaeluka/rust-genai that referenced this pull request Sep 14, 2026
Keep flat reasoning_effort for GPT-OSS and send nested reasoning.effort for other OpenAI models. Recognize the documented in. inference-profile prefix without broad substring matching. Preserve omission, numeric-budget handling, and token defaults.

Based on the live GPT-5.6 Converse/ConverseStream findings in vercel/ai#19403 and the merged fix in vercel/ai#19420.

Validation: 335 library tests passed; 354 with bedrock-sigv4. Expanded local-only HTTP tests passed 20 signed request cases across both shapes, omission, and budgets. The HTTP test remains uncommitted. Local AWS invocation still returns Error 002; no new live acceptance claim, particularly for Astra.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

amazon-bedrock: reasoning effort sent as reasoningConfig instead of reasoning_effort for CRIS-prefixed OpenAI model IDs

3 participants