ruby_llm 2.1.0 → 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 +162 -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 +522 -277
- 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 +1598 -332
- 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 +67 -28
- 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 +134 -109
- 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 +23 -2
- 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 +2 -9
- 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 +8 -3
- 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 +219 -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
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
class Protocol
|
|
5
|
+
# Turns the NotImplementedError of a missing seam into an Error naming the
|
|
6
|
+
# provider and the operation. It wraps only Protocol's own methods, so a
|
|
7
|
+
# subclass that replaces an operation answers for its own errors.
|
|
8
|
+
module UnsupportedOperations # :nodoc:
|
|
9
|
+
OPERATIONS = {
|
|
10
|
+
tool_approval_response: 'remote tool approvals',
|
|
11
|
+
render: 'chat',
|
|
12
|
+
count_tokens: 'token counting',
|
|
13
|
+
tokenize: 'text tokenization',
|
|
14
|
+
compact: 'manual compaction',
|
|
15
|
+
list_models: 'model listing',
|
|
16
|
+
embed: 'embeddings',
|
|
17
|
+
moderate: 'moderation',
|
|
18
|
+
paint: 'image generation',
|
|
19
|
+
animate_later: 'video generation',
|
|
20
|
+
find_video_job: 'video generation',
|
|
21
|
+
refresh_video_job: 'video generation',
|
|
22
|
+
speak: 'speech generation',
|
|
23
|
+
transcribe: 'transcription',
|
|
24
|
+
ocr: 'OCR',
|
|
25
|
+
rerank: 'reranking',
|
|
26
|
+
judge: 'judgments',
|
|
27
|
+
cache_content: 'explicit content caching',
|
|
28
|
+
find_cache: 'explicit content caching',
|
|
29
|
+
delete_cache: 'explicit content caching',
|
|
30
|
+
extend_cache: 'explicit content caching'
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
OPERATIONS.each do |operation, label|
|
|
34
|
+
define_method(operation) do |*args, **options, &block|
|
|
35
|
+
super(*args, **options, &block)
|
|
36
|
+
rescue NotImplementedError
|
|
37
|
+
raise Error, "#{@provider.name} doesn't support #{label}"
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
data/lib/ruby_llm/protocol.rb
CHANGED
|
@@ -38,6 +38,9 @@ module RubyLLM
|
|
|
38
38
|
include Streaming
|
|
39
39
|
include BinaryStreaming
|
|
40
40
|
include RequestShapes
|
|
41
|
+
include NativeContent
|
|
42
|
+
prepend UnsupportedOperations
|
|
43
|
+
prepend ToolNamespaces
|
|
41
44
|
|
|
42
45
|
# The Provider this protocol talks through.
|
|
43
46
|
attr_reader :provider
|
|
@@ -91,18 +94,37 @@ module RubyLLM
|
|
|
91
94
|
@model = model
|
|
92
95
|
end
|
|
93
96
|
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
97
|
+
# Returns the options of +operation+ (+:upload+, +:paint+, +:transcribe+,
|
|
98
|
+
# or +:embed+) this wire format has no way to carry, as an Array of
|
|
99
|
+
# Symbols. RubyLLM raises ArgumentError when a caller gives one, rather
|
|
100
|
+
# than dropping it. The default is none.
|
|
101
|
+
#
|
|
102
|
+
# def rejected_options(operation)
|
|
103
|
+
# operation == :paint ? %i[count] : super
|
|
104
|
+
# end
|
|
105
|
+
#
|
|
106
|
+
def rejected_options(_operation)
|
|
107
|
+
[]
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def reject_options(operation, **options) # :nodoc:
|
|
111
|
+
rejected = rejected_options(operation) & options.compact.keys
|
|
112
|
+
return if rejected.empty?
|
|
113
|
+
|
|
114
|
+
raise ArgumentError, "#{@provider.name} #{operation} does not accept #{rejected.join(', ')}"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def tool_approval_response(tool_call, approved:, note: nil)
|
|
118
|
+
Message.new(role: :tool, content: approved ? 'Approved' : note || 'Denied', tool_call_id: tool_call.id,
|
|
119
|
+
provider_call_id: tool_call.provider_call_id,
|
|
120
|
+
raw_content: render_tool_approval_response(tool_call, approved:, note:))
|
|
99
121
|
end
|
|
100
122
|
|
|
101
123
|
def complete(messages, tools:, temperature:, provider_options: {}, headers: {}, schema: nil, thinking: nil,
|
|
102
124
|
max_output_tokens: nil, citations: false, caching: nil, tool_prefs: nil, before_request: [],
|
|
103
|
-
usage_recorder: nil, provider_tools: [], compaction: nil, end_user: nil, &)
|
|
125
|
+
usage_recorder: nil, progress_recorder: nil, provider_tools: [], compaction: nil, end_user: nil, &)
|
|
104
126
|
resolution = resolve_provider_tools_for_request(provider_tools)
|
|
105
|
-
headers = resolution.headers
|
|
127
|
+
headers = merge_tool_headers(resolution.headers, headers) if resolution
|
|
106
128
|
headers = apply_compaction_headers(headers, compaction) if compaction
|
|
107
129
|
payload = render(
|
|
108
130
|
messages, tools:, tool_prefs:, temperature:, max_output_tokens:, provider_options:, schema:, thinking:,
|
|
@@ -110,6 +132,10 @@ module RubyLLM
|
|
|
110
132
|
stream: block_given?
|
|
111
133
|
)
|
|
112
134
|
|
|
135
|
+
complete_request(payload, headers, usage_recorder:, progress_recorder:, &)
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def complete_request(payload, headers, usage_recorder: nil, **) # :nodoc:
|
|
113
139
|
# The caller's block can raise the error of another operation it ran.
|
|
114
140
|
yielding = false
|
|
115
141
|
track_usage(:chat, on_finish: usage_recorder) do
|
|
@@ -124,11 +150,23 @@ module RubyLLM
|
|
|
124
150
|
sync_response payload, headers
|
|
125
151
|
end
|
|
126
152
|
end
|
|
153
|
+
rescue Error => e
|
|
154
|
+
raise e if yielding
|
|
155
|
+
|
|
156
|
+
e = unsupported_parameter_error(e)
|
|
157
|
+
claim_error(e, payload)
|
|
158
|
+
raise e
|
|
127
159
|
rescue NotImplementedError
|
|
160
|
+
raise if yielding
|
|
161
|
+
|
|
128
162
|
raise Error, "#{@provider.name} doesn't support chat"
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
# Returns the parameter a rejected request named as one the model does
|
|
166
|
+
# not take, as UnsupportedParameterError#parameter describes, or +nil+.
|
|
167
|
+
# Protocols whose errors say so in structured fields override it.
|
|
168
|
+
def parse_unsupported_parameter(_response)
|
|
169
|
+
nil
|
|
132
170
|
end
|
|
133
171
|
|
|
134
172
|
def render(messages, tools:, temperature:, provider_options: {}, schema: nil, thinking: nil,
|
|
@@ -153,8 +191,35 @@ module RubyLLM
|
|
|
153
191
|
payload = apply_provider_tools(payload, provider_tools)
|
|
154
192
|
payload = apply_tool_search(payload)
|
|
155
193
|
apply_before_request_hooks(payload, before_request)
|
|
156
|
-
|
|
157
|
-
|
|
194
|
+
end
|
|
195
|
+
|
|
196
|
+
# Returns whether the API searches deferred tools itself. Chat gives a
|
|
197
|
+
# model on any other API a +tool_search+ tool of its own instead.
|
|
198
|
+
def tool_search?
|
|
199
|
+
false
|
|
200
|
+
end
|
|
201
|
+
|
|
202
|
+
# Returns +caching+ for a fallback attempt, without the options this
|
|
203
|
+
# wire format cannot express, so a hint meant for the model the caller
|
|
204
|
+
# chose does not stop another provider from answering. An explicit
|
|
205
|
+
# cache +id+ names content the prompt relies on and stays.
|
|
206
|
+
def fallback_caching(caching) # :nodoc:
|
|
207
|
+
names = cache_option_names
|
|
208
|
+
return caching unless names
|
|
209
|
+
|
|
210
|
+
dropped = caching.keys - names - [:id]
|
|
211
|
+
return caching if dropped.empty?
|
|
212
|
+
|
|
213
|
+
RubyLLM.logger.debug do
|
|
214
|
+
"RubyLLM left out #{dropped.map { |name| ":#{name}" }.join(', ')} from caching on a fallback to " \
|
|
215
|
+
"#{@provider.name}, which has no use for it"
|
|
216
|
+
end
|
|
217
|
+
caching.except(*dropped)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
# Returns whether this wire format takes a cache key.
|
|
221
|
+
def cache_key? # :nodoc:
|
|
222
|
+
cache_option_names&.include?(:key) || false
|
|
158
223
|
end
|
|
159
224
|
|
|
160
225
|
# Writes the chat's safety identifier into the request payload and
|
|
@@ -218,8 +283,6 @@ module RubyLLM
|
|
|
218
283
|
caching: caching
|
|
219
284
|
)
|
|
220
285
|
parse_count_tokens_response post_count_tokens(payload)
|
|
221
|
-
rescue NotImplementedError
|
|
222
|
-
raise Error, "#{@provider.name} doesn't support token counting"
|
|
223
286
|
rescue Error => e
|
|
224
287
|
claim_error(e, payload)
|
|
225
288
|
raise
|
|
@@ -234,8 +297,6 @@ module RubyLLM
|
|
|
234
297
|
payload = render_tokenization_payload(text, model:)
|
|
235
298
|
response = @connection.post tokenization_url, payload
|
|
236
299
|
parse_tokenization_response(response, model:)
|
|
237
|
-
rescue NotImplementedError
|
|
238
|
-
raise Error, "#{@provider.name} doesn't support text tokenization"
|
|
239
300
|
end
|
|
240
301
|
|
|
241
302
|
def compact(messages, headers: {}, before_request: [], usage_recorder: nil)
|
|
@@ -246,8 +307,6 @@ module RubyLLM
|
|
|
246
307
|
end
|
|
247
308
|
parse_compaction_response(response)
|
|
248
309
|
end
|
|
249
|
-
rescue NotImplementedError
|
|
250
|
-
raise Error, "#{@provider.name} doesn't support manual compaction"
|
|
251
310
|
rescue Error => e
|
|
252
311
|
claim_error(e, payload)
|
|
253
312
|
raise
|
|
@@ -255,7 +314,7 @@ module RubyLLM
|
|
|
255
314
|
|
|
256
315
|
def embed(text, model:, dimensions:, task_type: nil, title: nil, with: nil, provider_options: {})
|
|
257
316
|
attachments = Attachment.wrap(with, config: @config)
|
|
258
|
-
raise UnsupportedAttachmentError, attachments.first.mime_type if attachments.any? && !
|
|
317
|
+
raise UnsupportedAttachmentError, attachments.first.mime_type if attachments.any? && !embedding_media?
|
|
259
318
|
|
|
260
319
|
track_usage(:embedding) do
|
|
261
320
|
payload = if attachments.any?
|
|
@@ -265,10 +324,8 @@ module RubyLLM
|
|
|
265
324
|
render_embedding_payload(text, model:, dimensions:, task_type:, title:, provider_options:)
|
|
266
325
|
end
|
|
267
326
|
response = @connection.post(embedding_url(model:), payload, usage: @usage_tracker)
|
|
268
|
-
parse_embedding_response(response, model:, text:)
|
|
327
|
+
ensure_embedding_per_text(parse_embedding_response(response, model:, text:), text, attachments, response)
|
|
269
328
|
end
|
|
270
|
-
rescue NotImplementedError
|
|
271
|
-
raise Error, "#{@provider.name} doesn't support embeddings"
|
|
272
329
|
end
|
|
273
330
|
|
|
274
331
|
def render_embedding(text, model:, dimensions: nil, task_type: nil, title: nil, provider_options: {}) # :nodoc:
|
|
@@ -282,8 +339,6 @@ module RubyLLM
|
|
|
282
339
|
response = @connection.post moderation_url, payload, usage: @usage_tracker
|
|
283
340
|
parse_moderation_response(response, model:)
|
|
284
341
|
end
|
|
285
|
-
rescue NotImplementedError
|
|
286
|
-
raise Error, "#{@provider.name} doesn't support moderation"
|
|
287
342
|
end
|
|
288
343
|
|
|
289
344
|
def paint(prompt, model:, size:, count: nil, with: nil, mask: nil, provider_options: {})
|
|
@@ -295,8 +350,6 @@ module RubyLLM
|
|
|
295
350
|
images.each { |image| image.config = @config }
|
|
296
351
|
images.size <= 1 ? images.first : images
|
|
297
352
|
end
|
|
298
|
-
rescue NotImplementedError
|
|
299
|
-
raise Error, "#{@provider.name} doesn't support image generation"
|
|
300
353
|
end
|
|
301
354
|
|
|
302
355
|
def post_image(payload, with:, mask:)
|
|
@@ -329,8 +382,6 @@ module RubyLLM
|
|
|
329
382
|
end
|
|
330
383
|
response = post_video(url, payload)
|
|
331
384
|
parse_video_job(response, model:, **parse_video_request(payload))
|
|
332
|
-
rescue NotImplementedError
|
|
333
|
-
raise Error, "#{@provider.name} doesn't support video generation"
|
|
334
385
|
end
|
|
335
386
|
|
|
336
387
|
# Returns the duration in seconds and the resolution a rendered video
|
|
@@ -387,6 +438,10 @@ module RubyLLM
|
|
|
387
438
|
parse_video_job_status @connection.get(video_job_url(job)), job: job
|
|
388
439
|
end
|
|
389
440
|
|
|
441
|
+
def find_video_job(id, model:)
|
|
442
|
+
VideoJob.new(id:, protocol: self, model:).refresh
|
|
443
|
+
end
|
|
444
|
+
|
|
390
445
|
def speak(input, model:, voice:, format:, provider_options: {}, &block)
|
|
391
446
|
track_usage(:speech) do
|
|
392
447
|
payload = render_speech_payload(input, model:, voice:, format:, provider_options:)
|
|
@@ -395,8 +450,6 @@ module RubyLLM
|
|
|
395
450
|
response = @connection.post speech_url(model:), payload, usage: @usage_tracker
|
|
396
451
|
parse_speech_response(response, model:, voice:, format:)
|
|
397
452
|
end
|
|
398
|
-
rescue NotImplementedError
|
|
399
|
-
raise Error, "#{@provider.name} doesn't support speech generation"
|
|
400
453
|
end
|
|
401
454
|
|
|
402
455
|
def stream_speech(*, **, &)
|
|
@@ -431,8 +484,6 @@ module RubyLLM
|
|
|
431
484
|
response = @connection.post transcription_url, payload, usage: @usage_tracker
|
|
432
485
|
parse_transcription_response(response, model:)
|
|
433
486
|
end
|
|
434
|
-
rescue NotImplementedError
|
|
435
|
-
raise Error, "#{@provider.name} doesn't support transcription"
|
|
436
487
|
end
|
|
437
488
|
|
|
438
489
|
# Streams a transcription, yielding TranscriptionChunk objects and
|
|
@@ -449,7 +500,7 @@ module RubyLLM
|
|
|
449
500
|
# Whether the protocol can embed media attachments alongside text.
|
|
450
501
|
# Protocols that support multimodal embeddings override this and accept
|
|
451
502
|
# a +with:+ array of Attachments in render_embedding_payload.
|
|
452
|
-
def
|
|
503
|
+
def embedding_media?
|
|
453
504
|
false
|
|
454
505
|
end
|
|
455
506
|
|
|
@@ -459,8 +510,6 @@ module RubyLLM
|
|
|
459
510
|
response = @connection.post ocr_url, payload, usage: @usage_tracker
|
|
460
511
|
parse_ocr_response(response, model:)
|
|
461
512
|
end
|
|
462
|
-
rescue NotImplementedError
|
|
463
|
-
raise Error, "#{@provider.name} doesn't support OCR"
|
|
464
513
|
end
|
|
465
514
|
|
|
466
515
|
def rerank(query, documents, model:, top_n: nil, provider_options: {})
|
|
@@ -469,8 +518,6 @@ module RubyLLM
|
|
|
469
518
|
response = @connection.post rerank_url, payload, usage: @usage_tracker
|
|
470
519
|
parse_rerank_response(response, model:, documents:)
|
|
471
520
|
end
|
|
472
|
-
rescue NotImplementedError
|
|
473
|
-
raise Error, "#{@provider.name} doesn't support reranking"
|
|
474
521
|
end
|
|
475
522
|
|
|
476
523
|
def cache_content(content, model:, ttl: nil, instructions: nil, with: nil)
|
|
@@ -478,8 +525,6 @@ module RubyLLM
|
|
|
478
525
|
attachments: Attachment.wrap(with, config: @config))
|
|
479
526
|
response = @connection.post caches_url, payload, idempotent: false
|
|
480
527
|
parse_cache_response(response.body)
|
|
481
|
-
rescue NotImplementedError
|
|
482
|
-
raise Error, "#{@provider.name} doesn't support explicit content caching"
|
|
483
528
|
end
|
|
484
529
|
|
|
485
530
|
def judge(input, questions:, model:, with: [], provider_options: {}) # :nodoc:
|
|
@@ -488,35 +533,35 @@ module RubyLLM
|
|
|
488
533
|
response = @connection.post judgment_url, payload, usage: @usage_tracker
|
|
489
534
|
parse_judgment_response(response, questions:)
|
|
490
535
|
end
|
|
491
|
-
rescue NotImplementedError
|
|
492
|
-
raise Error, "#{@provider.name} doesn't support judgments"
|
|
493
536
|
end
|
|
494
537
|
|
|
495
538
|
def find_cache(name)
|
|
496
539
|
response = @connection.get cache_url(name)
|
|
497
540
|
parse_cache_response(response.body)
|
|
498
|
-
rescue NotImplementedError
|
|
499
|
-
raise Error, "#{@provider.name} doesn't support explicit content caching"
|
|
500
541
|
end
|
|
501
542
|
|
|
502
543
|
def delete_cache(name)
|
|
503
544
|
@connection.delete cache_url(name)
|
|
504
545
|
true
|
|
505
|
-
rescue NotImplementedError
|
|
506
|
-
raise Error, "#{@provider.name} doesn't support explicit content caching"
|
|
507
546
|
end
|
|
508
547
|
|
|
509
548
|
def extend_cache(name, ttl:)
|
|
510
549
|
response = @connection.patch cache_url(name), render_cache_update_payload(ttl:)
|
|
511
550
|
parse_cache_response(response.body)
|
|
512
|
-
rescue NotImplementedError
|
|
513
|
-
raise Error, "#{@provider.name} doesn't support explicit content caching"
|
|
514
551
|
end
|
|
515
552
|
|
|
516
553
|
def parse_error(response)
|
|
517
554
|
@provider.parse_error(response)
|
|
518
555
|
end
|
|
519
556
|
|
|
557
|
+
def slug # :nodoc:
|
|
558
|
+
@provider.slug
|
|
559
|
+
end
|
|
560
|
+
|
|
561
|
+
def parse_error_code(response) # :nodoc:
|
|
562
|
+
@provider.parse_error_code(response)
|
|
563
|
+
end
|
|
564
|
+
|
|
520
565
|
# Returns a RequestShape describing +payload+, a conversation request
|
|
521
566
|
# this protocol rendered, as JSON parsed back with String keys.
|
|
522
567
|
# Error#request_shape asks for it when the request fails. The default
|
|
@@ -577,68 +622,29 @@ module RubyLLM
|
|
|
577
622
|
|
|
578
623
|
private
|
|
579
624
|
|
|
580
|
-
#
|
|
581
|
-
|
|
582
|
-
|
|
583
|
-
|
|
584
|
-
error.request_protocol = self
|
|
585
|
-
error.request_payload = payload
|
|
586
|
-
end
|
|
587
|
-
|
|
588
|
-
# A thinking signature or a provider-shaped content block belongs to the
|
|
589
|
-
# model that produced it, and one provider can serve models that do not
|
|
590
|
-
# read each other's, so a message another model produced replays
|
|
591
|
-
# without them. A message with no known producer replays as it is.
|
|
592
|
-
def foreign_native_content?(message)
|
|
593
|
-
return false unless message.role == :assistant && carries_native_content?(message)
|
|
594
|
-
|
|
595
|
-
producer = producing_entry(message)
|
|
596
|
-
!producer.nil? && (producer.provider != @provider.slug || !current_model?(producer.model))
|
|
597
|
-
end
|
|
598
|
-
|
|
599
|
-
# Usage records the id a chat resolved its model to, and a message built
|
|
600
|
-
# by hand may name the model by an alias of it.
|
|
601
|
-
def current_model?(model_id)
|
|
602
|
-
return true if @model.nil? || model_id == @model.id
|
|
603
|
-
|
|
604
|
-
RubyLLM.models.find(model_id, provider: @provider.slug, config: @config).id == @model.id
|
|
605
|
-
rescue ModelNotFoundError
|
|
606
|
-
false
|
|
607
|
-
end
|
|
608
|
-
|
|
609
|
-
# Another model of this provider gets the raw content any of its models
|
|
610
|
-
# reads, such as a tool search, and none of the producing model's own.
|
|
611
|
-
def without_foreign_native_content(message)
|
|
612
|
-
portable = portable_raw_content(message) if producer_slug(message) == @provider.slug
|
|
613
|
-
message.without_native_content(raw_content: portable)
|
|
614
|
-
end
|
|
615
|
-
|
|
616
|
-
def portable_raw_content(_message)
|
|
625
|
+
# The with_caching options this wire format can express, or +nil+ when
|
|
626
|
+
# it ignores the ones it has no use for.
|
|
627
|
+
def cache_option_names
|
|
617
628
|
nil
|
|
618
629
|
end
|
|
619
630
|
|
|
620
|
-
def
|
|
621
|
-
return
|
|
631
|
+
def unsupported_parameter_error(error)
|
|
632
|
+
return error unless error.instance_of?(BadRequestError) && error.response
|
|
622
633
|
|
|
623
|
-
|
|
624
|
-
|
|
634
|
+
parameter = parse_unsupported_parameter(error.response)
|
|
635
|
+
return error unless parameter
|
|
625
636
|
|
|
626
|
-
|
|
627
|
-
|
|
628
|
-
|
|
629
|
-
producing_entry(message)&.provider
|
|
637
|
+
UnsupportedParameterError.new(nil, parameter:, response: error.response, provider: error.provider,
|
|
638
|
+
provider_message: error.provider_message, error_code: error.error_code,
|
|
639
|
+
redact: @provider.config&.redact_error_messages)
|
|
630
640
|
end
|
|
631
641
|
|
|
632
|
-
|
|
633
|
-
|
|
634
|
-
|
|
642
|
+
# An error raised before the request went out answers no request.
|
|
643
|
+
def claim_error(error, payload)
|
|
644
|
+
return if error.request_protocol || error.response.nil? || payload.nil?
|
|
635
645
|
|
|
636
|
-
|
|
637
|
-
|
|
638
|
-
# protocol that verifies signatures gets back only its own.
|
|
639
|
-
def own_signature(message)
|
|
640
|
-
signature = message.thinking&.signature
|
|
641
|
-
signature if signature && producer_slug(message) == @provider.slug
|
|
646
|
+
error.request_protocol = self
|
|
647
|
+
error.request_payload = payload
|
|
642
648
|
end
|
|
643
649
|
|
|
644
650
|
def resolve_provider_tools_for_request(entries)
|
|
@@ -654,6 +660,10 @@ module RubyLLM
|
|
|
654
660
|
RubyLLM::Tools::ProviderTools.resolve(entries, aliases: aliases, owner: @provider.name)
|
|
655
661
|
end
|
|
656
662
|
|
|
663
|
+
def merge_tool_headers(tool_headers, headers)
|
|
664
|
+
tool_headers.merge(headers)
|
|
665
|
+
end
|
|
666
|
+
|
|
657
667
|
def apply_provider_tools(payload, entries)
|
|
658
668
|
resolution = resolve_provider_tools_for_request(entries)
|
|
659
669
|
return payload unless resolution
|
|
@@ -673,6 +683,13 @@ module RubyLLM
|
|
|
673
683
|
payload
|
|
674
684
|
end
|
|
675
685
|
|
|
686
|
+
def ensure_embedding_per_text(embedding, text, attachments, response)
|
|
687
|
+
count = Array(embedding.vectors).size
|
|
688
|
+
return embedding if attachments.any? || !text.is_a?(Array) || count == text.size
|
|
689
|
+
|
|
690
|
+
raise Error.new("Expected #{text.size} embeddings, got #{count}", response:)
|
|
691
|
+
end
|
|
692
|
+
|
|
676
693
|
def track_usage(operation, on_finish: nil, model_id: nil)
|
|
677
694
|
@usage_tracker = Accounting::Usage::Tracker.new(
|
|
678
695
|
operation:,
|
|
@@ -689,19 +706,22 @@ module RubyLLM
|
|
|
689
706
|
@usage_tracker.fail_pending(e)
|
|
690
707
|
raise
|
|
691
708
|
ensure
|
|
709
|
+
@usage_tracker.cancel_pending
|
|
692
710
|
@usage_tracker = nil
|
|
693
711
|
end
|
|
694
712
|
|
|
695
713
|
def apply_before_request_hooks(payload, hooks)
|
|
696
|
-
|
|
697
|
-
payload
|
|
714
|
+
hooks = Array(hooks)
|
|
715
|
+
return payload if hooks.empty?
|
|
716
|
+
|
|
717
|
+
Support::Utils.deep_dup(payload).tap { |copy| hooks.each { |hook| hook.call(copy) } }
|
|
698
718
|
end
|
|
699
719
|
|
|
700
720
|
def auto_upload_large_files?
|
|
701
|
-
@config.auto_upload_large_files && @provider.files? &&
|
|
721
|
+
@config.auto_upload_large_files && @provider.files? && provider_file_references?
|
|
702
722
|
end
|
|
703
723
|
|
|
704
|
-
def
|
|
724
|
+
def provider_file_references?
|
|
705
725
|
false
|
|
706
726
|
end
|
|
707
727
|
|
|
@@ -709,7 +729,8 @@ module RubyLLM
|
|
|
709
729
|
return attachment if attachment.provider_file?
|
|
710
730
|
return attachment unless upload_large_attachment?(attachment)
|
|
711
731
|
|
|
712
|
-
Attachment.new(provider_upload(attachment), resolution: attachment.resolution,
|
|
732
|
+
Attachment.new(provider_upload(attachment), resolution: attachment.resolution,
|
|
733
|
+
processing: attachment.processing, config: @config)
|
|
713
734
|
end
|
|
714
735
|
|
|
715
736
|
def supported_message_attachment?(_message, attachment)
|
|
@@ -753,12 +774,16 @@ module RubyLLM
|
|
|
753
774
|
end
|
|
754
775
|
|
|
755
776
|
def missing_uploads(messages, error, scope)
|
|
756
|
-
uploads = messages
|
|
777
|
+
uploads = attachment_uploads(messages, scope)
|
|
778
|
+
named = uploads.select { |_, upload| names_file?(error, upload.id) }
|
|
779
|
+
named.empty? && error.response&.status == 404 ? uploads : named
|
|
780
|
+
end
|
|
781
|
+
|
|
782
|
+
def attachment_uploads(messages, scope)
|
|
783
|
+
messages.flat_map(&:attachments).uniq(&:object_id).filter_map do |attachment|
|
|
757
784
|
upload = attachment.provider_uploads[scope]
|
|
758
785
|
[attachment, upload] if upload
|
|
759
786
|
end
|
|
760
|
-
named = uploads.select { |_, upload| names_file?(error, upload.id) }
|
|
761
|
-
named.empty? && error.response&.status == 404 ? uploads : named
|
|
762
787
|
end
|
|
763
788
|
|
|
764
789
|
def names_file?(error, id)
|
|
@@ -9,8 +9,11 @@ module RubyLLM
|
|
|
9
9
|
include RubyLLM::Batch::Helpers
|
|
10
10
|
|
|
11
11
|
def create_batch(requests)
|
|
12
|
+
headers = batch_betas(requests)
|
|
12
13
|
requests = requests.map { |request| { custom_id: request[:custom_id], params: request[:payload] } }
|
|
13
|
-
response = @connection.post
|
|
14
|
+
response = @connection.post(batches_url, { requests: requests }, idempotent: false) do |request|
|
|
15
|
+
request.headers.merge!(headers)
|
|
16
|
+
end
|
|
14
17
|
parse_batch_response response.body
|
|
15
18
|
end
|
|
16
19
|
|
|
@@ -31,6 +34,23 @@ module RubyLLM
|
|
|
31
34
|
|
|
32
35
|
private
|
|
33
36
|
|
|
37
|
+
# One request carries the whole batch, so it takes every beta any
|
|
38
|
+
# of its entries needs.
|
|
39
|
+
def batch_betas(requests)
|
|
40
|
+
requests.reduce({}) do |headers, request|
|
|
41
|
+
payload = request[:payload]
|
|
42
|
+
headers = beta_headers(with_request_betas(headers, request), payload)
|
|
43
|
+
headers = apply_mcp_beta(headers) if block_value(payload, :mcp_servers)
|
|
44
|
+
compaction = block_value(payload, :context_management)
|
|
45
|
+
compaction ? apply_compaction_headers(headers, compaction) : headers
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def with_request_betas(headers, request)
|
|
50
|
+
betas = canonical_beta_headers(request[:headers].to_h)[Chat::BETA_HEADER]
|
|
51
|
+
betas ? add_beta(headers, betas) : headers
|
|
52
|
+
end
|
|
53
|
+
|
|
34
54
|
def batches_url
|
|
35
55
|
'v1/messages/batches'
|
|
36
56
|
end
|
|
@@ -41,18 +61,35 @@ module RubyLLM
|
|
|
41
61
|
|
|
42
62
|
def parse_batch_response(data)
|
|
43
63
|
request_counts = data['request_counts']
|
|
64
|
+
completed = data['processing_status'] == 'ended'
|
|
44
65
|
|
|
45
66
|
{
|
|
46
67
|
id: data['id'],
|
|
47
|
-
raw_status: data['processing_status'],
|
|
48
|
-
completed
|
|
68
|
+
raw_status: completed ? ended_batch_status(request_counts) : data['processing_status'],
|
|
69
|
+
completed:,
|
|
49
70
|
request_counts:,
|
|
50
71
|
request_count: request_counts&.values&.sum
|
|
51
72
|
}
|
|
52
73
|
end
|
|
53
74
|
|
|
75
|
+
# Anthropic ends every batch as "ended"; only the request counts say
|
|
76
|
+
# whether it was cancelled or ran out of time. A batch with any
|
|
77
|
+
# success ended, and its cancelled or expired slots fail on their own.
|
|
78
|
+
def ended_batch_status(request_counts)
|
|
79
|
+
return 'ended' if request_counts&.fetch('succeeded', 0).to_i.positive?
|
|
80
|
+
|
|
81
|
+
%w[canceled expired].find { |outcome| request_counts&.fetch(outcome, 0).to_i.positive? } || 'ended'
|
|
82
|
+
end
|
|
83
|
+
|
|
54
84
|
def parse_batch_status(raw_status, completed:)
|
|
55
|
-
|
|
85
|
+
return :pending unless completed
|
|
86
|
+
|
|
87
|
+
case raw_status
|
|
88
|
+
when 'ended' then :succeeded
|
|
89
|
+
when 'canceled' then :cancelled
|
|
90
|
+
when 'expired' then :failed
|
|
91
|
+
else :pending
|
|
92
|
+
end
|
|
56
93
|
end
|
|
57
94
|
|
|
58
95
|
def parse_batch_result(line)
|
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Protocols
|
|
5
|
+
class Anthropic
|
|
6
|
+
# These Claude models bind a signed thinking block to the instructions,
|
|
7
|
+
# tools, and messages it was written under, and refuse it once they
|
|
8
|
+
# change. Dropping the block lets the model think that turn again.
|
|
9
|
+
module BlockBinding # :nodoc:
|
|
10
|
+
BETA = 'thinking-binding-controls-2026-08-01'
|
|
11
|
+
MODELS = /claude-(?:(?:opus|sonnet|haiku)-5-5|fable-5-1)/
|
|
12
|
+
DROP_STALE_BLOCKS = { prefix_mismatch_behavior: 'drop_block' }.freeze
|
|
13
|
+
|
|
14
|
+
module_function
|
|
15
|
+
|
|
16
|
+
def binds?(model_id)
|
|
17
|
+
MODELS.match?(model_id.to_s)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
# Returns +thinking+ with the binding added, or +nil+ when thinking
|
|
21
|
+
# is set to anything but adaptive, which the binding would replace.
|
|
22
|
+
def apply(thinking)
|
|
23
|
+
return unless thinking.nil? || thinking[:type] == 'adaptive'
|
|
24
|
+
|
|
25
|
+
{ type: 'adaptive', **thinking.to_h, block_binding: DROP_STALE_BLOCKS }
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
30
|
+
end
|