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
data/lib/ruby_llm/provider.rb
CHANGED
|
@@ -4,223 +4,603 @@ require 'json'
|
|
|
4
4
|
require 'ruby_llm/error'
|
|
5
5
|
|
|
6
6
|
module RubyLLM
|
|
7
|
-
#
|
|
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.
|
|
8
34
|
class Provider
|
|
9
|
-
include
|
|
35
|
+
include Support::Inspectable
|
|
10
36
|
|
|
11
|
-
|
|
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
|
|
12
45
|
|
|
13
|
-
|
|
46
|
+
# The Configuration the provider was built with.
|
|
47
|
+
attr_reader :config
|
|
48
|
+
|
|
49
|
+
attr_reader :connection # :nodoc:
|
|
50
|
+
|
|
51
|
+
def initialize(config) # :nodoc:
|
|
14
52
|
@config = config
|
|
15
53
|
ensure_configured!
|
|
16
|
-
@connection = Connection.new(self, @config)
|
|
54
|
+
@connection = Transport::Connection.new(self, @config)
|
|
17
55
|
end
|
|
18
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
|
+
#
|
|
19
65
|
def api_base
|
|
20
66
|
raise NotImplementedError
|
|
21
67
|
end
|
|
22
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
|
+
#
|
|
23
76
|
def headers
|
|
24
77
|
{}
|
|
25
78
|
end
|
|
26
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.
|
|
27
95
|
def slug
|
|
28
96
|
self.class.slug
|
|
29
97
|
end
|
|
30
98
|
|
|
31
|
-
|
|
32
|
-
|
|
99
|
+
# Returns the human-readable provider name, delegating to
|
|
100
|
+
# ::display_name.
|
|
101
|
+
def name # :nodoc:
|
|
102
|
+
self.class.display_name
|
|
33
103
|
end
|
|
34
104
|
|
|
35
|
-
def capabilities
|
|
105
|
+
def capabilities # :nodoc:
|
|
36
106
|
self.class.capabilities
|
|
37
107
|
end
|
|
38
108
|
|
|
39
|
-
def configuration_requirements
|
|
109
|
+
def configuration_requirements # :nodoc:
|
|
40
110
|
self.class.configuration_requirements
|
|
41
111
|
end
|
|
42
112
|
|
|
43
|
-
#
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
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
|
+
&
|
|
60
154
|
)
|
|
155
|
+
end
|
|
61
156
|
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
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]
|
|
66
272
|
end
|
|
67
273
|
end
|
|
68
|
-
# rubocop:enable Metrics/ParameterLists
|
|
69
274
|
|
|
70
|
-
def
|
|
71
|
-
|
|
72
|
-
|
|
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
|
|
73
285
|
end
|
|
74
286
|
|
|
75
|
-
def
|
|
76
|
-
|
|
77
|
-
response = @connection.post(embedding_url(model:), payload)
|
|
78
|
-
parse_embedding_response(response, model:, text:)
|
|
287
|
+
def files? # :nodoc:
|
|
288
|
+
protocols.key?(:files)
|
|
79
289
|
end
|
|
80
290
|
|
|
81
|
-
def
|
|
82
|
-
|
|
83
|
-
response = @connection.post moderation_url, payload
|
|
84
|
-
parse_moderation_response(response, model:)
|
|
291
|
+
def list_models # :nodoc:
|
|
292
|
+
listing_protocol.new(self).list_models
|
|
85
293
|
end
|
|
86
294
|
|
|
87
|
-
def
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
response = @connection.post images_url(with:, mask:), payload
|
|
91
|
-
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))
|
|
92
298
|
end
|
|
93
299
|
|
|
94
|
-
def
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
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
|
+
)
|
|
99
305
|
end
|
|
100
306
|
|
|
101
|
-
def
|
|
102
|
-
|
|
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:)
|
|
103
310
|
end
|
|
104
311
|
|
|
105
|
-
def
|
|
106
|
-
|
|
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
|
+
)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def research_later(prompt, **options) # :nodoc:
|
|
320
|
+
fetch_protocol(:research).new(self).create_research_job(prompt, **options)
|
|
321
|
+
end
|
|
322
|
+
|
|
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:)
|
|
107
402
|
end
|
|
108
403
|
|
|
109
|
-
def
|
|
110
|
-
|
|
404
|
+
def find_file(file_id) # :nodoc:
|
|
405
|
+
ensure_files_supported!
|
|
406
|
+
protocols.fetch(:files).new(self).find(file_id)
|
|
111
407
|
end
|
|
112
408
|
|
|
113
|
-
def
|
|
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:
|
|
114
428
|
self.class.assume_models_exist?
|
|
115
429
|
end
|
|
116
430
|
|
|
117
|
-
def parse_error(response)
|
|
118
|
-
|
|
431
|
+
def parse_error(response) # :nodoc:
|
|
432
|
+
body = parse_error_body(response)
|
|
433
|
+
return unless body
|
|
119
434
|
|
|
120
|
-
body = try_parse_json(response.body)
|
|
121
435
|
case body
|
|
122
436
|
when Hash
|
|
123
|
-
|
|
124
|
-
return error if error.is_a?(String)
|
|
125
|
-
|
|
126
|
-
body.dig('error', 'message')
|
|
437
|
+
error_part_message(body)
|
|
127
438
|
when Array
|
|
128
|
-
body.
|
|
129
|
-
|
|
130
|
-
error.is_a?(String) ? error : part.dig('error', 'message')
|
|
131
|
-
end.join('. ')
|
|
439
|
+
messages = body.filter_map { |part| error_part_message(part) }.reject(&:empty?)
|
|
440
|
+
messages.join('. ') unless messages.empty?
|
|
132
441
|
else
|
|
133
442
|
body
|
|
134
443
|
end
|
|
135
444
|
end
|
|
136
445
|
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
role: msg.role.to_s,
|
|
141
|
-
content: msg.content
|
|
142
|
-
}
|
|
143
|
-
end
|
|
144
|
-
end
|
|
145
|
-
|
|
146
|
-
def format_tool_calls(_tool_calls)
|
|
147
|
-
nil
|
|
148
|
-
end
|
|
446
|
+
class << self
|
|
447
|
+
attr_reader :default_protocol # :nodoc:
|
|
448
|
+
attr_writer :slug # :nodoc:
|
|
149
449
|
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
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
|
|
153
456
|
|
|
154
|
-
|
|
155
|
-
|
|
457
|
+
# Returns the human-readable provider name, derived from the
|
|
458
|
+
# class name. Override for custom branding.
|
|
459
|
+
def display_name
|
|
156
460
|
to_s.split('::').last
|
|
157
461
|
end
|
|
158
462
|
|
|
159
|
-
|
|
160
|
-
|
|
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
|
|
161
467
|
end
|
|
162
468
|
|
|
163
|
-
def
|
|
469
|
+
def models_dev_alias(_model_id, _models_dev_by_key, _provider_model = nil) # :nodoc:
|
|
164
470
|
nil
|
|
165
471
|
end
|
|
166
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
|
+
#
|
|
167
487
|
def configuration_requirements
|
|
168
488
|
[]
|
|
169
489
|
end
|
|
170
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
|
+
#
|
|
171
499
|
def configuration_options
|
|
172
500
|
[]
|
|
173
501
|
end
|
|
174
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+.
|
|
175
506
|
def local?
|
|
176
507
|
false
|
|
177
508
|
end
|
|
178
509
|
|
|
179
|
-
def remote?
|
|
510
|
+
def remote? # :nodoc:
|
|
180
511
|
!local?
|
|
181
512
|
end
|
|
182
513
|
|
|
514
|
+
# Returns whether the provider accepts model ids missing from the
|
|
515
|
+
# model registry. The base implementation returns +false+.
|
|
183
516
|
def assume_models_exist?
|
|
184
517
|
false
|
|
185
518
|
end
|
|
186
519
|
|
|
187
|
-
|
|
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:
|
|
188
527
|
configuration_requirements.all? { |req| config.send(req) }
|
|
189
528
|
end
|
|
190
529
|
|
|
191
|
-
|
|
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
|
|
192
558
|
providers[name.to_sym] = provider_class
|
|
193
|
-
|
|
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"])
|
|
194
561
|
end
|
|
195
562
|
|
|
196
|
-
def resolve(name)
|
|
563
|
+
def resolve(name) # :nodoc:
|
|
197
564
|
providers[name.to_sym]
|
|
198
565
|
end
|
|
199
566
|
|
|
200
|
-
def
|
|
201
|
-
|
|
202
|
-
|
|
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
|
|
203
577
|
end
|
|
204
578
|
|
|
579
|
+
# Returns the global registry of providers, a hash mapping slug
|
|
580
|
+
# symbols to provider classes.
|
|
205
581
|
def providers
|
|
206
582
|
@providers ||= {}
|
|
207
583
|
end
|
|
208
584
|
|
|
209
|
-
def
|
|
585
|
+
def model_registry_files # :nodoc:
|
|
586
|
+
@model_registry_files ||= {}
|
|
587
|
+
end
|
|
588
|
+
|
|
589
|
+
def local_providers # :nodoc:
|
|
210
590
|
providers.select { |_slug, provider_class| provider_class.local? }
|
|
211
591
|
end
|
|
212
592
|
|
|
213
|
-
def remote_providers
|
|
593
|
+
def remote_providers # :nodoc:
|
|
214
594
|
providers.select { |_slug, provider_class| provider_class.remote? }
|
|
215
595
|
end
|
|
216
596
|
|
|
217
|
-
def configured_providers(config)
|
|
597
|
+
def configured_providers(config) # :nodoc:
|
|
218
598
|
providers.select do |_slug, provider_class|
|
|
219
599
|
provider_class.configured?(config)
|
|
220
600
|
end.values
|
|
221
601
|
end
|
|
222
602
|
|
|
223
|
-
def configured_remote_providers(config)
|
|
603
|
+
def configured_remote_providers(config) # :nodoc:
|
|
224
604
|
providers.select do |_slug, provider_class|
|
|
225
605
|
provider_class.remote? && provider_class.configured?(config)
|
|
226
606
|
end.values
|
|
@@ -229,25 +609,62 @@ module RubyLLM
|
|
|
229
609
|
|
|
230
610
|
private
|
|
231
611
|
|
|
232
|
-
def
|
|
233
|
-
|
|
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?
|
|
234
618
|
|
|
235
|
-
raise
|
|
619
|
+
raise Error, "#{slug} doesn't support file uploads"
|
|
236
620
|
end
|
|
237
621
|
|
|
238
|
-
def
|
|
239
|
-
|
|
240
|
-
|
|
241
|
-
|
|
622
|
+
def resolve_protocol(name, model, **request)
|
|
623
|
+
explicit = name || configured_protocol
|
|
624
|
+
explicit ? fetch_protocol(explicit) : protocol_for(model, **request)
|
|
625
|
+
end
|
|
242
626
|
|
|
243
|
-
|
|
244
|
-
|
|
627
|
+
def default_protocol
|
|
628
|
+
fetch_protocol(configured_protocol || self.class.default_protocol)
|
|
629
|
+
end
|
|
245
630
|
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
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
|
|
251
668
|
end
|
|
252
669
|
|
|
253
670
|
def try_parse_json(maybe_json)
|
|
@@ -258,24 +675,39 @@ module RubyLLM
|
|
|
258
675
|
maybe_json
|
|
259
676
|
end
|
|
260
677
|
|
|
678
|
+
def parse_error_body(response)
|
|
679
|
+
body = response.body
|
|
680
|
+
return if body.nil? || (body.respond_to?(:empty?) && body.empty?)
|
|
681
|
+
|
|
682
|
+
try_parse_json(body)
|
|
683
|
+
end
|
|
684
|
+
|
|
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) }
|
|
693
|
+
end
|
|
694
|
+
|
|
261
695
|
def ensure_configured!
|
|
696
|
+
return if configured?
|
|
697
|
+
|
|
262
698
|
missing = configuration_requirements.reject { |req| @config.send(req) }
|
|
263
|
-
|
|
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
|
|
264
704
|
|
|
265
705
|
raise ConfigurationError,
|
|
266
|
-
"
|
|
267
|
-
'Set these keys on RubyLLM.config before using this provider.'
|
|
268
|
-
end
|
|
269
|
-
|
|
270
|
-
def maybe_normalize_temperature(temperature, _model)
|
|
271
|
-
temperature
|
|
706
|
+
"#{name} provider is not configured. Add this to your initialization:\n\n#{config_block}"
|
|
272
707
|
end
|
|
273
708
|
|
|
274
|
-
def
|
|
275
|
-
|
|
276
|
-
req.headers = additional_headers.merge(req.headers) unless additional_headers.empty?
|
|
277
|
-
end
|
|
278
|
-
parse_completion_response response
|
|
709
|
+
def inspect_attributes # :nodoc:
|
|
710
|
+
{ slug: slug }
|
|
279
711
|
end
|
|
280
712
|
end
|
|
281
713
|
end
|