ruby_llm 1.16.0 → 2.0.0.rc4
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 +108 -44
- 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/legacy_content_sql.rb +34 -0
- data/lib/generators/ruby_llm/upgrade/online_copy_migration/data.rb +341 -0
- data/lib/generators/ruby_llm/upgrade/online_copy_migration/journal.rb +171 -0
- data/lib/generators/ruby_llm/upgrade/online_copy_migration/verification.rb +197 -0
- data/lib/generators/ruby_llm/upgrade/online_copy_migration.rb +174 -0
- data/lib/generators/ruby_llm/upgrade/templates/backfill_v2_data.rb.tt +468 -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 +247 -0
- data/lib/generators/ruby_llm/upgrade/templates/prepare_v2_upgrade.rb.tt +662 -0
- data/lib/generators/ruby_llm/upgrade/templates/ruby_llm_upgrade.rb.tt +251 -0
- data/lib/generators/ruby_llm/upgrade/templates/upgrade_initializer.rb.tt +13 -0
- data/lib/generators/ruby_llm/upgrade/upgrade_generator.rb +188 -0
- data/lib/generators/ruby_llm/upgrade/upgrade_migration.rb +359 -0
- data/lib/ruby_llm/accounting/usage.rb +254 -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 +1066 -151
- data/lib/ruby_llm/aliases.json +291 -101
- data/lib/ruby_llm/attachment.rb +192 -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 +1131 -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 +242 -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 +76038 -34173
- 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 +543 -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 +110 -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 +484 -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 +63 -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 +59 -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 +694 -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 +91 -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 +53 -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 +466 -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 +67 -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 +51 -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 +126 -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 +344 -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,251 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLMUpgrade # :nodoc: all
|
|
4
|
+
TABLE = 'ruby_llm_v2_upgrades'
|
|
5
|
+
VERSION_COLUMN = 'ruby_llm_version'
|
|
6
|
+
OPERATIONS = %i[ask say ask_later add_message generate compact step complete run_tools cancel resume
|
|
7
|
+
approve deny with_model with_instructions cache_until_here reset_messages!].freeze
|
|
8
|
+
READ_OPERATIONS = %i[reload to_llm count_tokens].freeze
|
|
9
|
+
|
|
10
|
+
module Records
|
|
11
|
+
extend ActiveSupport::Concern
|
|
12
|
+
|
|
13
|
+
included do
|
|
14
|
+
class_attribute :ruby_llm_upgrade_kind, instance_writer: false
|
|
15
|
+
self.enumerate_columns_in_select_statements = true
|
|
16
|
+
default_scope { RubyLLMUpgrade.visible(self) }
|
|
17
|
+
after_initialize { @ruby_llm_upgrade_epoch = RubyLLMUpgrade.epoch }
|
|
18
|
+
around_save :guard_ruby_llm_upgrade
|
|
19
|
+
around_destroy :guard_ruby_llm_upgrade, prepend: true
|
|
20
|
+
before_save { RubyLLMUpgrade.prepare_model(self) if ruby_llm_upgrade_kind == :chat }
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
private
|
|
24
|
+
|
|
25
|
+
def guard_ruby_llm_upgrade(&)
|
|
26
|
+
RubyLLMUpgrade.guard(self, &)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
def install(chat:, message:, tool_call:)
|
|
32
|
+
if version == 2 && message.table_exists? && message.column_names.include?('ruby_llm_content')
|
|
33
|
+
message.alias_attribute :content, :ruby_llm_content
|
|
34
|
+
end
|
|
35
|
+
{ chat => :chat, message => :message, tool_call => :tool_call }.each do |model, kind|
|
|
36
|
+
next if model < Records
|
|
37
|
+
|
|
38
|
+
model.include Records
|
|
39
|
+
model.ruby_llm_upgrade_kind = kind
|
|
40
|
+
model.prepend guarded_operations(model, READ_OPERATIONS, claim: false)
|
|
41
|
+
end
|
|
42
|
+
return if chat.instance_variable_defined?(:@ruby_llm_upgrade_operations)
|
|
43
|
+
|
|
44
|
+
operations = guarded_operations(chat, OPERATIONS, claim: true)
|
|
45
|
+
%i[persist_usage_entry persist_new_message persist_message_completion].each do |name|
|
|
46
|
+
next unless chat.private_method_defined?(name)
|
|
47
|
+
|
|
48
|
+
operations.define_method(name) do |*args, **options, &block|
|
|
49
|
+
RubyLLMUpgrade.guard(self) { super(*args, **options, &block) }
|
|
50
|
+
end
|
|
51
|
+
operations.send(:private, name)
|
|
52
|
+
end
|
|
53
|
+
chat.prepend operations
|
|
54
|
+
chat.instance_variable_set(:@ruby_llm_upgrade_operations, operations)
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def visible(model)
|
|
58
|
+
return model.where(nil) unless available?
|
|
59
|
+
|
|
60
|
+
state = current_state
|
|
61
|
+
check_version(state)
|
|
62
|
+
return model.where(nil) if version == 2
|
|
63
|
+
|
|
64
|
+
settings = state.settings
|
|
65
|
+
return model.where(nil) unless ownership_column?(settings)
|
|
66
|
+
|
|
67
|
+
chats = records(settings.fetch('chat_table')).where(VERSION_COLUMN => 1)
|
|
68
|
+
case model.ruby_llm_upgrade_kind
|
|
69
|
+
when :chat
|
|
70
|
+
model.where(VERSION_COLUMN => 1)
|
|
71
|
+
when :message
|
|
72
|
+
model.where(settings.fetch('chat_foreign_key') => chats.select(chats.primary_key))
|
|
73
|
+
when :tool_call
|
|
74
|
+
messages = records(settings.fetch('message_table'))
|
|
75
|
+
.where(settings.fetch('chat_foreign_key') => chats.select(chats.primary_key))
|
|
76
|
+
model.where(settings.fetch('message_foreign_key') => messages.select(messages.primary_key))
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def prepare_model(chat)
|
|
81
|
+
return unless available?
|
|
82
|
+
|
|
83
|
+
state = current_state
|
|
84
|
+
settings = state.settings
|
|
85
|
+
return if version == 1 && settings['online'] && state.status == 'preparing'
|
|
86
|
+
if version == 1
|
|
87
|
+
return unless ActiveRecord::Base.connection.column_exists?(settings.fetch('chat_table'), :ruby_llm_model_id)
|
|
88
|
+
return unless ActiveRecord::Base.connection.table_exists?('ruby_llm_models')
|
|
89
|
+
end
|
|
90
|
+
ensure_model_reference(chat, settings)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
def guard(record, claim: true)
|
|
94
|
+
return yield unless available?
|
|
95
|
+
|
|
96
|
+
states = records(TABLE)
|
|
97
|
+
states.uncached do
|
|
98
|
+
states.transaction do
|
|
99
|
+
if ActiveRecord::Base.connection.adapter_name == 'SQLite' && claim
|
|
100
|
+
states.update_all('epoch = epoch')
|
|
101
|
+
end
|
|
102
|
+
state = states.lock(lock_clause).first!
|
|
103
|
+
check_version(state)
|
|
104
|
+
protect(record, state, claim: claim)
|
|
105
|
+
if record.instance_variable_get(:@ruby_llm_upgrade_epoch) != state[:epoch].to_i
|
|
106
|
+
raise ActiveRecord::ReadOnlyRecord, 'Reload the conversation after switching RubyLLM versions'
|
|
107
|
+
end
|
|
108
|
+
yield
|
|
109
|
+
end
|
|
110
|
+
end
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def epoch
|
|
114
|
+
available? ? current_state[:epoch].to_i : 0
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
private
|
|
118
|
+
|
|
119
|
+
def lock_clause
|
|
120
|
+
ActiveRecord::Base.connection.adapter_name == 'Mysql2' ? 'LOCK IN SHARE MODE' : 'FOR SHARE'
|
|
121
|
+
end
|
|
122
|
+
|
|
123
|
+
def guarded_operations(model, names, claim:)
|
|
124
|
+
Module.new do
|
|
125
|
+
names.each do |name|
|
|
126
|
+
next unless model.method_defined?(name)
|
|
127
|
+
|
|
128
|
+
define_method(name) do |*args, **options, &block|
|
|
129
|
+
RubyLLMUpgrade.guard(self, claim: claim) { nil }
|
|
130
|
+
super(*args, **options, &block)
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
134
|
+
end
|
|
135
|
+
|
|
136
|
+
def protect(record, state, claim:)
|
|
137
|
+
return if state.status == 'finalized'
|
|
138
|
+
|
|
139
|
+
settings = state.settings
|
|
140
|
+
return if version == 1 && !ownership_column?(settings)
|
|
141
|
+
|
|
142
|
+
owners(record, settings).each { |chat| protect_owner(chat, claim: claim) }
|
|
143
|
+
return unless claim && version == 2 && record.class.ruby_llm_upgrade_kind == :chat
|
|
144
|
+
|
|
145
|
+
record[VERSION_COLUMN] = 2
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def protect_owner(chat, claim:)
|
|
149
|
+
chat.lock!
|
|
150
|
+
if version == 1 && chat[VERSION_COLUMN] != 1
|
|
151
|
+
raise ActiveRecord::ReadOnlyRecord, 'This conversation contains RubyLLM 2.0 changes'
|
|
152
|
+
end
|
|
153
|
+
|
|
154
|
+
chat.update!(VERSION_COLUMN => 2) if claim && version == 2 && chat[VERSION_COLUMN] != 2
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def version
|
|
158
|
+
Gem::Version.new(RubyLLM::VERSION).segments.first >= 2 ? 2 : 1
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def available?
|
|
162
|
+
ActiveRecord::Base.connection.table_exists?(TABLE)
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def records(table)
|
|
166
|
+
@records ||= {}
|
|
167
|
+
model = @records[table] ||= Class.new(ActiveRecord::Base) do
|
|
168
|
+
self.table_name = table
|
|
169
|
+
self.inheritance_column = :_type_disabled
|
|
170
|
+
end
|
|
171
|
+
if !model.column_names.include?(VERSION_COLUMN) &&
|
|
172
|
+
ActiveRecord::Base.connection.column_exists?(table, VERSION_COLUMN)
|
|
173
|
+
model.reset_column_information
|
|
174
|
+
end
|
|
175
|
+
model
|
|
176
|
+
end
|
|
177
|
+
|
|
178
|
+
def current_state
|
|
179
|
+
records(TABLE).uncached { records(TABLE).first! }
|
|
180
|
+
end
|
|
181
|
+
|
|
182
|
+
def ownership_column?(settings)
|
|
183
|
+
ActiveRecord::Base.connection.column_exists?(settings.fetch('chat_table'), VERSION_COLUMN)
|
|
184
|
+
end
|
|
185
|
+
|
|
186
|
+
def check_version(state)
|
|
187
|
+
return if version == 1 && state.active_version == 1 && state.settings['online'] && state.status == 'preparing'
|
|
188
|
+
return if state.active_version == version && %w[active finalized].include?(state.status)
|
|
189
|
+
|
|
190
|
+
raise ActiveRecord::ReadOnlyRecord,
|
|
191
|
+
"RubyLLM #{version} cannot access conversations while the copy upgrade is " \
|
|
192
|
+
"#{state.status} on #{state.active_version}"
|
|
193
|
+
end
|
|
194
|
+
|
|
195
|
+
def ensure_model_reference(chat, settings)
|
|
196
|
+
if version == 1
|
|
197
|
+
legacy_id = chat[settings.fetch('model_foreign_key')]
|
|
198
|
+
legacy_models = records(settings.fetch('model_table'))
|
|
199
|
+
legacy = legacy_models.find_by(legacy_models.primary_key => legacy_id)
|
|
200
|
+
unless legacy
|
|
201
|
+
chat.errors.add(:model, 'must identify an existing legacy model')
|
|
202
|
+
raise ActiveRecord::RecordInvalid, chat
|
|
203
|
+
end
|
|
204
|
+
|
|
205
|
+
target = records('ruby_llm_models')
|
|
206
|
+
model = target.find_by(provider: legacy.provider, model_id: legacy.model_id) ||
|
|
207
|
+
target.create!(legacy.attributes.slice(*target.column_names).except(target.primary_key))
|
|
208
|
+
chat[:ruby_llm_model_id] = model.id
|
|
209
|
+
end
|
|
210
|
+
return if chat[:ruby_llm_model_id]
|
|
211
|
+
|
|
212
|
+
chat.errors.add(:model, 'must exist before saving the conversation')
|
|
213
|
+
raise ActiveRecord::RecordInvalid, chat
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def owners(record, settings)
|
|
217
|
+
chats = records(settings.fetch('chat_table'))
|
|
218
|
+
return chat_owner(record, chats) if record.class.ruby_llm_upgrade_kind == :chat
|
|
219
|
+
|
|
220
|
+
values = owner_attributes(record, settings)
|
|
221
|
+
ids = values.filter_map { |value| value[settings.fetch('chat_foreign_key')] }
|
|
222
|
+
raise ActiveRecord::ReadOnlyRecord, 'The conversation no longer exists' if ids.empty?
|
|
223
|
+
|
|
224
|
+
found = chats.where(chats.primary_key => ids).to_a
|
|
225
|
+
raise ActiveRecord::ReadOnlyRecord, 'The conversation no longer exists' unless found.length == ids.uniq.length
|
|
226
|
+
|
|
227
|
+
found
|
|
228
|
+
end
|
|
229
|
+
|
|
230
|
+
def chat_owner(record, chats)
|
|
231
|
+
found = chats.where(chats.primary_key => record.id).to_a
|
|
232
|
+
return found unless record.persisted? && found.empty?
|
|
233
|
+
|
|
234
|
+
raise ActiveRecord::ReadOnlyRecord, 'The conversation no longer exists'
|
|
235
|
+
end
|
|
236
|
+
|
|
237
|
+
def owner_attributes(record, settings)
|
|
238
|
+
values = [record.attributes]
|
|
239
|
+
if record.persisted?
|
|
240
|
+
stored = records(record.class.table_name).find(record.id)
|
|
241
|
+
values << stored.attributes
|
|
242
|
+
end
|
|
243
|
+
if record.class.ruby_llm_upgrade_kind == :tool_call
|
|
244
|
+
message_key = version == 1 ? settings.fetch('message_foreign_key') : 'message_id'
|
|
245
|
+
messages = records(settings.fetch('message_table'))
|
|
246
|
+
values = messages.where(messages.primary_key => values.map { |value| value[message_key] }).map(&:attributes)
|
|
247
|
+
end
|
|
248
|
+
values
|
|
249
|
+
end
|
|
250
|
+
end
|
|
251
|
+
end
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require Rails.root.join('app/models/concerns/ruby_llm_upgrade')
|
|
4
|
+
|
|
5
|
+
Rails.application.config.to_prepare do
|
|
6
|
+
require 'active_record/base'
|
|
7
|
+
tool_call = if Gem::Version.new(RubyLLM::VERSION).segments.first >= 2
|
|
8
|
+
RubyLLM::ActiveRecord::ToolCall
|
|
9
|
+
else
|
|
10
|
+
<%= v1_model_name(:tool_call) %>
|
|
11
|
+
end
|
|
12
|
+
RubyLLMUpgrade.install(chat: <%= chat_model_name %>, message: <%= message_model_name %>, tool_call: tool_call)
|
|
13
|
+
end
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'rails/generators'
|
|
4
|
+
require 'rails/generators/active_record'
|
|
5
|
+
require_relative '../generator_helpers'
|
|
6
|
+
|
|
7
|
+
module RubyLLM
|
|
8
|
+
module Generators
|
|
9
|
+
# Upgrades the schema generated by RubyLLM 1.16 to RubyLLM 2.0.
|
|
10
|
+
class UpgradeGenerator < Rails::Generators::Base
|
|
11
|
+
include Rails::Generators::Migration
|
|
12
|
+
include RubyLLM::Generators::GeneratorHelpers
|
|
13
|
+
|
|
14
|
+
MODEL_MAPPING_DEFAULTS = {
|
|
15
|
+
chat: 'Chat',
|
|
16
|
+
message: 'Message',
|
|
17
|
+
model: 'Model',
|
|
18
|
+
tool_call: 'ToolCall'
|
|
19
|
+
}.freeze
|
|
20
|
+
MODEL_MAPPING_TYPES = MODEL_MAPPING_DEFAULTS.keys.map(&:to_s).freeze
|
|
21
|
+
|
|
22
|
+
namespace 'ruby_llm:upgrade'
|
|
23
|
+
source_root File.expand_path('templates', __dir__)
|
|
24
|
+
|
|
25
|
+
class_option :phase, type: :string, enum: %w[prepare backfill finish cleanup],
|
|
26
|
+
desc: 'Generate one upgrade phase; cleanup runs in a later deployment'
|
|
27
|
+
class_option :mode, type: :string, enum: %w[rename copy], default: 'rename',
|
|
28
|
+
desc: 'Copy prepares and backfills online, with protected 1.16 rollback until cleanup'
|
|
29
|
+
class_option :discard_incomplete_tool_calls, type: :boolean, default: false,
|
|
30
|
+
desc: 'Discard all incomplete legacy tool calls at copy finish'
|
|
31
|
+
|
|
32
|
+
argument :model_mappings,
|
|
33
|
+
type: :array,
|
|
34
|
+
default: [],
|
|
35
|
+
banner: 'chat:ChatName message:MessageName model:ModelName tool_call:ToolCallName'
|
|
36
|
+
|
|
37
|
+
desc 'Upgrades a RubyLLM 1.16 Rails schema to 2.0'
|
|
38
|
+
|
|
39
|
+
def self.next_migration_number(dirname)
|
|
40
|
+
::ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def create_migration_files
|
|
44
|
+
validate_cleanup_options
|
|
45
|
+
parse_model_mappings(allowed_types: MODEL_MAPPING_TYPES, defaults: MODEL_MAPPING_DEFAULTS)
|
|
46
|
+
say_status :models, resolved_model_mappings
|
|
47
|
+
|
|
48
|
+
if copy_mode? && !options[:phase].in?(%w[backfill finish cleanup])
|
|
49
|
+
template 'ruby_llm_upgrade.rb.tt', 'app/models/concerns/ruby_llm_upgrade.rb'
|
|
50
|
+
template 'upgrade_initializer.rb.tt', 'config/initializers/ruby_llm_upgrade.rb'
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
phases = options[:phase] ? [options[:phase]] : %w[prepare backfill finish]
|
|
54
|
+
phases.each do |phase|
|
|
55
|
+
template = phase == 'backfill' ? 'backfill_v2_data' : "#{phase}_v2_upgrade"
|
|
56
|
+
migration = phase == 'backfill' ? 'backfill_ruby_llm_v2_data' : "#{phase}_ruby_llm_v2_upgrade"
|
|
57
|
+
migration_template "#{template}.rb.tt", "db/migrate/#{migration}.rb"
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def show_next_steps
|
|
62
|
+
say_status :success, 'Upgrade prepared!', :green
|
|
63
|
+
if copy_mode?
|
|
64
|
+
say <<~INSTRUCTIONS
|
|
65
|
+
|
|
66
|
+
Copy mode retains the legacy tables and a separate chat model reference.
|
|
67
|
+
Install the generated RubyLLMUpgrade concern and initializer in BOTH the
|
|
68
|
+
running 1.16 build and the 2.0 build BEFORE running prepare. Restart
|
|
69
|
+
all affected 1.16 processes so they load the guards and explicit column
|
|
70
|
+
selects. The guards protect whole conversations
|
|
71
|
+
changed by 2.0. Direct SQL, bulk updates/deletes and attachment purges
|
|
72
|
+
bypass these guards; review those application paths before upgrading.
|
|
73
|
+
|
|
74
|
+
Rehearse prepare, backfill and finish on a production database copy.
|
|
75
|
+
Run prepare and backfill from the 2.0 build while 1.16 serves traffic.
|
|
76
|
+
Stop affected traffic, workers, scheduled jobs and retries BEFORE finish.
|
|
77
|
+
Finish catches up intervening writes, validates and activates 2.0.
|
|
78
|
+
Then load models and restart the 2.0 application.
|
|
79
|
+
Keep the old model/tool-call classes in the 1.16 build.
|
|
80
|
+
|
|
81
|
+
PostgreSQL, MySQL and SQLite support this workflow. Schema changes can
|
|
82
|
+
block writes; SQLite also serializes backfill and app writes.
|
|
83
|
+
Use a direct connection or session-mode pool for copy migrations.
|
|
84
|
+
db:migrate runs ALL pending phases. Stop at the backfill timestamp to
|
|
85
|
+
defer finish, or pause AI before running all three together.
|
|
86
|
+
|
|
87
|
+
With all affected processes stopped, use the 2.0 build to run:
|
|
88
|
+
bin/rails ruby_llm:upgrade:rollback # activates the protected 1.16 view
|
|
89
|
+
bin/rails ruby_llm:upgrade:resume # reconciles 1.16 writes, activates 2.0
|
|
90
|
+
|
|
91
|
+
Finish pending tool calls, approvals and batches before switching.
|
|
92
|
+
Once rollback is no longer needed, run ruby_llm:upgrade:finalize, then
|
|
93
|
+
generate --mode copy --phase cleanup in a later deployment. Keep the
|
|
94
|
+
compatibility files until cleanup succeeds; remove them afterward.
|
|
95
|
+
|
|
96
|
+
Pass the same --mode and model mappings to every generator invocation.
|
|
97
|
+
Full guide: https://rubyllm.com/next/upgrading/
|
|
98
|
+
|
|
99
|
+
INSTRUCTIONS
|
|
100
|
+
return
|
|
101
|
+
end
|
|
102
|
+
say <<~INSTRUCTIONS
|
|
103
|
+
|
|
104
|
+
These migrations expect the schema generated by RubyLLM 1.16. They move
|
|
105
|
+
RubyLLM's model and tool-call records, preserve message content and usage,
|
|
106
|
+
and retain the 1.x message columns until a later cleanup deployment.
|
|
107
|
+
|
|
108
|
+
Next steps:
|
|
109
|
+
1. Review the generated migrations
|
|
110
|
+
2. Rehearse them on a recent production snapshot
|
|
111
|
+
3. Stop affected web processes, workers, scheduled jobs, and retries
|
|
112
|
+
4. Run preparation, backfill, and finish before reopening traffic
|
|
113
|
+
5. Run: bin/rails ruby_llm:load_models
|
|
114
|
+
6. Keep only the current declarations in your application models:
|
|
115
|
+
#{chat_model_path}: #{acts_as_chat_declaration}
|
|
116
|
+
#{message_model_path}: #{acts_as_message_declaration}
|
|
117
|
+
7. Remove the 1.x model and tool-call classes, config.model_registry_class,
|
|
118
|
+
and config.use_new_acts_as
|
|
119
|
+
8. Update 1.x API calls using the upgrade guide
|
|
120
|
+
|
|
121
|
+
Generate one phase with --phase prepare, backfill, or finish when the
|
|
122
|
+
backfill needs to run outside your deployment's release phase.
|
|
123
|
+
After verifying the upgrade, generate --phase cleanup in a later release.
|
|
124
|
+
Pass the same model mappings to every invocation.
|
|
125
|
+
|
|
126
|
+
The migrations are irreversible. Retained columns do not make a code
|
|
127
|
+
rollback safe; recovery requires the previous database and application.
|
|
128
|
+
|
|
129
|
+
Full upgrade guide:
|
|
130
|
+
https://rubyllm.com/next/upgrading/
|
|
131
|
+
|
|
132
|
+
INSTRUCTIONS
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
private
|
|
136
|
+
|
|
137
|
+
def copy_mode? = options[:mode] == 'copy'
|
|
138
|
+
def discard_incomplete_tool_calls? = options[:discard_incomplete_tool_calls]
|
|
139
|
+
|
|
140
|
+
def validate_cleanup_options
|
|
141
|
+
return unless discard_incomplete_tool_calls?
|
|
142
|
+
return if copy_mode? && options[:phase].in?([nil, 'finish'])
|
|
143
|
+
|
|
144
|
+
raise Thor::Error, 'Discard incomplete tool calls only with --mode copy and the finish phase'
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
def copy_upgrade_settings
|
|
148
|
+
{
|
|
149
|
+
chat_table: chat_table_name, message_table: message_table_name,
|
|
150
|
+
model_table: v1_model_table_name, tool_call_table: v1_tool_call_table_name,
|
|
151
|
+
chat_class: chat_model_name, message_class: message_model_name,
|
|
152
|
+
model_foreign_key: v1_model_foreign_key, tool_call_foreign_key: v1_tool_call_foreign_key,
|
|
153
|
+
chat_foreign_key: chat_foreign_key, message_foreign_key: message_foreign_key
|
|
154
|
+
}
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
def prepare_migration_class_name = 'prepare_ruby_llm_v2_upgrade'.camelize
|
|
158
|
+
def backfill_migration_class_name = 'backfill_ruby_llm_v2_data'.camelize
|
|
159
|
+
def finish_migration_class_name = 'finish_ruby_llm_v2_upgrade'.camelize
|
|
160
|
+
def cleanup_migration_class_name = 'cleanup_ruby_llm_v2_upgrade'.camelize
|
|
161
|
+
|
|
162
|
+
def resolved_model_mappings
|
|
163
|
+
@model_names.map do |type, model_name|
|
|
164
|
+
"#{type}:#{model_name} -> #{table_name_for(model_name)}"
|
|
165
|
+
end.join(', ')
|
|
166
|
+
end
|
|
167
|
+
|
|
168
|
+
def v1_model_name(type)
|
|
169
|
+
@model_names.fetch(type)
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
def v1_model_table_name = table_name_for(v1_model_name(:model))
|
|
173
|
+
def v1_tool_call_table_name = table_name_for(v1_model_name(:tool_call))
|
|
174
|
+
def v1_model_foreign_key = "#{v1_model_table_name.singularize}_id"
|
|
175
|
+
def v1_tool_call_foreign_key = "#{v1_tool_call_table_name.singularize}_id"
|
|
176
|
+
def message_foreign_key = "#{message_table_name.singularize}_id"
|
|
177
|
+
def chat_foreign_key = "#{chat_table_name.singularize}_id"
|
|
178
|
+
|
|
179
|
+
def chat_model_path
|
|
180
|
+
"app/models/#{chat_model_name.underscore}.rb"
|
|
181
|
+
end
|
|
182
|
+
|
|
183
|
+
def message_model_path
|
|
184
|
+
"app/models/#{message_model_name.underscore}.rb"
|
|
185
|
+
end
|
|
186
|
+
end
|
|
187
|
+
end
|
|
188
|
+
end
|