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/error.rb
CHANGED
|
@@ -1,112 +1,184 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module RubyLLM
|
|
4
|
-
#
|
|
5
|
-
#
|
|
4
|
+
# Error is the base class for provider-operation errors raised by
|
|
5
|
+
# RubyLLM, including API, network, capability, and provider response-shape
|
|
6
|
+
# failures. When an HTTP response is available, it wraps that response and
|
|
7
|
+
# normalizes the message across providers. Subclasses map common HTTP
|
|
8
|
+
# status codes: BadRequestError (400), UnauthorizedError (401),
|
|
9
|
+
# PaymentRequiredError (402), ForbiddenError (403), RateLimitError (429),
|
|
10
|
+
# ServerError (500), ServiceUnavailableError (502 to 504), and
|
|
11
|
+
# OverloadedError (529).
|
|
12
|
+
#
|
|
13
|
+
# begin
|
|
14
|
+
# RubyLLM.chat.ask "Translate 'hello' to French."
|
|
15
|
+
# rescue RubyLLM::RateLimitError
|
|
16
|
+
# puts "Rate limit hit. Please wait a moment."
|
|
17
|
+
# rescue RubyLLM::Error => e
|
|
18
|
+
# puts "API error: #{e.message}"
|
|
19
|
+
# puts e.response&.status
|
|
20
|
+
# end
|
|
21
|
+
#
|
|
22
|
+
# Local setup and programming errors, such as ConfigurationError and
|
|
23
|
+
# ModelNotFoundError, inherit from StandardError directly and are not
|
|
24
|
+
# caught by rescuing Error.
|
|
6
25
|
class Error < StandardError
|
|
26
|
+
# The HTTP response that caused the error, or +nil+ when none is
|
|
27
|
+
# available. Its +status+ and +body+ carry the provider's reply.
|
|
7
28
|
attr_reader :response
|
|
8
29
|
|
|
9
|
-
def
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
response = nil
|
|
13
|
-
end
|
|
30
|
+
def self.default_message # :nodoc:
|
|
31
|
+
nil
|
|
32
|
+
end
|
|
14
33
|
|
|
34
|
+
# Creates an error with +message+. Pass +response:+ to attach the HTTP
|
|
35
|
+
# response; its body supplies the message when +message+ is +nil+.
|
|
36
|
+
def initialize(message = nil, response: nil)
|
|
15
37
|
@response = response
|
|
16
|
-
super(message || response&.body)
|
|
38
|
+
super(message || response&.body || self.class.default_message)
|
|
17
39
|
end
|
|
18
40
|
end
|
|
19
41
|
|
|
20
|
-
#
|
|
42
|
+
# Raised when a deprecated API is used and
|
|
43
|
+
# Configuration#deprecation_behavior is +:raise+. With the default
|
|
44
|
+
# +:warn+, deprecations are logged instead.
|
|
45
|
+
#
|
|
46
|
+
# RubyLLM.configure do |config|
|
|
47
|
+
# config.deprecation_behavior = :raise
|
|
48
|
+
# end
|
|
49
|
+
class DeprecationError < StandardError; end
|
|
50
|
+
|
|
51
|
+
# Raised when required configuration, such as a provider API key, is
|
|
52
|
+
# missing.
|
|
21
53
|
class ConfigurationError < StandardError; end
|
|
54
|
+
|
|
55
|
+
# Raised when RubyLLM.render_prompt cannot find the named prompt file.
|
|
22
56
|
class PromptNotFoundError < StandardError; end
|
|
57
|
+
|
|
58
|
+
# Raised when a message role outside +:system+, +:user+, +:assistant+, or
|
|
59
|
+
# +:tool+ is used.
|
|
23
60
|
class InvalidRoleError < StandardError; end
|
|
61
|
+
|
|
62
|
+
# Raised when the +choice:+ option of Chat#with_tool_options is neither a
|
|
63
|
+
# known mode nor the name of a registered tool.
|
|
24
64
|
class InvalidToolChoiceError < StandardError; end
|
|
65
|
+
|
|
66
|
+
# Raised when a user message is staged while the last response still has
|
|
67
|
+
# unanswered tool calls. Providers reject such a transcript, so the chat
|
|
68
|
+
# refuses it up front: finish the round with #complete, recording
|
|
69
|
+
# #approve or #deny decisions for calls that require approval.
|
|
70
|
+
class PendingToolCallsError < StandardError; end
|
|
71
|
+
|
|
72
|
+
# Raised when a requested model id is not in the model registry.
|
|
25
73
|
class ModelNotFoundError < StandardError; end
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
class
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
#
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
74
|
+
|
|
75
|
+
# Raised when a model registry cannot be fetched, parsed, or persisted.
|
|
76
|
+
class ModelRegistryError < StandardError; end
|
|
77
|
+
|
|
78
|
+
# Raised when an in-flight chat operation is cancelled with Chat#cancel.
|
|
79
|
+
class CancelledError < StandardError
|
|
80
|
+
# Creates an error for a cancelled chat operation.
|
|
81
|
+
def initialize(message = 'Chat generation cancelled')
|
|
82
|
+
super
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
# Raised when Chat#with_server_tools is used with a provider RubyLLM has
|
|
87
|
+
# no server-tool support for, or with an alias the provider's protocol
|
|
88
|
+
# does not define.
|
|
89
|
+
class UnsupportedServerToolError < Error; end
|
|
90
|
+
|
|
91
|
+
# Raised when an attachment cannot be formatted for the selected provider,
|
|
92
|
+
# for example an audio file sent to a model without audio input.
|
|
93
|
+
class UnsupportedAttachmentError < Error
|
|
94
|
+
GUIDANCE = 'Consider using a model that supports this attachment type.' # :nodoc:
|
|
95
|
+
|
|
96
|
+
def initialize(type = nil) # :nodoc:
|
|
97
|
+
message = 'Unsupported attachment type'
|
|
98
|
+
message = "#{message}: #{type}" if type
|
|
99
|
+
super("#{message}. #{GUIDANCE}")
|
|
100
|
+
end
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Raised for HTTP 400 responses when the request is invalid.
|
|
104
|
+
class BadRequestError < Error
|
|
105
|
+
def self.default_message # :nodoc:
|
|
106
|
+
'Invalid request - please check your input'
|
|
44
107
|
end
|
|
108
|
+
end
|
|
45
109
|
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
110
|
+
# Raised for HTTP 403 responses when the API key lacks permission for the
|
|
111
|
+
# requested resource.
|
|
112
|
+
class ForbiddenError < Error
|
|
113
|
+
def self.default_message # :nodoc:
|
|
114
|
+
'Forbidden - you do not have permission to access this resource'
|
|
50
115
|
end
|
|
116
|
+
end
|
|
51
117
|
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
118
|
+
# Raised when the request exceeds the model's context window or token
|
|
119
|
+
# limits.
|
|
120
|
+
class ContextLengthExceededError < Error
|
|
121
|
+
def self.default_message # :nodoc:
|
|
122
|
+
'Context length exceeded'
|
|
123
|
+
end
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# Raised when a provider returns tool-call arguments that are not valid
|
|
127
|
+
# JSON, often because the response was truncated mid-tool-call.
|
|
128
|
+
class ToolCallParseError < Error
|
|
129
|
+
# Returns the normalized reason generation stopped, or +nil+.
|
|
130
|
+
attr_reader :finish_reason
|
|
131
|
+
|
|
132
|
+
def initialize(message = nil, response: nil, finish_reason: nil) # :nodoc:
|
|
133
|
+
@finish_reason = finish_reason
|
|
134
|
+
message ||= 'Provider returned malformed tool call arguments'
|
|
135
|
+
message = "#{message} (finish_reason: #{finish_reason})" if finish_reason
|
|
136
|
+
super(message, response: response)
|
|
137
|
+
end
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Raised for HTTP 529 responses when the provider is temporarily
|
|
141
|
+
# overloaded.
|
|
142
|
+
class OverloadedError < Error
|
|
143
|
+
def self.default_message # :nodoc:
|
|
144
|
+
'Service overloaded - please try again later'
|
|
145
|
+
end
|
|
146
|
+
end
|
|
147
|
+
|
|
148
|
+
# Raised for HTTP 402 responses when the provider account has a billing
|
|
149
|
+
# or quota problem.
|
|
150
|
+
class PaymentRequiredError < Error
|
|
151
|
+
def self.default_message # :nodoc:
|
|
152
|
+
'Payment required - please top up your account'
|
|
153
|
+
end
|
|
154
|
+
end
|
|
155
|
+
|
|
156
|
+
# Raised for HTTP 429 responses when the provider rate limit is exceeded.
|
|
157
|
+
class RateLimitError < Error
|
|
158
|
+
def self.default_message # :nodoc:
|
|
159
|
+
'Rate limit exceeded - please wait a moment'
|
|
160
|
+
end
|
|
161
|
+
end
|
|
162
|
+
|
|
163
|
+
# Raised for HTTP 500 responses when the provider reports a server error.
|
|
164
|
+
class ServerError < Error
|
|
165
|
+
def self.default_message # :nodoc:
|
|
166
|
+
'API server error - please try again'
|
|
167
|
+
end
|
|
168
|
+
end
|
|
169
|
+
|
|
170
|
+
# Raised for HTTP 502, 503, and 504 responses when the provider is
|
|
171
|
+
# temporarily unavailable.
|
|
172
|
+
class ServiceUnavailableError < Error
|
|
173
|
+
def self.default_message # :nodoc:
|
|
174
|
+
'API server unavailable - please try again later'
|
|
108
175
|
end
|
|
109
176
|
end
|
|
110
|
-
end
|
|
111
177
|
|
|
112
|
-
|
|
178
|
+
# Raised for HTTP 401 responses when the API key is missing or invalid.
|
|
179
|
+
class UnauthorizedError < Error
|
|
180
|
+
def self.default_message # :nodoc:
|
|
181
|
+
'Invalid API key - check your credentials'
|
|
182
|
+
end
|
|
183
|
+
end
|
|
184
|
+
end
|
|
@@ -0,0 +1,133 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
# A Fallback is a fallback model target configured with Chat#with_fallbacks.
|
|
5
|
+
# When the active model fails with a matching error, the chat retries the
|
|
6
|
+
# request with each fallback in order. Instances are yielded to
|
|
7
|
+
# Chat#before_fallback and Chat#after_fallback callbacks, enriched with the
|
|
8
|
+
# details of that attempt.
|
|
9
|
+
#
|
|
10
|
+
# chat = RubyLLM.chat(model: "gpt-4.1")
|
|
11
|
+
# .with_fallbacks("gpt-4.1-mini", "claude-haiku-4-5")
|
|
12
|
+
#
|
|
13
|
+
# chat.before_fallback do |fallback|
|
|
14
|
+
# puts "Falling back from #{fallback.from.id} to #{fallback.to.id}"
|
|
15
|
+
# end
|
|
16
|
+
#
|
|
17
|
+
# chat.after_fallback do |fallback|
|
|
18
|
+
# puts "Fallback #{fallback.succeeded? ? 'succeeded' : 'failed'}"
|
|
19
|
+
# end
|
|
20
|
+
class Fallback
|
|
21
|
+
include Support::Inspectable
|
|
22
|
+
|
|
23
|
+
# The error classes that trigger a fallback when Chat#with_fallbacks is
|
|
24
|
+
# called without +on:+.
|
|
25
|
+
DEFAULT_ERRORS = [
|
|
26
|
+
RateLimitError,
|
|
27
|
+
ServerError,
|
|
28
|
+
ServiceUnavailableError,
|
|
29
|
+
OverloadedError,
|
|
30
|
+
Faraday::TimeoutError,
|
|
31
|
+
Faraday::ConnectionFailed
|
|
32
|
+
].freeze
|
|
33
|
+
|
|
34
|
+
# The model id of the fallback target.
|
|
35
|
+
attr_reader :id
|
|
36
|
+
|
|
37
|
+
# The provider slug of the fallback target as a String, or +nil+.
|
|
38
|
+
attr_reader :provider
|
|
39
|
+
|
|
40
|
+
# The Model object the fallback was configured with, or +nil+ when it was
|
|
41
|
+
# configured with a model id.
|
|
42
|
+
attr_reader :model
|
|
43
|
+
|
|
44
|
+
# The Chat performing the fallback attempt.
|
|
45
|
+
attr_reader :chat
|
|
46
|
+
|
|
47
|
+
# The error that triggered the fallback.
|
|
48
|
+
attr_reader :error
|
|
49
|
+
|
|
50
|
+
# The Model the chat is falling back from.
|
|
51
|
+
attr_reader :from
|
|
52
|
+
|
|
53
|
+
# The Model the chat is falling back to.
|
|
54
|
+
attr_reader :to
|
|
55
|
+
|
|
56
|
+
# The fallback attempt number, starting at 1.
|
|
57
|
+
attr_reader :attempt
|
|
58
|
+
|
|
59
|
+
# The response Message from a successful fallback attempt, or +nil+.
|
|
60
|
+
attr_reader :response
|
|
61
|
+
|
|
62
|
+
# The error raised by the fallback attempt itself, or +nil+ if it
|
|
63
|
+
# succeeded.
|
|
64
|
+
attr_reader :fallback_error
|
|
65
|
+
|
|
66
|
+
attr_reader :streaming # :nodoc:
|
|
67
|
+
|
|
68
|
+
def self.build(value) # :nodoc:
|
|
69
|
+
case value
|
|
70
|
+
when self
|
|
71
|
+
value
|
|
72
|
+
when RubyLLM::Model
|
|
73
|
+
new(model: value)
|
|
74
|
+
when String, Symbol
|
|
75
|
+
new(id: value.to_s)
|
|
76
|
+
else
|
|
77
|
+
raise ArgumentError, 'Expected a model id or RubyLLM::Model'
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
def inspect_attributes # :nodoc:
|
|
82
|
+
{ id: id, provider: provider, attempt: attempt }
|
|
83
|
+
end
|
|
84
|
+
|
|
85
|
+
def initialize(id: nil, provider: nil, model: nil, **attributes) # :nodoc:
|
|
86
|
+
@id = id || model&.id
|
|
87
|
+
@provider = (provider || model&.provider)&.to_s
|
|
88
|
+
@model = model
|
|
89
|
+
@chat = attributes[:chat]
|
|
90
|
+
@error = attributes[:error]
|
|
91
|
+
@from = attributes[:from]
|
|
92
|
+
@to = attributes[:to]
|
|
93
|
+
@attempt = attributes[:attempt]
|
|
94
|
+
@streaming = attributes.fetch(:streaming, false)
|
|
95
|
+
@chunks_yielded = attributes.fetch(:chunks_yielded, false)
|
|
96
|
+
@response = attributes[:response]
|
|
97
|
+
@fallback_error = attributes[:fallback_error]
|
|
98
|
+
end
|
|
99
|
+
|
|
100
|
+
def with_attempt(**attributes) # :nodoc:
|
|
101
|
+
self.class.new(id: id, provider: provider, model: model, **attributes)
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
def finish(response: nil, fallback_error: nil) # :nodoc:
|
|
105
|
+
@response = response
|
|
106
|
+
@fallback_error = fallback_error
|
|
107
|
+
self
|
|
108
|
+
end
|
|
109
|
+
|
|
110
|
+
# Returns whether the fallback happened during a streaming request.
|
|
111
|
+
def streaming?
|
|
112
|
+
streaming
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Returns whether the failed model already yielded stream chunks before
|
|
116
|
+
# the fallback.
|
|
117
|
+
def chunks_yielded?
|
|
118
|
+
@chunks_yielded
|
|
119
|
+
end
|
|
120
|
+
|
|
121
|
+
# Returns +true+ if the fallback attempt raised an error, +false+
|
|
122
|
+
# otherwise.
|
|
123
|
+
def failed?
|
|
124
|
+
!fallback_error.nil?
|
|
125
|
+
end
|
|
126
|
+
|
|
127
|
+
# Returns +true+ if the fallback attempt produced a response without
|
|
128
|
+
# error, +false+ otherwise.
|
|
129
|
+
def succeeded?
|
|
130
|
+
!response.nil? && !failed?
|
|
131
|
+
end
|
|
132
|
+
end
|
|
133
|
+
end
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'marcel'
|
|
4
|
+
|
|
5
|
+
module RubyLLM
|
|
6
|
+
module Files # :nodoc:
|
|
7
|
+
module MimeType # :nodoc: all
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def for(...)
|
|
11
|
+
Marcel::MimeType.for(...)
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def image?(type)
|
|
15
|
+
type.start_with?('image/')
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def video?(type)
|
|
19
|
+
type.start_with?('video/')
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def audio?(type)
|
|
23
|
+
type.start_with?('audio/')
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def pdf?(type)
|
|
27
|
+
type == 'application/pdf'
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def document?(type)
|
|
31
|
+
return false if pdf?(type) || text?(type)
|
|
32
|
+
|
|
33
|
+
DOCUMENT_MIME_TYPES.include?(type) ||
|
|
34
|
+
DOCUMENT_MIME_PREFIXES.any? { |prefix| type.start_with?(prefix) }
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def text?(type)
|
|
38
|
+
type.start_with?('text/') ||
|
|
39
|
+
TEXT_SUFFIXES.any? { |suffix| type.end_with?(suffix) } ||
|
|
40
|
+
NON_TEXT_PREFIX_TEXT_MIME_TYPES.include?(type)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Structured-syntax suffixes (+json, +xml, ...) that mark a non-text/ type as text.
|
|
44
|
+
TEXT_SUFFIXES = ['+json', '+xml', '+html', '+yaml', '+csv', '+plain', '+javascript', '+svg'].freeze
|
|
45
|
+
|
|
46
|
+
# MIME types that don't have a text/ prefix but should be treated as text
|
|
47
|
+
NON_TEXT_PREFIX_TEXT_MIME_TYPES = [
|
|
48
|
+
'application/json', # Base type, even if specific ones end with +json
|
|
49
|
+
'application/xml', # Base type, even if specific ones end with +xml
|
|
50
|
+
'application/javascript',
|
|
51
|
+
'application/ecmascript',
|
|
52
|
+
'application/rtf',
|
|
53
|
+
'application/sql',
|
|
54
|
+
'application/x-sh',
|
|
55
|
+
'application/x-csh',
|
|
56
|
+
'application/x-httpd-php',
|
|
57
|
+
'application/sdp',
|
|
58
|
+
'application/sparql-query',
|
|
59
|
+
'application/graphql',
|
|
60
|
+
'application/yang', # Data modeling language, often serialized as XML/JSON but the type itself is distinct
|
|
61
|
+
'application/mbox', # Mailbox format
|
|
62
|
+
'application/x-tex',
|
|
63
|
+
'application/x-latex',
|
|
64
|
+
'application/x-perl',
|
|
65
|
+
'application/x-python',
|
|
66
|
+
'application/x-tcl',
|
|
67
|
+
'application/pgp-signature', # Often ASCII armored
|
|
68
|
+
'application/pgp-keys', # Often ASCII armored
|
|
69
|
+
'application/vnd.coffeescript',
|
|
70
|
+
'application/vnd.dart',
|
|
71
|
+
'application/vnd.oai.openapi', # Base for OpenAPI, often with +json or +yaml suffix
|
|
72
|
+
'application/vnd.zul', # ZK User Interface Language (can be XML-like)
|
|
73
|
+
'application/x-yaml', # Common non-standard for YAML
|
|
74
|
+
'application/yaml', # Standard for YAML
|
|
75
|
+
'application/toml' # TOML configuration files
|
|
76
|
+
].freeze
|
|
77
|
+
|
|
78
|
+
DOCUMENT_MIME_TYPES = [
|
|
79
|
+
'application/msword',
|
|
80
|
+
'application/rtf',
|
|
81
|
+
'application/vnd.apple.keynote',
|
|
82
|
+
'application/vnd.apple.numbers',
|
|
83
|
+
'application/vnd.apple.pages',
|
|
84
|
+
'application/vnd.google-apps.document',
|
|
85
|
+
'application/vnd.google-apps.presentation',
|
|
86
|
+
'application/vnd.google-apps.spreadsheet',
|
|
87
|
+
'application/vnd.ms-excel',
|
|
88
|
+
'application/vnd.ms-powerpoint'
|
|
89
|
+
].freeze
|
|
90
|
+
|
|
91
|
+
DOCUMENT_MIME_PREFIXES = [
|
|
92
|
+
'application/vnd.openxmlformats-officedocument.',
|
|
93
|
+
'application/vnd.oasis.opendocument.'
|
|
94
|
+
].freeze
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|