Module: RubyLLM::Providers::Bedrock::Chat
- Included in:
- RubyLLM::Providers::Bedrock
- Defined in:
- lib/ruby_llm/providers/bedrock/chat.rb
Overview
Chat methods for Bedrock Converse API.
Class Method Summary collapse
- .budget_reasoning_config(thinking) ⇒ Object
- .build_output_config(schema) ⇒ Object
- .completion_url ⇒ Object
- .default_input_schema ⇒ Object
- .effort_reasoning_config(thinking) ⇒ Object
- .input_tokens(usage) ⇒ Object
- .normalize_tool_result_block(block) ⇒ Object
-
.parse_completion_response(response) ⇒ Object
rubocop:enable Metrics/ParameterLists,Lint/UnusedMethodArgument.
- .parse_reasoning_content_block(block) ⇒ Object
- .parse_text_content(content_blocks) ⇒ Object
- .parse_thinking(content_blocks) ⇒ Object
- .parse_tool_calls(content_blocks) ⇒ Object
- .render_additional_model_request_fields(thinking) ⇒ Object
- .render_content_tool_result_content(content) ⇒ Object
- .render_inference_config(_model, temperature) ⇒ Object
- .render_message_content(msg) ⇒ Object
- .render_messages(messages) ⇒ Object
- .render_non_tool_message(msg) ⇒ Object
-
.render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil, tool_prefs: nil) ⇒ Object
rubocop:disable Metrics/ParameterLists,Lint/UnusedMethodArgument.
- .render_raw_content(content) ⇒ Object
- .render_raw_tool_result_content(raw_value) ⇒ Object
- .render_reasoning_fields(thinking) ⇒ Object
- .render_role(role) ⇒ Object
- .render_system(messages) ⇒ Object
- .render_thinking_block(thinking) ⇒ Object
- .render_tool(tool) ⇒ Object
- .render_tool_choice(choice) ⇒ Object
- .render_tool_config(tools, tool_prefs) ⇒ Object
- .render_tool_result_block(msg) ⇒ Object
- .render_tool_result_content(content) ⇒ Object
- .sanitize_non_assistant_raw_blocks(blocks) ⇒ Object
- .text_tool_result_block(text) ⇒ Object
- .tool_result_content_block?(block) ⇒ Boolean
Class Method Details
.budget_reasoning_config(thinking) ⇒ Object
318 319 320 321 322 323 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 318 def budget_reasoning_config(thinking) budget = thinking.respond_to?(:budget) ? thinking.budget : thinking return nil unless budget.is_a?(Integer) { reasoning_config: { type: 'enabled', budget_tokens: budget } } end |
.build_output_config(schema) ⇒ Object
277 278 279 280 281 282 283 284 285 286 287 288 289 290 291 292 293 294 295 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 277 def build_output_config(schema) return nil unless schema cleaned = RubyLLM::Utils.deep_dup(schema[:schema]) cleaned.delete(:strict) cleaned.delete('strict') { textFormat: { type: 'json_schema', structure: { jsonSchema: { schema: JSON.generate(cleaned), name: schema[:name] } } } } end |
.completion_url ⇒ Object
10 11 12 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 10 def completion_url "/model/#{@model.id}/converse" end |
.default_input_schema ⇒ Object
393 394 395 396 397 398 399 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 393 def default_input_schema { 'type' => 'object', 'properties' => {}, 'required' => [] } end |
.effort_reasoning_config(thinking) ⇒ Object
306 307 308 309 310 311 312 313 314 315 316 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 306 def effort_reasoning_config(thinking) effort = thinking.respond_to?(:effort) ? thinking.effort : nil effort = effort.to_s if effort return nil if effort.nil? || effort.empty? || effort == 'none' if (@model) { reasoning_config: { type: 'enabled', reasoning_effort: effort } } else { reasoning_effort: effort } end end |
.input_tokens(usage) ⇒ Object
69 70 71 72 73 74 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 69 def input_tokens(usage) input_tokens = usage['inputTokens'] return unless input_tokens [input_tokens.to_i - usage['cacheReadInputTokens'].to_i - usage['cacheWriteInputTokens'].to_i, 0].max end |
.normalize_tool_result_block(block) ⇒ Object
193 194 195 196 197 198 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 193 def normalize_tool_result_block(block) return nil unless block.is_a?(Hash) return block if tool_result_content_block?(block) nil end |
.parse_completion_response(response) ⇒ Object
rubocop:enable Metrics/ParameterLists,Lint/UnusedMethodArgument
46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 46 def parse_completion_response(response) data = response.body return if data.nil? || data.empty? content_blocks = data.dig('output', 'message', 'content') || [] usage = data['usage'] || {} thinking_text, thinking_signature = parse_thinking(content_blocks) Message.new( role: :assistant, content: parse_text_content(content_blocks), thinking: Thinking.build(text: thinking_text, signature: thinking_signature), tool_calls: parse_tool_calls(content_blocks), input_tokens: input_tokens(usage), output_tokens: usage['outputTokens'], cached_tokens: usage['cacheReadInputTokens'], cache_creation_tokens: usage['cacheWriteInputTokens'], thinking_tokens: usage['reasoningTokens'], model_id: data['modelId'], raw: response ) end |
.parse_reasoning_content_block(block) ⇒ Object
364 365 366 367 368 369 370 371 372 373 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 364 def parse_reasoning_content_block(block) reasoning_content = block['reasoningContent'] return [nil, nil] unless reasoning_content.is_a?(Hash) reasoning_text = reasoning_content['reasoningText'] || {} text = reasoning_text['text'].is_a?(String) ? reasoning_text['text'] : nil signature = reasoning_text['signature'] if reasoning_text['signature'].is_a?(String) signature ||= reasoning_content['redactedContent'] if reasoning_content['redactedContent'].is_a?(String) [text, signature] end |
.parse_text_content(content_blocks) ⇒ Object
346 347 348 349 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 346 def parse_text_content(content_blocks) text = content_blocks.filter_map { |block| block['text'] if block['text'].is_a?(String) }.join text.empty? ? nil : text end |
.parse_thinking(content_blocks) ⇒ Object
351 352 353 354 355 356 357 358 359 360 361 362 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 351 def parse_thinking(content_blocks) text = +'' signature = nil content_blocks.each do |block| chunk_text, chunk_signature = parse_reasoning_content_block(block) text << chunk_text if chunk_text signature ||= chunk_signature end [text.empty? ? nil : text, signature] end |
.parse_tool_calls(content_blocks) ⇒ Object
375 376 377 378 379 380 381 382 383 384 385 386 387 388 389 390 391 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 375 def parse_tool_calls(content_blocks) tool_calls = {} content_blocks.each do |block| tool_use = block['toolUse'] next unless tool_use tool_call_id = tool_use['toolUseId'] tool_calls[tool_call_id] = ToolCall.new( id: tool_call_id, name: tool_use['name'], arguments: tool_use['input'] || {} ) end tool_calls.empty? ? nil : tool_calls end |
.render_additional_model_request_fields(thinking) ⇒ Object
268 269 270 271 272 273 274 275 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 268 def render_additional_model_request_fields(thinking) fields = {} reasoning_fields = render_reasoning_fields(thinking) fields = RubyLLM::Utils.deep_merge(fields, reasoning_fields) if reasoning_fields fields.empty? ? nil : fields end |
.render_content_tool_result_content(content) ⇒ Object
170 171 172 173 174 175 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 170 def render_content_tool_result_content(content) blocks = [] blocks << text_tool_result_block(content.text) unless content.text.to_s.empty? content..each { || blocks << text_tool_result_block(.for_llm) } blocks.empty? ? [text_tool_result_block(nil)] : blocks end |
.render_inference_config(_model, temperature) ⇒ Object
217 218 219 220 221 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 217 def render_inference_config(_model, temperature) config = {} config[:temperature] = temperature unless temperature.nil? config end |
.render_message_content(msg) ⇒ Object
109 110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 129 130 131 132 133 134 135 136 137 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 109 def (msg) if msg.content.is_a?(RubyLLM::Content::Raw) return render_raw_content(msg.content) if msg.role == :assistant return sanitize_non_assistant_raw_blocks(render_raw_content(msg.content)) end blocks = [] thinking_block = render_thinking_block(msg.thinking) blocks << thinking_block if msg.role == :assistant && thinking_block text_and_media_blocks = Media.render_content(msg.content, used_document_names: @used_document_names) blocks.concat(text_and_media_blocks) if text_and_media_blocks if msg.tool_call? msg.tool_calls.each_value do |tool_call| blocks << { toolUse: { toolUseId: tool_call.id, name: tool_call.name, input: tool_call.arguments } } end end blocks end |
.render_messages(messages) ⇒ Object
76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 76 def () rendered = [] tool_result_blocks = [] .each do |msg| if msg.tool_result? tool_result_blocks << render_tool_result_block(msg) next end unless tool_result_blocks.empty? rendered << { role: 'user', content: tool_result_blocks } tool_result_blocks = [] end = (msg) rendered << if end rendered << { role: 'user', content: tool_result_blocks } unless tool_result_blocks.empty? rendered end |
.render_non_tool_message(msg) ⇒ Object
99 100 101 102 103 104 105 106 107 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 99 def (msg) content = (msg) return nil if content.empty? { role: render_role(msg.role), content: content } end |
.render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil, tool_prefs: nil) ⇒ Object
rubocop:disable Metrics/ParameterLists,Lint/UnusedMethodArgument
15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 15 def render_payload(, tools:, temperature:, model:, stream: false, schema: nil, thinking: nil, tool_prefs: nil) tool_prefs ||= {} @model = model @used_document_names = {} , = .partition { |msg| msg.role == :system } payload = { messages: () } system_blocks = render_system() payload[:system] = system_blocks unless system_blocks.empty? payload[:inferenceConfig] = render_inference_config(model, temperature) tool_config = render_tool_config(tools, tool_prefs) if tool_config payload[:toolConfig] = tool_config payload[:tools] = tool_config[:tools] # Internal mirror for shared payload inspections in specs. end additional_fields = render_additional_model_request_fields(thinking) payload[:additionalModelRequestFields] = additional_fields if additional_fields output_config = build_output_config(schema) payload[:outputConfig] = output_config if output_config payload end |
.render_raw_content(content) ⇒ Object
139 140 141 142 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 139 def render_raw_content(content) value = content.value value.is_a?(Array) ? value : [value] end |
.render_raw_tool_result_content(raw_value) ⇒ Object
183 184 185 186 187 188 189 190 191 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 183 def render_raw_tool_result_content(raw_value) blocks = raw_value.is_a?(Array) ? raw_value : [raw_value] normalized = blocks.filter_map do |block| normalize_tool_result_block(block) end normalized.empty? ? [{ text: raw_value.to_s }] : normalized end |
.render_reasoning_fields(thinking) ⇒ Object
297 298 299 300 301 302 303 304 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 297 def render_reasoning_fields(thinking) return nil unless thinking&.enabled? effort_config = effort_reasoning_config(thinking) return effort_config if effort_config budget_reasoning_config(thinking) end |
.render_role(role) ⇒ Object
206 207 208 209 210 211 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 206 def render_role(role) case role when :assistant then 'assistant' else 'user' end end |
.render_system(messages) ⇒ Object
213 214 215 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 213 def render_system() .flat_map { |msg| Media.render_content(msg.content, used_document_names: @used_document_names) } end |
.render_thinking_block(thinking) ⇒ Object
325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 325 def render_thinking_block(thinking) return nil unless thinking if thinking.text { reasoningContent: { reasoningText: { text: thinking.text, signature: thinking.signature }.compact } } elsif thinking.signature { reasoningContent: { redactedContent: thinking.signature } } end end |
.render_tool(tool) ⇒ Object
250 251 252 253 254 255 256 257 258 259 260 261 262 263 264 265 266 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 250 def render_tool(tool) input_schema = tool.params_schema || RubyLLM::Tool::SchemaDefinition.from_parameters(tool.parameters)&.json_schema tool_spec = { toolSpec: { name: tool.name, description: tool.description, inputSchema: { json: input_schema || default_input_schema } } } return tool_spec if tool.provider_params.empty? RubyLLM::Utils.deep_merge(tool_spec, tool.provider_params) end |
.render_tool_choice(choice) ⇒ Object
237 238 239 240 241 242 243 244 245 246 247 248 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 237 def render_tool_choice(choice) case choice when :auto { auto: {} } when :none nil when :required { any: {} } else { tool: { name: choice.to_s } } end end |
.render_tool_config(tools, tool_prefs) ⇒ Object
223 224 225 226 227 228 229 230 231 232 233 234 235 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 223 def render_tool_config(tools, tool_prefs) return nil if tools.empty? config = { tools: tools.values.map { |tool| render_tool(tool) } } return config if tool_prefs.nil? || tool_prefs[:choice].nil? tool_choice = render_tool_choice(tool_prefs[:choice]) config[:toolChoice] = tool_choice if tool_choice config end |
.render_tool_result_block(msg) ⇒ Object
153 154 155 156 157 158 159 160 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 153 def render_tool_result_block(msg) { toolResult: { toolUseId: msg.tool_call_id, content: render_tool_result_content(msg.content) } } end |
.render_tool_result_content(content) ⇒ Object
162 163 164 165 166 167 168 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 162 def render_tool_result_content(content) return render_raw_tool_result_content(content.value) if content.is_a?(RubyLLM::Content::Raw) return [{ json: content }] if content.is_a?(Hash) || content.is_a?(Array) return render_content_tool_result_content(content) if content.is_a?(RubyLLM::Content) [text_tool_result_block(content)] end |
.sanitize_non_assistant_raw_blocks(blocks) ⇒ Object
144 145 146 147 148 149 150 151 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 144 def sanitize_non_assistant_raw_blocks(blocks) blocks.filter_map do |block| next unless block.is_a?(Hash) next if block.key?(:reasoningContent) || block.key?('reasoningContent') block end end |
.text_tool_result_block(text) ⇒ Object
177 178 179 180 181 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 177 def text_tool_result_block(text) text = text.to_s text = '(no output)' if text.empty? { text: text } end |
.tool_result_content_block?(block) ⇒ Boolean
200 201 202 203 204 |
# File 'lib/ruby_llm/providers/bedrock/chat.rb', line 200 def tool_result_content_block?(block) %w[text json document image].any? do |key| block.key?(key) || block.key?(key.to_sym) end end |