ruby_llm 1.16.0 → 2.0.0.rc2
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/.rdoc_options +25 -0
- data/README.md +85 -32
- data/exe/ruby_llm +8 -0
- data/lib/generators/ruby_llm/agent/templates/agent.rb.tt +0 -1
- data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +3 -43
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +11 -3
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +8 -0
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +4 -4
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +2 -2
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +1 -1
- 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.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +6 -4
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +2 -2
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt +5 -6
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt +2 -2
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt +5 -5
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +2 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +1 -1
- 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.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +6 -4
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_user.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +4 -6
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +2 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +5 -6
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +2 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +3 -3
- data/lib/generators/ruby_llm/generator_helpers.rb +106 -62
- data/lib/generators/ruby_llm/install/install_generator.rb +3 -11
- data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +2 -0
- data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +14 -8
- data/lib/generators/ruby_llm/install/templates/create_ruby_llm_records_migration.rb.tt +117 -0
- data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +0 -8
- data/lib/generators/ruby_llm/provider/cli.rb +175 -0
- data/lib/generators/ruby_llm/provider/scaffold.rb +323 -0
- data/lib/generators/ruby_llm/provider/templates/core/provider.rb.erb +37 -0
- data/lib/generators/ruby_llm/provider/templates/core/provider_spec.rb.erb +34 -0
- data/lib/generators/ruby_llm/provider/templates/gem/archspec.rb.erb +14 -0
- data/lib/generators/ruby_llm/provider/templates/gem/bin/console.erb +15 -0
- data/lib/generators/ruby_llm/provider/templates/gem/bin/setup.erb +5 -0
- data/lib/generators/ruby_llm/provider/templates/gem/chat_schema_spec.rb.erb +30 -0
- data/lib/generators/ruby_llm/provider/templates/gem/chat_spec.rb.erb +35 -0
- data/lib/generators/ruby_llm/provider/templates/gem/chat_streaming_spec.rb.erb +22 -0
- data/lib/generators/ruby_llm/provider/templates/gem/chat_tools_spec.rb.erb +30 -0
- data/lib/generators/ruby_llm/provider/templates/gem/ci.yml.erb +32 -0
- data/lib/generators/ruby_llm/provider/templates/gem/embedding_spec.rb.erb +49 -0
- data/lib/generators/ruby_llm/provider/templates/gem/env.erb +2 -0
- data/lib/generators/ruby_llm/provider/templates/gem/fixtures_gitkeep.erb +1 -0
- data/lib/generators/ruby_llm/provider/templates/gem/flayignore.erb +1 -0
- data/lib/generators/ruby_llm/provider/templates/gem/gemfile.erb +23 -0
- data/lib/generators/ruby_llm/provider/templates/gem/gemspec.erb +28 -0
- data/lib/generators/ruby_llm/provider/templates/gem/gitignore.erb +7 -0
- data/lib/generators/ruby_llm/provider/templates/gem/gitleaks.yml.erb +22 -0
- data/lib/generators/ruby_llm/provider/templates/gem/image_spec.rb.erb +23 -0
- data/lib/generators/ruby_llm/provider/templates/gem/license.erb +21 -0
- data/lib/generators/ruby_llm/provider/templates/gem/models.rb.erb +19 -0
- data/lib/generators/ruby_llm/provider/templates/gem/models_spec.rb.erb +17 -0
- data/lib/generators/ruby_llm/provider/templates/gem/moderation_spec.rb.erb +22 -0
- data/lib/generators/ruby_llm/provider/templates/gem/overcommit.yml.erb +31 -0
- data/lib/generators/ruby_llm/provider/templates/gem/provider.rb.erb +52 -0
- data/lib/generators/ruby_llm/provider/templates/gem/provider_spec.rb.erb +38 -0
- data/lib/generators/ruby_llm/provider/templates/gem/rakefile.erb +38 -0
- data/lib/generators/ruby_llm/provider/templates/gem/readme.md.erb +50 -0
- data/lib/generators/ruby_llm/provider/templates/gem/release.yml.erb +36 -0
- data/lib/generators/ruby_llm/provider/templates/gem/rerank_spec.rb.erb +23 -0
- data/lib/generators/ruby_llm/provider/templates/gem/rspec.erb +2 -0
- data/lib/generators/ruby_llm/provider/templates/gem/rubocop.yml.erb +29 -0
- data/lib/generators/ruby_llm/provider/templates/gem/rubyllm_configuration.rb.erb +14 -0
- data/lib/generators/ruby_llm/provider/templates/gem/spec_helper.rb.erb +26 -0
- data/lib/generators/ruby_llm/provider/templates/gem/speech_spec.rb.erb +25 -0
- data/lib/generators/ruby_llm/provider/templates/gem/vcr_configuration.rb.erb +16 -0
- data/lib/generators/ruby_llm/provider/templates/gem/video_spec.rb.erb +27 -0
- data/lib/generators/ruby_llm/schema/schema_generator.rb +5 -1
- data/lib/generators/ruby_llm/schema/templates/schema.rb.tt +1 -1
- data/lib/generators/ruby_llm/tool/templates/tailwind/tool_call.html.erb.tt +13 -0
- data/lib/generators/ruby_llm/tool/templates/tailwind/tool_result.html.erb.tt +21 -0
- data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +3 -3
- data/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt +7 -12
- data/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt +5 -2
- data/lib/generators/ruby_llm/tool/tool_generator.rb +25 -59
- data/lib/generators/ruby_llm/upgrade/templates/backfill_v2_data.rb.tt +461 -0
- data/lib/generators/ruby_llm/upgrade/templates/cleanup_v2_upgrade.rb.tt +101 -0
- data/lib/generators/ruby_llm/upgrade/templates/finish_v2_upgrade.rb.tt +215 -0
- data/lib/generators/ruby_llm/upgrade/templates/prepare_v2_upgrade.rb.tt +660 -0
- data/lib/generators/ruby_llm/upgrade/templates/ruby_llm_upgrade.rb.tt +222 -0
- data/lib/generators/ruby_llm/upgrade/templates/upgrade_initializer.rb.tt +13 -0
- data/lib/generators/ruby_llm/upgrade/upgrade_generator.rb +167 -0
- data/lib/generators/ruby_llm/upgrade/upgrade_migration.rb +344 -0
- data/lib/ruby_llm/accounting/usage.rb +245 -0
- data/lib/ruby_llm/active_record/acts_as.rb +94 -111
- data/lib/ruby_llm/active_record/attachment_helpers.rb +186 -0
- data/lib/ruby_llm/active_record/batch.rb +97 -0
- data/lib/ruby_llm/active_record/chat_methods.rb +828 -305
- data/lib/ruby_llm/active_record/message_methods.rb +113 -136
- data/lib/ruby_llm/active_record/model.rb +135 -0
- data/lib/ruby_llm/active_record/payload_helpers.rb +1 -2
- data/lib/ruby_llm/active_record/tool_call.rb +33 -0
- data/lib/ruby_llm/active_record/usage.rb +61 -0
- data/lib/ruby_llm/agent.rb +1065 -151
- data/lib/ruby_llm/aliases.json +268 -100
- data/lib/ruby_llm/attachment.rb +187 -48
- data/lib/ruby_llm/batch.rb +432 -0
- data/lib/ruby_llm/cached_content.rb +112 -0
- data/lib/ruby_llm/chat/tool_concurrency.rb +111 -0
- data/lib/ruby_llm/chat.rb +1127 -198
- data/lib/ruby_llm/chunk.rb +10 -0
- data/lib/ruby_llm/citation.rb +105 -0
- data/lib/ruby_llm/configuration.rb +261 -24
- data/lib/ruby_llm/context.rb +128 -6
- data/lib/ruby_llm/cost.rb +217 -80
- data/lib/ruby_llm/downloaded_file.rb +33 -0
- data/lib/ruby_llm/embedding.rb +121 -17
- data/lib/ruby_llm/embedding_request.rb +53 -0
- data/lib/ruby_llm/error.rb +159 -23
- data/lib/ruby_llm/fallback.rb +133 -0
- data/lib/ruby_llm/files/mime_type.rb +97 -0
- data/lib/ruby_llm/image.rb +153 -32
- data/lib/ruby_llm/message.rb +233 -54
- data/lib/ruby_llm/model/modalities.rb +17 -4
- data/lib/ruby_llm/model/pricing.rb +24 -5
- data/lib/ruby_llm/model/pricing_category.rb +103 -14
- data/lib/ruby_llm/model/pricing_tier.rb +55 -15
- data/lib/ruby_llm/model.rb +244 -2
- data/lib/ruby_llm/models/aliases.rb +41 -0
- data/lib/ruby_llm/models/registry.rb +165 -0
- data/lib/ruby_llm/models/schema.rb +99 -0
- data/lib/ruby_llm/models.json +73261 -33733
- data/lib/ruby_llm/models.rb +477 -215
- data/lib/ruby_llm/moderation.rb +139 -26
- data/lib/ruby_llm/ocr.rb +112 -0
- data/lib/ruby_llm/prompt.rb +79 -0
- data/lib/ruby_llm/protocol/binary_streaming.rb +65 -0
- data/lib/ruby_llm/protocol/stream_accumulator.rb +214 -0
- data/lib/ruby_llm/protocol/streaming.rb +230 -0
- data/lib/ruby_llm/protocol.rb +662 -0
- data/lib/ruby_llm/protocols/anthropic/batches.rb +73 -0
- data/lib/ruby_llm/protocols/anthropic/chat.rb +548 -0
- data/lib/ruby_llm/protocols/anthropic/embeddings.rb +14 -0
- data/lib/ruby_llm/protocols/anthropic/files.rb +38 -0
- data/lib/ruby_llm/protocols/anthropic/media.rb +141 -0
- data/lib/ruby_llm/protocols/anthropic/models.rb +129 -0
- data/lib/ruby_llm/protocols/anthropic/streaming.rb +167 -0
- data/lib/ruby_llm/{providers → protocols}/anthropic/tools.rb +24 -41
- data/lib/ruby_llm/protocols/anthropic.rb +101 -0
- data/lib/ruby_llm/protocols/azure/files.rb +16 -0
- data/lib/ruby_llm/protocols/bedrock/async_videos.rb +109 -0
- data/lib/ruby_llm/protocols/bedrock/batches.rb +129 -0
- data/lib/ruby_llm/protocols/bedrock/files.rb +110 -0
- data/lib/ruby_llm/protocols/bedrock/guardrails.rb +122 -0
- data/lib/ruby_llm/protocols/bedrock/rerank.rb +95 -0
- data/lib/ruby_llm/protocols/chat_completions/batches.rb +32 -0
- data/lib/ruby_llm/protocols/chat_completions/chat.rb +493 -0
- data/lib/ruby_llm/protocols/chat_completions/embedding_batches.rb +35 -0
- data/lib/ruby_llm/protocols/chat_completions/embeddings.rb +60 -0
- data/lib/ruby_llm/protocols/chat_completions/images.rb +127 -0
- data/lib/ruby_llm/{providers/openai → protocols/chat_completions}/media.rb +30 -17
- data/lib/ruby_llm/protocols/chat_completions/models.rb +39 -0
- data/lib/ruby_llm/protocols/chat_completions/moderation.rb +52 -0
- data/lib/ruby_llm/protocols/chat_completions/rerank.rb +56 -0
- data/lib/ruby_llm/protocols/chat_completions/speech.rb +40 -0
- data/lib/ruby_llm/protocols/chat_completions/streaming.rb +69 -0
- data/lib/ruby_llm/{providers/openai → protocols/chat_completions}/tools.rb +15 -17
- data/lib/ruby_llm/protocols/chat_completions/transcription.rb +150 -0
- data/lib/ruby_llm/protocols/chat_completions.rb +21 -0
- data/lib/ruby_llm/protocols/cohere/batch_requests.rb +75 -0
- data/lib/ruby_llm/protocols/cohere/batches.rb +98 -0
- data/lib/ruby_llm/protocols/cohere/chat.rb +227 -0
- data/lib/ruby_llm/protocols/cohere/datasets.rb +102 -0
- data/lib/ruby_llm/protocols/cohere/embeddings.rb +68 -0
- data/lib/ruby_llm/protocols/cohere/media.rb +77 -0
- data/lib/ruby_llm/protocols/cohere/models.rb +92 -0
- data/lib/ruby_llm/protocols/cohere/ocr.rb +63 -0
- data/lib/ruby_llm/protocols/cohere/rerank.rb +52 -0
- data/lib/ruby_llm/protocols/cohere/streaming.rb +105 -0
- data/lib/ruby_llm/protocols/cohere/tokenization.rb +22 -0
- data/lib/ruby_llm/protocols/cohere/tools.rb +132 -0
- data/lib/ruby_llm/protocols/cohere/transcription.rb +41 -0
- data/lib/ruby_llm/protocols/cohere.rb +21 -0
- data/lib/ruby_llm/protocols/converse/batches.rb +55 -0
- data/lib/ruby_llm/protocols/converse/chat.rb +695 -0
- data/lib/ruby_llm/protocols/converse/media.rb +178 -0
- data/lib/ruby_llm/protocols/converse/streaming.rb +432 -0
- data/lib/ruby_llm/protocols/converse/thinking_stream.rb +56 -0
- data/lib/ruby_llm/protocols/converse.rb +54 -0
- data/lib/ruby_llm/protocols/deepgram/models.rb +74 -0
- data/lib/ruby_llm/protocols/deepgram/speech.rb +93 -0
- data/lib/ruby_llm/protocols/deepgram/streaming_transcription.rb +89 -0
- data/lib/ruby_llm/protocols/deepgram/transcription.rb +96 -0
- data/lib/ruby_llm/protocols/deepgram.rb +19 -0
- data/lib/ruby_llm/protocols/deepseek/files.rb +31 -0
- data/lib/ruby_llm/protocols/elevenlabs/assets.rb +36 -0
- data/lib/ruby_llm/protocols/elevenlabs/flows/images.rb +74 -0
- data/lib/ruby_llm/protocols/elevenlabs/flows/media.rb +42 -0
- data/lib/ruby_llm/protocols/elevenlabs/flows/videos.rb +93 -0
- data/lib/ruby_llm/protocols/elevenlabs/flows.rb +14 -0
- data/lib/ruby_llm/protocols/elevenlabs/models.rb +64 -0
- data/lib/ruby_llm/protocols/elevenlabs/speech.rb +67 -0
- data/lib/ruby_llm/protocols/elevenlabs/streaming_transcription.rb +127 -0
- data/lib/ruby_llm/protocols/elevenlabs/transcription.rb +61 -0
- data/lib/ruby_llm/protocols/elevenlabs.rb +15 -0
- data/lib/ruby_llm/protocols/files.rb +119 -0
- data/lib/ruby_llm/protocols/gemini/batches.rb +162 -0
- data/lib/ruby_llm/protocols/gemini/caches.rb +59 -0
- data/lib/ruby_llm/protocols/gemini/chat.rb +453 -0
- data/lib/ruby_llm/protocols/gemini/embedding_batches.rb +86 -0
- data/lib/ruby_llm/protocols/gemini/embeddings.rb +70 -0
- data/lib/ruby_llm/protocols/gemini/file_transcription.rb +32 -0
- data/lib/ruby_llm/protocols/gemini/files.rb +115 -0
- data/lib/ruby_llm/protocols/gemini/images.rb +183 -0
- data/lib/ruby_llm/protocols/gemini/live_transcription.rb +140 -0
- data/lib/ruby_llm/{providers → protocols}/gemini/media.rb +17 -11
- data/lib/ruby_llm/protocols/gemini/models.rb +71 -0
- data/lib/ruby_llm/protocols/gemini/speech.rb +56 -0
- data/lib/ruby_llm/protocols/gemini/streaming.rb +96 -0
- data/lib/ruby_llm/protocols/gemini/tools.rb +157 -0
- data/lib/ruby_llm/{providers → protocols}/gemini/transcription.rb +22 -22
- data/lib/ruby_llm/protocols/gemini/videos.rb +103 -0
- data/lib/ruby_llm/protocols/gemini.rb +35 -0
- data/lib/ruby_llm/protocols/gpustack/responses.rb +111 -0
- data/lib/ruby_llm/protocols/gpustack/tokenization.rb +22 -0
- data/lib/ruby_llm/protocols/gpustack/videos.rb +96 -0
- data/lib/ruby_llm/protocols/interactions/chat.rb +145 -0
- data/lib/ruby_llm/protocols/interactions/content.rb +90 -0
- data/lib/ruby_llm/protocols/interactions/streaming.rb +91 -0
- data/lib/ruby_llm/protocols/interactions/tools.rb +51 -0
- data/lib/ruby_llm/protocols/interactions/transcription.rb +58 -0
- data/lib/ruby_llm/protocols/interactions.rb +29 -0
- data/lib/ruby_llm/protocols/invoke_model/cohere_embeddings.rb +51 -0
- data/lib/ruby_llm/protocols/invoke_model/embedding_batches.rb +111 -0
- data/lib/ruby_llm/protocols/invoke_model/nova_embeddings.rb +50 -0
- data/lib/ruby_llm/protocols/invoke_model/stability_images.rb +103 -0
- data/lib/ruby_llm/protocols/invoke_model/titan_multimodal_embeddings.rb +33 -0
- data/lib/ruby_llm/protocols/invoke_model/titan_text_embeddings.rb +44 -0
- data/lib/ruby_llm/protocols/invoke_model.rb +57 -0
- data/lib/ruby_llm/protocols/mistral/content.rb +49 -0
- data/lib/ruby_llm/protocols/mistral/conversations/chat.rb +160 -0
- data/lib/ruby_llm/protocols/mistral/conversations/images.rb +43 -0
- data/lib/ruby_llm/protocols/mistral/conversations/streaming.rb +83 -0
- data/lib/ruby_llm/protocols/mistral/conversations.rb +30 -0
- data/lib/ruby_llm/protocols/mistral/files.rb +36 -0
- data/lib/ruby_llm/protocols/mistral/multi_completion.rb +160 -0
- data/lib/ruby_llm/protocols/openai/batches.rb +126 -0
- data/lib/ruby_llm/protocols/openai/files.rb +42 -0
- data/lib/ruby_llm/protocols/openrouter/batches.rb +147 -0
- data/lib/ruby_llm/protocols/openrouter/files.rb +24 -0
- data/lib/ruby_llm/protocols/openrouter/responses.rb +53 -0
- data/lib/ruby_llm/protocols/openrouter/transcription.rb +51 -0
- data/lib/ruby_llm/protocols/perplexity/files.rb +48 -0
- data/lib/ruby_llm/protocols/perplexity/router.rb +59 -0
- data/lib/ruby_llm/protocols/responses/approvals.rb +32 -0
- data/lib/ruby_llm/protocols/responses/batches.rb +32 -0
- data/lib/ruby_llm/protocols/responses/chat.rb +476 -0
- data/lib/ruby_llm/protocols/responses/compaction.rb +29 -0
- data/lib/ruby_llm/protocols/responses/media.rb +61 -0
- data/lib/ruby_llm/protocols/responses/streaming.rb +117 -0
- data/lib/ruby_llm/protocols/responses/token_counting.rb +26 -0
- data/lib/ruby_llm/protocols/responses/tools.rb +39 -0
- data/lib/ruby_llm/protocols/responses.rb +35 -0
- data/lib/ruby_llm/protocols/vertexai/batch_prediction.rb +155 -0
- data/lib/ruby_llm/protocols/vertexai/embedding_prediction/requests.rb +85 -0
- data/lib/ruby_llm/protocols/vertexai/embedding_prediction/results.rb +74 -0
- data/lib/ruby_llm/protocols/vertexai/embedding_prediction.rb +56 -0
- data/lib/ruby_llm/protocols/vertexai/files.rb +101 -0
- data/lib/ruby_llm/protocols/vertexai/ranking.rb +69 -0
- data/lib/ruby_llm/protocols/vertexai/research.rb +193 -0
- data/lib/ruby_llm/protocols/xai/files.rb +30 -0
- data/lib/ruby_llm/protocols/xai/streaming_transcription.rb +120 -0
- data/lib/ruby_llm/protocols/xai/tokenization.rb +23 -0
- data/lib/ruby_llm/provider.rb +560 -128
- data/lib/ruby_llm/providers/anthropic/capabilities.rb +5 -7
- data/lib/ruby_llm/providers/anthropic.rb +4 -6
- data/lib/ruby_llm/providers/azure/audio.rb +18 -0
- data/lib/ruby_llm/providers/azure/capabilities.rb +16 -0
- data/lib/ruby_llm/providers/azure/chat.rb +2 -9
- data/lib/ruby_llm/providers/azure/chat_completions/batches.rb +29 -0
- data/lib/ruby_llm/providers/azure/chat_completions.rb +80 -0
- data/lib/ruby_llm/providers/azure/cohere.rb +33 -0
- data/lib/ruby_llm/providers/azure/embeddings.rb +3 -2
- data/lib/ruby_llm/providers/azure/images.rb +22 -0
- data/lib/ruby_llm/providers/azure/media.rb +5 -14
- data/lib/ruby_llm/providers/azure/models.rb +35 -0
- data/lib/ruby_llm/providers/azure/responses.rb +26 -0
- data/lib/ruby_llm/providers/azure/videos.rb +64 -0
- data/lib/ruby_llm/providers/azure.rb +77 -78
- data/lib/ruby_llm/providers/bedrock/auth.rb +60 -41
- data/lib/ruby_llm/providers/bedrock/capabilities.rb +18 -0
- data/lib/ruby_llm/providers/bedrock/mantle/anthropic.rb +39 -0
- data/lib/ruby_llm/providers/bedrock/mantle/chat_completions.rb +23 -0
- data/lib/ruby_llm/providers/bedrock/mantle/responses.rb +24 -0
- data/lib/ruby_llm/providers/bedrock/mantle/voxtral.rb +96 -0
- data/lib/ruby_llm/providers/bedrock/mantle.rb +57 -0
- data/lib/ruby_llm/providers/bedrock/models.rb +193 -41
- data/lib/ruby_llm/providers/bedrock.rb +216 -45
- data/lib/ruby_llm/providers/cohere.rb +31 -0
- data/lib/ruby_llm/providers/deepgram.rb +37 -0
- data/lib/ruby_llm/providers/deepseek/capabilities.rb +4 -51
- data/lib/ruby_llm/providers/deepseek/chat.rb +49 -2
- data/lib/ruby_llm/providers/deepseek/responses.rb +68 -0
- data/lib/ruby_llm/providers/deepseek.rb +9 -2
- data/lib/ruby_llm/providers/elevenlabs.rb +35 -0
- data/lib/ruby_llm/providers/gemini/capabilities.rb +8 -107
- data/lib/ruby_llm/providers/gemini.rb +15 -8
- data/lib/ruby_llm/providers/gpustack/chat.rb +2 -16
- data/lib/ruby_llm/providers/gpustack/embeddings.rb +28 -0
- data/lib/ruby_llm/providers/gpustack/media.rb +17 -16
- data/lib/ruby_llm/providers/gpustack/models.rb +72 -62
- data/lib/ruby_llm/providers/gpustack/speech.rb +15 -0
- data/lib/ruby_llm/providers/gpustack/transcription.rb +29 -0
- data/lib/ruby_llm/providers/gpustack.rb +35 -11
- data/lib/ruby_llm/providers/mistral/capabilities.rb +7 -155
- data/lib/ruby_llm/providers/mistral/chat.rb +37 -61
- data/lib/ruby_llm/providers/mistral/chat_completions/batches.rb +120 -0
- data/lib/ruby_llm/providers/mistral/chat_completions.rb +21 -0
- data/lib/ruby_llm/providers/mistral/conversations.rb +12 -0
- data/lib/ruby_llm/providers/mistral/embeddings.rb +6 -4
- data/lib/ruby_llm/providers/mistral/media.rb +6 -18
- data/lib/ruby_llm/providers/mistral/models.rb +57 -23
- data/lib/ruby_llm/providers/mistral/ocr.rb +47 -0
- data/lib/ruby_llm/providers/mistral/speech.rb +51 -0
- data/lib/ruby_llm/providers/mistral/transcription.rb +62 -0
- data/lib/ruby_llm/providers/mistral.rb +16 -4
- data/lib/ruby_llm/providers/ollama/chat.rb +7 -13
- data/lib/ruby_llm/providers/ollama/media.rb +6 -15
- data/lib/ruby_llm/providers/ollama/models.rb +50 -9
- data/lib/ruby_llm/providers/ollama.rb +9 -8
- data/lib/ruby_llm/providers/ollama_cloud/models.rb +14 -0
- data/lib/ruby_llm/providers/ollama_cloud.rb +40 -0
- data/lib/ruby_llm/providers/openai/capabilities.rb +54 -259
- data/lib/ruby_llm/providers/openai/models.rb +23 -23
- data/lib/ruby_llm/providers/openai/responses.rb +13 -0
- data/lib/ruby_llm/providers/openai.rb +92 -11
- data/lib/ruby_llm/providers/openrouter/chat.rb +130 -108
- data/lib/ruby_llm/providers/openrouter/embeddings.rb +51 -0
- data/lib/ruby_llm/providers/openrouter/images.rb +44 -43
- data/lib/ruby_llm/providers/openrouter/media.rb +34 -0
- data/lib/ruby_llm/providers/openrouter/models.rb +50 -11
- data/lib/ruby_llm/providers/openrouter/speech.rb +32 -0
- data/lib/ruby_llm/providers/openrouter/streaming.rb +31 -38
- data/lib/ruby_llm/providers/openrouter/videos.rb +81 -0
- data/lib/ruby_llm/providers/openrouter.rb +78 -20
- data/lib/ruby_llm/providers/perplexity/chat.rb +2 -9
- data/lib/ruby_llm/providers/perplexity/embeddings.rb +32 -0
- data/lib/ruby_llm/providers/perplexity/media.rb +5 -21
- data/lib/ruby_llm/providers/perplexity/models.rb +80 -13
- data/lib/ruby_llm/providers/perplexity.rb +28 -20
- data/lib/ruby_llm/providers/vertexai/anthropic/batches.rb +52 -0
- data/lib/ruby_llm/providers/vertexai/anthropic.rb +34 -0
- data/lib/ruby_llm/providers/vertexai/capabilities.rb +19 -0
- data/lib/ruby_llm/providers/vertexai/chat_completions/batches.rb +54 -0
- data/lib/ruby_llm/providers/vertexai/chat_completions.rb +15 -0
- data/lib/ruby_llm/providers/vertexai/embed_content.rb +42 -0
- data/lib/ruby_llm/providers/vertexai/embeddings.rb +22 -7
- data/lib/ruby_llm/providers/vertexai/gemini/batches.rb +42 -0
- data/lib/ruby_llm/providers/vertexai/gemini.rb +69 -0
- data/lib/ruby_llm/providers/vertexai/live_transcription.rb +24 -0
- data/lib/ruby_llm/providers/vertexai/mistral.rb +28 -0
- data/lib/ruby_llm/providers/vertexai/models.rb +145 -43
- data/lib/ruby_llm/providers/vertexai/transcription.rb +49 -4
- data/lib/ruby_llm/providers/vertexai/videos.rb +61 -0
- data/lib/ruby_llm/providers/vertexai.rb +160 -17
- data/lib/ruby_llm/providers/xai/capabilities.rb +18 -0
- data/lib/ruby_llm/providers/xai/chat.rb +3 -2
- data/lib/ruby_llm/providers/xai/chat_completions/batches.rb +108 -0
- data/lib/ruby_llm/providers/xai/chat_completions.rb +19 -0
- data/lib/ruby_llm/providers/xai/images.rb +91 -0
- data/lib/ruby_llm/providers/xai/models.rb +32 -36
- data/lib/ruby_llm/providers/xai/reported_cost.rb +18 -0
- data/lib/ruby_llm/providers/xai/responses.rb +52 -0
- data/lib/ruby_llm/providers/xai/speech.rb +45 -0
- data/lib/ruby_llm/providers/xai/transcription.rb +48 -0
- data/lib/ruby_llm/providers/xai/videos.rb +87 -0
- data/lib/ruby_llm/providers/xai.rb +15 -5
- data/lib/ruby_llm/railtie.rb +7 -16
- data/lib/ruby_llm/rerank.rb +105 -0
- data/lib/ruby_llm/research_job.rb +241 -0
- data/lib/ruby_llm/search_results.rb +68 -0
- data/lib/ruby_llm/server_tool_call.rb +73 -0
- data/lib/ruby_llm/speech.rb +159 -0
- data/lib/ruby_llm/speech_chunk.rb +33 -0
- data/lib/ruby_llm/support/deprecator.rb +22 -0
- data/lib/ruby_llm/support/inspectable.rb +49 -0
- data/lib/ruby_llm/support/instrumentation.rb +41 -0
- data/lib/ruby_llm/support/utils.rb +147 -0
- data/lib/ruby_llm/thinking.rb +127 -20
- data/lib/ruby_llm/tokenization.rb +59 -0
- data/lib/ruby_llm/tokens.rb +103 -33
- data/lib/ruby_llm/tool.rb +266 -91
- data/lib/ruby_llm/tool_call.rb +36 -3
- data/lib/ruby_llm/tools/server_tools.rb +109 -0
- data/lib/ruby_llm/transcription/wav_audio.rb +62 -0
- data/lib/ruby_llm/transcription.rb +138 -13
- data/lib/ruby_llm/transcription_chunk.rb +68 -0
- data/lib/ruby_llm/transport/connection.rb +193 -0
- data/lib/ruby_llm/transport/error_middleware.rb +131 -0
- data/lib/ruby_llm/transport/usage_middleware.rb +28 -0
- data/lib/ruby_llm/transport/websocket_connection.rb +220 -0
- data/lib/ruby_llm/uploaded_file.rb +144 -0
- data/lib/ruby_llm/version.rb +2 -1
- data/lib/ruby_llm/video.rb +136 -0
- data/lib/ruby_llm/video_job.rb +150 -0
- data/lib/ruby_llm/workflow.rb +91 -0
- data/lib/ruby_llm.rb +380 -6
- data/lib/tasks/ruby_llm.rake +21 -16
- data/skills/rubyllm/SKILL.md +81 -0
- data/skills/rubyllm/agents/openai.yaml +4 -0
- metadata +339 -97
- data/lib/generators/ruby_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt +0 -9
- data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +0 -39
- data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +0 -21
- data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +0 -3
- data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +0 -3
- data/lib/generators/ruby_llm/upgrade_to_v1_10/templates/add_v1_10_message_columns.rb.tt +0 -19
- data/lib/generators/ruby_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +0 -50
- data/lib/generators/ruby_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +0 -7
- data/lib/generators/ruby_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +0 -49
- data/lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt +0 -145
- data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +0 -122
- data/lib/generators/ruby_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +0 -15
- data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +0 -49
- data/lib/ruby_llm/active_record/acts_as_legacy.rb +0 -597
- data/lib/ruby_llm/active_record/model_methods.rb +0 -82
- data/lib/ruby_llm/active_record/tool_call_methods.rb +0 -18
- data/lib/ruby_llm/aliases.rb +0 -41
- data/lib/ruby_llm/connection.rb +0 -159
- data/lib/ruby_llm/content.rb +0 -91
- data/lib/ruby_llm/deprecator.rb +0 -24
- data/lib/ruby_llm/error_middleware.rb +0 -81
- data/lib/ruby_llm/instrumentation.rb +0 -36
- data/lib/ruby_llm/mime_type.rb +0 -96
- data/lib/ruby_llm/model/info.rb +0 -164
- data/lib/ruby_llm/model_registry.rb +0 -39
- data/lib/ruby_llm/models_schema.json +0 -171
- data/lib/ruby_llm/providers/anthropic/chat.rb +0 -291
- data/lib/ruby_llm/providers/anthropic/content.rb +0 -44
- data/lib/ruby_llm/providers/anthropic/embeddings.rb +0 -20
- data/lib/ruby_llm/providers/anthropic/media.rb +0 -92
- data/lib/ruby_llm/providers/anthropic/models.rb +0 -59
- data/lib/ruby_llm/providers/anthropic/streaming.rb +0 -71
- data/lib/ruby_llm/providers/bedrock/chat.rb +0 -405
- data/lib/ruby_llm/providers/bedrock/media.rb +0 -108
- data/lib/ruby_llm/providers/bedrock/streaming.rb +0 -328
- data/lib/ruby_llm/providers/gemini/chat.rb +0 -542
- data/lib/ruby_llm/providers/gemini/embeddings.rb +0 -37
- data/lib/ruby_llm/providers/gemini/images.rb +0 -47
- data/lib/ruby_llm/providers/gemini/models.rb +0 -38
- data/lib/ruby_llm/providers/gemini/streaming.rb +0 -98
- data/lib/ruby_llm/providers/gemini/tools.rb +0 -234
- data/lib/ruby_llm/providers/gpustack/capabilities.rb +0 -20
- data/lib/ruby_llm/providers/ollama/capabilities.rb +0 -20
- data/lib/ruby_llm/providers/openai/chat.rb +0 -236
- data/lib/ruby_llm/providers/openai/embeddings.rb +0 -33
- data/lib/ruby_llm/providers/openai/images.rb +0 -90
- data/lib/ruby_llm/providers/openai/moderation.rb +0 -34
- data/lib/ruby_llm/providers/openai/streaming.rb +0 -55
- data/lib/ruby_llm/providers/openai/temperature.rb +0 -28
- data/lib/ruby_llm/providers/openai/transcription.rb +0 -71
- data/lib/ruby_llm/providers/perplexity/capabilities.rb +0 -72
- data/lib/ruby_llm/providers/vertexai/chat.rb +0 -14
- data/lib/ruby_llm/providers/vertexai/streaming.rb +0 -14
- data/lib/ruby_llm/stream_accumulator.rb +0 -218
- data/lib/ruby_llm/streaming.rb +0 -179
- data/lib/ruby_llm/tool_concurrency.rb +0 -105
- data/lib/ruby_llm/utils.rb +0 -130
- data/lib/tasks/models.rake +0 -593
- data/lib/tasks/release.rake +0 -94
- data/lib/tasks/vcr.rake +0 -124
|
@@ -0,0 +1,660 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
<% if copy_mode? -%>
|
|
3
|
+
|
|
4
|
+
require 'generators/ruby_llm/upgrade/upgrade_migration'
|
|
5
|
+
<% end -%>
|
|
6
|
+
|
|
7
|
+
class <%= prepare_migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
|
|
8
|
+
disable_ddl_transaction!
|
|
9
|
+
|
|
10
|
+
def up
|
|
11
|
+
<% if copy_mode? -%>
|
|
12
|
+
RubyLLM::Generators::UpgradeMigration.new.prepare(<%= copy_upgrade_settings.inspect %>)
|
|
13
|
+
<% else -%>
|
|
14
|
+
raise 'This database uses --mode copy' if table_exists?(:ruby_llm_v2_upgrades)
|
|
15
|
+
<% end -%>
|
|
16
|
+
validate_upgrade
|
|
17
|
+
move_models
|
|
18
|
+
add_chat_and_message_columns
|
|
19
|
+
move_tool_calls
|
|
20
|
+
create_usages
|
|
21
|
+
create_batches
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def down
|
|
25
|
+
raise ActiveRecord::IrreversibleMigration, 'RubyLLM 2.0 changes persisted record ownership'
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
private
|
|
29
|
+
|
|
30
|
+
def validate_upgrade
|
|
31
|
+
%i[<%= chat_table_name %> <%= message_table_name %>].each do |table|
|
|
32
|
+
raise "Expected #{table} to exist before upgrading RubyLLM" unless table_exists?(table)
|
|
33
|
+
end
|
|
34
|
+
validate_legacy_message_columns
|
|
35
|
+
validate_table_move(:<%= v1_model_table_name %>, :ruby_llm_models)
|
|
36
|
+
validate_table_move(:<%= v1_tool_call_table_name %>, :ruby_llm_tool_calls)
|
|
37
|
+
|
|
38
|
+
models = existing_table(:<%= v1_model_table_name %>, :ruby_llm_models)
|
|
39
|
+
raise 'Expected the RubyLLM 1.16 model table to exist' unless models
|
|
40
|
+
|
|
41
|
+
validate_table_shape(models, %i[model_id name provider], 'model')
|
|
42
|
+
validate_unique_values(models, %i[provider model_id])
|
|
43
|
+
validate_chat_model_references(models)
|
|
44
|
+
|
|
45
|
+
tool_calls = existing_table(:<%= v1_tool_call_table_name %>, :ruby_llm_tool_calls)
|
|
46
|
+
raise 'Expected the RubyLLM 1.16 tool-call table to exist' unless tool_calls
|
|
47
|
+
|
|
48
|
+
validate_table_shape(tool_calls, %i[tool_call_id name arguments created_at updated_at], 'tool-call')
|
|
49
|
+
<% if copy_mode? -%>
|
|
50
|
+
validate_unique_values(tool_calls, %i[tool_call_id])
|
|
51
|
+
<% end -%>
|
|
52
|
+
unless column_exists?(tool_calls, :<%= message_foreign_key %>) || column_exists?(tool_calls, :message_id)
|
|
53
|
+
raise "#{tool_calls} does not have a message foreign key"
|
|
54
|
+
end
|
|
55
|
+
if migration_record(tool_calls).where(tool_call_id: nil).exists?
|
|
56
|
+
raise "#{tool_calls} contains a NULL tool_call_id. Set it before retrying."
|
|
57
|
+
end
|
|
58
|
+
validate_unique_tool_results
|
|
59
|
+
|
|
60
|
+
validate_usage_identities(models)
|
|
61
|
+
validate_existing_table(
|
|
62
|
+
:ruby_llm_usages,
|
|
63
|
+
%i[
|
|
64
|
+
chat_type chat_id message_type message_id operation provider model status
|
|
65
|
+
input_tokens output_tokens cache_read_tokens cache_write_tokens thinking_tokens
|
|
66
|
+
input_cost output_cost cache_read_cost cache_write_cost thinking_cost total_cost created_at updated_at
|
|
67
|
+
],
|
|
68
|
+
'usage'
|
|
69
|
+
)
|
|
70
|
+
validate_existing_table(
|
|
71
|
+
:ruby_llm_batches,
|
|
72
|
+
%i[
|
|
73
|
+
provider_batch_id provider status raw_status completed chat_type batch_protocol
|
|
74
|
+
chat_ids request_counts created_at updated_at
|
|
75
|
+
],
|
|
76
|
+
'batch'
|
|
77
|
+
)
|
|
78
|
+
validate_unique_values(:ruby_llm_batches, %i[provider provider_batch_id]) if table_exists?(:ruby_llm_batches)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# A schema the finish migration already cleaned has none of these columns;
|
|
82
|
+
# running the backfill again there would invent usage rows.
|
|
83
|
+
def validate_legacy_message_columns
|
|
84
|
+
if table_exists?(:ruby_llm_v2_backfills) &&
|
|
85
|
+
migration_record(:ruby_llm_v2_backfills).where(task: 'finished', completed: true).exists?
|
|
86
|
+
raise 'The RubyLLM 2.0 upgrade is already finished. Generate --phase cleanup to remove the legacy columns.'
|
|
87
|
+
end
|
|
88
|
+
|
|
89
|
+
legacy_columns = %i[content_raw input_tokens output_tokens <%= v1_model_foreign_key %> <%= v1_tool_call_foreign_key %>]
|
|
90
|
+
return if legacy_columns.any? { |column| column_exists?(:<%= message_table_name %>, column) }
|
|
91
|
+
return unless table_exists?(:ruby_llm_usages)
|
|
92
|
+
|
|
93
|
+
raise '<%= message_table_name %> already has the RubyLLM 2.0 schema. ' \
|
|
94
|
+
'Remove the generated upgrade migrations instead of running them again.'
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def validate_table_move(source, target)
|
|
98
|
+
<% if copy_mode? -%>
|
|
99
|
+
raise 'Copy mode needs distinct legacy and RubyLLM table names' if source == target
|
|
100
|
+
raise "Expected legacy table #{source}" unless table_exists?(source)
|
|
101
|
+
<% else -%>
|
|
102
|
+
return if source == target || !table_exists?(source) || !table_exists?(target)
|
|
103
|
+
|
|
104
|
+
raise "Both #{source} and #{target} exist. Merge or remove one before running this migration."
|
|
105
|
+
<% end -%>
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
def existing_table(source, target)
|
|
109
|
+
return source if table_exists?(source)
|
|
110
|
+
|
|
111
|
+
target if table_exists?(target)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def validate_existing_table(table, columns, label)
|
|
115
|
+
validate_table_shape(table, columns, label) if table_exists?(table)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def validate_table_shape(table, columns, label)
|
|
119
|
+
primary_key = connection.primary_key(table)
|
|
120
|
+
unless primary_key.is_a?(String) && column_exists?(table, primary_key)
|
|
121
|
+
raise "#{table} does not look like a RubyLLM #{label} table; expected one primary key column"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
missing = columns.reject { |column| column_exists?(table, column) }
|
|
125
|
+
return if missing.empty?
|
|
126
|
+
|
|
127
|
+
raise "#{table} does not look like a RubyLLM #{label} table; missing columns: #{missing.join(', ')}"
|
|
128
|
+
end
|
|
129
|
+
|
|
130
|
+
def validate_unique_values(table, columns)
|
|
131
|
+
duplicates = migration_record(table).group(*columns).having('COUNT(*) > 1').limit(5).pluck(*columns)
|
|
132
|
+
return if duplicates.empty?
|
|
133
|
+
|
|
134
|
+
raise "Cannot add a unique index to #{table} on #{columns.join(', ')}; " \
|
|
135
|
+
"duplicate values include #{duplicates.inspect}. Reconcile them before retrying."
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def validate_chat_model_references(models)
|
|
139
|
+
chats = :<%= chat_table_name %>
|
|
140
|
+
model_column = first_existing_column(chats, :<%= v1_model_foreign_key %>, :ruby_llm_model_id)
|
|
141
|
+
return unless model_column
|
|
142
|
+
|
|
143
|
+
ensure_reference_types_match(chats, model_column, models)
|
|
144
|
+
orphan = select_value(<<~SQL)
|
|
145
|
+
SELECT legacy_chats.#{quoted_primary_key(chats)}
|
|
146
|
+
FROM #{quote_table(chats)} legacy_chats
|
|
147
|
+
LEFT JOIN #{quote_table(models)} legacy_models
|
|
148
|
+
ON legacy_chats.#{quote_column(model_column)} = legacy_models.#{quoted_primary_key(models)}
|
|
149
|
+
WHERE legacy_chats.#{quote_column(model_column)} IS NOT NULL
|
|
150
|
+
AND legacy_models.#{quoted_primary_key(models)} IS NULL
|
|
151
|
+
LIMIT 1
|
|
152
|
+
SQL
|
|
153
|
+
return unless orphan
|
|
154
|
+
|
|
155
|
+
raise "Could not find #{models} record for #{chats} id #{orphan}. Repair its model reference before retrying."
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
def validate_usage_identities(models)
|
|
159
|
+
conditions = legacy_usage_conditions
|
|
160
|
+
return if conditions.empty?
|
|
161
|
+
|
|
162
|
+
joins, provider, model = usage_identity_sql(models)
|
|
163
|
+
message_id = "legacy_messages.#{quoted_primary_key(:<%= message_table_name %>)}"
|
|
164
|
+
missing = select_value(<<~SQL)
|
|
165
|
+
SELECT #{message_id}
|
|
166
|
+
FROM #{quote_table(:<%= message_table_name %>)} legacy_messages
|
|
167
|
+
#{joins}
|
|
168
|
+
WHERE (#{conditions.join(' OR ')})
|
|
169
|
+
AND (#{provider} IS NULL OR #{model} IS NULL)
|
|
170
|
+
LIMIT 1
|
|
171
|
+
SQL
|
|
172
|
+
return unless missing
|
|
173
|
+
|
|
174
|
+
raise "Could not determine provider and model for <%= message_table_name %> id #{missing}. " \
|
|
175
|
+
'Set its model reference, or its chat model reference, before retrying.'
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def validate_unique_tool_results
|
|
179
|
+
column = :<%= v1_tool_call_foreign_key %>
|
|
180
|
+
return unless column_exists?(:<%= message_table_name %>, column)
|
|
181
|
+
|
|
182
|
+
duplicates = migration_record(:<%= message_table_name %>)
|
|
183
|
+
.where.not(column => nil)
|
|
184
|
+
.group(column)
|
|
185
|
+
.having('COUNT(*) > 1')
|
|
186
|
+
.limit(5)
|
|
187
|
+
.pluck(column)
|
|
188
|
+
return if duplicates.empty?
|
|
189
|
+
|
|
190
|
+
raise "Multiple messages reference the same tool call: #{duplicates.inspect}. " \
|
|
191
|
+
'Keep one result message per tool call before retrying.'
|
|
192
|
+
end
|
|
193
|
+
|
|
194
|
+
def move_models
|
|
195
|
+
move_table(:<%= v1_model_table_name %>, :ruby_llm_models)
|
|
196
|
+
<% if postgresql? -%>
|
|
197
|
+
normalize_postgresql_json_columns(:ruby_llm_models, %i[modalities capabilities pricing metadata])
|
|
198
|
+
<% end -%>
|
|
199
|
+
<% unless mysql? -%>
|
|
200
|
+
normalize_json_defaults(:ruby_llm_models, modalities: {}, capabilities: [], pricing: {}, metadata: {})
|
|
201
|
+
<% end -%>
|
|
202
|
+
with_upgrade_safety do
|
|
203
|
+
change_table :ruby_llm_models, bulk: true do |table|
|
|
204
|
+
table.string :family unless column_exists?(:ruby_llm_models, :family)
|
|
205
|
+
table.datetime :model_created_at unless column_exists?(:ruby_llm_models, :model_created_at)
|
|
206
|
+
table.integer :context_window unless column_exists?(:ruby_llm_models, :context_window)
|
|
207
|
+
table.integer :max_output_tokens unless column_exists?(:ruby_llm_models, :max_output_tokens)
|
|
208
|
+
table.date :knowledge_cutoff unless column_exists?(:ruby_llm_models, :knowledge_cutoff)
|
|
209
|
+
table.datetime :unlisted_at unless column_exists?(:ruby_llm_models, :unlisted_at)
|
|
210
|
+
<% if postgresql? -%>
|
|
211
|
+
table.jsonb :modalities, default: {} unless column_exists?(:ruby_llm_models, :modalities)
|
|
212
|
+
table.jsonb :capabilities, default: [] unless column_exists?(:ruby_llm_models, :capabilities)
|
|
213
|
+
table.jsonb :pricing, default: {} unless column_exists?(:ruby_llm_models, :pricing)
|
|
214
|
+
table.jsonb :metadata, default: {} unless column_exists?(:ruby_llm_models, :metadata)
|
|
215
|
+
<% elsif mysql? -%>
|
|
216
|
+
table.json :modalities unless column_exists?(:ruby_llm_models, :modalities)
|
|
217
|
+
table.json :capabilities unless column_exists?(:ruby_llm_models, :capabilities)
|
|
218
|
+
table.json :pricing unless column_exists?(:ruby_llm_models, :pricing)
|
|
219
|
+
table.json :metadata unless column_exists?(:ruby_llm_models, :metadata)
|
|
220
|
+
<% else -%>
|
|
221
|
+
table.json :modalities, default: {} unless column_exists?(:ruby_llm_models, :modalities)
|
|
222
|
+
table.json :capabilities, default: [] unless column_exists?(:ruby_llm_models, :capabilities)
|
|
223
|
+
table.json :pricing, default: {} unless column_exists?(:ruby_llm_models, :pricing)
|
|
224
|
+
table.json :metadata, default: {} unless column_exists?(:ruby_llm_models, :metadata)
|
|
225
|
+
<% end -%>
|
|
226
|
+
table.datetime :created_at unless column_exists?(:ruby_llm_models, :created_at)
|
|
227
|
+
table.datetime :updated_at unless column_exists?(:ruby_llm_models, :updated_at)
|
|
228
|
+
end
|
|
229
|
+
end
|
|
230
|
+
ensure_unique_index(:ruby_llm_models, %i[provider model_id])
|
|
231
|
+
add_upgrade_index(:ruby_llm_models, :provider) unless valid_index_exists?(:ruby_llm_models, :provider)
|
|
232
|
+
add_upgrade_index(:ruby_llm_models, :family) unless valid_index_exists?(:ruby_llm_models, :family)
|
|
233
|
+
<% if postgresql? -%>
|
|
234
|
+
ensure_index_method(:ruby_llm_models, :capabilities, using: :gin)
|
|
235
|
+
ensure_index_method(:ruby_llm_models, :modalities, using: :gin)
|
|
236
|
+
<% end -%>
|
|
237
|
+
normalize_chat_model_reference
|
|
238
|
+
end
|
|
239
|
+
|
|
240
|
+
def normalize_chat_model_reference
|
|
241
|
+
table = :<%= chat_table_name %>
|
|
242
|
+
column = first_existing_column(table, :<%= v1_model_foreign_key %>, :ruby_llm_model_id)
|
|
243
|
+
return unless column
|
|
244
|
+
|
|
245
|
+
ensure_reference_types_match(table, column, :ruby_llm_models)
|
|
246
|
+
<% if copy_mode? -%>
|
|
247
|
+
unless column_exists?(table, :ruby_llm_model_id)
|
|
248
|
+
add_column table, :ruby_llm_model_id, reference_type_for(:ruby_llm_models)
|
|
249
|
+
end
|
|
250
|
+
RubyLLM::Generators::UpgradeMigration.new.copy_model_references
|
|
251
|
+
with_upgrade_safety { change_column_null table, column, true } unless column_definition(table, column).null
|
|
252
|
+
<% else -%>
|
|
253
|
+
with_upgrade_safety { rename_column table, column, :ruby_llm_model_id } if column != :ruby_llm_model_id
|
|
254
|
+
<% end -%>
|
|
255
|
+
add_upgrade_index(table, :ruby_llm_model_id) unless valid_index_exists?(table, :ruby_llm_model_id)
|
|
256
|
+
return if foreign_key_exists?(table, :ruby_llm_models, column: :ruby_llm_model_id)
|
|
257
|
+
|
|
258
|
+
add_upgrade_foreign_key(table, :ruby_llm_models, column: :ruby_llm_model_id)
|
|
259
|
+
end
|
|
260
|
+
|
|
261
|
+
def add_chat_and_message_columns
|
|
262
|
+
<% if copy_mode? -%>
|
|
263
|
+
unless column_exists?(:<%= chat_table_name %>, :ruby_llm_version)
|
|
264
|
+
add_column :<%= chat_table_name %>, :ruby_llm_version, :integer, null: false, default: 1
|
|
265
|
+
end
|
|
266
|
+
<% end -%>
|
|
267
|
+
ensure_boolean_column(:<%= chat_table_name %>, :cancelled)
|
|
268
|
+
ensure_boolean_column(:<%= message_table_name %>, :cache_until_here)
|
|
269
|
+
with_upgrade_safety do
|
|
270
|
+
change_table :<%= message_table_name %>, bulk: true do |table|
|
|
271
|
+
table.string :finish_reason unless column_exists?(:<%= message_table_name %>, :finish_reason)
|
|
272
|
+
<% if postgresql? -%>
|
|
273
|
+
table.jsonb :citations unless column_exists?(:<%= message_table_name %>, :citations)
|
|
274
|
+
table.jsonb :server_tool_calls unless column_exists?(:<%= message_table_name %>, :server_tool_calls)
|
|
275
|
+
table.jsonb :raw_content unless column_exists?(:<%= message_table_name %>, :raw_content)
|
|
276
|
+
table.jsonb :raw_reasoning unless column_exists?(:<%= message_table_name %>, :raw_reasoning)
|
|
277
|
+
<% else -%>
|
|
278
|
+
table.json :citations unless column_exists?(:<%= message_table_name %>, :citations)
|
|
279
|
+
table.json :server_tool_calls unless column_exists?(:<%= message_table_name %>, :server_tool_calls)
|
|
280
|
+
table.json :raw_content unless column_exists?(:<%= message_table_name %>, :raw_content)
|
|
281
|
+
table.json :raw_reasoning unless column_exists?(:<%= message_table_name %>, :raw_reasoning)
|
|
282
|
+
<% end -%>
|
|
283
|
+
end
|
|
284
|
+
end
|
|
285
|
+
<% if postgresql? -%>
|
|
286
|
+
normalize_postgresql_json_columns(:<%= message_table_name %>, %i[citations server_tool_calls raw_content raw_reasoning])
|
|
287
|
+
<% end -%>
|
|
288
|
+
end
|
|
289
|
+
|
|
290
|
+
def ensure_boolean_column(table, column)
|
|
291
|
+
add_column table, column, :boolean unless column_exists?(table, column)
|
|
292
|
+
return if ActiveRecord::Type::Boolean.new.cast(column_definition(table, column).default) == false
|
|
293
|
+
|
|
294
|
+
with_upgrade_safety { change_column_default table, column, false }
|
|
295
|
+
end
|
|
296
|
+
|
|
297
|
+
def move_tool_calls
|
|
298
|
+
move_table(:<%= v1_tool_call_table_name %>, :ruby_llm_tool_calls)
|
|
299
|
+
<% if copy_mode? -%>
|
|
300
|
+
remove_column_foreign_keys(:<%= v1_tool_call_table_name %>, :<%= message_foreign_key %>)
|
|
301
|
+
<% end -%>
|
|
302
|
+
table = :ruby_llm_tool_calls
|
|
303
|
+
<% if postgresql? -%>
|
|
304
|
+
normalize_postgresql_json_columns(table, %i[arguments])
|
|
305
|
+
<% end -%>
|
|
306
|
+
<% unless mysql? -%>
|
|
307
|
+
normalize_json_defaults(table, arguments: {})
|
|
308
|
+
<% end -%>
|
|
309
|
+
legacy_message_column = :<%= message_foreign_key %>
|
|
310
|
+
if legacy_message_column != :message_id && column_exists?(table, legacy_message_column)
|
|
311
|
+
remove_column_foreign_keys(table, legacy_message_column)
|
|
312
|
+
remove_matching_indexes(table, legacy_message_column)
|
|
313
|
+
with_upgrade_safety { rename_column table, legacy_message_column, :message_id }
|
|
314
|
+
end
|
|
315
|
+
remove_column_foreign_keys(table, :message_id)
|
|
316
|
+
remove_matching_indexes(table, :message_id)
|
|
317
|
+
|
|
318
|
+
if column_exists?(table, :message_type)
|
|
319
|
+
unless column_definition(table, :message_type).default.nil?
|
|
320
|
+
with_upgrade_safety { change_column_default table, :message_type, nil }
|
|
321
|
+
end
|
|
322
|
+
else
|
|
323
|
+
add_column table, :message_type, :string
|
|
324
|
+
end
|
|
325
|
+
with_upgrade_safety do
|
|
326
|
+
change_table table, bulk: true do |definition|
|
|
327
|
+
definition.string :result_type unless column_exists?(table, :result_type)
|
|
328
|
+
definition.column :result_id, reference_type_for(:<%= message_table_name %>) unless column_exists?(table, :result_id)
|
|
329
|
+
definition.text :thought_signature unless column_exists?(table, :thought_signature)
|
|
330
|
+
definition.string :approval unless column_exists?(table, :approval)
|
|
331
|
+
definition.boolean :remote, default: false, null: false unless column_exists?(table, :remote)
|
|
332
|
+
end
|
|
333
|
+
end
|
|
334
|
+
if column_exists?(table, :thought_signature, :string)
|
|
335
|
+
with_upgrade_safety { change_column table, :thought_signature, :text }
|
|
336
|
+
end
|
|
337
|
+
normalize_tool_call_indexes
|
|
338
|
+
end
|
|
339
|
+
|
|
340
|
+
def normalize_tool_call_indexes
|
|
341
|
+
table = :ruby_llm_tool_calls
|
|
342
|
+
indexes = []
|
|
343
|
+
indexes << [%i[message_type message_id], {}] unless valid_index_exists?(table, %i[message_type message_id])
|
|
344
|
+
indexes << [%i[result_type result_id], {}] unless valid_index_exists?(table, %i[result_type result_id])
|
|
345
|
+
unless valid_index_exists?(table, :tool_call_id, unique: true)
|
|
346
|
+
deduplicate_tool_call_ids
|
|
347
|
+
remove_matching_indexes(table, :tool_call_id)
|
|
348
|
+
indexes << [:tool_call_id, { unique: true }]
|
|
349
|
+
end
|
|
350
|
+
indexes << [:name, {}] unless valid_index_exists?(table, :name)
|
|
351
|
+
add_upgrade_indexes(table, indexes)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def deduplicate_tool_call_ids
|
|
355
|
+
records = migration_record(:ruby_llm_tool_calls)
|
|
356
|
+
loop do
|
|
357
|
+
duplicate_ids = records.group(:tool_call_id).having('COUNT(*) > 1').limit(1_000).pluck(:tool_call_id)
|
|
358
|
+
break if duplicate_ids.empty?
|
|
359
|
+
|
|
360
|
+
duplicate_ids.each do |tool_call_id|
|
|
361
|
+
matches = records.where(tool_call_id: tool_call_id)
|
|
362
|
+
keeper = matches.order(records.primary_key).pick(records.primary_key)
|
|
363
|
+
matches.where.not(records.primary_key => keeper).find_each(batch_size: 1_000) do |record|
|
|
364
|
+
record.update!(tool_call_id: available_tool_call_id(records, record))
|
|
365
|
+
end
|
|
366
|
+
end
|
|
367
|
+
end
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def available_tool_call_id(records, record)
|
|
371
|
+
counter = nil
|
|
372
|
+
loop do
|
|
373
|
+
suffix = "-migrated-#{record.id}#{"-#{counter}" if counter}"
|
|
374
|
+
candidate = "#{record.tool_call_id.to_s.slice(0, 255 - suffix.length)}#{suffix}"
|
|
375
|
+
return candidate unless records.where(tool_call_id: candidate).exists?
|
|
376
|
+
|
|
377
|
+
counter = counter.to_i + 1
|
|
378
|
+
end
|
|
379
|
+
end
|
|
380
|
+
|
|
381
|
+
def create_usages
|
|
382
|
+
unless table_exists?(:ruby_llm_usages)
|
|
383
|
+
create_table :ruby_llm_usages do |table|
|
|
384
|
+
table.references :chat, polymorphic: true, null: false,
|
|
385
|
+
type: reference_type_for(:<%= chat_table_name %>), index: false
|
|
386
|
+
table.references :message, polymorphic: true,
|
|
387
|
+
type: reference_type_for(:<%= message_table_name %>), index: false
|
|
388
|
+
table.string :operation, null: false
|
|
389
|
+
table.string :provider, null: false
|
|
390
|
+
table.string :model, null: false
|
|
391
|
+
table.string :status, null: false
|
|
392
|
+
table.integer :input_tokens
|
|
393
|
+
table.integer :output_tokens
|
|
394
|
+
table.integer :cache_read_tokens
|
|
395
|
+
table.integer :cache_write_tokens
|
|
396
|
+
table.integer :thinking_tokens
|
|
397
|
+
table.decimal :input_cost, precision: 16, scale: 10
|
|
398
|
+
table.decimal :output_cost, precision: 16, scale: 10
|
|
399
|
+
table.decimal :cache_read_cost, precision: 16, scale: 10
|
|
400
|
+
table.decimal :cache_write_cost, precision: 16, scale: 10
|
|
401
|
+
table.decimal :thinking_cost, precision: 16, scale: 10
|
|
402
|
+
table.decimal :total_cost, precision: 16, scale: 10
|
|
403
|
+
table.timestamps
|
|
404
|
+
table.check_constraint "operation IN (<%= usage_operations_sql %>)"
|
|
405
|
+
table.check_constraint "status IN (<%= usage_statuses_sql %>)"
|
|
406
|
+
end
|
|
407
|
+
end
|
|
408
|
+
model_column = connection.columns(:ruby_llm_usages).find { |column| column.name == 'model' }
|
|
409
|
+
with_upgrade_safety { change_column_null :ruby_llm_usages, :model, false } if model_column.null
|
|
410
|
+
indexes = [%i[chat_type chat_id], %i[message_type message_id], :status]
|
|
411
|
+
missing = indexes.reject { |columns| valid_index_exists?(:ruby_llm_usages, columns) }
|
|
412
|
+
.map { |columns| [columns, {}] }
|
|
413
|
+
add_upgrade_indexes(:ruby_llm_usages, missing)
|
|
414
|
+
end
|
|
415
|
+
|
|
416
|
+
def create_batches
|
|
417
|
+
unless table_exists?(:ruby_llm_batches)
|
|
418
|
+
create_table :ruby_llm_batches do |table|
|
|
419
|
+
table.string :provider_batch_id, null: false
|
|
420
|
+
table.string :provider, null: false
|
|
421
|
+
table.string :status, null: false
|
|
422
|
+
table.string :raw_status
|
|
423
|
+
table.boolean :completed, null: false, default: false
|
|
424
|
+
table.string :chat_type
|
|
425
|
+
table.string :batch_protocol
|
|
426
|
+
<% if postgresql? -%>
|
|
427
|
+
table.jsonb :chat_ids, default: []
|
|
428
|
+
table.jsonb :request_counts
|
|
429
|
+
table.jsonb :reported_cost
|
|
430
|
+
<% elsif mysql? -%>
|
|
431
|
+
table.json :chat_ids
|
|
432
|
+
table.json :request_counts
|
|
433
|
+
table.json :reported_cost
|
|
434
|
+
<% else -%>
|
|
435
|
+
table.json :chat_ids, default: []
|
|
436
|
+
table.json :request_counts
|
|
437
|
+
table.json :reported_cost
|
|
438
|
+
<% end -%>
|
|
439
|
+
table.timestamps
|
|
440
|
+
end
|
|
441
|
+
end
|
|
442
|
+
add_column :ruby_llm_batches, :raw_status, :string unless column_exists?(:ruby_llm_batches, :raw_status)
|
|
443
|
+
add_column :ruby_llm_batches, :chat_type, :string unless column_exists?(:ruby_llm_batches, :chat_type)
|
|
444
|
+
add_column :ruby_llm_batches, :batch_protocol, :string unless column_exists?(:ruby_llm_batches, :batch_protocol)
|
|
445
|
+
<% if postgresql? -%>
|
|
446
|
+
add_column :ruby_llm_batches, :request_counts, :jsonb unless column_exists?(:ruby_llm_batches, :request_counts)
|
|
447
|
+
add_column :ruby_llm_batches, :reported_cost, :jsonb unless column_exists?(:ruby_llm_batches, :reported_cost)
|
|
448
|
+
normalize_postgresql_json_columns(:ruby_llm_batches, %i[chat_ids request_counts reported_cost])
|
|
449
|
+
<% else -%>
|
|
450
|
+
add_column :ruby_llm_batches, :request_counts, :json unless column_exists?(:ruby_llm_batches, :request_counts)
|
|
451
|
+
add_column :ruby_llm_batches, :reported_cost, :json unless column_exists?(:ruby_llm_batches, :reported_cost)
|
|
452
|
+
<% end -%>
|
|
453
|
+
<% unless mysql? -%>
|
|
454
|
+
normalize_json_defaults(:ruby_llm_batches, chat_ids: [])
|
|
455
|
+
<% end -%>
|
|
456
|
+
ensure_unique_index(:ruby_llm_batches, %i[provider provider_batch_id])
|
|
457
|
+
add_upgrade_index(:ruby_llm_batches, :status) unless valid_index_exists?(:ruby_llm_batches, :status)
|
|
458
|
+
end
|
|
459
|
+
|
|
460
|
+
def usage_identity_sql(models)
|
|
461
|
+
providers = []
|
|
462
|
+
model_ids = []
|
|
463
|
+
joins = []
|
|
464
|
+
if column_exists?(:<%= message_table_name %>, :<%= v1_model_foreign_key %>)
|
|
465
|
+
model_column = column_definition(:<%= message_table_name %>, :<%= v1_model_foreign_key %>)
|
|
466
|
+
if model_column.type == :string
|
|
467
|
+
providers << message_value(:provider) if column_exists?(:<%= message_table_name %>, :provider)
|
|
468
|
+
model_ids << message_value(:<%= v1_model_foreign_key %>)
|
|
469
|
+
else
|
|
470
|
+
joins << "LEFT JOIN #{quote_table(models)} legacy_message_models " \
|
|
471
|
+
"ON #{message_value(:<%= v1_model_foreign_key %>)} = legacy_message_models.#{quoted_primary_key(models)}"
|
|
472
|
+
providers << "legacy_message_models.#{quote_column(:provider)}"
|
|
473
|
+
model_ids << "legacy_message_models.#{quote_column(:model_id)}"
|
|
474
|
+
end
|
|
475
|
+
end
|
|
476
|
+
|
|
477
|
+
chat_model_column = first_existing_column(:<%= chat_table_name %>, :<%= v1_model_foreign_key %>, :ruby_llm_model_id)
|
|
478
|
+
if chat_model_column
|
|
479
|
+
joins << "LEFT JOIN #{quote_table(:<%= chat_table_name %>)} legacy_chats " \
|
|
480
|
+
"ON #{message_value(:<%= chat_foreign_key %>)} = legacy_chats.#{quoted_primary_key(:<%= chat_table_name %>)}"
|
|
481
|
+
joins << "LEFT JOIN #{quote_table(models)} legacy_chat_models " \
|
|
482
|
+
"ON legacy_chats.#{quote_column(chat_model_column)} = legacy_chat_models.#{quoted_primary_key(models)}"
|
|
483
|
+
providers << "legacy_chat_models.#{quote_column(:provider)}"
|
|
484
|
+
model_ids << "legacy_chat_models.#{quote_column(:model_id)}"
|
|
485
|
+
end
|
|
486
|
+
|
|
487
|
+
[joins.join("\n"), coalesce_sql(providers), coalesce_sql(model_ids)]
|
|
488
|
+
end
|
|
489
|
+
|
|
490
|
+
def legacy_usage_conditions
|
|
491
|
+
columns = %i[
|
|
492
|
+
input_tokens output_tokens cached_tokens cache_creation_tokens cache_read_tokens cache_write_tokens
|
|
493
|
+
thinking_tokens total_cost cost_details
|
|
494
|
+
]
|
|
495
|
+
conditions = columns.filter_map do |column|
|
|
496
|
+
"#{message_value(column)} IS NOT NULL" if column_exists?(:<%= message_table_name %>, column)
|
|
497
|
+
end
|
|
498
|
+
conditions << "#{message_value(:role)} = 'assistant'" if column_exists?(:<%= message_table_name %>, :role)
|
|
499
|
+
conditions
|
|
500
|
+
end
|
|
501
|
+
|
|
502
|
+
def coalesce_sql(values)
|
|
503
|
+
values = Array(values).compact
|
|
504
|
+
return 'NULL' if values.empty?
|
|
505
|
+
return values.first if values.one?
|
|
506
|
+
|
|
507
|
+
"COALESCE(#{values.join(', ')})"
|
|
508
|
+
end
|
|
509
|
+
|
|
510
|
+
def message_value(column) = "legacy_messages.#{quote_column(column)}"
|
|
511
|
+
def first_existing_column(table, *columns) = columns.find { |column| column_exists?(table, column) }
|
|
512
|
+
def column_definition(table, column) = connection.columns(table).find { |candidate| candidate.name == column.to_s }
|
|
513
|
+
def quoted_primary_key(table) = quote_column(connection.primary_key(table))
|
|
514
|
+
def quote_table(table) = connection.quote_table_name(table)
|
|
515
|
+
def quote_column(column) = connection.quote_column_name(column)
|
|
516
|
+
|
|
517
|
+
def ensure_reference_types_match(source, column, target)
|
|
518
|
+
source_column = column_definition(source, column)
|
|
519
|
+
target_column = column_definition(target, connection.primary_key(target))
|
|
520
|
+
return if source_column.type == target_column.type && source_column.limit == target_column.limit
|
|
521
|
+
|
|
522
|
+
raise "Expected #{source}.#{column} to match #{target}.#{connection.primary_key(target)}"
|
|
523
|
+
end
|
|
524
|
+
|
|
525
|
+
def move_table(source, target)
|
|
526
|
+
<% if copy_mode? -%>
|
|
527
|
+
with_upgrade_safety { RubyLLM::Generators::UpgradeMigration.new.copy_table(source, target) }
|
|
528
|
+
<% else -%>
|
|
529
|
+
return if source == target || !table_exists?(source)
|
|
530
|
+
|
|
531
|
+
raise "Both #{source} and #{target} exist. Merge or remove one before retrying." if table_exists?(target)
|
|
532
|
+
|
|
533
|
+
with_upgrade_safety { rename_table source, target }
|
|
534
|
+
<% end -%>
|
|
535
|
+
end
|
|
536
|
+
|
|
537
|
+
<% if postgresql? -%>
|
|
538
|
+
def normalize_postgresql_json_columns(table, columns)
|
|
539
|
+
columns.each do |column_name|
|
|
540
|
+
column = column_definition(table, column_name)
|
|
541
|
+
next unless column&.type == :json
|
|
542
|
+
|
|
543
|
+
with_upgrade_safety do
|
|
544
|
+
change_column table, column_name, :jsonb, using: "#{quote_column(column_name)}::jsonb"
|
|
545
|
+
end
|
|
546
|
+
end
|
|
547
|
+
end
|
|
548
|
+
|
|
549
|
+
<% end -%>
|
|
550
|
+
<% unless mysql? -%>
|
|
551
|
+
def normalize_json_defaults(table, defaults)
|
|
552
|
+
defaults.each do |column_name, value|
|
|
553
|
+
column = column_definition(table, column_name)
|
|
554
|
+
next unless column&.default.nil?
|
|
555
|
+
|
|
556
|
+
with_upgrade_safety { change_column_default table, column_name, from: nil, to: value }
|
|
557
|
+
end
|
|
558
|
+
end
|
|
559
|
+
|
|
560
|
+
<% end -%>
|
|
561
|
+
def ensure_unique_index(table, columns)
|
|
562
|
+
return if valid_index_exists?(table, columns, unique: true)
|
|
563
|
+
|
|
564
|
+
validate_unique_values(table, columns)
|
|
565
|
+
remove_matching_indexes(table, columns)
|
|
566
|
+
add_upgrade_index(table, columns, unique: true)
|
|
567
|
+
end
|
|
568
|
+
|
|
569
|
+
def ensure_index_method(table, columns, using:)
|
|
570
|
+
return if valid_index_exists?(table, columns, using: using)
|
|
571
|
+
|
|
572
|
+
remove_matching_indexes(table, columns)
|
|
573
|
+
add_upgrade_index(table, columns, using: using)
|
|
574
|
+
end
|
|
575
|
+
|
|
576
|
+
def remove_matching_indexes(table, columns)
|
|
577
|
+
connection.indexes(table).each do |index|
|
|
578
|
+
remove_upgrade_index(table, index.name) if index.columns == Array(columns).map(&:to_s)
|
|
579
|
+
end
|
|
580
|
+
end
|
|
581
|
+
|
|
582
|
+
def remove_column_foreign_keys(table, column)
|
|
583
|
+
connection.foreign_keys(table).each do |key|
|
|
584
|
+
remove_foreign_key(table, key.to_table, column: column) if key.column == column.to_s
|
|
585
|
+
end
|
|
586
|
+
end
|
|
587
|
+
|
|
588
|
+
def valid_index_exists?(table, columns, unique: nil, using: nil)
|
|
589
|
+
matching = connection.indexes(table).select do |index|
|
|
590
|
+
index.columns == Array(columns).map(&:to_s) &&
|
|
591
|
+
(unique.nil? || index.unique == unique) &&
|
|
592
|
+
(using.nil? || index.using.to_s == using.to_s)
|
|
593
|
+
end
|
|
594
|
+
valid, invalid = matching.partition { |index| !index.respond_to?(:valid?) || index.valid? }
|
|
595
|
+
invalid.each { |index| remove_upgrade_index(table, index.name) }
|
|
596
|
+
valid.any?
|
|
597
|
+
end
|
|
598
|
+
|
|
599
|
+
def add_upgrade_index(table, columns, **options)
|
|
600
|
+
<% if postgresql? -%>
|
|
601
|
+
options[:algorithm] = :concurrently
|
|
602
|
+
<% end -%>
|
|
603
|
+
add_index table, columns, **options
|
|
604
|
+
end
|
|
605
|
+
|
|
606
|
+
def add_upgrade_indexes(table, indexes)
|
|
607
|
+
return if indexes.empty?
|
|
608
|
+
|
|
609
|
+
<% if mysql? -%>
|
|
610
|
+
with_upgrade_safety do
|
|
611
|
+
change_table table, bulk: true do |definition|
|
|
612
|
+
indexes.each { |columns, options| definition.index(columns, **options) }
|
|
613
|
+
end
|
|
614
|
+
end
|
|
615
|
+
<% else -%>
|
|
616
|
+
indexes.each { |columns, options| add_upgrade_index(table, columns, **options) }
|
|
617
|
+
<% end -%>
|
|
618
|
+
end
|
|
619
|
+
|
|
620
|
+
def remove_upgrade_index(table, name)
|
|
621
|
+
options = { name: name }
|
|
622
|
+
<% if postgresql? -%>
|
|
623
|
+
options[:algorithm] = :concurrently
|
|
624
|
+
<% end -%>
|
|
625
|
+
remove_index table, **options
|
|
626
|
+
end
|
|
627
|
+
|
|
628
|
+
def add_upgrade_foreign_key(table, target, column:)
|
|
629
|
+
<% if postgresql? -%>
|
|
630
|
+
add_foreign_key table, target, column: column, validate: false
|
|
631
|
+
validate_foreign_key table, target, column: column
|
|
632
|
+
<% elsif mysql? -%>
|
|
633
|
+
connection.disable_referential_integrity { add_foreign_key table, target, column: column }
|
|
634
|
+
<% else -%>
|
|
635
|
+
add_foreign_key table, target, column: column
|
|
636
|
+
<% end -%>
|
|
637
|
+
end
|
|
638
|
+
|
|
639
|
+
def reference_type_for(table)
|
|
640
|
+
column = column_definition(table, connection.primary_key(table))
|
|
641
|
+
raise "Could not determine the primary key type for #{table}" unless column
|
|
642
|
+
|
|
643
|
+
return :bigint if column.type == :integer && column.limit == 8
|
|
644
|
+
|
|
645
|
+
column.type
|
|
646
|
+
end
|
|
647
|
+
|
|
648
|
+
def with_upgrade_safety(&)
|
|
649
|
+
return safety_assured(&) if respond_to?(:safety_assured, true)
|
|
650
|
+
|
|
651
|
+
yield
|
|
652
|
+
end
|
|
653
|
+
|
|
654
|
+
def migration_record(table)
|
|
655
|
+
Class.new(ActiveRecord::Base) do
|
|
656
|
+
self.table_name = table.to_s
|
|
657
|
+
self.inheritance_column = :_type_disabled
|
|
658
|
+
end
|
|
659
|
+
end
|
|
660
|
+
end
|