ruby_llm 1.15.0 → 2.0.0.rc1
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 +87 -33
- 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 -6
- 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 +25 -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 +100 -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 -136
- data/lib/ruby_llm/active_record/attachment_helpers.rb +180 -0
- data/lib/ruby_llm/active_record/batch.rb +97 -0
- data/lib/ruby_llm/active_record/chat_methods.rb +823 -305
- data/lib/ruby_llm/active_record/message_methods.rb +119 -75
- 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 -150
- data/lib/ruby_llm/aliases.json +338 -167
- data/lib/ruby_llm/attachment.rb +217 -61
- 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 +1208 -150
- data/lib/ruby_llm/chunk.rb +10 -0
- data/lib/ruby_llm/citation.rb +105 -0
- data/lib/ruby_llm/configuration.rb +274 -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 +141 -7
- data/lib/ruby_llm/embedding_request.rb +53 -0
- data/lib/ruby_llm/error.rb +161 -89
- data/lib/ruby_llm/fallback.rb +133 -0
- data/lib/ruby_llm/files/mime_type.rb +97 -0
- data/lib/ruby_llm/image.rb +155 -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 +43 -14
- data/lib/ruby_llm/model/pricing_category.rb +103 -14
- data/lib/ruby_llm/model/pricing_tier.rb +66 -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 +70380 -33380
- data/lib/ruby_llm/models.rb +528 -201
- 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 +540 -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 +166 -0
- data/lib/ruby_llm/{providers → protocols}/anthropic/tools.rb +43 -34
- data/lib/ruby_llm/protocols/anthropic.rb +100 -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/protocols/chat_completions/media.rb +121 -0
- 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 +17 -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 +685 -0
- data/lib/ruby_llm/protocols/converse/media.rb +178 -0
- data/lib/ruby_llm/protocols/converse/streaming.rb +424 -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 +33 -20
- 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 +565 -124
- 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 +6 -15
- 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 +61 -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 +194 -42
- data/lib/ruby_llm/providers/bedrock.rb +217 -46
- 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 -8
- data/lib/ruby_llm/providers/deepseek/chat.rb +56 -0
- 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 -9
- 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 -60
- 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 +43 -0
- data/lib/ruby_llm/providers/mistral/models.rb +57 -21
- 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 +18 -6
- data/lib/ruby_llm/providers/ollama/chat.rb +9 -8
- 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 -104
- 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 +4 -0
- data/lib/ruby_llm/providers/perplexity/embeddings.rb +32 -0
- data/lib/ruby_llm/providers/perplexity/media.rb +46 -0
- data/lib/ruby_llm/providers/perplexity/models.rb +80 -13
- data/lib/ruby_llm/providers/perplexity.rb +29 -21
- 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 +164 -17
- data/lib/ruby_llm/providers/xai/capabilities.rb +18 -0
- data/lib/ruby_llm/providers/xai/chat.rb +10 -0
- 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 -48
- 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 +17 -7
- data/lib/ruby_llm/railtie.rb +11 -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 +139 -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 +385 -4
- data/lib/tasks/ruby_llm.rake +21 -16
- data/skills/rubyllm/SKILL.md +81 -0
- data/skills/rubyllm/agents/openai.yaml +4 -0
- metadata +340 -92
- 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 -530
- 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 -38
- data/lib/ruby_llm/connection.rb +0 -130
- data/lib/ruby_llm/content.rb +0 -77
- data/lib/ruby_llm/mime_type.rb +0 -71
- data/lib/ruby_llm/model/info.rb +0 -130
- data/lib/ruby_llm/models_schema.json +0 -171
- data/lib/ruby_llm/providers/anthropic/chat.rb +0 -257
- 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 -57
- data/lib/ruby_llm/providers/anthropic/streaming.rb +0 -69
- data/lib/ruby_llm/providers/bedrock/chat.rb +0 -403
- data/lib/ruby_llm/providers/bedrock/media.rb +0 -90
- data/lib/ruby_llm/providers/bedrock/streaming.rb +0 -322
- data/lib/ruby_llm/providers/gemini/chat.rb +0 -543
- 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 -96
- data/lib/ruby_llm/providers/gemini/tools.rb +0 -232
- 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 -221
- 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/media.rb +0 -84
- data/lib/ruby_llm/providers/openai/moderation.rb +0 -34
- data/lib/ruby_llm/providers/openai/streaming.rb +0 -53
- data/lib/ruby_llm/providers/openai/temperature.rb +0 -28
- data/lib/ruby_llm/providers/openai/transcription.rb +0 -70
- 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 -203
- data/lib/ruby_llm/streaming.rb +0 -175
- data/lib/ruby_llm/utils.rb +0 -91
- data/lib/tasks/models.rake +0 -565
- data/lib/tasks/release.rake +0 -67
- data/lib/tasks/vcr.rake +0 -124
data/lib/ruby_llm/provider.rb
CHANGED
|
@@ -1,223 +1,606 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require 'json'
|
|
4
|
+
require 'ruby_llm/error'
|
|
5
|
+
|
|
3
6
|
module RubyLLM
|
|
4
|
-
#
|
|
7
|
+
# A Provider connects RubyLLM to one AI service. It knows where to talk
|
|
8
|
+
# (host, authentication headers, configuration) and which protocol to
|
|
9
|
+
# speak for a given model and request. The wire formats themselves live
|
|
10
|
+
# under RubyLLM::Protocols.
|
|
11
|
+
#
|
|
12
|
+
# Subclass Provider to support a new service, then make it available
|
|
13
|
+
# with ::register:
|
|
14
|
+
#
|
|
15
|
+
# class Acme < RubyLLM::Provider
|
|
16
|
+
# protocol :chat_completions, RubyLLM::Protocols::ChatCompletions
|
|
17
|
+
#
|
|
18
|
+
# def self.configuration_options
|
|
19
|
+
# %i[acme_api_key]
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# def api_base
|
|
23
|
+
# 'https://api.acme.ai/v1'
|
|
24
|
+
# end
|
|
25
|
+
#
|
|
26
|
+
# def headers
|
|
27
|
+
# { 'Authorization' => "Bearer #{@config.acme_api_key}" }
|
|
28
|
+
# end
|
|
29
|
+
# end
|
|
30
|
+
#
|
|
31
|
+
# RubyLLM::Provider.register :acme, Acme
|
|
32
|
+
#
|
|
33
|
+
# See the custom providers guide for the full walkthrough.
|
|
5
34
|
class Provider
|
|
6
|
-
include
|
|
35
|
+
include Support::Inspectable
|
|
36
|
+
|
|
37
|
+
BATCH_RATE_BY_COMPONENT = {
|
|
38
|
+
input: :input_per_million,
|
|
39
|
+
output: :output_per_million,
|
|
40
|
+
cache_read: :cache_read_input_per_million,
|
|
41
|
+
cache_write: :cache_write_input_per_million,
|
|
42
|
+
thinking: :reasoning_output_per_million
|
|
43
|
+
}.freeze
|
|
44
|
+
private_constant :BATCH_RATE_BY_COMPONENT
|
|
45
|
+
|
|
46
|
+
# The Configuration the provider was built with.
|
|
47
|
+
attr_reader :config
|
|
7
48
|
|
|
8
|
-
attr_reader :
|
|
49
|
+
attr_reader :connection # :nodoc:
|
|
9
50
|
|
|
10
|
-
def initialize(config)
|
|
51
|
+
def initialize(config) # :nodoc:
|
|
11
52
|
@config = config
|
|
12
53
|
ensure_configured!
|
|
13
|
-
@connection = Connection.new(self, @config)
|
|
54
|
+
@connection = Transport::Connection.new(self, @config)
|
|
14
55
|
end
|
|
15
56
|
|
|
57
|
+
# Returns the base URL that relative endpoint paths resolve against.
|
|
58
|
+
# The base implementation raises NotImplementedError, so every
|
|
59
|
+
# subclass must define it.
|
|
60
|
+
#
|
|
61
|
+
# def api_base
|
|
62
|
+
# @config.acme_api_base || 'https://api.acme.ai/v1'
|
|
63
|
+
# end
|
|
64
|
+
#
|
|
16
65
|
def api_base
|
|
17
66
|
raise NotImplementedError
|
|
18
67
|
end
|
|
19
68
|
|
|
69
|
+
# Returns the headers merged into every request. The default is an
|
|
70
|
+
# empty hash. Override to supply authentication.
|
|
71
|
+
#
|
|
72
|
+
# def headers
|
|
73
|
+
# { 'Authorization' => "Bearer #{@config.acme_api_key}" }
|
|
74
|
+
# end
|
|
75
|
+
#
|
|
20
76
|
def headers
|
|
21
77
|
{}
|
|
22
78
|
end
|
|
23
79
|
|
|
80
|
+
# Returns how many seconds the service asked us to wait before
|
|
81
|
+
# retrying a rate-limited request, or +nil+ when the response carries
|
|
82
|
+
# no timing information. The retry middleware already honors the
|
|
83
|
+
# standard <tt>Retry-After</tt> header; override this to read
|
|
84
|
+
# provider-specific rate-limit headers.
|
|
85
|
+
#
|
|
86
|
+
# def retry_delay(response)
|
|
87
|
+
# response.response_headers['x-acme-ratelimit-reset']&.to_f
|
|
88
|
+
# end
|
|
89
|
+
#
|
|
90
|
+
def retry_delay(_response)
|
|
91
|
+
nil
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Returns the provider slug, delegating to ::slug.
|
|
24
95
|
def slug
|
|
25
96
|
self.class.slug
|
|
26
97
|
end
|
|
27
98
|
|
|
28
|
-
|
|
29
|
-
|
|
99
|
+
# Returns the human-readable provider name, delegating to
|
|
100
|
+
# ::display_name.
|
|
101
|
+
def name # :nodoc:
|
|
102
|
+
self.class.display_name
|
|
30
103
|
end
|
|
31
104
|
|
|
32
|
-
def capabilities
|
|
105
|
+
def capabilities # :nodoc:
|
|
33
106
|
self.class.capabilities
|
|
34
107
|
end
|
|
35
108
|
|
|
36
|
-
def configuration_requirements
|
|
109
|
+
def configuration_requirements # :nodoc:
|
|
37
110
|
self.class.configuration_requirements
|
|
38
111
|
end
|
|
39
112
|
|
|
40
|
-
#
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
113
|
+
def protocols # :nodoc:
|
|
114
|
+
self.class.protocols
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Returns the protocol class to use for +model+. Override to route
|
|
118
|
+
# between registered protocols per model or request operation. An
|
|
119
|
+
# explicit <tt>protocol:</tt> override on the chat or the provider's
|
|
120
|
+
# <tt><slug>_protocol</tt> configuration option takes precedence
|
|
121
|
+
# over this hook.
|
|
122
|
+
#
|
|
123
|
+
# def protocol_for(model, **)
|
|
124
|
+
# model.id.match?(/audio|realtime/) ? protocols[:chat_completions] : super
|
|
125
|
+
# end
|
|
126
|
+
#
|
|
127
|
+
def protocol_for(_model, **)
|
|
128
|
+
default_protocol
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
def complete(messages, tools:, temperature:, model:, provider_options: {}, headers: {}, schema: nil, # :nodoc:
|
|
132
|
+
max_output_tokens: nil, thinking: nil, citations: false, caching: nil, tool_prefs: nil,
|
|
133
|
+
protocol: nil, before_request: [], usage_recorder: nil, server_tools: [],
|
|
134
|
+
compaction: nil, end_user: nil, &)
|
|
135
|
+
protocol_class = resolve_protocol(protocol, model, tools:, schema:, thinking:, tool_prefs:, citations:)
|
|
136
|
+
protocol_class.new(self, model).complete(
|
|
137
|
+
messages,
|
|
138
|
+
tools: tools,
|
|
139
|
+
server_tools: server_tools,
|
|
140
|
+
tool_prefs: tool_prefs,
|
|
141
|
+
temperature: temperature,
|
|
142
|
+
max_output_tokens: max_output_tokens,
|
|
143
|
+
provider_options: provider_options,
|
|
144
|
+
headers: headers,
|
|
145
|
+
schema: schema,
|
|
146
|
+
thinking: thinking,
|
|
147
|
+
citations: citations,
|
|
148
|
+
caching: caching,
|
|
149
|
+
compaction: compaction,
|
|
150
|
+
end_user: end_user,
|
|
151
|
+
before_request: before_request,
|
|
152
|
+
usage_recorder: usage_recorder,
|
|
153
|
+
&
|
|
57
154
|
)
|
|
155
|
+
end
|
|
58
156
|
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
157
|
+
def tool_approval_response(tool_call, approved:, model:, protocol: nil) # :nodoc:
|
|
158
|
+
resolve_protocol(protocol, model).new(self, model).tool_approval_response(tool_call, approved:)
|
|
159
|
+
end
|
|
160
|
+
|
|
161
|
+
def compact(messages, model:, protocol: nil, headers: {}, before_request: [], usage_recorder: nil) # :nodoc:
|
|
162
|
+
resolve_protocol(protocol, model).new(self, model).compact(
|
|
163
|
+
messages, headers:, before_request:, usage_recorder:
|
|
164
|
+
)
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
def render(messages, tools:, temperature:, model:, provider_options: {}, schema: nil, thinking: nil, # :nodoc:
|
|
168
|
+
max_output_tokens: nil, citations: false, caching: nil, tool_prefs: nil, protocol: nil,
|
|
169
|
+
before_request: [], server_tools: [], compaction: nil, end_user: nil)
|
|
170
|
+
protocol_class = resolve_protocol(protocol, model, tools:, schema:, thinking:, tool_prefs:, citations:)
|
|
171
|
+
protocol_class.new(self, model).render(
|
|
172
|
+
messages,
|
|
173
|
+
tools: tools,
|
|
174
|
+
server_tools: server_tools,
|
|
175
|
+
tool_prefs: tool_prefs,
|
|
176
|
+
temperature: temperature,
|
|
177
|
+
max_output_tokens: max_output_tokens,
|
|
178
|
+
provider_options: provider_options,
|
|
179
|
+
schema: schema,
|
|
180
|
+
thinking: thinking,
|
|
181
|
+
citations: citations,
|
|
182
|
+
caching: caching,
|
|
183
|
+
compaction: compaction,
|
|
184
|
+
end_user: end_user,
|
|
185
|
+
before_request: before_request
|
|
186
|
+
)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
def count_tokens(messages, model:, tools: {}, tool_prefs: nil, thinking: nil, schema: nil, # :nodoc:
|
|
190
|
+
citations: false, caching: nil, protocol: nil)
|
|
191
|
+
protocol_class = resolve_protocol(protocol, model, tools:, schema:, thinking:, tool_prefs:, citations:)
|
|
192
|
+
protocol_class.new(self, model).count_tokens(
|
|
193
|
+
messages,
|
|
194
|
+
tools: tools,
|
|
195
|
+
tool_prefs: tool_prefs,
|
|
196
|
+
thinking: thinking,
|
|
197
|
+
schema: schema,
|
|
198
|
+
citations: citations,
|
|
199
|
+
caching: caching
|
|
200
|
+
)
|
|
201
|
+
end
|
|
202
|
+
|
|
203
|
+
def preprocess_message(message, model:, protocol: nil) # :nodoc:
|
|
204
|
+
protocol_class = resolve_protocol(
|
|
205
|
+
protocol,
|
|
206
|
+
model,
|
|
207
|
+
tools: {},
|
|
208
|
+
schema: nil,
|
|
209
|
+
thinking: nil,
|
|
210
|
+
tool_prefs: nil,
|
|
211
|
+
citations: false
|
|
212
|
+
)
|
|
213
|
+
protocol_class.new(self, model).preprocess_message(message)
|
|
214
|
+
end
|
|
215
|
+
|
|
216
|
+
def batches? # :nodoc:
|
|
217
|
+
batch_protocol.public_method_defined?(:create_batch)
|
|
218
|
+
end
|
|
219
|
+
|
|
220
|
+
def create_batch(requests) # :nodoc:
|
|
221
|
+
protocol = batch_protocol_for(requests)
|
|
222
|
+
ensure_batches_supported!(protocol)
|
|
223
|
+
protocol.new(self).create_batch(requests).merge(batch_protocol: protocol)
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def find_batch(id) # :nodoc:
|
|
227
|
+
ensure_batches_supported!
|
|
228
|
+
batch_protocol.new(self).find_batch(id)
|
|
229
|
+
end
|
|
230
|
+
|
|
231
|
+
def cancel_batch(id) # :nodoc:
|
|
232
|
+
ensure_batches_supported!
|
|
233
|
+
batch_protocol.new(self).cancel_batch(id)
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
def batch_results(id, batch_protocol: nil) # :nodoc:
|
|
237
|
+
protocol = resolve_batch_protocol(batch_protocol) || self.batch_protocol
|
|
238
|
+
ensure_batches_supported!(protocol)
|
|
239
|
+
protocol.new(self).batch_results(id)
|
|
240
|
+
end
|
|
241
|
+
|
|
242
|
+
def batch_status(raw_status, completed:, batch_protocol: nil) # :nodoc:
|
|
243
|
+
protocol = resolve_batch_protocol(batch_protocol) || self.batch_protocol
|
|
244
|
+
ensure_batches_supported!(protocol)
|
|
245
|
+
parser = protocol.new(self)
|
|
246
|
+
return parser.send(:parse_batch_status, raw_status, completed:) if parser.respond_to?(:parse_batch_status, true)
|
|
247
|
+
|
|
248
|
+
completed ? :succeeded : :pending
|
|
249
|
+
end
|
|
250
|
+
|
|
251
|
+
def batch_cost(tokens, model:, category: :text_tokens) # :nodoc:
|
|
252
|
+
standard = Cost.new(tokens:, model:, category:)
|
|
253
|
+
return standard if tokens.reported_cost
|
|
254
|
+
|
|
255
|
+
pricing = model.pricing.public_send(category)
|
|
256
|
+
batch_tier = pricing.batch unless long_context_pricing?(pricing, tokens)
|
|
257
|
+
batch = Cost.new(tokens:, model:, category:, tier: :batch) if batch_tier
|
|
258
|
+
amounts = batch_cost_amounts(standard:, batch:, batch_tier:, model:)
|
|
259
|
+
Cost.from_h(amounts, tokens:)
|
|
260
|
+
end
|
|
261
|
+
|
|
262
|
+
def batch_cost_amounts(standard:, batch:, batch_tier:, model:) # :nodoc:
|
|
263
|
+
BATCH_RATE_BY_COMPONENT.to_h do |component, rate|
|
|
264
|
+
amount = if batch_tier&.public_send(rate)
|
|
265
|
+
batch.public_send(component)
|
|
266
|
+
else
|
|
267
|
+
value = standard.public_send(component)
|
|
268
|
+
multiplier = batch_cost_multiplier(model:, component:)
|
|
269
|
+
value * multiplier if value && multiplier
|
|
270
|
+
end
|
|
271
|
+
[component, amount]
|
|
63
272
|
end
|
|
64
273
|
end
|
|
65
|
-
# rubocop:enable Metrics/ParameterLists
|
|
66
274
|
|
|
67
|
-
def
|
|
68
|
-
|
|
69
|
-
|
|
275
|
+
def batch_cost_multiplier(**) = nil # :nodoc:
|
|
276
|
+
|
|
277
|
+
def long_context_pricing?(pricing, tokens) # :nodoc:
|
|
278
|
+
pricing.long_context &&
|
|
279
|
+
pricing.long_context_threshold &&
|
|
280
|
+
tokens.input.to_i + tokens.cache_read.to_i + tokens.cache_write.to_i > pricing.long_context_threshold
|
|
281
|
+
end
|
|
282
|
+
|
|
283
|
+
def batch_protocol_name(protocol) # :nodoc:
|
|
284
|
+
protocols.key(protocol)&.to_s
|
|
70
285
|
end
|
|
71
286
|
|
|
72
|
-
def
|
|
73
|
-
|
|
74
|
-
response = @connection.post(embedding_url(model:), payload)
|
|
75
|
-
parse_embedding_response(response, model:, text:)
|
|
287
|
+
def files? # :nodoc:
|
|
288
|
+
protocols.key?(:files)
|
|
76
289
|
end
|
|
77
290
|
|
|
78
|
-
def
|
|
79
|
-
|
|
80
|
-
response = @connection.post moderation_url, payload
|
|
81
|
-
parse_moderation_response(response, model:)
|
|
291
|
+
def list_models # :nodoc:
|
|
292
|
+
listing_protocol.new(self).list_models
|
|
82
293
|
end
|
|
83
294
|
|
|
84
|
-
def
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
response = @connection.post images_url(with:, mask:), payload
|
|
88
|
-
parse_image_response(response, model:)
|
|
295
|
+
def tokenize(text, model:) # :nodoc:
|
|
296
|
+
protocol = resolve_protocol(nil, model, operation: :tokenize)
|
|
297
|
+
protocol.new(self, model).tokenize(text, model: model_id_for(model))
|
|
89
298
|
end
|
|
90
299
|
|
|
91
|
-
def
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
300
|
+
def embed(text, model:, dimensions:, task_type: nil, title: nil, with: nil, provider_options: {}) # :nodoc:
|
|
301
|
+
protocol = resolve_protocol(nil, model, operation: :embed)
|
|
302
|
+
protocol.new(self, model).embed(
|
|
303
|
+
text, model: model_id_for(model), dimensions:, task_type:, title:, with:, provider_options:
|
|
304
|
+
)
|
|
96
305
|
end
|
|
97
306
|
|
|
98
|
-
def
|
|
99
|
-
|
|
307
|
+
def render_embedding(text, model:, dimensions: nil) # :nodoc:
|
|
308
|
+
protocol = resolve_protocol(nil, model, operation: :embed)
|
|
309
|
+
protocol.new(self, model).render_embedding(text, model: model_id_for(model), dimensions:)
|
|
100
310
|
end
|
|
101
311
|
|
|
102
|
-
def
|
|
103
|
-
|
|
312
|
+
def moderate(input, model:, with: [], provider_options: {}) # :nodoc:
|
|
313
|
+
protocol = resolve_protocol(nil, model, operation: :moderate)
|
|
314
|
+
protocol.new(self, model).moderate(
|
|
315
|
+
input, model: model_id_for(model), with:, provider_options:
|
|
316
|
+
)
|
|
104
317
|
end
|
|
105
318
|
|
|
106
|
-
def
|
|
107
|
-
self.
|
|
319
|
+
def research_later(prompt, **options) # :nodoc:
|
|
320
|
+
fetch_protocol(:research).new(self).create_research_job(prompt, **options)
|
|
108
321
|
end
|
|
109
322
|
|
|
110
|
-
def
|
|
323
|
+
def find_research_job(id) # :nodoc:
|
|
324
|
+
fetch_protocol(:research).new(self).find_research_job(id)
|
|
325
|
+
end
|
|
326
|
+
|
|
327
|
+
def paint(prompt, model:, size:, count: nil, with: nil, mask: nil, provider_options: {}) # :nodoc:
|
|
328
|
+
protocol = resolve_protocol(nil, model, operation: :paint)
|
|
329
|
+
protocol.new(self, model).paint(
|
|
330
|
+
prompt, model: model_id_for(model), size:, count:, with:, mask:, provider_options:
|
|
331
|
+
)
|
|
332
|
+
end
|
|
333
|
+
|
|
334
|
+
def animate_later(prompt, model:, with: nil, extend: nil, provider_options: {}) # :nodoc:
|
|
335
|
+
protocol = resolve_protocol(nil, model, operation: :animate)
|
|
336
|
+
protocol.new(self, model).animate_later(
|
|
337
|
+
prompt, model: model_id_for(model), with:, extend:, provider_options:
|
|
338
|
+
)
|
|
339
|
+
end
|
|
340
|
+
|
|
341
|
+
def speak(input, model:, voice:, format:, provider_options: {}, &) # :nodoc:
|
|
342
|
+
protocol = resolve_protocol(nil, model, operation: :speak)
|
|
343
|
+
protocol.new(self, model).speak(
|
|
344
|
+
input, model: model_id_for(model), voice:, format:, provider_options:, &
|
|
345
|
+
)
|
|
346
|
+
end
|
|
347
|
+
|
|
348
|
+
def transcribe(audio_file, model:, language:, format: nil, timestamps: nil, speaker_names: nil, # :nodoc:
|
|
349
|
+
speaker_references: nil, provider_options: {}, prompt: nil, temperature: nil, &)
|
|
350
|
+
protocol = resolve_protocol(nil, model, operation: :transcribe).new(self, model)
|
|
351
|
+
options = protocol.render_transcription_options(timestamps:, format:, streaming: block_given?)
|
|
352
|
+
provider_options = Support::Utils.deep_merge(options, provider_options)
|
|
353
|
+
protocol.transcribe(
|
|
354
|
+
audio_file,
|
|
355
|
+
model: model_id_for(model),
|
|
356
|
+
language:,
|
|
357
|
+
format:,
|
|
358
|
+
speaker_names:,
|
|
359
|
+
speaker_references:,
|
|
360
|
+
provider_options:,
|
|
361
|
+
prompt:,
|
|
362
|
+
temperature:,
|
|
363
|
+
&
|
|
364
|
+
)
|
|
365
|
+
end
|
|
366
|
+
|
|
367
|
+
def ocr(file, model:, pages: nil, provider_options: {}) # :nodoc:
|
|
368
|
+
protocol = resolve_protocol(nil, model, operation: :ocr)
|
|
369
|
+
protocol.new(self, model).ocr(file, model: model_id_for(model), pages:, provider_options:)
|
|
370
|
+
end
|
|
371
|
+
|
|
372
|
+
def rerank(query, documents, model:, top_n: nil, provider_options: {}) # :nodoc:
|
|
373
|
+
protocol = resolve_protocol(nil, model, operation: :rerank)
|
|
374
|
+
protocol.new(self, model).rerank(query, documents, model: model_id_for(model), top_n:, provider_options:)
|
|
375
|
+
end
|
|
376
|
+
|
|
377
|
+
def upload_file(file, filename: nil, purpose: nil, expires_in: nil, uri: nil, content_type: nil, # :nodoc:
|
|
378
|
+
provider_options: {})
|
|
379
|
+
ensure_files_supported!
|
|
380
|
+
options = { filename:, purpose:, expires_in:, uri:, content_type:, provider_options: }.compact
|
|
381
|
+
|
|
382
|
+
protocols.fetch(:files).new(self).upload(file, **options)
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def cache_content(content, model:, ttl: nil, instructions: nil, with: nil) # :nodoc:
|
|
386
|
+
protocol = resolve_protocol(nil, model, operation: :cache)
|
|
387
|
+
protocol.new(self, model).cache_content(
|
|
388
|
+
content, model: model_id_for(model), ttl:, instructions:, with:
|
|
389
|
+
)
|
|
390
|
+
end
|
|
391
|
+
|
|
392
|
+
def find_cache(name) # :nodoc:
|
|
393
|
+
default_protocol.new(self).find_cache(name)
|
|
394
|
+
end
|
|
395
|
+
|
|
396
|
+
def delete_cache(name) # :nodoc:
|
|
397
|
+
default_protocol.new(self).delete_cache(name)
|
|
398
|
+
end
|
|
399
|
+
|
|
400
|
+
def extend_cache(name, ttl:) # :nodoc:
|
|
401
|
+
default_protocol.new(self).extend_cache(name, ttl:)
|
|
402
|
+
end
|
|
403
|
+
|
|
404
|
+
def find_file(file_id) # :nodoc:
|
|
405
|
+
ensure_files_supported!
|
|
406
|
+
protocols.fetch(:files).new(self).find(file_id)
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
def download_file(file_id) # :nodoc:
|
|
410
|
+
ensure_files_supported!
|
|
411
|
+
protocols.fetch(:files).new(self).download(file_id)
|
|
412
|
+
end
|
|
413
|
+
|
|
414
|
+
def list_file_uris(uri) # :nodoc:
|
|
415
|
+
ensure_files_supported!
|
|
416
|
+
protocols.fetch(:files).new(self).list_uris(uri)
|
|
417
|
+
end
|
|
418
|
+
|
|
419
|
+
def configured? # :nodoc:
|
|
420
|
+
self.class.configured?(@config)
|
|
421
|
+
end
|
|
422
|
+
|
|
423
|
+
def local? # :nodoc:
|
|
424
|
+
self.class.local?
|
|
425
|
+
end
|
|
426
|
+
|
|
427
|
+
def assume_models_exist? # :nodoc:
|
|
111
428
|
self.class.assume_models_exist?
|
|
112
429
|
end
|
|
113
430
|
|
|
114
|
-
def parse_error(response)
|
|
115
|
-
|
|
431
|
+
def parse_error(response) # :nodoc:
|
|
432
|
+
body = parse_error_body(response)
|
|
433
|
+
return unless body
|
|
116
434
|
|
|
117
|
-
body = try_parse_json(response.body)
|
|
118
435
|
case body
|
|
119
436
|
when Hash
|
|
120
|
-
|
|
121
|
-
return error if error.is_a?(String)
|
|
122
|
-
|
|
123
|
-
body.dig('error', 'message')
|
|
437
|
+
error_part_message(body)
|
|
124
438
|
when Array
|
|
125
|
-
body.
|
|
126
|
-
|
|
127
|
-
error.is_a?(String) ? error : part.dig('error', 'message')
|
|
128
|
-
end.join('. ')
|
|
439
|
+
messages = body.filter_map { |part| error_part_message(part) }.reject(&:empty?)
|
|
440
|
+
messages.join('. ') unless messages.empty?
|
|
129
441
|
else
|
|
130
442
|
body
|
|
131
443
|
end
|
|
132
444
|
end
|
|
133
445
|
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
role: msg.role.to_s,
|
|
138
|
-
content: msg.content
|
|
139
|
-
}
|
|
140
|
-
end
|
|
141
|
-
end
|
|
142
|
-
|
|
143
|
-
def format_tool_calls(_tool_calls)
|
|
144
|
-
nil
|
|
145
|
-
end
|
|
446
|
+
class << self
|
|
447
|
+
attr_reader :default_protocol # :nodoc:
|
|
448
|
+
attr_writer :slug # :nodoc:
|
|
146
449
|
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
450
|
+
# Returns the provider slug, a short lowercase string that
|
|
451
|
+
# identifies the provider and prefixes its configuration keys.
|
|
452
|
+
# Set by ::register, or derived from the class name.
|
|
453
|
+
def slug
|
|
454
|
+
@slug ||= to_s.split('::').last.downcase
|
|
455
|
+
end
|
|
150
456
|
|
|
151
|
-
|
|
152
|
-
|
|
457
|
+
# Returns the human-readable provider name, derived from the
|
|
458
|
+
# class name. Override for custom branding.
|
|
459
|
+
def display_name
|
|
153
460
|
to_s.split('::').last
|
|
154
461
|
end
|
|
155
462
|
|
|
156
|
-
|
|
157
|
-
|
|
463
|
+
# Returns the provider's narrow model capability augmenter, or +nil+
|
|
464
|
+
# when models.dev and the provider listing are sufficient.
|
|
465
|
+
def capabilities
|
|
466
|
+
nil
|
|
158
467
|
end
|
|
159
468
|
|
|
160
|
-
def
|
|
469
|
+
def models_dev_alias(_model_id, _models_dev_by_key, _provider_model = nil) # :nodoc:
|
|
161
470
|
nil
|
|
162
471
|
end
|
|
163
472
|
|
|
473
|
+
# The id RubyLLM registers for a models.dev entry. Providers whose
|
|
474
|
+
# catalog spells ids differently from models.dev override this.
|
|
475
|
+
def models_dev_model_id(id) # :nodoc:
|
|
476
|
+
id
|
|
477
|
+
end
|
|
478
|
+
|
|
479
|
+
# Returns the configuration keys that must be set before the
|
|
480
|
+
# provider is usable. The base implementation returns an empty
|
|
481
|
+
# array.
|
|
482
|
+
#
|
|
483
|
+
# def self.configuration_requirements
|
|
484
|
+
# %i[acme_api_key]
|
|
485
|
+
# end
|
|
486
|
+
#
|
|
164
487
|
def configuration_requirements
|
|
165
488
|
[]
|
|
166
489
|
end
|
|
167
490
|
|
|
491
|
+
# Returns every configuration key the provider contributes.
|
|
492
|
+
# ::register defines a Configuration accessor for each one.
|
|
493
|
+
# The base implementation returns an empty array.
|
|
494
|
+
#
|
|
495
|
+
# def self.configuration_options
|
|
496
|
+
# %i[acme_api_key acme_api_base]
|
|
497
|
+
# end
|
|
498
|
+
#
|
|
168
499
|
def configuration_options
|
|
169
500
|
[]
|
|
170
501
|
end
|
|
171
502
|
|
|
503
|
+
# Returns whether the provider talks to a locally hosted service.
|
|
504
|
+
# The base implementation returns +false+. Local providers such as
|
|
505
|
+
# Ollama return +true+.
|
|
172
506
|
def local?
|
|
173
507
|
false
|
|
174
508
|
end
|
|
175
509
|
|
|
176
|
-
def remote?
|
|
510
|
+
def remote? # :nodoc:
|
|
177
511
|
!local?
|
|
178
512
|
end
|
|
179
513
|
|
|
514
|
+
# Returns whether the provider accepts model ids missing from the
|
|
515
|
+
# model registry. The base implementation returns +false+.
|
|
180
516
|
def assume_models_exist?
|
|
181
517
|
false
|
|
182
518
|
end
|
|
183
519
|
|
|
184
|
-
|
|
520
|
+
# Returns whether +operation+ requires an inference model. Override
|
|
521
|
+
# for endpoints that operate on an explicitly configured resource.
|
|
522
|
+
def model_required?(**)
|
|
523
|
+
true
|
|
524
|
+
end
|
|
525
|
+
|
|
526
|
+
def configured?(config) # :nodoc:
|
|
185
527
|
configuration_requirements.all? { |req| config.send(req) }
|
|
186
528
|
end
|
|
187
529
|
|
|
188
|
-
|
|
530
|
+
# Registers +protocol_class+ under +name+. The first registered
|
|
531
|
+
# protocol becomes the provider's default. Pass +batches:+ to compose
|
|
532
|
+
# batch operations into the registered protocol.
|
|
533
|
+
#
|
|
534
|
+
# protocol :chat_completions, ChatCompletions
|
|
535
|
+
# protocol :responses, Protocols::Responses, batches: Protocols::Responses::Batches
|
|
536
|
+
#
|
|
537
|
+
def protocol(name, protocol_class, batches: nil)
|
|
538
|
+
@default_protocol = name.to_sym if protocols.empty?
|
|
539
|
+
protocols[name.to_sym] = batches ? Class.new(protocol_class) { include batches } : protocol_class
|
|
540
|
+
end
|
|
541
|
+
|
|
542
|
+
def protocols # :nodoc:
|
|
543
|
+
@protocols ||= {}
|
|
544
|
+
end
|
|
545
|
+
|
|
546
|
+
# Registers +provider_class+ under the slug +name+, making it
|
|
547
|
+
# available to RubyLLM.chat and the other top-level helpers.
|
|
548
|
+
# Stamps the class's slug, adds it to ::providers, and defines a
|
|
549
|
+
# Configuration accessor for each of its configuration options. A
|
|
550
|
+
# provider gem may pass the path to its bundled model catalog.
|
|
551
|
+
#
|
|
552
|
+
# RubyLLM::Provider.register :acme, RubyLLM::Providers::Acme
|
|
553
|
+
# RubyLLM::Provider.register :acme, RubyLLM::Providers::Acme,
|
|
554
|
+
# models: File.expand_path('../../../models.json', __dir__)
|
|
555
|
+
#
|
|
556
|
+
def register(name, provider_class, models: nil)
|
|
557
|
+
provider_class.slug = name.to_s
|
|
189
558
|
providers[name.to_sym] = provider_class
|
|
190
|
-
|
|
559
|
+
models ? model_registry_files[name.to_sym] = models : model_registry_files.delete(name.to_sym)
|
|
560
|
+
RubyLLM::Configuration.register_provider_options(provider_class.configuration_options + [:"#{name}_protocol"])
|
|
191
561
|
end
|
|
192
562
|
|
|
193
|
-
def resolve(name)
|
|
563
|
+
def resolve(name) # :nodoc:
|
|
194
564
|
providers[name.to_sym]
|
|
195
565
|
end
|
|
196
566
|
|
|
197
|
-
def
|
|
198
|
-
|
|
199
|
-
|
|
567
|
+
def resolve!(name) # :nodoc:
|
|
568
|
+
providers[name.to_sym] ||
|
|
569
|
+
raise(Error, "Unknown provider: #{name.inspect}. Available providers: #{providers.keys.join(', ')}")
|
|
570
|
+
end
|
|
571
|
+
|
|
572
|
+
# Resolves +model_id+ to the id the registry stores it under for this
|
|
573
|
+
# provider. Defaults to the id unchanged; providers whose catalog ids
|
|
574
|
+
# differ from their request ids (Bedrock's region prefixes) override it.
|
|
575
|
+
def resolve_registry_id(model_id, _models, _config = nil)
|
|
576
|
+
model_id
|
|
200
577
|
end
|
|
201
578
|
|
|
579
|
+
# Returns the global registry of providers, a hash mapping slug
|
|
580
|
+
# symbols to provider classes.
|
|
202
581
|
def providers
|
|
203
582
|
@providers ||= {}
|
|
204
583
|
end
|
|
205
584
|
|
|
206
|
-
def
|
|
585
|
+
def model_registry_files # :nodoc:
|
|
586
|
+
@model_registry_files ||= {}
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
def local_providers # :nodoc:
|
|
207
590
|
providers.select { |_slug, provider_class| provider_class.local? }
|
|
208
591
|
end
|
|
209
592
|
|
|
210
|
-
def remote_providers
|
|
593
|
+
def remote_providers # :nodoc:
|
|
211
594
|
providers.select { |_slug, provider_class| provider_class.remote? }
|
|
212
595
|
end
|
|
213
596
|
|
|
214
|
-
def configured_providers(config)
|
|
597
|
+
def configured_providers(config) # :nodoc:
|
|
215
598
|
providers.select do |_slug, provider_class|
|
|
216
599
|
provider_class.configured?(config)
|
|
217
600
|
end.values
|
|
218
601
|
end
|
|
219
602
|
|
|
220
|
-
def configured_remote_providers(config)
|
|
603
|
+
def configured_remote_providers(config) # :nodoc:
|
|
221
604
|
providers.select do |_slug, provider_class|
|
|
222
605
|
provider_class.remote? && provider_class.configured?(config)
|
|
223
606
|
end.values
|
|
@@ -226,21 +609,62 @@ module RubyLLM
|
|
|
226
609
|
|
|
227
610
|
private
|
|
228
611
|
|
|
229
|
-
def
|
|
230
|
-
|
|
612
|
+
def ensure_batches_supported!(protocol = batch_protocol)
|
|
613
|
+
raise Error, "#{slug} doesn't support batch requests" unless protocol.public_method_defined?(:create_batch)
|
|
614
|
+
end
|
|
615
|
+
|
|
616
|
+
def ensure_files_supported!
|
|
617
|
+
return if files?
|
|
231
618
|
|
|
232
|
-
raise
|
|
619
|
+
raise Error, "#{slug} doesn't support file uploads"
|
|
233
620
|
end
|
|
234
621
|
|
|
235
|
-
def
|
|
236
|
-
|
|
237
|
-
|
|
622
|
+
def resolve_protocol(name, model, **request)
|
|
623
|
+
explicit = name || configured_protocol
|
|
624
|
+
explicit ? fetch_protocol(explicit) : protocol_for(model, **request)
|
|
625
|
+
end
|
|
238
626
|
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
242
|
-
|
|
243
|
-
|
|
627
|
+
def default_protocol
|
|
628
|
+
fetch_protocol(configured_protocol || self.class.default_protocol)
|
|
629
|
+
end
|
|
630
|
+
|
|
631
|
+
# The model catalog lives at the provider's own listing endpoint, which
|
|
632
|
+
# the chat protocol override has no say over.
|
|
633
|
+
def listing_protocol
|
|
634
|
+
fetch_protocol(self.class.default_protocol)
|
|
635
|
+
end
|
|
636
|
+
|
|
637
|
+
def batch_protocol
|
|
638
|
+
fetch_protocol(self.class.default_protocol)
|
|
639
|
+
end
|
|
640
|
+
|
|
641
|
+
def batch_protocol_for(_requests)
|
|
642
|
+
batch_protocol
|
|
643
|
+
end
|
|
644
|
+
|
|
645
|
+
def batch_protocol_for_name(name)
|
|
646
|
+
protocol = protocols[name.to_sym]
|
|
647
|
+
protocol if protocol&.public_method_defined?(:create_batch)
|
|
648
|
+
end
|
|
649
|
+
|
|
650
|
+
def resolve_batch_protocol(protocol)
|
|
651
|
+
return protocol if protocol.is_a?(Module)
|
|
652
|
+
|
|
653
|
+
protocol && batch_protocol_for_name(protocol)
|
|
654
|
+
end
|
|
655
|
+
|
|
656
|
+
def configured_protocol
|
|
657
|
+
@config.send(:"#{slug}_protocol")
|
|
658
|
+
end
|
|
659
|
+
|
|
660
|
+
def fetch_protocol(name)
|
|
661
|
+
protocols.fetch(name.to_sym) do
|
|
662
|
+
raise Error, "#{name} is not a protocol of #{self.class.display_name}. Available: #{protocols.keys.join(', ')}"
|
|
663
|
+
end
|
|
664
|
+
end
|
|
665
|
+
|
|
666
|
+
def model_id_for(model)
|
|
667
|
+
model.respond_to?(:id) ? model.id : model
|
|
244
668
|
end
|
|
245
669
|
|
|
246
670
|
def try_parse_json(maybe_json)
|
|
@@ -251,22 +675,39 @@ module RubyLLM
|
|
|
251
675
|
maybe_json
|
|
252
676
|
end
|
|
253
677
|
|
|
254
|
-
def
|
|
255
|
-
|
|
256
|
-
return if
|
|
678
|
+
def parse_error_body(response)
|
|
679
|
+
body = response.body
|
|
680
|
+
return if body.nil? || (body.respond_to?(:empty?) && body.empty?)
|
|
257
681
|
|
|
258
|
-
|
|
682
|
+
try_parse_json(body)
|
|
259
683
|
end
|
|
260
684
|
|
|
261
|
-
def
|
|
262
|
-
|
|
685
|
+
def error_part_message(part)
|
|
686
|
+
return part.to_s unless part.is_a?(Hash)
|
|
687
|
+
|
|
688
|
+
error = part['error']
|
|
689
|
+
return error if error.is_a?(String)
|
|
690
|
+
|
|
691
|
+
nested_message = error['message'] if error.is_a?(Hash)
|
|
692
|
+
[nested_message, part['message'], part['detail']].find { |message| message.is_a?(String) }
|
|
263
693
|
end
|
|
264
694
|
|
|
265
|
-
def
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
695
|
+
def ensure_configured!
|
|
696
|
+
return if configured?
|
|
697
|
+
|
|
698
|
+
missing = configuration_requirements.reject { |req| @config.send(req) }
|
|
699
|
+
config_block = <<~RUBY
|
|
700
|
+
RubyLLM.configure do |config|
|
|
701
|
+
#{missing.map { |key| "config.#{key} = ENV['#{key.to_s.upcase}']" }.join("\n ")}
|
|
702
|
+
end
|
|
703
|
+
RUBY
|
|
704
|
+
|
|
705
|
+
raise ConfigurationError,
|
|
706
|
+
"#{name} provider is not configured. Add this to your initialization:\n\n#{config_block}"
|
|
707
|
+
end
|
|
708
|
+
|
|
709
|
+
def inspect_attributes # :nodoc:
|
|
710
|
+
{ slug: slug }
|
|
270
711
|
end
|
|
271
712
|
end
|
|
272
713
|
end
|