Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion lib/ruby_llm/protocols/converse/chat.rb
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,13 @@ module Chat
module_function

def completion_url
"/model/#{@model.id}/converse"
"/model/#{escape_model_id(@model.id)}/converse"
end

# Application inference profile ARNs contain '/', but Bedrock expects the model id
# to remain one path segment.
def escape_model_id(model_id)
model_id.to_s.gsub('/', '%2F')
end

# rubocop:disable Metrics/ParameterLists,Lint/UnusedMethodArgument
Expand Down
2 changes: 1 addition & 1 deletion lib/ruby_llm/protocols/converse/streaming.rb
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ module Streaming
private

def stream_url
"/model/#{@model.id}/converse-stream"
"/model/#{escape_model_id(@model.id)}/converse-stream"
end

def stream_response(payload, additional_headers = {}, &block)
Expand Down
38 changes: 38 additions & 0 deletions spec/ruby_llm/providers/bedrock_spec.rb
Comment thread
mattwebbio marked this conversation as resolved.
Original file line number Diff line number Diff line change
Expand Up @@ -160,4 +160,42 @@ def credential_provider(credentials = self.credentials)
expect(provider.protocol_for(model)).to eq(RubyLLM::Protocols::Converse)
end
end

describe 'model id path encoding' do
let(:converse) { RubyLLM::Protocols::Converse.allocate }
let(:arn) { 'arn:aws:bedrock:us-west-2:123:application-inference-profile/p' }

def with_model(id)
converse.instance_variable_set(:@model, instance_double(RubyLLM::Model, id: id))
end

it 'keeps an application inference profile ARN as a single path segment in the converse URL' do
with_model(arn)
expect(converse.send(:completion_url)).to eq(
'/model/arn:aws:bedrock:us-west-2:123:application-inference-profile%2Fp/converse'
)
end

it 'encodes the ARN for the converse-stream URL too' do
with_model(arn)
expect(converse.send(:stream_url)).to eq(
'/model/arn:aws:bedrock:us-west-2:123:application-inference-profile%2Fp/converse-stream'
)
end

it 'leaves ordinary model ids (including a ":" version suffix) unchanged' do
with_model('us.anthropic.claude-sonnet-4-5-20250929-v1:0')
expect(converse.send(:completion_url)).to eq(
'/model/us.anthropic.claude-sonnet-4-5-20250929-v1:0/converse'
)
end

it 'signs the ARN as one segment (SigV4 canonical path double-encodes "/", not truncates)' do
with_model(arn)
path = URI.parse(converse.send(:completion_url)).path
expect(described_class.allocate.send(:canonical_uri, path)).to eq(
'/model/arn%3Aaws%3Abedrock%3Aus-west-2%3A123%3Aapplication-inference-profile%252Fp/converse'
)
end
end
end
Loading