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
|
@@ -4,153 +4,136 @@ require 'active_support/concern'
|
|
|
4
4
|
require 'active_support/inflector'
|
|
5
5
|
|
|
6
6
|
module RubyLLM
|
|
7
|
+
# Rails integration brings the RubyLLM API to Active Record models, with
|
|
8
|
+
# Active Storage attachments and a persisted message lifecycle for Hotwire
|
|
9
|
+
# streaming and background jobs. Applications own chats and messages;
|
|
10
|
+
# RubyLLM owns the supporting usage, tool-call, registry, and batch records.
|
|
7
11
|
module ActiveRecord
|
|
8
|
-
# Adds
|
|
12
|
+
# Adds the RubyLLM conversation API to application-owned records.
|
|
13
|
+
#
|
|
14
|
+
# class Chat < ApplicationRecord
|
|
15
|
+
# acts_as_chat
|
|
16
|
+
# end
|
|
17
|
+
#
|
|
18
|
+
# class Message < ApplicationRecord
|
|
19
|
+
# acts_as_message
|
|
20
|
+
# has_many_attached :attachments
|
|
21
|
+
# end
|
|
22
|
+
#
|
|
23
|
+
# chat = Chat.create!(model: "gpt-5.6-luna")
|
|
24
|
+
# response = chat.ask "Summarize this report.", with: report.document
|
|
25
|
+
# response.content
|
|
26
|
+
# chat.cost.total
|
|
27
|
+
#
|
|
28
|
+
# The install generator creates the required schema. The chat_ui
|
|
29
|
+
# generator adds controllers, an Active Job, and Turbo Stream views.
|
|
30
|
+
#
|
|
9
31
|
module ActsAs
|
|
10
32
|
extend ActiveSupport::Concern
|
|
11
33
|
|
|
12
|
-
def self.included(base)
|
|
34
|
+
def self.included(base) # :nodoc:
|
|
13
35
|
super
|
|
14
|
-
RubyLLM.config.
|
|
36
|
+
RubyLLM.config.model_registry_store ||= RubyLLM::ActiveRecord::Model
|
|
37
|
+
RubyLLM.config.batch_store ||= RubyLLM::ActiveRecord::Batch
|
|
15
38
|
end
|
|
16
39
|
|
|
40
|
+
##
|
|
41
|
+
# :singleton-method: acts_as_chat
|
|
42
|
+
# :call-seq:
|
|
43
|
+
# acts_as_chat(messages: :messages, message_class: nil, messages_foreign_key: nil)
|
|
44
|
+
#
|
|
45
|
+
# Turns an application record into a persisted RubyLLM chat.
|
|
46
|
+
#
|
|
47
|
+
# class Chat < ApplicationRecord
|
|
48
|
+
# acts_as_chat
|
|
49
|
+
# end
|
|
50
|
+
#
|
|
51
|
+
# Adds the RubyLLM::Chat API (see ChatMethods), a +has_many+
|
|
52
|
+
# association to the messages, a +belongs_to :model+ association to
|
|
53
|
+
# the registry row, and a +has_many :ruby_llm_usages+ association over
|
|
54
|
+
# the attempts the chat paid for.
|
|
55
|
+
#
|
|
56
|
+
# Options:
|
|
57
|
+
#
|
|
58
|
+
# [+messages:+] Name of the messages association. Defaults to +:messages+.
|
|
59
|
+
# [+message_class:+] Class of the messages. Inferred from the association name.
|
|
60
|
+
# [+messages_foreign_key:+] Foreign key on the messages table. Inferred by Rails.
|
|
61
|
+
|
|
62
|
+
##
|
|
63
|
+
# :singleton-method: acts_as_message
|
|
64
|
+
# :call-seq:
|
|
65
|
+
# acts_as_message(chat: :chat, chat_class: nil, chat_foreign_key: nil, touch_chat: false)
|
|
66
|
+
#
|
|
67
|
+
# Turns an application record into a persisted RubyLLM message.
|
|
68
|
+
#
|
|
69
|
+
# class Message < ApplicationRecord
|
|
70
|
+
# acts_as_message
|
|
71
|
+
# end
|
|
72
|
+
#
|
|
73
|
+
# Adds the RubyLLM::Message readers (see MessageMethods), a
|
|
74
|
+
# +belongs_to+ association to the chat, and the tool-call and usage
|
|
75
|
+
# associations behind +tool_calls+, +tokens+, and +cost+.
|
|
76
|
+
#
|
|
77
|
+
# Options:
|
|
78
|
+
#
|
|
79
|
+
# [+chat:+] Name of the chat association. Defaults to +:chat+.
|
|
80
|
+
# [+chat_class:+] Class of the chat. Inferred from the association name.
|
|
81
|
+
# [+chat_foreign_key:+] Foreign key on this table. Inferred by Rails.
|
|
82
|
+
# [+touch_chat:+] Touch the chat when a message is saved. Defaults to +false+.
|
|
83
|
+
|
|
17
84
|
class_methods do # rubocop:disable Metrics/BlockLength
|
|
18
|
-
def acts_as_chat(messages: :messages, message_class: nil, messages_foreign_key: nil
|
|
19
|
-
model: :model, model_class: nil, model_foreign_key: nil)
|
|
85
|
+
def acts_as_chat(messages: :messages, message_class: nil, messages_foreign_key: nil)
|
|
20
86
|
include RubyLLM::ActiveRecord::ChatMethods
|
|
21
87
|
|
|
22
|
-
class_attribute :messages_association_name, :
|
|
23
|
-
|
|
88
|
+
class_attribute :messages_association_name, :message_class
|
|
24
89
|
self.messages_association_name = messages
|
|
25
|
-
self.model_association_name = model
|
|
26
90
|
self.message_class = (message_class || messages.to_s.classify).to_s
|
|
27
|
-
self.model_class = (model_class || model.to_s.classify).to_s
|
|
28
91
|
|
|
29
92
|
has_many messages,
|
|
30
|
-
-> { order(created_at
|
|
93
|
+
-> { order(:created_at, :id) },
|
|
31
94
|
class_name: self.message_class,
|
|
32
95
|
foreign_key: messages_foreign_key,
|
|
33
96
|
dependent: :destroy
|
|
34
97
|
|
|
35
|
-
belongs_to model,
|
|
36
|
-
class_name:
|
|
37
|
-
foreign_key:
|
|
98
|
+
belongs_to :model,
|
|
99
|
+
class_name: 'RubyLLM::ActiveRecord::Model',
|
|
100
|
+
foreign_key: :ruby_llm_model_id,
|
|
38
101
|
optional: true
|
|
39
102
|
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
send(model_association_name)
|
|
46
|
-
end
|
|
47
|
-
|
|
48
|
-
define_method :'model_association=' do |value|
|
|
49
|
-
send("#{model_association_name}=", value)
|
|
50
|
-
end
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
def acts_as_model(chats: :chats, chat_class: nil, chats_foreign_key: nil)
|
|
54
|
-
include RubyLLM::ActiveRecord::ModelMethods
|
|
55
|
-
|
|
56
|
-
class_attribute :chats_association_name, :chat_class
|
|
57
|
-
|
|
58
|
-
self.chats_association_name = chats
|
|
59
|
-
self.chat_class = (chat_class || chats.to_s.classify).to_s
|
|
60
|
-
|
|
61
|
-
validates :model_id, presence: true, uniqueness: { scope: :provider }
|
|
62
|
-
validates :provider, presence: true
|
|
63
|
-
validates :name, presence: true
|
|
64
|
-
|
|
65
|
-
has_many chats, class_name: self.chat_class, foreign_key: chats_foreign_key
|
|
66
|
-
|
|
67
|
-
define_method :chats_association do
|
|
68
|
-
send(chats_association_name)
|
|
69
|
-
end
|
|
103
|
+
has_many :ruby_llm_usages,
|
|
104
|
+
-> { chronological },
|
|
105
|
+
as: :chat,
|
|
106
|
+
class_name: 'RubyLLM::ActiveRecord::Usage',
|
|
107
|
+
dependent: :destroy
|
|
70
108
|
end
|
|
71
109
|
|
|
72
|
-
def acts_as_message(chat: :chat, chat_class: nil, chat_foreign_key: nil, touch_chat: false
|
|
73
|
-
tool_calls: :tool_calls, tool_call_class: nil, tool_calls_foreign_key: nil,
|
|
74
|
-
model: :model, model_class: nil, model_foreign_key: nil)
|
|
110
|
+
def acts_as_message(chat: :chat, chat_class: nil, chat_foreign_key: nil, touch_chat: false)
|
|
75
111
|
include RubyLLM::ActiveRecord::MessageMethods
|
|
76
112
|
|
|
77
|
-
class_attribute :chat_association_name, :
|
|
78
|
-
:chat_class, :tool_call_class, :model_class
|
|
79
|
-
|
|
113
|
+
class_attribute :chat_association_name, :chat_class
|
|
80
114
|
self.chat_association_name = chat
|
|
81
|
-
self.tool_calls_association_name = tool_calls
|
|
82
|
-
self.model_association_name = model
|
|
83
115
|
self.chat_class = (chat_class || chat.to_s.classify).to_s
|
|
84
|
-
self.tool_call_class = (tool_call_class || tool_calls.to_s.classify).to_s
|
|
85
|
-
self.model_class = (model_class || model.to_s.classify).to_s
|
|
86
116
|
|
|
87
117
|
belongs_to chat,
|
|
88
118
|
class_name: self.chat_class,
|
|
89
119
|
foreign_key: chat_foreign_key,
|
|
90
120
|
touch: touch_chat
|
|
91
121
|
|
|
92
|
-
has_many
|
|
93
|
-
|
|
94
|
-
|
|
122
|
+
has_many :ruby_llm_tool_calls,
|
|
123
|
+
as: :message,
|
|
124
|
+
class_name: 'RubyLLM::ActiveRecord::ToolCall',
|
|
95
125
|
dependent: :destroy
|
|
96
126
|
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
optional: true
|
|
101
|
-
|
|
102
|
-
has_many :tool_results,
|
|
103
|
-
through: tool_calls,
|
|
104
|
-
source: :result,
|
|
105
|
-
class_name: name
|
|
106
|
-
|
|
107
|
-
belongs_to model,
|
|
108
|
-
class_name: self.model_class,
|
|
109
|
-
foreign_key: model_foreign_key,
|
|
110
|
-
optional: true
|
|
111
|
-
|
|
112
|
-
delegate :tool_call?, :tool_result?, to: :to_llm
|
|
113
|
-
|
|
114
|
-
define_method :chat_association do
|
|
115
|
-
send(chat_association_name)
|
|
116
|
-
end
|
|
117
|
-
|
|
118
|
-
define_method :tool_calls_association do
|
|
119
|
-
send(tool_calls_association_name)
|
|
120
|
-
end
|
|
121
|
-
|
|
122
|
-
define_method :model_association do
|
|
123
|
-
send(model_association_name)
|
|
124
|
-
end
|
|
125
|
-
end
|
|
126
|
-
|
|
127
|
-
def acts_as_tool_call(message: :message, message_class: nil, message_foreign_key: nil, # rubocop:disable Metrics/ParameterLists
|
|
128
|
-
result: :result, result_class: nil, result_foreign_key: nil)
|
|
129
|
-
include RubyLLM::ActiveRecord::ToolCallMethods
|
|
130
|
-
|
|
131
|
-
class_attribute :message_association_name, :result_association_name, :message_class, :result_class
|
|
132
|
-
|
|
133
|
-
self.message_association_name = message
|
|
134
|
-
self.result_association_name = result
|
|
135
|
-
self.message_class = (message_class || message.to_s.classify).to_s
|
|
136
|
-
self.result_class = (result_class || self.message_class).to_s
|
|
137
|
-
|
|
138
|
-
belongs_to message,
|
|
139
|
-
class_name: self.message_class,
|
|
140
|
-
foreign_key: message_foreign_key
|
|
141
|
-
|
|
142
|
-
has_one result,
|
|
143
|
-
class_name: self.result_class,
|
|
144
|
-
foreign_key: result_foreign_key,
|
|
127
|
+
has_one :ruby_llm_parent_tool_call,
|
|
128
|
+
as: :result,
|
|
129
|
+
class_name: 'RubyLLM::ActiveRecord::ToolCall',
|
|
145
130
|
dependent: :nullify
|
|
146
131
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
send(result_association_name)
|
|
153
|
-
end
|
|
132
|
+
has_many :ruby_llm_usages,
|
|
133
|
+
-> { chronological },
|
|
134
|
+
as: :message,
|
|
135
|
+
class_name: 'RubyLLM::ActiveRecord::Usage',
|
|
136
|
+
dependent: :nullify
|
|
154
137
|
end
|
|
155
138
|
end
|
|
156
139
|
end
|
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'active_support/core_ext/object/blank'
|
|
4
|
+
require 'tempfile'
|
|
5
|
+
|
|
6
|
+
module RubyLLM
|
|
7
|
+
module ActiveRecord
|
|
8
|
+
module AttachmentHelpers # :nodoc:
|
|
9
|
+
private
|
|
10
|
+
|
|
11
|
+
def persist_content(message_record, attachments)
|
|
12
|
+
return unless message_record.respond_to?(:attachments)
|
|
13
|
+
|
|
14
|
+
attachables = prepare_for_active_storage(attachments)
|
|
15
|
+
message_record.attachments.attach(attachables) if attachables.any?
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def prepare_for_active_storage(attachments)
|
|
19
|
+
Support::Utils.to_safe_array(attachments).filter_map do |attachment|
|
|
20
|
+
case attachment
|
|
21
|
+
when ActionDispatch::Http::UploadedFile, ActiveStorage::Blob
|
|
22
|
+
attachment
|
|
23
|
+
when ActiveStorage::Attachment, ActiveStorage::Attached::One, ActiveStorage::Attached::Many
|
|
24
|
+
active_storage_blobs(attachment)
|
|
25
|
+
when Hash
|
|
26
|
+
attachment.values.map { |v| prepare_for_active_storage(v) }
|
|
27
|
+
else
|
|
28
|
+
convert_to_active_storage_format(attachment)
|
|
29
|
+
end
|
|
30
|
+
end.flatten.compact
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def convert_to_active_storage_format(source)
|
|
34
|
+
return if source.blank?
|
|
35
|
+
|
|
36
|
+
attachment = source.is_a?(RubyLLM::Attachment) ? source : RubyLLM::Attachment.new(source)
|
|
37
|
+
|
|
38
|
+
if attachment.active_storage?
|
|
39
|
+
active_storage_blobs(attachment.source)
|
|
40
|
+
else
|
|
41
|
+
{
|
|
42
|
+
io: StringIO.new(attachment.content),
|
|
43
|
+
filename: attachment.filename,
|
|
44
|
+
content_type: attachment.mime_type
|
|
45
|
+
}
|
|
46
|
+
end
|
|
47
|
+
rescue StandardError => e
|
|
48
|
+
RubyLLM.logger.warn "Failed to process attachment #{source}: #{e.message}"
|
|
49
|
+
nil
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def active_storage_blobs(attachment)
|
|
53
|
+
case attachment
|
|
54
|
+
when ActiveStorage::Blob then attachment
|
|
55
|
+
when ActiveStorage::Attachment, ActiveStorage::Attached::One then attachment.blob
|
|
56
|
+
when ActiveStorage::Attached::Many then attachment.blobs
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
def plain_text_content(content_value)
|
|
61
|
+
return content_value.to_plain_text if content_value.respond_to?(:to_plain_text)
|
|
62
|
+
|
|
63
|
+
content_value
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def action_text_attachment_sources(content_value)
|
|
67
|
+
return [] unless content_value.respond_to?(:body)
|
|
68
|
+
|
|
69
|
+
body = content_value.body
|
|
70
|
+
return [] unless body.respond_to?(:attachables)
|
|
71
|
+
|
|
72
|
+
body.attachables.flat_map { |attachable| action_text_attachable_sources(attachable) }.compact
|
|
73
|
+
end
|
|
74
|
+
|
|
75
|
+
def action_text_attachable_sources(attachable)
|
|
76
|
+
source = active_storage_blobs(attachable)
|
|
77
|
+
source ||= attachable.blob if attachable.respond_to?(:blob)
|
|
78
|
+
|
|
79
|
+
Support::Utils.to_safe_array(source)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def content_attachments?(action_text_attachments)
|
|
83
|
+
action_text_attachments.any? || active_storage_attachments?
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def active_storage_attachments?
|
|
87
|
+
respond_to?(:attachments) && attachments.attached?
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
def collect_attachments(action_text_attachments)
|
|
91
|
+
list = action_text_attachments.map { |source| RubyLLM::Attachment.new(source) }
|
|
92
|
+
return list unless active_storage_attachments?
|
|
93
|
+
|
|
94
|
+
list + attachment_sources.map { |attachment, attachable| stored_attachment(attachment, attachable) }
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
def attachment_sources
|
|
98
|
+
change = pending_attachment_change
|
|
99
|
+
return attachments_with_blobs.map { |attachment| [attachment, nil] } unless pending_attachment_change?(change)
|
|
100
|
+
|
|
101
|
+
change.attachments.zip(change.attachables)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def attachments_with_blobs
|
|
105
|
+
records = attachments.to_a
|
|
106
|
+
::ActiveRecord::Associations::Preloader.new(records: records, associations: :blob).call
|
|
107
|
+
records
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
def pending_attachment_change
|
|
111
|
+
attachment_changes['attachments'] if respond_to?(:attachment_changes)
|
|
112
|
+
end
|
|
113
|
+
|
|
114
|
+
def pending_attachment_change?(change)
|
|
115
|
+
change.respond_to?(:attachments) && change.respond_to?(:attachables)
|
|
116
|
+
end
|
|
117
|
+
|
|
118
|
+
def stored_attachment(attachment, attachable)
|
|
119
|
+
if pending_upload_attachable?(attachable)
|
|
120
|
+
pending_upload_attachment(attachable)
|
|
121
|
+
else
|
|
122
|
+
tempfile = download_attachment(attachment)
|
|
123
|
+
RubyLLM::Attachment.new(tempfile, filename: attachment.filename.to_s)
|
|
124
|
+
end
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
def pending_upload_attachable?(attachable)
|
|
128
|
+
return false if attachable.nil? || attachable.is_a?(String)
|
|
129
|
+
return false if instance_of_class?(attachable, 'ActiveStorage::Blob')
|
|
130
|
+
|
|
131
|
+
uploaded_file?(attachable) || active_storage_upload_hash?(attachable) ||
|
|
132
|
+
attachable.is_a?(File) || pathname?(attachable)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
def uploaded_file?(attachable)
|
|
136
|
+
instance_of_class?(attachable, 'ActionDispatch::Http::UploadedFile') ||
|
|
137
|
+
instance_of_class?(attachable, 'Rack::Test::UploadedFile')
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def active_storage_upload_hash?(attachable)
|
|
141
|
+
attachable.is_a?(Hash) && attachment_hash_io(attachable).present?
|
|
142
|
+
end
|
|
143
|
+
|
|
144
|
+
def pathname?(attachable)
|
|
145
|
+
defined?(Pathname) && attachable.is_a?(Pathname)
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
def pending_upload_attachment(attachable)
|
|
149
|
+
if attachable.is_a?(Hash)
|
|
150
|
+
RubyLLM::Attachment.new(attachment_hash_io(attachable), filename: attachment_hash_filename(attachable))
|
|
151
|
+
else
|
|
152
|
+
RubyLLM::Attachment.new(attachable)
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
def attachment_hash_io(attachable)
|
|
157
|
+
attachable[:io] || attachable['io']
|
|
158
|
+
end
|
|
159
|
+
|
|
160
|
+
def attachment_hash_filename(attachable)
|
|
161
|
+
filename = attachable[:filename] || attachable['filename']
|
|
162
|
+
filename&.to_s
|
|
163
|
+
end
|
|
164
|
+
|
|
165
|
+
def instance_of_class?(object, class_name)
|
|
166
|
+
Object.const_get(class_name).then { |klass| object.is_a?(klass) }
|
|
167
|
+
rescue NameError
|
|
168
|
+
false
|
|
169
|
+
end
|
|
170
|
+
|
|
171
|
+
def download_attachment(attachment)
|
|
172
|
+
ext = File.extname(attachment.filename.to_s)
|
|
173
|
+
basename = File.basename(attachment.filename.to_s, ext)
|
|
174
|
+
tempfile = Tempfile.new([basename, ext])
|
|
175
|
+
tempfile.binmode
|
|
176
|
+
|
|
177
|
+
attachment.download { |chunk| tempfile.write(chunk) }
|
|
178
|
+
|
|
179
|
+
tempfile.flush
|
|
180
|
+
tempfile.rewind
|
|
181
|
+
@_tempfiles << tempfile
|
|
182
|
+
tempfile
|
|
183
|
+
end
|
|
184
|
+
end
|
|
185
|
+
end
|
|
186
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
# RubyLLM's private persistence for provider-side batches.
|
|
6
|
+
class Batch < ::ActiveRecord::Base # :nodoc:
|
|
7
|
+
self.table_name = 'ruby_llm_batches'
|
|
8
|
+
|
|
9
|
+
STATUSES = %w[pending succeeded failed cancelled].freeze
|
|
10
|
+
|
|
11
|
+
validates :provider_batch_id, :provider, :status, presence: true
|
|
12
|
+
validates :status, inclusion: { in: STATUSES }
|
|
13
|
+
|
|
14
|
+
class << self
|
|
15
|
+
def persist(batch, chats)
|
|
16
|
+
records = Array(chats)
|
|
17
|
+
return unless table_exists?
|
|
18
|
+
return unless persistable_chats?(records)
|
|
19
|
+
|
|
20
|
+
create!(
|
|
21
|
+
provider_batch_id: batch.id,
|
|
22
|
+
provider: batch.provider,
|
|
23
|
+
status: batch.status,
|
|
24
|
+
raw_status: batch.raw_status,
|
|
25
|
+
completed: batch.complete?,
|
|
26
|
+
request_counts: batch.request_counts,
|
|
27
|
+
reported_cost: batch.reported_cost&.to_h,
|
|
28
|
+
batch_protocol: batch.batch_protocol,
|
|
29
|
+
chat_type: records.first.class.polymorphic_name,
|
|
30
|
+
chat_ids: records.map(&:id)
|
|
31
|
+
)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def fetch(id, provider: nil, context: nil)
|
|
35
|
+
find_record(id, provider:)&.to_llm(context:, store: self)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def sync(batch)
|
|
39
|
+
find_record(batch.id, provider: batch.provider)&.sync_from(batch)
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
private
|
|
43
|
+
|
|
44
|
+
def find_record(id, provider: nil)
|
|
45
|
+
return unless table_exists?
|
|
46
|
+
|
|
47
|
+
scope = where(provider_batch_id: id)
|
|
48
|
+
scope = scope.where(provider: provider.to_s) if provider
|
|
49
|
+
scope.order(id: :desc).first
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
def persistable_chats?(records)
|
|
53
|
+
records.all? { |chat| persisted_chat?(chat) } && records.map(&:class).uniq.one?
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def persisted_chat?(chat)
|
|
57
|
+
chat.respond_to?(:to_llm) && chat.respond_to?(:id) && chat.persisted?
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def chats
|
|
62
|
+
klass = chat_type.constantize
|
|
63
|
+
by_id = klass.where(klass.primary_key => Array(chat_ids))
|
|
64
|
+
.index_by { |chat| chat.public_send(klass.primary_key) }
|
|
65
|
+
Array(chat_ids).map { |id| by_id[id] }
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def to_llm(context: nil, store: nil)
|
|
69
|
+
config = context&.config || RubyLLM.config
|
|
70
|
+
provider_instance = RubyLLM::Provider.resolve!(provider).new(config)
|
|
71
|
+
RubyLLM::Batch.new(
|
|
72
|
+
provider: provider_instance,
|
|
73
|
+
chats: chats.map { |chat| chat&.to_llm },
|
|
74
|
+
id: provider_batch_id,
|
|
75
|
+
raw_status: raw_status,
|
|
76
|
+
completed: completed,
|
|
77
|
+
request_counts: request_counts,
|
|
78
|
+
reported_cost: reported_cost && RubyLLM::Cost.from_h(reported_cost),
|
|
79
|
+
batch_protocol: batch_protocol,
|
|
80
|
+
store: store
|
|
81
|
+
)
|
|
82
|
+
end
|
|
83
|
+
|
|
84
|
+
def sync_from(batch)
|
|
85
|
+
attributes = {
|
|
86
|
+
status: batch.status,
|
|
87
|
+
raw_status: batch.raw_status,
|
|
88
|
+
completed: batch.complete?,
|
|
89
|
+
request_counts: batch.request_counts,
|
|
90
|
+
batch_protocol: batch.batch_protocol
|
|
91
|
+
}
|
|
92
|
+
attributes[:reported_cost] = batch.reported_cost.to_h if batch.reported_cost
|
|
93
|
+
update!(attributes)
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|