ruby_llm 2.1.1 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +160 -120
- data/app/assets/javascripts/ruby_llm/chat_message_controller.js +163 -0
- data/app/assets/javascripts/ruby_llm/dompurify.js +9 -0
- data/app/assets/javascripts/ruby_llm/marked.js +71 -0
- data/config/importmap.rb +5 -0
- data/lib/generators/ruby_llm/agent/agent_generator.rb +11 -3
- data/lib/generators/ruby_llm/agent/templates/agent.rb.tt +6 -3
- data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +201 -55
- data/lib/generators/ruby_llm/chat_ui/templates/agents/chat_agent.rb.tt +15 -0
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/answers_controller.rb.tt +31 -0
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +16 -5
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +27 -9
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/queued_messages_controller.rb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/javascript/chat_message_controller.js +1 -0
- data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +5 -8
- data/lib/generators/ruby_llm/chat_ui/templates/jobs/resume_interrupted_chats_job.rb.tt +7 -0
- data/lib/generators/ruby_llm/chat_ui/templates/migrations/add_reply_error_migration.rb.tt +5 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_files.html.erb.tt +15 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +5 -7
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_model_select.html.erb.tt +7 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +32 -3
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +17 -2
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_attachments.html.erb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_error.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_form.html.erb.tt +28 -18
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_queued_message.html.erb.tt +18 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +4 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +4 -2
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt +4 -2
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/questions/_question.html.erb.tt +101 -0
- data/lib/generators/ruby_llm/chat_ui/templates/test/agents/chat_agent_test.rb.tt +28 -0
- data/lib/generators/ruby_llm/chat_ui/templates/test/application_system_test_case.rb.tt +5 -0
- data/lib/generators/ruby_llm/chat_ui/templates/test/system/chats_test.rb.tt +240 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/answers/create.turbo_stream.erb.tt +6 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_files.html.erb.tt +15 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +5 -7
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_model_select.html.erb.tt +7 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_questions.html.erb.tt +13 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +29 -3
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +16 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_attachments.html.erb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_error.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +26 -16
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_queued_message.html.erb.tt +8 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_system.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +4 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_user.html.erb.tt +3 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +1 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt +3 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/questions/_question.html.erb.tt +78 -0
- data/lib/generators/ruby_llm/evaluation/evaluation_generator.rb +43 -0
- data/lib/generators/ruby_llm/evaluation/templates/evaluation.rb.tt +9 -0
- data/lib/generators/ruby_llm/evaluation/templates/evaluation.yml.tt +4 -0
- data/lib/generators/ruby_llm/generator_helpers.rb +116 -3
- data/lib/generators/ruby_llm/install/install_generator.rb +5 -47
- data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +4 -0
- data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +5 -3
- data/lib/generators/ruby_llm/install/templates/create_ruby_llm_records_migration.rb.tt +41 -1
- data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +2 -1
- data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +1 -2
- data/lib/generators/ruby_llm/judge/judge_generator.rb +35 -0
- data/lib/generators/ruby_llm/judge/templates/judge.rb.tt +2 -0
- data/lib/generators/ruby_llm/mcp/mcp_generator.rb +58 -0
- data/lib/generators/ruby_llm/mcp/templates/mcp.rb.tt +7 -0
- data/lib/generators/ruby_llm/provider/scaffold.rb +5 -12
- data/lib/generators/ruby_llm/provider/templates/core/provider.rb.erb +6 -0
- data/lib/generators/ruby_llm/provider/templates/gem/chat_spec.rb.erb +0 -1
- data/lib/generators/ruby_llm/provider/templates/gem/chat_streaming_spec.rb.erb +0 -1
- data/lib/generators/ruby_llm/provider/templates/gem/moderation_spec.rb.erb +1 -1
- data/lib/generators/ruby_llm/sandbox/sandbox_generator.rb +68 -0
- data/lib/generators/ruby_llm/sandbox/templates/sandbox.rb.tt +3 -0
- data/lib/generators/ruby_llm/skill/skill_generator.rb +48 -0
- data/lib/generators/ruby_llm/tool/templates/tailwind/tool_call.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/tool/templates/tailwind/tool_result.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +0 -3
- data/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/tool/tool_generator.rb +1 -5
- data/lib/generators/ruby_llm/upgrade/templates/upgrade_ruby_llm_to_2_2.rb.tt +164 -0
- data/lib/generators/ruby_llm/upgrade/upgrade_generator.rb +41 -3
- data/lib/ruby_llm/accounting/operation.rb +73 -0
- data/lib/ruby_llm/accounting/usage.rb +74 -24
- data/lib/ruby_llm/active_record/acts_as.rb +78 -11
- data/lib/ruby_llm/active_record/attachment_helpers.rb +10 -4
- data/lib/ruby_llm/active_record/batch.rb +67 -9
- data/lib/ruby_llm/active_record/chat_agent_hooks.rb +17 -0
- data/lib/ruby_llm/active_record/chat_approvals.rb +71 -0
- data/lib/ruby_llm/active_record/chat_cancellation.rb +106 -0
- data/lib/ruby_llm/active_record/chat_column_guard.rb +51 -0
- data/lib/ruby_llm/active_record/chat_file.rb +37 -0
- data/lib/ruby_llm/active_record/chat_files.rb +30 -0
- data/lib/ruby_llm/active_record/chat_helper.rb +73 -0
- data/lib/ruby_llm/active_record/chat_methods.rb +515 -274
- data/lib/ruby_llm/active_record/chat_placeholders.rb +67 -0
- data/lib/ruby_llm/active_record/chat_questions.rb +177 -0
- data/lib/ruby_llm/active_record/chat_queue.rb +120 -0
- data/lib/ruby_llm/active_record/chat_reply_error.rb +35 -0
- data/lib/ruby_llm/active_record/chat_run.rb +51 -0
- data/lib/ruby_llm/active_record/chat_tool_call_state.rb +77 -0
- data/lib/ruby_llm/active_record/chunk_methods.rb +49 -0
- data/lib/ruby_llm/active_record/claim_heartbeat.rb +149 -0
- data/lib/ruby_llm/active_record/file_store.rb +269 -0
- data/lib/ruby_llm/active_record/mcp_credential.rb +24 -9
- data/lib/ruby_llm/active_record/message_broadcasting.rb +85 -0
- data/lib/ruby_llm/active_record/message_methods.rb +83 -11
- data/lib/ruby_llm/active_record/model.rb +37 -9
- data/lib/ruby_llm/active_record/provider_file.rb +21 -4
- data/lib/ruby_llm/active_record/queued_message.rb +50 -0
- data/lib/ruby_llm/active_record/record.rb +3 -1
- data/lib/ruby_llm/active_record/run_claims.rb +608 -0
- data/lib/ruby_llm/active_record/solid_queue_shutdown.rb +33 -0
- data/lib/ruby_llm/active_record/system_instructions.rb +30 -0
- data/lib/ruby_llm/active_record/tool_call.rb +55 -4
- data/lib/ruby_llm/active_record/usage.rb +79 -9
- data/lib/ruby_llm/agent/callbacks.rb +98 -0
- data/lib/ruby_llm/agent/context_budget.rb +26 -0
- data/lib/ruby_llm/agent/join_callbacks.rb +28 -0
- data/lib/ruby_llm/agent/model_options.rb +56 -0
- data/lib/ruby_llm/agent/ownership.rb +22 -0
- data/lib/ruby_llm/agent/tool_conversion.rb +19 -0
- data/lib/ruby_llm/agent.rb +620 -236
- data/lib/ruby_llm/agent_tool.rb +91 -0
- data/lib/ruby_llm/aliases.json +84 -91
- data/lib/ruby_llm/attachment.rb +101 -20
- data/lib/ruby_llm/batch.rb +132 -46
- data/lib/ruby_llm/cached_content.rb +4 -4
- data/lib/ruby_llm/chat/agent_run.rb +22 -0
- data/lib/ruby_llm/chat/context_budget.rb +119 -0
- data/lib/ruby_llm/chat/file_references.rb +259 -0
- data/lib/ruby_llm/chat/message_queue.rb +56 -0
- data/lib/ruby_llm/chat/ownership.rb +18 -0
- data/lib/ruby_llm/chat/rescuable.rb +48 -0
- data/lib/ruby_llm/chat/run.rb +32 -0
- data/lib/ruby_llm/chat/tool_call_ids.rb +46 -0
- data/lib/ruby_llm/chat/tool_call_state.rb +115 -0
- data/lib/ruby_llm/chat/tool_concurrency.rb +90 -49
- data/lib/ruby_llm/chat/tool_search.rb +36 -1
- data/lib/ruby_llm/chat/tool_sequences.rb +84 -0
- data/lib/ruby_llm/chat.rb +1593 -331
- data/lib/ruby_llm/chunk.rb +27 -1
- data/lib/ruby_llm/citation.rb +2 -1
- data/lib/ruby_llm/code_execution/language.rb +28 -0
- data/lib/ruby_llm/code_execution/node_installation.rb +56 -0
- data/lib/ruby_llm/code_execution/packages.rb +241 -0
- data/lib/ruby_llm/code_execution/ruby_installation.rb +170 -0
- data/lib/ruby_llm/code_execution/workspace.rb +314 -0
- data/lib/ruby_llm/code_execution.rb +781 -0
- data/lib/ruby_llm/configuration.rb +151 -4
- data/lib/ruby_llm/content_filter.rb +68 -0
- data/lib/ruby_llm/cost.rb +68 -41
- data/lib/ruby_llm/downloaded_file.rb +10 -9
- data/lib/ruby_llm/embedding.rb +30 -45
- data/lib/ruby_llm/embedding_request.rb +1 -1
- data/lib/ruby_llm/error.rb +167 -14
- data/lib/ruby_llm/evaluation/dataset.rb +5 -4
- data/lib/ruby_llm/evaluation/evidence.rb +5 -3
- data/lib/ruby_llm/evaluation/report.rb +7 -3
- data/lib/ruby_llm/evaluation/result.rb +1 -1
- data/lib/ruby_llm/evaluation.rb +2 -1
- data/lib/ruby_llm/fallback.rb +32 -4
- data/lib/ruby_llm/file_store/converters/ocr.rb +28 -0
- data/lib/ruby_llm/file_store/converters/office/markup.rb +148 -0
- data/lib/ruby_llm/file_store/converters/office/outline.rb +193 -0
- data/lib/ruby_llm/file_store/converters/office/prose.rb +161 -0
- data/lib/ruby_llm/file_store/converters/office/workbook.rb +187 -0
- data/lib/ruby_llm/file_store/converters/office/zip.rb +149 -0
- data/lib/ruby_llm/file_store/converters/office.rb +106 -0
- data/lib/ruby_llm/file_store/converters/text.rb +27 -0
- data/lib/ruby_llm/file_store/converters.rb +84 -0
- data/lib/ruby_llm/file_store/directory.rb +190 -0
- data/lib/ruby_llm/file_store/memory.rb +75 -0
- data/lib/ruby_llm/file_store/tools/base.rb +99 -0
- data/lib/ruby_llm/file_store/tools/delete.rb +48 -0
- data/lib/ruby_llm/file_store/tools/edit.rb +155 -0
- data/lib/ruby_llm/file_store/tools/list.rb +57 -0
- data/lib/ruby_llm/file_store/tools/read.rb +120 -0
- data/lib/ruby_llm/file_store/tools/search.rb +208 -0
- data/lib/ruby_llm/file_store/tools/write.rb +40 -0
- data/lib/ruby_llm/file_store/tools.rb +39 -0
- data/lib/ruby_llm/file_store.rb +338 -0
- data/lib/ruby_llm/files/saving.rb +14 -0
- data/lib/ruby_llm/image.rb +67 -45
- data/lib/ruby_llm/input_required_error.rb +28 -0
- data/lib/ruby_llm/judge/builder.rb +6 -1
- data/lib/ruby_llm/judge.rb +1 -1
- data/lib/ruby_llm/judgment.rb +6 -24
- data/lib/ruby_llm/mcp/client.rb +50 -24
- data/lib/ruby_llm/mcp/collection.rb +39 -2
- data/lib/ruby_llm/mcp/content.rb +7 -2
- data/lib/ruby_llm/mcp/error.rb +8 -2
- data/lib/ruby_llm/mcp/http.rb +117 -37
- data/lib/ruby_llm/mcp/input_request.rb +3 -112
- data/lib/ruby_llm/mcp/input_required_error.rb +3 -21
- data/lib/ruby_llm/mcp/insufficient_scope_error.rb +27 -0
- data/lib/ruby_llm/mcp/listener.rb +7 -1
- data/lib/ruby_llm/mcp/oauth.rb +102 -39
- data/lib/ruby_llm/mcp/outages.rb +172 -0
- data/lib/ruby_llm/mcp/resource.rb +9 -6
- data/lib/ruby_llm/mcp/resource_template.rb +3 -2
- data/lib/ruby_llm/mcp/session_expired.rb +10 -2
- data/lib/ruby_llm/mcp/stdio.rb +66 -18
- data/lib/ruby_llm/mcp/task.rb +13 -40
- data/lib/ruby_llm/mcp/tool.rb +34 -11
- data/lib/ruby_llm/mcp.rb +123 -59
- data/lib/ruby_llm/message.rb +217 -23
- data/lib/ruby_llm/model/modalities.rb +1 -1
- data/lib/ruby_llm/model/pricing.rb +26 -4
- data/lib/ruby_llm/model/pricing_category.rb +38 -15
- data/lib/ruby_llm/model/pricing_tier.rb +3 -2
- data/lib/ruby_llm/model.rb +24 -6
- data/lib/ruby_llm/models/lookup.rb +37 -4
- data/lib/ruby_llm/models/registry.rb +4 -19
- data/lib/ruby_llm/models.json +36186 -27832
- data/lib/ruby_llm/models.rb +44 -39
- data/lib/ruby_llm/moderation.rb +10 -30
- data/lib/ruby_llm/ocr.rb +5 -22
- data/lib/ruby_llm/open_telemetry/attributes.rb +68 -19
- data/lib/ruby_llm/open_telemetry/content.rb +105 -0
- data/lib/ruby_llm/open_telemetry.rb +18 -4
- data/lib/ruby_llm/prompt/context.rb +2 -2
- data/lib/ruby_llm/prompt.rb +7 -5
- data/lib/ruby_llm/protocol/binary_streaming.rb +2 -8
- data/lib/ruby_llm/protocol/cache_lifetime.rb +22 -0
- data/lib/ruby_llm/protocol/native_content.rb +69 -0
- data/lib/ruby_llm/protocol/replayed_arguments.rb +28 -0
- data/lib/ruby_llm/protocol/stream_accumulator.rb +73 -33
- data/lib/ruby_llm/protocol/streaming.rb +105 -22
- data/lib/ruby_llm/protocol/tool_namespaces.rb +100 -0
- data/lib/ruby_llm/protocol/unsupported_operations.rb +42 -0
- data/lib/ruby_llm/protocol.rb +127 -106
- data/lib/ruby_llm/protocols/anthropic/batches.rb +41 -4
- data/lib/ruby_llm/protocols/anthropic/block_binding.rb +30 -0
- data/lib/ruby_llm/protocols/anthropic/chat.rb +135 -60
- data/lib/ruby_llm/protocols/anthropic/files.rb +9 -0
- data/lib/ruby_llm/protocols/anthropic/media.rb +14 -14
- data/lib/ruby_llm/protocols/anthropic/models.rb +16 -10
- data/lib/ruby_llm/protocols/anthropic/streaming.rb +43 -22
- data/lib/ruby_llm/protocols/anthropic/tools.rb +36 -24
- data/lib/ruby_llm/protocols/anthropic.rb +61 -6
- data/lib/ruby_llm/protocols/azure/speech/transcription.rb +125 -0
- data/lib/ruby_llm/protocols/azure/speech.rb +14 -0
- data/lib/ruby_llm/protocols/bedrock/batches.rb +26 -8
- data/lib/ruby_llm/protocols/bedrock/event_stream.rb +205 -0
- data/lib/ruby_llm/protocols/bedrock/files.rb +15 -6
- data/lib/ruby_llm/protocols/bedrock/guardrails.rb +1 -1
- data/lib/ruby_llm/protocols/bedrock/rerank.rb +2 -0
- data/lib/ruby_llm/protocols/chat_completions/chat.rb +158 -58
- data/lib/ruby_llm/protocols/chat_completions/content_filters.rb +45 -0
- data/lib/ruby_llm/protocols/chat_completions/embedding_batches.rb +15 -15
- data/lib/ruby_llm/protocols/chat_completions/embeddings.rb +26 -3
- data/lib/ruby_llm/protocols/chat_completions/images.rb +7 -9
- data/lib/ruby_llm/protocols/chat_completions/media.rb +20 -20
- data/lib/ruby_llm/protocols/chat_completions/moderation.rb +8 -5
- data/lib/ruby_llm/protocols/chat_completions/speech.rb +8 -2
- data/lib/ruby_llm/protocols/chat_completions/streaming.rb +38 -17
- data/lib/ruby_llm/protocols/chat_completions/think_tags.rb +148 -0
- data/lib/ruby_llm/protocols/chat_completions/tools.rb +15 -28
- data/lib/ruby_llm/protocols/chat_completions/transcription.rb +17 -2
- data/lib/ruby_llm/protocols/chat_completions.rb +4 -0
- data/lib/ruby_llm/protocols/cohere/batches.rb +15 -8
- data/lib/ruby_llm/protocols/cohere/chat.rb +47 -25
- data/lib/ruby_llm/protocols/cohere/datasets.rb +6 -2
- data/lib/ruby_llm/protocols/cohere/embeddings.rb +7 -5
- data/lib/ruby_llm/protocols/cohere/media.rb +7 -7
- data/lib/ruby_llm/protocols/cohere/rerank.rb +2 -1
- data/lib/ruby_llm/protocols/cohere/streaming.rb +26 -11
- data/lib/ruby_llm/protocols/cohere/tools.rb +17 -28
- data/lib/ruby_llm/protocols/cohere/transcription.rb +5 -4
- data/lib/ruby_llm/protocols/converse/batches.rb +2 -24
- data/lib/ruby_llm/protocols/converse/chat.rb +210 -75
- data/lib/ruby_llm/protocols/converse/media.rb +24 -22
- data/lib/ruby_llm/protocols/converse/reasoning.rb +23 -9
- data/lib/ruby_llm/protocols/converse/streaming.rb +68 -220
- data/lib/ruby_llm/protocols/converse.rb +17 -1
- data/lib/ruby_llm/protocols/deepgram/speech.rb +1 -2
- data/lib/ruby_llm/protocols/deepgram/streaming_transcription.rb +1 -3
- data/lib/ruby_llm/protocols/deepgram/transcription.rb +28 -19
- data/lib/ruby_llm/protocols/deepgram.rb +3 -2
- data/lib/ruby_llm/protocols/deepseek/files.rb +1 -1
- data/lib/ruby_llm/protocols/elevenlabs/assets.rb +6 -4
- data/lib/ruby_llm/protocols/elevenlabs/flows/images.rb +4 -0
- data/lib/ruby_llm/protocols/elevenlabs/speech.rb +3 -3
- data/lib/ruby_llm/protocols/elevenlabs/streaming_transcription.rb +5 -2
- data/lib/ruby_llm/protocols/elevenlabs/transcription.rb +6 -3
- data/lib/ruby_llm/protocols/files.rb +27 -12
- data/lib/ruby_llm/protocols/gemini/caches.rb +4 -4
- data/lib/ruby_llm/protocols/gemini/chat.rb +118 -51
- data/lib/ruby_llm/protocols/gemini/embedding_batches.rb +9 -5
- data/lib/ruby_llm/protocols/gemini/embeddings.rb +3 -3
- data/lib/ruby_llm/protocols/gemini/file_transcription.rb +4 -0
- data/lib/ruby_llm/protocols/gemini/files.rb +5 -4
- data/lib/ruby_llm/protocols/gemini/images.rb +11 -9
- data/lib/ruby_llm/protocols/gemini/live_transcription.rb +1 -1
- data/lib/ruby_llm/protocols/gemini/long_decode.rb +104 -0
- data/lib/ruby_llm/protocols/gemini/media.rb +31 -28
- data/lib/ruby_llm/protocols/gemini/retry_info.rb +29 -0
- data/lib/ruby_llm/protocols/gemini/schemas.rb +126 -0
- data/lib/ruby_llm/protocols/gemini/speech.rb +9 -3
- data/lib/ruby_llm/protocols/gemini/streaming.rb +37 -13
- data/lib/ruby_llm/protocols/gemini/tools.rb +18 -10
- data/lib/ruby_llm/protocols/gemini/transcription.rb +13 -9
- data/lib/ruby_llm/protocols/gemini/videos.rb +14 -3
- data/lib/ruby_llm/protocols/gemini.rb +16 -0
- data/lib/ruby_llm/protocols/gpustack/responses.rb +3 -3
- data/lib/ruby_llm/protocols/gpustack/tokenization.rb +14 -0
- data/lib/ruby_llm/protocols/interactions/chat.rb +29 -14
- data/lib/ruby_llm/protocols/interactions/content.rb +19 -14
- data/lib/ruby_llm/protocols/interactions/streaming.rb +1 -1
- data/lib/ruby_llm/protocols/interactions/tools.rb +10 -13
- data/lib/ruby_llm/protocols/interactions/videos.rb +99 -0
- data/lib/ruby_llm/protocols/interactions.rb +1 -0
- data/lib/ruby_llm/protocols/invoke_model/cohere_embeddings.rb +28 -10
- data/lib/ruby_llm/protocols/invoke_model/embedding_batches.rb +1 -1
- data/lib/ruby_llm/protocols/invoke_model/nova_embeddings.rb +45 -11
- data/lib/ruby_llm/protocols/invoke_model/stability_images.rb +5 -2
- data/lib/ruby_llm/protocols/invoke_model/titan_multimodal_embeddings.rb +11 -3
- data/lib/ruby_llm/protocols/invoke_model.rb +21 -2
- data/lib/ruby_llm/protocols/meta/transcription/stream.rb +108 -0
- data/lib/ruby_llm/protocols/meta/transcription.rb +76 -0
- data/lib/ruby_llm/protocols/minimax/files.rb +58 -0
- data/lib/ruby_llm/protocols/minimax/images.rb +54 -0
- data/lib/ruby_llm/protocols/minimax/speech.rb +53 -0
- data/lib/ruby_llm/protocols/minimax/transcription.rb +82 -0
- data/lib/ruby_llm/protocols/minimax/videos.rb +93 -0
- data/lib/ruby_llm/protocols/minimax.rb +21 -0
- data/lib/ruby_llm/protocols/mistral/conversations/chat.rb +44 -21
- data/lib/ruby_llm/protocols/mistral/files.rb +6 -4
- data/lib/ruby_llm/protocols/mistral/multi_completion.rb +15 -4
- data/lib/ruby_llm/protocols/moonshot/files.rb +48 -0
- data/lib/ruby_llm/protocols/openai/batches.rb +3 -2
- data/lib/ruby_llm/protocols/openai/decisions.rb +6 -4
- data/lib/ruby_llm/protocols/openai/files.rb +13 -6
- data/lib/ruby_llm/protocols/openrouter/batches.rb +13 -17
- data/lib/ruby_llm/protocols/openrouter/responses.rb +6 -10
- data/lib/ruby_llm/protocols/perplexity/agent.rb +19 -35
- data/lib/ruby_llm/protocols/perplexity/batches.rb +37 -0
- data/lib/ruby_llm/protocols/perplexity/files.rb +19 -9
- data/lib/ruby_llm/protocols/responses/approvals.rb +12 -11
- data/lib/ruby_llm/protocols/responses/chat.rb +165 -75
- data/lib/ruby_llm/protocols/responses/citation_markers.rb +167 -0
- data/lib/ruby_llm/protocols/responses/compaction.rb +2 -2
- data/lib/ruby_llm/protocols/responses/media.rb +17 -9
- data/lib/ruby_llm/protocols/responses/streaming.rb +71 -12
- data/lib/ruby_llm/protocols/responses/token_counting.rb +1 -1
- data/lib/ruby_llm/protocols/responses/tools.rb +48 -1
- data/lib/ruby_llm/protocols/responses.rb +21 -0
- data/lib/ruby_llm/protocols/system_one/responses.rb +9 -2
- data/lib/ruby_llm/protocols/vertexai/files.rb +4 -3
- data/lib/ruby_llm/protocols/vertexai/text_to_speech.rb +116 -0
- data/lib/ruby_llm/protocols/xai/files.rb +13 -6
- data/lib/ruby_llm/protocols/zai/files.rb +37 -0
- data/lib/ruby_llm/provider.rb +128 -31
- data/lib/ruby_llm/providers/anthropic.rb +5 -1
- data/lib/ruby_llm/providers/azure/chat.rb +17 -3
- data/lib/ruby_llm/providers/azure/chat_completions.rb +1 -0
- data/lib/ruby_llm/providers/azure/decisions.rb +17 -0
- data/lib/ruby_llm/providers/azure/embeddings.rb +1 -2
- data/lib/ruby_llm/providers/azure/media.rb +6 -6
- data/lib/ruby_llm/providers/azure/mistral.rb +17 -0
- data/lib/ruby_llm/providers/azure/models.rb +16 -2
- data/lib/ruby_llm/providers/azure/moderation.rb +16 -0
- data/lib/ruby_llm/providers/azure/responses.rb +9 -0
- data/lib/ruby_llm/providers/azure/videos.rb +2 -2
- data/lib/ruby_llm/providers/azure.rb +43 -1
- data/lib/ruby_llm/providers/bedrock/anthropic/batches.rb +32 -0
- data/lib/ruby_llm/providers/bedrock/anthropic.rb +134 -0
- data/lib/ruby_llm/providers/bedrock/auth.rb +12 -3
- data/lib/ruby_llm/providers/bedrock/connection.rb +19 -0
- data/lib/ruby_llm/providers/bedrock/mantle/anthropic.rb +5 -1
- data/lib/ruby_llm/providers/bedrock/mantle/chat_completions.rb +2 -2
- data/lib/ruby_llm/providers/bedrock/mantle/responses.rb +12 -2
- data/lib/ruby_llm/providers/bedrock/mantle/voxtral.rb +1 -1
- data/lib/ruby_llm/providers/bedrock/mantle.rb +7 -0
- data/lib/ruby_llm/providers/bedrock/models.rb +1 -7
- data/lib/ruby_llm/providers/bedrock.rb +51 -8
- data/lib/ruby_llm/providers/cohere.rb +5 -1
- data/lib/ruby_llm/providers/deepgram/chat.rb +25 -0
- data/lib/ruby_llm/providers/deepgram.rb +21 -2
- data/lib/ruby_llm/providers/deepseek/chat.rb +7 -16
- data/lib/ruby_llm/providers/deepseek/responses.rb +25 -8
- data/lib/ruby_llm/providers/deepseek.rb +5 -1
- data/lib/ruby_llm/providers/elevenlabs.rb +2 -1
- data/lib/ruby_llm/providers/gemini.rb +13 -1
- data/lib/ruby_llm/providers/gpustack/chat.rb +23 -3
- data/lib/ruby_llm/providers/gpustack/embeddings.rb +2 -2
- data/lib/ruby_llm/providers/gpustack/media.rb +8 -8
- data/lib/ruby_llm/providers/gpustack/transcription.rb +14 -6
- data/lib/ruby_llm/providers/gpustack.rb +1 -1
- data/lib/ruby_llm/providers/hetzner/chat.rb +23 -6
- data/lib/ruby_llm/providers/hetzner.rb +5 -1
- data/lib/ruby_llm/providers/meta/responses.rb +81 -0
- data/lib/ruby_llm/providers/meta.rb +37 -0
- data/lib/ruby_llm/providers/minimax/anthropic.rb +95 -0
- data/lib/ruby_llm/providers/minimax/chat_completions.rb +48 -0
- data/lib/ruby_llm/providers/minimax/responses.rb +52 -0
- data/lib/ruby_llm/providers/minimax.rb +104 -0
- data/lib/ruby_llm/providers/mistral/chat.rb +34 -4
- data/lib/ruby_llm/providers/mistral/chat_completions/batches.rb +5 -14
- data/lib/ruby_llm/providers/mistral/embeddings.rb +1 -12
- data/lib/ruby_llm/providers/mistral/input_tokens.rb +35 -0
- data/lib/ruby_llm/providers/mistral/media.rb +8 -8
- data/lib/ruby_llm/providers/mistral/transcription.rb +8 -6
- data/lib/ruby_llm/providers/mistral.rb +13 -1
- data/lib/ruby_llm/providers/moonshot/capabilities.rb +16 -0
- data/lib/ruby_llm/providers/moonshot/chat.rb +100 -0
- data/lib/ruby_llm/providers/moonshot/documents.rb +79 -0
- data/lib/ruby_llm/providers/moonshot/responses.rb +55 -0
- data/lib/ruby_llm/providers/moonshot.rb +54 -0
- data/lib/ruby_llm/providers/ollama/chat.rb +5 -6
- data/lib/ruby_llm/providers/ollama/embeddings.rb +41 -0
- data/lib/ruby_llm/providers/ollama/media.rb +6 -6
- data/lib/ruby_llm/providers/ollama/speech.rb +24 -0
- data/lib/ruby_llm/providers/ollama.rb +13 -1
- data/lib/ruby_llm/providers/ollama_cloud.rb +22 -1
- data/lib/ruby_llm/providers/openai/responses.rb +8 -0
- data/lib/ruby_llm/providers/openai.rb +7 -3
- data/lib/ruby_llm/providers/openrouter/chat.rb +15 -9
- data/lib/ruby_llm/providers/openrouter/embeddings.rb +12 -8
- data/lib/ruby_llm/providers/openrouter/images.rb +16 -10
- data/lib/ruby_llm/providers/openrouter/judgments.rb +47 -0
- data/lib/ruby_llm/providers/openrouter/media.rb +5 -5
- data/lib/ruby_llm/providers/openrouter/models.rb +19 -7
- data/lib/ruby_llm/providers/openrouter/streaming.rb +4 -4
- data/lib/ruby_llm/providers/openrouter/videos.rb +1 -1
- data/lib/ruby_llm/providers/openrouter.rb +32 -5
- data/lib/ruby_llm/providers/perplexity/chat.rb +3 -3
- data/lib/ruby_llm/providers/perplexity/embeddings.rb +1 -1
- data/lib/ruby_llm/providers/perplexity/media.rb +6 -6
- data/lib/ruby_llm/providers/perplexity.rb +13 -2
- data/lib/ruby_llm/providers/typesafe.rb +1 -1
- data/lib/ruby_llm/providers/vertexai/anthropic.rb +5 -1
- data/lib/ruby_llm/providers/vertexai/credentials.rb +27 -3
- data/lib/ruby_llm/providers/vertexai/embed_content.rb +1 -1
- data/lib/ruby_llm/providers/vertexai/embeddings.rb +2 -3
- data/lib/ruby_llm/providers/vertexai/gemini.rb +1 -1
- data/lib/ruby_llm/providers/vertexai/mistral.rb +5 -0
- data/lib/ruby_llm/providers/vertexai/models.rb +24 -2
- data/lib/ruby_llm/providers/vertexai/transcription.rb +2 -2
- data/lib/ruby_llm/providers/vertexai/videos.rb +1 -1
- data/lib/ruby_llm/providers/vertexai.rb +56 -9
- data/lib/ruby_llm/providers/xai/chat.rb +3 -3
- data/lib/ruby_llm/providers/xai/chat_completions/batches.rb +8 -3
- data/lib/ruby_llm/providers/xai/images.rb +9 -4
- data/lib/ruby_llm/providers/xai/models.rb +2 -2
- data/lib/ruby_llm/providers/xai/responses.rb +10 -0
- data/lib/ruby_llm/providers/xai/speech.rb +2 -2
- data/lib/ruby_llm/providers/xai/transcription.rb +14 -9
- data/lib/ruby_llm/providers/xai/videos.rb +1 -1
- data/lib/ruby_llm/providers/xai.rb +9 -1
- data/lib/ruby_llm/providers/zai/chat.rb +117 -0
- data/lib/ruby_llm/providers/zai/ocr.rb +65 -0
- data/lib/ruby_llm/providers/zai/token_counting.rb +25 -0
- data/lib/ruby_llm/providers/zai/videos.rb +67 -0
- data/lib/ruby_llm/providers/zai.rb +66 -0
- data/lib/ruby_llm/question.rb +390 -0
- data/lib/ruby_llm/queued_message.rb +74 -0
- data/lib/ruby_llm/railtie.rb +43 -0
- data/lib/ruby_llm/request_shape/part.rb +2 -2
- data/lib/ruby_llm/request_shape/problem.rb +2 -2
- data/lib/ruby_llm/request_shape/tool_round.rb +2 -2
- data/lib/ruby_llm/request_shape/turn.rb +2 -2
- data/lib/ruby_llm/request_shape.rb +1 -1
- data/lib/ruby_llm/rerank.rb +10 -37
- data/lib/ruby_llm/research_job.rb +26 -13
- data/lib/ruby_llm/sandbox/landlock.rb +78 -0
- data/lib/ruby_llm/sandbox/mxc.rb +137 -0
- data/lib/ruby_llm/sandbox.rb +141 -0
- data/lib/ruby_llm/search_results.rb +9 -4
- data/lib/ruby_llm/server_tool_call.rb +1 -1
- data/lib/ruby_llm/skill/loader.rb +131 -0
- data/lib/ruby_llm/skill.rb +263 -0
- data/lib/ruby_llm/speech.rb +25 -53
- data/lib/ruby_llm/stored_file.rb +94 -0
- data/lib/ruby_llm/support/cancellation.rb +4 -4
- data/lib/ruby_llm/support/deprecated_enumerable.rb +13 -0
- data/lib/ruby_llm/support/deprecated_job_predicates.rb +24 -0
- data/lib/ruby_llm/support/inspectable.rb +1 -1
- data/lib/ruby_llm/support/instrumentation.rb +6 -5
- data/lib/ruby_llm/support/rescuable.rb +84 -0
- data/lib/ruby_llm/support/utils.rb +26 -3
- data/lib/ruby_llm/task.rb +126 -0
- data/lib/ruby_llm/test_helper.rb +129 -0
- data/lib/ruby_llm/thinking.rb +3 -3
- data/lib/ruby_llm/tokens.rb +52 -12
- data/lib/ruby_llm/tool.rb +339 -5
- data/lib/ruby_llm/tool_call/collection.rb +132 -0
- data/lib/ruby_llm/tool_call.rb +68 -6
- data/lib/ruby_llm/tools/provider_tools.rb +23 -0
- data/lib/ruby_llm/tools/search.rb +118 -0
- data/lib/ruby_llm/transcription/segment.rb +67 -0
- data/lib/ruby_llm/transcription/wav_audio.rb +2 -1
- data/lib/ruby_llm/transcription.rb +25 -49
- data/lib/ruby_llm/transcription_chunk.rb +9 -9
- data/lib/ruby_llm/transport/connection.rb +146 -33
- data/lib/ruby_llm/transport/error_middleware.rb +94 -49
- data/lib/ruby_llm/transport/event_stream_parser.rb +18 -4
- data/lib/ruby_llm/transport/json_response.rb +9 -3
- data/lib/ruby_llm/transport/log_formatter.rb +20 -0
- data/lib/ruby_llm/transport/release_request.rb +35 -0
- data/lib/ruby_llm/transport/websocket_connection.rb +26 -30
- data/lib/ruby_llm/uploaded_file.rb +15 -6
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm/video.rb +34 -10
- data/lib/ruby_llm/video_job.rb +55 -42
- data/lib/ruby_llm/web_source.rb +38 -0
- data/lib/ruby_llm/workflow.rb +2 -2
- data/lib/ruby_llm.rb +63 -25
- data/lib/tasks/ruby_llm/evaluations.rb +1 -1
- data/skills/rubyllm/SKILL.md +128 -54
- data/skills/rubyllm/references/cost-and-tracing.md +8 -0
- data/skills/rubyllm/references/files-code-and-context.md +6 -0
- data/skills/rubyllm/references/operations.md +8 -0
- data/skills/rubyllm/references/rails-chats.md +22 -0
- data/skills/rubyllm/references/tools-and-agents.md +11 -0
- metadata +213 -14
- data/lib/generators/ruby_llm/chat_ui/templates/helpers/messages_helper.rb.tt +0 -25
- data/lib/generators/ruby_llm/upgrade/templates/upgrade_ruby_llm_to_2_1.rb.tt +0 -78
|
@@ -7,6 +7,10 @@ module RubyLLM
|
|
|
7
7
|
module Transcription
|
|
8
8
|
DEFAULT_LANGUAGE = 'en'
|
|
9
9
|
|
|
10
|
+
def rejected_options(operation)
|
|
11
|
+
operation == :transcribe ? %i[format prompt speaker_names speaker_references] : super
|
|
12
|
+
end
|
|
13
|
+
|
|
10
14
|
module_function
|
|
11
15
|
|
|
12
16
|
def transcription_url
|
|
@@ -16,10 +20,7 @@ module RubyLLM
|
|
|
16
20
|
# Cohere parses the multipart body as it streams, so every form field
|
|
17
21
|
# has to be written before the file part or the request is rejected as
|
|
18
22
|
# missing them. The file goes in last, after provider options.
|
|
19
|
-
|
|
20
|
-
def render_transcription_payload(file_part, model:, language:, format: nil, speaker_names: nil,
|
|
21
|
-
speaker_references: nil, provider_options: {}, prompt: nil,
|
|
22
|
-
temperature: nil)
|
|
23
|
+
def render_transcription_payload(file_part, model:, language:, provider_options: {}, temperature: nil, **)
|
|
23
24
|
{
|
|
24
25
|
model: model,
|
|
25
26
|
language: language || DEFAULT_LANGUAGE,
|
|
@@ -13,12 +13,6 @@ module RubyLLM
|
|
|
13
13
|
'Converse'
|
|
14
14
|
end
|
|
15
15
|
|
|
16
|
-
def bedrock_batch_jsonl(requests)
|
|
17
|
-
requests.map do |request|
|
|
18
|
-
JSON.generate(recordId: request[:custom_id], modelInput: batch_payload(request))
|
|
19
|
-
end.join("\n")
|
|
20
|
-
end
|
|
21
|
-
|
|
22
16
|
def validate_bedrock_batch_requests!(requests)
|
|
23
17
|
unsupported = requests.any? do |request|
|
|
24
18
|
payload = request.fetch(:payload)
|
|
@@ -30,24 +24,8 @@ module RubyLLM
|
|
|
30
24
|
raise Error, 'bedrock batch requests do not support tools or structured output'
|
|
31
25
|
end
|
|
32
26
|
|
|
33
|
-
def
|
|
34
|
-
|
|
35
|
-
end
|
|
36
|
-
|
|
37
|
-
def parse_bedrock_output(body)
|
|
38
|
-
body.to_s.each_line.filter_map { |line| parse_bedrock_record(JSON.parse(line)) }
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def parse_bedrock_record(line)
|
|
42
|
-
record_id = line['recordId'] || line['record_id']
|
|
43
|
-
index = batch_result_index(record_id)
|
|
44
|
-
|
|
45
|
-
if line['modelOutput']
|
|
46
|
-
body = line['modelOutput']
|
|
47
|
-
[index, parse_completion_body(body, raw: body)]
|
|
48
|
-
else
|
|
49
|
-
[index, nil, batch_failure(record_id, line.dig('error', 'message') || line['errorMessage'])]
|
|
50
|
-
end
|
|
27
|
+
def parse_bedrock_model_output(body, model:)
|
|
28
|
+
parse_completion_body({ 'modelId' => model }.merge(body), raw: body)
|
|
51
29
|
end
|
|
52
30
|
end
|
|
53
31
|
end
|
|
@@ -15,6 +15,22 @@ module RubyLLM
|
|
|
15
15
|
|
|
16
16
|
BEDROCK_INLINE_DOCUMENT_LIMIT = 4_500_000
|
|
17
17
|
PROMPT_CACHE_OPTIONS = %i[ttl].freeze
|
|
18
|
+
# Bedrock accepts at most four cache points in one request, and
|
|
19
|
+
# Converse takes them only for Claude and Nova models; Nova takes none
|
|
20
|
+
# in toolConfig.tools. See "Supported models, Regions, and explicit
|
|
21
|
+
# caching limits" in
|
|
22
|
+
# https://docs.aws.amazon.com/bedrock/latest/userguide/prompt-caching.html
|
|
23
|
+
MAX_CACHE_POINTS = 4
|
|
24
|
+
CACHE_POINT_MODELS = %w[anthropic. amazon.nova].freeze
|
|
25
|
+
TOOL_CACHE_POINT_MODELS = %w[anthropic.].freeze
|
|
26
|
+
# Converse refuses searchResult blocks from every model but Claude, so an
|
|
27
|
+
# application inference profile, which doesn't name its model, gets text.
|
|
28
|
+
SEARCH_RESULT_MODELS = %w[anthropic.].freeze
|
|
29
|
+
# Bedrock forwards an S3 source to other models untranslated, and
|
|
30
|
+
# Claude rejects it.
|
|
31
|
+
S3_SOURCE_MODELS = %w[amazon.nova].freeze
|
|
32
|
+
OFFICE_DOCUMENT_FORMATS = %w[doc docx xls xlsx].freeze
|
|
33
|
+
CACHE_LIFETIME_UNITS = { 's' => 1, 'm' => 60, 'h' => 3600 }.freeze
|
|
18
34
|
COUNT_TOKENS_KEYS = %i[messages system toolConfig additionalModelRequestFields].freeze
|
|
19
35
|
|
|
20
36
|
module_function
|
|
@@ -24,7 +40,7 @@ module RubyLLM
|
|
|
24
40
|
def normalize_finish_reason(reason)
|
|
25
41
|
return nil if reason.nil?
|
|
26
42
|
|
|
27
|
-
finish_reasons.fetch(reason.to_s) { reason.to_s.to_sym }
|
|
43
|
+
finish_reasons.fetch(reason.to_s) { reason.to_s.downcase.to_sym }
|
|
28
44
|
end
|
|
29
45
|
|
|
30
46
|
def completion_url
|
|
@@ -41,28 +57,31 @@ module RubyLLM
|
|
|
41
57
|
def render_payload(messages, tools:, temperature:, model:, stream: false, max_output_tokens: nil,
|
|
42
58
|
schema: nil, thinking: nil, citations: false, caching: nil, tool_prefs: nil)
|
|
43
59
|
tool_prefs ||= {}
|
|
60
|
+
max_output_tokens ||= model.max_output_tokens
|
|
44
61
|
@used_document_names = {}
|
|
45
62
|
system_messages, chat_messages = messages.partition { |msg| msg.role == :system }
|
|
46
63
|
prompt_cache_options(caching)
|
|
47
|
-
automatic_cache_target = automatic_cache_target(system_messages, chat_messages, caching)
|
|
48
64
|
payload = {
|
|
49
|
-
messages:
|
|
65
|
+
messages: render_messages(chat_messages, caching:, citations:, search_results: search_results?(model),
|
|
66
|
+
tool_text: tools.empty?)
|
|
50
67
|
}
|
|
51
68
|
|
|
52
|
-
system_blocks =
|
|
69
|
+
system_blocks = render_system(system_messages, caching:)
|
|
53
70
|
payload[:system] = system_blocks unless system_blocks.empty?
|
|
54
71
|
|
|
55
|
-
payload[:inferenceConfig] =
|
|
72
|
+
payload[:inferenceConfig] = render_inference_config(model, temperature, max_output_tokens)
|
|
56
73
|
|
|
57
|
-
tool_config =
|
|
74
|
+
tool_config = render_tool_config(tools, tool_prefs)
|
|
58
75
|
payload[:toolConfig] = tool_config if tool_config
|
|
59
76
|
|
|
60
|
-
additional_fields =
|
|
77
|
+
additional_fields = render_additional_model_request_fields(thinking, model, max_output_tokens)
|
|
78
|
+
additional_fields = bind_thinking(additional_fields, model)
|
|
61
79
|
payload[:additionalModelRequestFields] = additional_fields if additional_fields
|
|
62
80
|
|
|
63
81
|
output_config = build_output_config(schema)
|
|
64
82
|
payload[:outputConfig] = output_config if output_config
|
|
65
83
|
|
|
84
|
+
add_automatic_cache_points(payload, model, caching)
|
|
66
85
|
payload
|
|
67
86
|
end
|
|
68
87
|
|
|
@@ -91,8 +110,8 @@ module RubyLLM
|
|
|
91
110
|
response.body['inputTokens']
|
|
92
111
|
end
|
|
93
112
|
|
|
94
|
-
def
|
|
95
|
-
|
|
113
|
+
def provider_file_references?
|
|
114
|
+
S3_SOURCE_MODELS.any? { |prefix| foundation_model_id(@model&.id).start_with?(prefix) }
|
|
96
115
|
end
|
|
97
116
|
|
|
98
117
|
def default_large_file_upload_threshold
|
|
@@ -108,7 +127,7 @@ module RubyLLM
|
|
|
108
127
|
content_blocks = data.dig('output', 'message', 'content') || []
|
|
109
128
|
usage = data['usage'] || {}
|
|
110
129
|
thinking_text, thinking_signature = parse_thinking(content_blocks)
|
|
111
|
-
text_content, citations =
|
|
130
|
+
text_content, citations = parse_text_and_citations(content_blocks)
|
|
112
131
|
|
|
113
132
|
Message.new(
|
|
114
133
|
role: :assistant,
|
|
@@ -117,7 +136,7 @@ module RubyLLM
|
|
|
117
136
|
thinking: Thinking.build(text: thinking_text, signature: thinking_signature),
|
|
118
137
|
raw_reasoning: parse_thinking_blocks(content_blocks),
|
|
119
138
|
tool_calls: parse_tool_calls(content_blocks),
|
|
120
|
-
server_tool_calls:
|
|
139
|
+
server_tool_calls: parse_server_tool_calls(content_blocks),
|
|
121
140
|
input_tokens: input_tokens(usage),
|
|
122
141
|
output_tokens: usage['outputTokens'],
|
|
123
142
|
cache_read_tokens: usage['cacheReadInputTokens'],
|
|
@@ -151,22 +170,31 @@ module RubyLLM
|
|
|
151
170
|
end
|
|
152
171
|
end
|
|
153
172
|
|
|
154
|
-
|
|
173
|
+
# Converse refuses toolUse and toolResult blocks in a request without
|
|
174
|
+
# a toolConfig, and has no tool choice of none, so a request that
|
|
175
|
+
# declares no tools, such as a summary of a chat that called them,
|
|
176
|
+
# gets its tool rounds as text (+tool_text+).
|
|
177
|
+
def render_messages(messages, caching: nil, citations: false, search_results: false, tool_text: false)
|
|
155
178
|
rendered = []
|
|
156
179
|
tool_result_blocks = []
|
|
180
|
+
calls = {}
|
|
157
181
|
|
|
158
182
|
messages.each do |msg|
|
|
183
|
+
calls.merge!(msg.tool_calls.to_h)
|
|
159
184
|
if msg.tool_result?
|
|
160
|
-
|
|
185
|
+
call = calls[msg.tool_call_id] if tool_text
|
|
186
|
+
tool_result_blocks.concat(render_tool_result_blocks(msg, caching:, citations:, search_results:,
|
|
187
|
+
tool_text:, call:))
|
|
161
188
|
next
|
|
162
189
|
end
|
|
163
190
|
|
|
191
|
+
message = render_non_tool_message(msg, caching:, citations:, tool_text:)
|
|
192
|
+
next tool_result_blocks.concat(message[:content]) if joins_tool_results?(message, tool_result_blocks)
|
|
193
|
+
|
|
164
194
|
unless tool_result_blocks.empty?
|
|
165
195
|
rendered << { role: 'user', content: tool_result_blocks }
|
|
166
196
|
tool_result_blocks = []
|
|
167
197
|
end
|
|
168
|
-
|
|
169
|
-
message = format_non_tool_message(msg, caching:, automatic_cache_target:, citations:)
|
|
170
198
|
rendered << message if message
|
|
171
199
|
end
|
|
172
200
|
|
|
@@ -174,42 +202,50 @@ module RubyLLM
|
|
|
174
202
|
rendered
|
|
175
203
|
end
|
|
176
204
|
|
|
177
|
-
def
|
|
178
|
-
|
|
205
|
+
def joins_tool_results?(message, tool_result_blocks)
|
|
206
|
+
message && message[:role] == 'user' && !tool_result_blocks.empty?
|
|
207
|
+
end
|
|
208
|
+
|
|
209
|
+
def render_non_tool_message(msg, caching: nil, citations: false, tool_text: false)
|
|
210
|
+
content = render_message_content(msg, caching:, citations:, tool_text:)
|
|
179
211
|
return nil if content.empty?
|
|
180
212
|
|
|
181
213
|
{
|
|
182
|
-
role:
|
|
214
|
+
role: render_role(msg.role),
|
|
183
215
|
content: content
|
|
184
216
|
}
|
|
185
217
|
end
|
|
186
218
|
|
|
187
|
-
def
|
|
188
|
-
blocks =
|
|
219
|
+
def render_message_content(msg, caching: nil, citations: false, tool_text: false)
|
|
220
|
+
blocks = render_structured_message_content(msg, citations:)
|
|
189
221
|
|
|
190
222
|
if msg.tool_call?
|
|
191
|
-
msg.tool_calls.
|
|
223
|
+
msg.tool_calls.each do |tool_call|
|
|
224
|
+
next blocks << tool_call_text_block(tool_call) if tool_text
|
|
225
|
+
|
|
192
226
|
blocks << {
|
|
193
227
|
toolUse: {
|
|
194
|
-
toolUseId: tool_call.
|
|
228
|
+
toolUseId: tool_call.provider_call_id,
|
|
195
229
|
name: tool_call.name,
|
|
196
230
|
input: tool_call.arguments
|
|
197
231
|
}
|
|
198
232
|
}
|
|
199
233
|
end
|
|
200
234
|
end
|
|
201
|
-
if cache_boundary?(msg,
|
|
202
|
-
blocks << converse_cache_block_for(caching, ttl: msg.cache_ttl)
|
|
203
|
-
end
|
|
235
|
+
blocks << converse_cache_block_for(caching, ttl: msg.cache_ttl) if cache_boundary?(msg, caching:)
|
|
204
236
|
|
|
205
237
|
blocks
|
|
206
238
|
end
|
|
207
239
|
|
|
208
|
-
def
|
|
209
|
-
|
|
240
|
+
def tool_call_text_block(tool_call)
|
|
241
|
+
{ text: "Called the #{tool_call.name} tool with #{tool_call.arguments.to_json}" }
|
|
242
|
+
end
|
|
243
|
+
|
|
244
|
+
def render_structured_message_content(msg, citations: false)
|
|
245
|
+
blocks = msg.role == :assistant ? render_thinking_blocks(msg) : []
|
|
210
246
|
|
|
211
247
|
blocks.concat(
|
|
212
|
-
Media.
|
|
248
|
+
Media.render_content(msg.content, msg.attachments, used_document_names: @used_document_names, citations:)
|
|
213
249
|
)
|
|
214
250
|
|
|
215
251
|
blocks
|
|
@@ -218,35 +254,55 @@ module RubyLLM
|
|
|
218
254
|
# Reasoning rebuilt from thinking text goes back only to the provider
|
|
219
255
|
# that produced it: Claude refuses another provider's signature, and
|
|
220
256
|
# a block without one.
|
|
221
|
-
def
|
|
257
|
+
def render_thinking_blocks(msg)
|
|
222
258
|
blocks = msg.raw_reasoning['converse'] if msg.raw_reasoning.is_a?(Hash)
|
|
223
259
|
return Support::Utils.deep_dup(blocks) if blocks
|
|
224
260
|
return [] unless msg.thinking && producer_slug(msg) == @provider.slug
|
|
225
261
|
|
|
226
|
-
[
|
|
262
|
+
[render_thinking_block(msg.thinking)].compact
|
|
263
|
+
end
|
|
264
|
+
|
|
265
|
+
def render_tool_result_blocks(msg, caching: nil, citations: false, search_results: false, tool_text: false,
|
|
266
|
+
call: nil)
|
|
267
|
+
blocks = if tool_text
|
|
268
|
+
render_tool_result_text(msg, call, citations:)
|
|
269
|
+
else
|
|
270
|
+
[render_tool_result_block(msg, citations:, search_results:)]
|
|
271
|
+
end
|
|
272
|
+
blocks << converse_cache_block_for(caching, ttl: msg.cache_ttl) if cache_boundary?(msg, caching:)
|
|
273
|
+
blocks
|
|
227
274
|
end
|
|
228
275
|
|
|
229
|
-
def
|
|
276
|
+
def render_tool_result_block(msg, citations: false, search_results: false)
|
|
230
277
|
{
|
|
231
278
|
toolResult: {
|
|
232
|
-
toolUseId: msg.
|
|
233
|
-
content:
|
|
279
|
+
toolUseId: msg.provider_call_id,
|
|
280
|
+
content: render_tool_result_content(msg, citations:, search_results:)
|
|
234
281
|
}
|
|
235
282
|
}
|
|
236
283
|
end
|
|
237
284
|
|
|
238
|
-
def
|
|
239
|
-
|
|
240
|
-
|
|
285
|
+
def render_tool_result_text(msg, call, citations: false)
|
|
286
|
+
label = call ? "The #{call.name} tool returned:" : 'The tool returned:'
|
|
287
|
+
first, *rest = render_tool_result_content(msg, citations:)
|
|
288
|
+
return [{ text: "#{label}\n#{first[:text]}" }, *rest] if first.key?(:text)
|
|
241
289
|
|
|
242
|
-
|
|
290
|
+
[{ text: label }, first, *rest]
|
|
291
|
+
end
|
|
292
|
+
|
|
293
|
+
def render_tool_result_content(msg, citations: false, search_results: false)
|
|
294
|
+
results = RubyLLM::SearchResults.from_content(msg.content) if search_results
|
|
295
|
+
return results.results.map { |result| search_result_block(result) } if results
|
|
296
|
+
|
|
297
|
+
content = msg.content unless msg.content&.strip&.empty?
|
|
298
|
+
blocks = Media.render_content(content, msg.attachments, used_document_names: @used_document_names, citations:)
|
|
243
299
|
blocks.empty? ? [text_tool_result_block(nil)] : blocks
|
|
244
300
|
end
|
|
245
301
|
|
|
246
302
|
def search_result_block(result)
|
|
247
303
|
{
|
|
248
304
|
searchResult: {
|
|
249
|
-
source: result[:url] || result[:title],
|
|
305
|
+
source: result[:source_id] || result[:url] || result[:title],
|
|
250
306
|
title: result[:title],
|
|
251
307
|
content: [{ text: result[:text] }],
|
|
252
308
|
citations: { enabled: true }
|
|
@@ -260,43 +316,114 @@ module RubyLLM
|
|
|
260
316
|
{ text: text }
|
|
261
317
|
end
|
|
262
318
|
|
|
263
|
-
def
|
|
319
|
+
def render_role(role)
|
|
264
320
|
case role
|
|
265
321
|
when :assistant then 'assistant'
|
|
266
322
|
else 'user'
|
|
267
323
|
end
|
|
268
324
|
end
|
|
269
325
|
|
|
270
|
-
def
|
|
326
|
+
def render_system(messages, caching: nil)
|
|
271
327
|
messages.flat_map do |msg|
|
|
272
|
-
blocks = Media.
|
|
273
|
-
|
|
274
|
-
blocks + [converse_cache_block_for(caching, ttl: msg.cache_ttl)]
|
|
275
|
-
else
|
|
276
|
-
blocks
|
|
277
|
-
end
|
|
328
|
+
blocks = Media.render_content(msg.content, msg.attachments, used_document_names: @used_document_names)
|
|
329
|
+
cache_boundary?(msg, caching:) ? blocks + [converse_cache_block_for(caching, ttl: msg.cache_ttl)] : blocks
|
|
278
330
|
end
|
|
279
331
|
end
|
|
280
332
|
|
|
281
|
-
def
|
|
282
|
-
|
|
333
|
+
def cache_boundary?(message, caching: nil)
|
|
334
|
+
caching != false && message.cache_until_here?
|
|
335
|
+
end
|
|
336
|
+
|
|
337
|
+
# Bedrock's own caching of Claude and Nova prompts is best effort, so
|
|
338
|
+
# with caching on RubyLLM places explicit points: at the end of the
|
|
339
|
+
# conversation, so the next tool round or question can read what was
|
|
340
|
+
# sent so far; after the system prompt (or the tools without one),
|
|
341
|
+
# which stays cached when a later message changes; and after the
|
|
342
|
+
# previous assistant message, because Bedrock looks for an earlier
|
|
343
|
+
# cache entry only about 20 content blocks back from each point, and
|
|
344
|
+
# a round of parallel tool calls can be longer than that. Points the
|
|
345
|
+
# messages already mark count toward the limit of four, and an
|
|
346
|
+
# automatic point never puts a shorter lifetime before a longer one,
|
|
347
|
+
# which Bedrock refuses.
|
|
348
|
+
def add_automatic_cache_points(payload, model, caching)
|
|
349
|
+
return unless caching && automatic_cache_points?(model)
|
|
350
|
+
|
|
351
|
+
budget = MAX_CACHE_POINTS - cache_points(payload).size
|
|
352
|
+
automatic_cache_targets(payload, model).each do |blocks|
|
|
353
|
+
index = automatic_cache_point_index(blocks)
|
|
354
|
+
next unless index
|
|
355
|
+
break if budget <= 0
|
|
283
356
|
|
|
284
|
-
|
|
357
|
+
blocks.insert(index, converse_cache_block_for(caching))
|
|
358
|
+
next blocks.delete_at(index) unless cache_lifetimes_descend?(payload)
|
|
359
|
+
|
|
360
|
+
budget -= 1
|
|
361
|
+
end
|
|
285
362
|
end
|
|
286
363
|
|
|
287
|
-
|
|
288
|
-
|
|
364
|
+
# Claude refuses a cache point right after an Office document, so the
|
|
365
|
+
# point goes after the last block that takes one.
|
|
366
|
+
def automatic_cache_point_index(blocks)
|
|
367
|
+
last = blocks.rindex { |block| !office_document?(block) }
|
|
368
|
+
last + 1 if last && !cache_point?(blocks[last])
|
|
289
369
|
end
|
|
290
370
|
|
|
291
|
-
def
|
|
292
|
-
|
|
371
|
+
def office_document?(block)
|
|
372
|
+
block.is_a?(Hash) && OFFICE_DOCUMENT_FORMATS.include?(block.dig(:document, :format))
|
|
373
|
+
end
|
|
374
|
+
|
|
375
|
+
def search_results?(model)
|
|
376
|
+
model_id = foundation_model_id(model&.id)
|
|
377
|
+
SEARCH_RESULT_MODELS.any? { |prefix| model_id.start_with?(prefix) }
|
|
378
|
+
end
|
|
379
|
+
|
|
380
|
+
def automatic_cache_points?(model)
|
|
381
|
+
model_id = foundation_model_id(model&.id)
|
|
382
|
+
# An application inference profile ARN does not name its model.
|
|
383
|
+
CACHE_POINT_MODELS.any? { |prefix| model_id.start_with?(prefix) } || !model_id.include?('.')
|
|
384
|
+
end
|
|
385
|
+
|
|
386
|
+
def automatic_cache_targets(payload, model)
|
|
387
|
+
messages = payload[:messages]
|
|
388
|
+
earlier_answer = messages[0...-1].reverse.find { |message| message[:role] == 'assistant' }
|
|
389
|
+
|
|
390
|
+
[messages.last&.dig(:content), stable_prefix_blocks(payload, model), earlier_answer&.dig(:content)].compact
|
|
391
|
+
end
|
|
392
|
+
|
|
393
|
+
def stable_prefix_blocks(payload, model)
|
|
394
|
+
return payload[:system] if payload[:system]
|
|
395
|
+
|
|
396
|
+
tools = payload.dig(:toolConfig, :tools)
|
|
397
|
+
tools if tools && TOOL_CACHE_POINT_MODELS.any? { |prefix| foundation_model_id(model&.id).start_with?(prefix) }
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
# In the order Bedrock reads them: tools, system, then messages.
|
|
401
|
+
def cache_points(payload)
|
|
402
|
+
blocks = Array(payload.dig(:toolConfig, :tools)) + Array(payload[:system]) +
|
|
403
|
+
payload[:messages].flat_map { |message| message[:content] }
|
|
404
|
+
blocks.select { |block| cache_point?(block) }
|
|
405
|
+
end
|
|
406
|
+
|
|
407
|
+
def cache_lifetimes_descend?(payload)
|
|
408
|
+
lifetimes = cache_points(payload).map { |block| cache_lifetime(block[:cachePoint] || block['cachePoint']) }
|
|
409
|
+
lifetimes.each_cons(2).all? { |earlier, later| earlier >= later }
|
|
410
|
+
end
|
|
411
|
+
|
|
412
|
+
def cache_lifetime(point)
|
|
413
|
+
ttl = point.is_a?(Hash) ? (point[:ttl] || point['ttl']) : nil
|
|
414
|
+
amount, unit = ttl.to_s.match(/\A(\d+)([smh])\z/)&.captures
|
|
415
|
+
amount ? amount.to_i * CACHE_LIFETIME_UNITS.fetch(unit) : 300
|
|
416
|
+
end
|
|
417
|
+
|
|
418
|
+
def cache_point?(block)
|
|
419
|
+
block.is_a?(Hash) && (block.key?(:cachePoint) || block.key?('cachePoint'))
|
|
293
420
|
end
|
|
294
421
|
|
|
295
422
|
def converse_cache_block_for(caching, ttl: nil)
|
|
296
423
|
options = prompt_cache_options(caching)
|
|
297
424
|
ttl ||= options[:ttl]
|
|
298
425
|
point = { type: 'default' }
|
|
299
|
-
point[:ttl] = ttl if ttl
|
|
426
|
+
point[:ttl] = Protocol::CacheLifetime.render(ttl) if ttl
|
|
300
427
|
{ cachePoint: point }
|
|
301
428
|
end
|
|
302
429
|
|
|
@@ -311,37 +438,37 @@ module RubyLLM
|
|
|
311
438
|
"Bedrock Converse prompt caching accepts :ttl, got #{format_cache_option_keys(unsupported)}"
|
|
312
439
|
end
|
|
313
440
|
|
|
441
|
+
def cache_option_names = PROMPT_CACHE_OPTIONS
|
|
442
|
+
|
|
314
443
|
def format_cache_option_keys(keys)
|
|
315
444
|
keys.map { |key| ":#{key}" }.join(', ')
|
|
316
445
|
end
|
|
317
446
|
|
|
318
|
-
def
|
|
447
|
+
def render_inference_config(_model, temperature, max_output_tokens = nil)
|
|
319
448
|
config = {}
|
|
320
449
|
config[:temperature] = temperature unless temperature.nil?
|
|
321
450
|
config[:maxTokens] = max_output_tokens unless max_output_tokens.nil?
|
|
322
451
|
config
|
|
323
452
|
end
|
|
324
453
|
|
|
325
|
-
def
|
|
454
|
+
def render_tool_config(tools, tool_prefs)
|
|
326
455
|
return nil if tools.empty?
|
|
327
456
|
|
|
328
457
|
config = {
|
|
329
|
-
tools: tools.values.map { |tool|
|
|
458
|
+
tools: tools.values.map { |tool| render_tool(tool) }
|
|
330
459
|
}
|
|
331
460
|
|
|
332
461
|
return config if tool_prefs.nil? || tool_prefs[:choice].nil?
|
|
333
462
|
|
|
334
|
-
|
|
335
|
-
config[:toolChoice] = tool_choice if tool_choice
|
|
336
|
-
config
|
|
463
|
+
config.merge(toolChoice: render_tool_choice(tool_prefs[:choice]))
|
|
337
464
|
end
|
|
338
465
|
|
|
339
|
-
def
|
|
466
|
+
def render_tool_choice(choice)
|
|
340
467
|
case choice
|
|
341
468
|
when :auto
|
|
342
469
|
{ auto: {} }
|
|
343
470
|
when :none
|
|
344
|
-
|
|
471
|
+
raise ArgumentError, 'Bedrock Converse has no tool choice of none; remove the tools from this request'
|
|
345
472
|
when :required
|
|
346
473
|
{ any: {} }
|
|
347
474
|
else
|
|
@@ -349,7 +476,7 @@ module RubyLLM
|
|
|
349
476
|
end
|
|
350
477
|
end
|
|
351
478
|
|
|
352
|
-
def
|
|
479
|
+
def render_tool(tool)
|
|
353
480
|
input_schema = tool.parameters_schema ||
|
|
354
481
|
RubyLLM::Tool::SchemaDefinition.from_parameters(tool.declared_parameters)&.json_schema
|
|
355
482
|
|
|
@@ -368,15 +495,25 @@ module RubyLLM
|
|
|
368
495
|
RubyLLM::Support::Utils.deep_merge(tool_spec, tool.provider_options)
|
|
369
496
|
end
|
|
370
497
|
|
|
371
|
-
def
|
|
498
|
+
def render_additional_model_request_fields(thinking, model, max_output_tokens = nil)
|
|
372
499
|
fields = {}
|
|
373
500
|
|
|
374
|
-
reasoning_fields = Reasoning.
|
|
501
|
+
reasoning_fields = Reasoning.render_reasoning_fields(thinking, model, max_output_tokens)
|
|
375
502
|
fields = RubyLLM::Support::Utils.deep_merge(fields, reasoning_fields) if reasoning_fields
|
|
376
503
|
|
|
377
504
|
fields.empty? ? nil : fields
|
|
378
505
|
end
|
|
379
506
|
|
|
507
|
+
# Bound from the first request, as on the Anthropic API, so the
|
|
508
|
+
# thinking setting never changes mid-conversation.
|
|
509
|
+
def bind_thinking(fields, model)
|
|
510
|
+
return fields unless Anthropic::BlockBinding.binds?(foundation_model_id(model&.id))
|
|
511
|
+
return fields if fields&.key?(:reasoning_config)
|
|
512
|
+
|
|
513
|
+
bound = Anthropic::BlockBinding.apply(fields&.dig(:thinking))
|
|
514
|
+
bound ? { **fields.to_h, thinking: bound, anthropic_beta: [Anthropic::BlockBinding::BETA] } : fields
|
|
515
|
+
end
|
|
516
|
+
|
|
380
517
|
def build_output_config(schema)
|
|
381
518
|
return nil unless schema
|
|
382
519
|
|
|
@@ -403,7 +540,7 @@ module RubyLLM
|
|
|
403
540
|
model_id.to_s.rpartition('/').last.sub(/\A(?:#{prefixes})\./, '')
|
|
404
541
|
end
|
|
405
542
|
|
|
406
|
-
def
|
|
543
|
+
def render_thinking_block(thinking)
|
|
407
544
|
return nil unless thinking
|
|
408
545
|
|
|
409
546
|
if thinking.text
|
|
@@ -424,7 +561,7 @@ module RubyLLM
|
|
|
424
561
|
end
|
|
425
562
|
end
|
|
426
563
|
|
|
427
|
-
def
|
|
564
|
+
def parse_text_and_citations(content_blocks)
|
|
428
565
|
text = +''
|
|
429
566
|
citations = []
|
|
430
567
|
|
|
@@ -468,6 +605,7 @@ module RubyLLM
|
|
|
468
605
|
text: text,
|
|
469
606
|
start_index: start_index,
|
|
470
607
|
end_index: end_index,
|
|
608
|
+
source_id: data['source'],
|
|
471
609
|
source_index: citation_source_index(location),
|
|
472
610
|
start_page: page['start'],
|
|
473
611
|
end_page: end_page && (end_page - 1)
|
|
@@ -526,22 +664,19 @@ module RubyLLM
|
|
|
526
664
|
end
|
|
527
665
|
|
|
528
666
|
def parse_tool_calls(content_blocks)
|
|
529
|
-
|
|
530
|
-
|
|
531
|
-
content_blocks.each do |block|
|
|
667
|
+
calls = content_blocks.filter_map do |block|
|
|
532
668
|
tool_use = block['toolUse']
|
|
533
669
|
next unless tool_use
|
|
534
670
|
next if server_tool_use?(tool_use)
|
|
535
671
|
|
|
536
|
-
|
|
537
|
-
|
|
538
|
-
id: tool_call_id,
|
|
672
|
+
ToolCall.new(
|
|
673
|
+
provider_call_id: tool_use['toolUseId'],
|
|
539
674
|
name: tool_use['name'],
|
|
540
675
|
arguments: tool_use['input'] || {}
|
|
541
676
|
)
|
|
542
677
|
end
|
|
543
678
|
|
|
544
|
-
|
|
679
|
+
ToolCall.index(calls, by: :provider_call_id) unless calls.empty?
|
|
545
680
|
end
|
|
546
681
|
|
|
547
682
|
# Provider-executed tool steps come back with a distinguishing type,
|
|
@@ -557,7 +692,7 @@ module RubyLLM
|
|
|
557
692
|
!type.nil? && type != 'tool_result'
|
|
558
693
|
end
|
|
559
694
|
|
|
560
|
-
def
|
|
695
|
+
def parse_server_tool_calls(content_blocks)
|
|
561
696
|
content_blocks.filter_map do |block|
|
|
562
697
|
tool_use = block['toolUse']
|
|
563
698
|
tool_result = block['toolResult']
|