Add Bedrock structured output support#619
Merged
crmne merged 3 commits intocrmne:mainfrom Mar 1, 2026
Merged
Conversation
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>
1f9d608 to
d57bb64
Compare
This was referenced Feb 18, 2026
Contributor
Author
|
closing per community guidelines to have an issue first |
6 tasks
|
Hey @llenodo ! Not sure of the process here, but would love to open this PR up again for review! |
Contributor
Author
|
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." |
Contributor
Author
|
@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 } |
Contributor
Author
|
Opening up to see if we can get this in soon so I can get rid of this patch in my codebase :) |
crmne
requested changes
Mar 1, 2026
spec/ruby_llm/chat_schema_spec.rb
Outdated
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 | ||
|
|
Owner
There was a problem hiding this comment.
you should use the test above and simply add the provider to it
crmne
requested changes
Mar 1, 2026
| def supports_structured_output?(model_id) | ||
| return false unless model_id | ||
|
|
||
| normalized = model_id.sub(/\A(?:#{REGION_PREFIXES.join('|')})\./, '').delete_prefix('anthropic.') |
6 tasks
Contributor
Author
|
nice thanks for merging! |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
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.jsonSchemawith a requirednamefield, using camelCase Converse API naming.Type of change
Scope check
Required for new features
PRs for new features or enhancements without a prior approved issue will be closed.
Quality check
overcommit --installand all hooks passbundle exec rake vcr:record[provider_name]bundle exec rspecmodels.json,aliases.json)AI-generated code
API changes