Skip to content

Add Bedrock structured output support#619

Merged
crmne merged 3 commits intocrmne:mainfrom
llenodo:feature/bedrock-structured-output
Mar 1, 2026
Merged

Add Bedrock structured output support#619
crmne merged 3 commits intocrmne:mainfrom
llenodo:feature/bedrock-structured-output

Conversation

@llenodo
Copy link
Contributor

@llenodo llenodo commented Feb 18, 2026

What this does

Adds structured output support to the Bedrock Converse API provider. Bedrock uses a different payload format than the direct Anthropic API: the schema must be a JSON string (not a Hash), nested under outputConfig.textFormat.structure.jsonSchema with a required name field, using camelCase Converse API naming.

Type of change

  • Bug fix
  • New feature
  • Breaking change
  • Documentation
  • Performance improvement

Scope check

  • I read the Contributing Guide
  • This aligns with RubyLLM's focus on LLM communication
  • This isn't application-specific logic that belongs in user code
  • This benefits most users, not just my specific use case

Required for new features

PRs for new features or enhancements without a prior approved issue will be closed.

Quality check

  • I ran overcommit --install and all hooks pass
  • I tested my changes thoroughly
    • For provider changes: Re-recorded VCR cassettes with bundle exec rake vcr:record[provider_name]
    • All tests pass: bundle exec rspec
  • I updated documentation if needed
  • I didn't modify auto-generated files manually (models.json, aliases.json)

AI-generated code

  • I used AI tools to help write this code
  • I have reviewed and understand all generated code (required if above is checked)

API changes

  • Breaking change
  • New public methods/classes
  • Changed method signatures
  • No API changes

Extend the Bedrock Converse API provider to support structured output
via outputConfig.textFormat, building on crmne#608's Anthropic implementation.

Key differences from the direct Anthropic API: schema must be a JSON
string (not a Hash), nested under structure.jsonSchema with a required
name field, and uses camelCase Converse API naming.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
@llenodo
Copy link
Contributor Author

llenodo commented Feb 18, 2026

closing per community guidelines to have an issue first

@aniamisiorek
Copy link

Hey @llenodo ! Not sure of the process here, but would love to open this PR up again for review!

@llenodo
Copy link
Contributor Author

llenodo commented Feb 26, 2026

I'm not sure either tbh. the contributing guidlines specify "You must open an issue first and wait for maintainer feedback before writing code. PRs for new features without an approved issue will be closed without review."

@llenodo
Copy link
Contributor Author

llenodo commented Feb 27, 2026

@aniamisiorek in the meantime you can use this patch in your ruby_llm.rb initializer to get the functionality

# Patch to enable structured output (JSON schema) for AWS Bedrock Claude models
#
# Background:
# RubyLLM 1.12.1 doesn't support structured output for Bedrock. The `schema` parameter in
# `render_payload` is silently ignored (Lint/UnusedMethodArgument). This means `.with_schema()`
# has no effect when using Claude models via Bedrock — the LLM returns free-form text instead
# of JSON, causing parse failures in consumers like AgentTurn.
#
# This patch:
# 1. Overrides `render_payload` in Bedrock::Chat to inject `outputConfig` when a schema is present
# 2. Uses the Bedrock Converse API's `textFormat` type `json_schema` to enforce structured output
#
# Based on upstream PR: https://github.com/crmne/ruby_llm/pull/619
# Can be removed once RubyLLM ships native Bedrock structured output support.
module RubyLLMBedrockStructuredOutputPatch
  def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil)
    payload = super

    if schema
      output_config = build_output_config(schema)
      payload[:outputConfig] = output_config if output_config
    end

    payload
  end

  private


  def build_output_config(schema)
    cleaned = RubyLLM::Utils.deep_dup(schema)
    cleaned.delete(:strict)
    cleaned.delete('strict')

    {
      textFormat: {
        type: 'json_schema',
        structure: {
          jsonSchema: {
            schema: JSON.generate(cleaned),
            name: 'response'
          }
        }
      }
    }
  end
end

# Prepend to Bedrock Chat module to enable structured output
RubyLLM::Providers::Bedrock::Chat.module_eval { prepend RubyLLMBedrockStructuredOutputPatch }

@llenodo
Copy link
Contributor Author

llenodo commented Feb 28, 2026

Opening up to see if we can get this in soon so I can get rid of this patch in my codebase :)

@llenodo llenodo reopened this Feb 28, 2026
Comment on lines +71 to +87
# Test Bedrock provider with Claude model
context 'with bedrock/claude-haiku-4-5' do
let(:chat) { RubyLLM.chat(model: 'claude-haiku-4-5', provider: :bedrock) }

it 'accepts a JSON schema and returns structured output' do
skip 'Model does not support structured output' unless chat.model.structured_output?

response = chat
.with_schema(person_schema)
.ask('Generate a person named Bob who is 35 years old')

expect(response.content).to be_a(Hash)
expect(response.content['name']).to eq('Bob')
expect(response.content['age']).to eq(35)
end
end

Copy link
Owner

Choose a reason for hiding this comment

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

you should use the test above and simply add the provider to it

Copy link
Owner

@crmne crmne left a comment

Choose a reason for hiding this comment

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

additional comments

def supports_structured_output?(model_id)
return false unless model_id

normalized = model_id.sub(/\A(?:#{REGION_PREFIXES.join('|')})\./, '').delete_prefix('anthropic.')
Copy link
Owner

Choose a reason for hiding this comment

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

needs global in REGION_PREFIXES

@crmne crmne linked an issue Mar 1, 2026 that may be closed by this pull request
6 tasks
@crmne crmne merged commit 89709c7 into crmne:main Mar 1, 2026
@llenodo llenodo deleted the feature/bedrock-structured-output branch March 1, 2026 17:27
@llenodo
Copy link
Contributor Author

llenodo commented Mar 1, 2026

nice thanks for merging!

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.

[FEATURE] Add Structured Output for Amazon Bedrock

3 participants