Skip to content

improvement: Map response_format to Bedrock Converse outputConfig#1547

Open
austin-engle wants to merge 3 commits intoPortkey-AI:2.0.0from
austin-engle:feat/bedrock-response-format-outputconfig
Open

improvement: Map response_format to Bedrock Converse outputConfig#1547
austin-engle wants to merge 3 commits intoPortkey-AI:2.0.0from
austin-engle:feat/bedrock-response-format-outputconfig

Conversation

@austin-engle
Copy link
Copy Markdown

@austin-engle austin-engle commented Mar 3, 2026

Summary

Bedrock's Converse API supports native structured JSON output via outputConfig.textFormat (launched February 2025 for Claude 3.5+, Llama, Mistral, and others). However, the gateway currently places response_format into additionalModelRequestFields on the base BedrockConverseChatCompleteConfig, where Bedrock silently ignores it — structured output never actually takes effect for non-Anthropic models.

This PR adds proper mapping from OpenAI's response_format: { type: "json_schema", ... } to Bedrock's native Converse API outputConfig.textFormat parameter on the base config.

Note: The 2.0.0 branch already adds response_format handling for the Anthropic variant via additionalModelRequestFields.output_config (Anthropic's proprietary path). That override takes precedence for Anthropic models. This PR complements that by adding Converse-native outputConfig support for all other Bedrock models (Meta, Cohere, AI21, Amazon, Mistral, etc.) via the base config.

What changed

  • src/providers/bedrock/utils.ts

    • Removed response_format from transformAdditionalModelRequestFields() (was a no-op — Bedrock ignores unknown keys in additionalModelRequestFields)
    • Added transformOutputConfig() function that maps:
      // OpenAI format (input)
      { type: "json_schema", json_schema: { schema: {...}, name: "N", description: "D", strict: true } }
      
      // Bedrock Converse format (output)  
      { textFormat: { type: "json_schema", structure: { jsonSchema: { schema: "...", name: "N", description: "D" } } } }
      
    • Handles schema stringification: OpenAI sends schema as a JSON object, Bedrock expects a JSON string
    • Passes through name (defaults to "response") and description (omitted when not provided)
    • Guards against missing schema to prevent malformed requests
    • strict is intentionally dropped — Bedrock has no equivalent
  • src/providers/bedrock/chatComplete.ts

    • Added response_formatoutputConfig entry to BedrockConverseChatCompleteConfig
    • Imported transformOutputConfig from utils
  • src/providers/bedrock/utils.test.ts (new)

    • 14 unit tests covering: json_schema mapping, schema stringification (object and string), name defaults, description pass-through and omission, missing schema guard, json_object/text/absent response_format, and verification that response_format is no longer in additionalModelRequestFields

How it interacts with the Anthropic variant

The BedrockConverseAnthropicChatCompleteConfig in 2.0.0 already overrides response_format to route through transformAnthropicAdditionalModelRequestFields, which maps it to additionalModelRequestFields.output_config. Since the spread + override pattern means the Anthropic-specific entry takes precedence, Anthropic models are unaffected by this change. This PR only affects the base Converse config used by non-Anthropic models.

Model provider response_format path Source
Anthropic (Claude) additionalModelRequestFields.output_config Existing 2.0.0 code
All others (Meta, Cohere, AI21, etc.) outputConfig.textFormat This PR

Design decisions

  1. Only json_schema type is mapped — Bedrock Converse does not support json_object. For json_object and text types, transformOutputConfig returns undefined so no outputConfig is set (same behavior as before).

  2. strict is intentionally dropped — OpenAI's strict field has no Bedrock equivalent. Documented in the JSDoc.

  3. No model filtering — Rather than maintaining a list of which Bedrock models support structured output, we let Bedrock return a 400 ValidationException for unsupported models. This matches how other unsupported features (e.g., tool use on models without it) are handled.

  4. No streaming changes needed — The same chatComplete config is used for both Converse and ConverseStream API calls, so structured output works for both.

  5. Scoped to Bedrock only — All changes are within src/providers/bedrock/. Zero impact on any other provider.

Context

We discovered this issue while building a Terraform PR review agent that uses Portkey to route structured output calls to Bedrock (Claude Sonnet via Converse API). The model returned correct decisions but empty arrays in the structured response — because without outputConfig, Bedrock has no schema to enforce and the model was free to minimize output. Switching to tool-use (tools + tool_choice) as a workaround confirmed the model was capable; the gateway translation was the gap.

Bedrock API Reference

Test plan

  • 14 unit tests pass (npx jest src/providers/bedrock/utils.test.ts)
  • Prettier + lint-staged checks pass
  • Rollup build succeeds
  • Rebased onto 2.0.0 — no conflicts
  • Integration test with Bedrock Converse + non-Anthropic model using response_format: { type: "json_schema" }

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

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 fixes Bedrock Converse structured output support by correctly mapping OpenAI-style response_format: { type: "json_schema", ... } into Bedrock’s outputConfig.textFormat, instead of incorrectly placing it in additionalModelRequestFields (which Bedrock ignores).

Changes:

  • Remove response_format pass-through from transformAdditionalModelRequestFields() (previously ineffective for Bedrock).
  • Add transformOutputConfig() to translate OpenAI json_schema structured output into Bedrock outputConfig.textFormat, including schema stringification and sensible defaults.
  • Add Bedrock unit tests validating the mapping and ensuring response_format is no longer included in additionalModelRequestFields.

Reviewed changes

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

File Description
src/providers/bedrock/utils.ts Adds transformOutputConfig() and removes ineffective response_format placement under additionalModelRequestFields.
src/providers/bedrock/chatComplete.ts Wires response_format into Bedrock Converse request body as outputConfig via transformOutputConfig().
src/providers/bedrock/utils.test.ts Adds unit coverage for json_schema mapping behavior and guards.

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

Bedrock Converse API supports structured output via outputConfig.textFormat
(added Feb 2026), but the gateway currently puts response_format into
additionalModelRequestFields where Bedrock silently ignores it.

This change:
- Adds transformOutputConfig() to map OpenAI's response_format (json_schema)
  to Bedrock's outputConfig.textFormat parameter
- Adds response_format entry to BedrockConverseChatCompleteConfig
- Removes response_format from transformAdditionalModelRequestFields
  (where it was a no-op)
- Handles schema stringification (OpenAI sends object, Bedrock expects string)
- Only maps type=json_schema; json_object is not supported by Bedrock
- Includes unit tests for all transform cases

Ref: https://docs.aws.amazon.com/bedrock/latest/APIReference/API_runtime_OutputConfig.html
…ocument strict

- Pass through json_schema.description to Bedrock's jsonSchema.description
  when present (Bedrock's JsonSchemaDefinition supports it)
- Guard against undefined/missing schema to prevent JSON.stringify(undefined)
- Document that OpenAI's strict field is intentionally dropped (no Bedrock equivalent)
- Add 3 new test cases: description present, description absent, schema missing
The 2.0.0 branch restructured utils.ts imports (added apm, awsAuth,
cacheService). Update jest mocks to match the new import chain.
@austin-engle austin-engle changed the base branch from main to 2.0.0 March 3, 2026 20:38
@austin-engle austin-engle force-pushed the feat/bedrock-response-format-outputconfig branch from 25baa65 to 0b6578e Compare March 3, 2026 20:38
@austin-engle
Copy link
Copy Markdown
Author

Done — rebased onto 2.0.0, updated mocks for the new dependency chain, and all 14 tests pass.

I noticed 2.0.0 already adds response_format handling for the Anthropic variant via additionalModelRequestFields.output_config (Anthropic's proprietary path). This PR complements that by adding Converse-native outputConfig.textFormat support on the base config — so non-Anthropic models (Meta, Cohere, AI21, Amazon, Mistral) also get structured output.

The Anthropic variant's override takes precedence via the spread pattern, so Anthropic models are unaffected. Updated the PR description with a table showing the two paths.

Copy link
Copy Markdown
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

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

Pull request overview

Copilot reviewed 3 out of 4 changed files in this pull request and generated no new comments.


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

Copy link
Copy Markdown

@itsthusharashenoi itsthusharashenoi left a comment

Choose a reason for hiding this comment

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

Changes have been verified and tested.
The concept of structured outputs in bedrock is working via PortKey.
Changes may be merged.

Regards,
Thushara.

Copy link
Copy Markdown
Member

@narengogi narengogi left a comment

Choose a reason for hiding this comment

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

LGTM

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.

4 participants