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
|
@@ -1,43 +1,83 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module RubyLLM
|
|
4
|
-
|
|
5
|
-
#
|
|
4
|
+
class Model
|
|
5
|
+
# A PricingTier holds the prices a model charges within one billing tier,
|
|
6
|
+
# such as standard, batch, or long_context. Each price is in USD per one
|
|
7
|
+
# million tokens. Prices missing from the registry read as +nil+. A zero
|
|
8
|
+
# price means the usage is free.
|
|
9
|
+
#
|
|
10
|
+
# Instances come from PricingCategory#standard, #batch, and #long_context:
|
|
11
|
+
#
|
|
12
|
+
# model = RubyLLM.models.find "claude-sonnet-5"
|
|
13
|
+
# tier = model.pricing.text_tokens.standard
|
|
14
|
+
# tier.input_per_million # => 3
|
|
15
|
+
# tier.output_per_million # => 15
|
|
16
|
+
#
|
|
6
17
|
class PricingTier
|
|
18
|
+
# The names of the price readers a tier responds to.
|
|
7
19
|
ATTRIBUTES = %i[
|
|
8
20
|
input_per_million
|
|
9
21
|
output_per_million
|
|
10
22
|
cache_read_input_per_million
|
|
11
23
|
cache_write_input_per_million
|
|
12
|
-
cached_input_per_million
|
|
13
|
-
cache_creation_input_per_million
|
|
14
24
|
reasoning_output_per_million
|
|
15
25
|
].freeze
|
|
16
26
|
|
|
17
|
-
def initialize(data = {})
|
|
27
|
+
def initialize(data = {}) # :nodoc:
|
|
18
28
|
@values = {}
|
|
19
29
|
|
|
20
30
|
data.each do |key, value|
|
|
21
|
-
|
|
31
|
+
next if value.nil?
|
|
32
|
+
|
|
33
|
+
@values[key.to_sym] = value
|
|
22
34
|
end
|
|
23
35
|
end
|
|
24
36
|
|
|
37
|
+
##
|
|
38
|
+
# :method: input_per_million
|
|
39
|
+
#
|
|
40
|
+
# Returns the USD price per million input tokens, or +nil+ if the
|
|
41
|
+
# price is missing.
|
|
42
|
+
|
|
43
|
+
##
|
|
44
|
+
# :method: output_per_million
|
|
45
|
+
#
|
|
46
|
+
# Returns the USD price per million output tokens, or +nil+ if the
|
|
47
|
+
# price is missing.
|
|
48
|
+
|
|
49
|
+
##
|
|
50
|
+
# :method: cache_read_input_per_million
|
|
51
|
+
#
|
|
52
|
+
# Returns the USD price per million input tokens read from the
|
|
53
|
+
# provider's prompt cache, or +nil+ if the price is missing.
|
|
54
|
+
|
|
55
|
+
##
|
|
56
|
+
# :method: cache_write_input_per_million
|
|
57
|
+
#
|
|
58
|
+
# Returns the USD price per million input tokens written to the
|
|
59
|
+
# provider's prompt cache, or +nil+ if the price is missing.
|
|
60
|
+
|
|
61
|
+
##
|
|
62
|
+
# :method: reasoning_output_per_million
|
|
63
|
+
#
|
|
64
|
+
# Returns the USD price per million reasoning output tokens, or +nil+
|
|
65
|
+
# if the price is missing.
|
|
66
|
+
|
|
25
67
|
ATTRIBUTES.each do |attribute|
|
|
26
68
|
define_method(attribute) do
|
|
27
69
|
@values[attribute]
|
|
28
70
|
end
|
|
29
|
-
|
|
30
|
-
define_method("#{attribute}=") do |value|
|
|
31
|
-
@values[attribute] = value if value && value != 0.0
|
|
32
|
-
end
|
|
33
|
-
end
|
|
34
|
-
|
|
35
|
-
def [](key)
|
|
36
|
-
@values[key.to_sym]
|
|
37
71
|
end
|
|
38
72
|
|
|
73
|
+
# Returns a new Hash of the tier's prices with Symbol keys. Missing
|
|
74
|
+
# prices are omitted.
|
|
75
|
+
#
|
|
76
|
+
# tier.to_h
|
|
77
|
+
# # => {input_per_million: 3, output_per_million: 15, ...}
|
|
78
|
+
#
|
|
39
79
|
def to_h
|
|
40
|
-
@values
|
|
80
|
+
@values.dup
|
|
41
81
|
end
|
|
42
82
|
end
|
|
43
83
|
end
|
data/lib/ruby_llm/model.rb
CHANGED
|
@@ -1,7 +1,249 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module RubyLLM
|
|
4
|
-
# Model
|
|
5
|
-
|
|
4
|
+
# A Model describes one entry in the model registry: the model's identity,
|
|
5
|
+
# capabilities, modalities, pricing, and provider metadata. Instances come
|
|
6
|
+
# from the registry through RubyLLM.models and from Chat#model.
|
|
7
|
+
#
|
|
8
|
+
# model = RubyLLM.models.find('gpt-5.6')
|
|
9
|
+
# model.name # => "GPT-5.6"
|
|
10
|
+
# model.provider # => "openai"
|
|
11
|
+
# model.context_window # => 1050000
|
|
12
|
+
# model.supports?(:vision) # => true
|
|
13
|
+
#
|
|
14
|
+
class Model
|
|
15
|
+
include Support::Inspectable
|
|
16
|
+
|
|
17
|
+
# The provider's identifier for the model, e.g. <tt>"gpt-5.6"</tt>.
|
|
18
|
+
attr_reader :id
|
|
19
|
+
|
|
20
|
+
# The human-readable model name, e.g. <tt>"GPT-5.6"</tt>.
|
|
21
|
+
attr_reader :name
|
|
22
|
+
|
|
23
|
+
# The provider slug as a String, e.g. <tt>"openai"</tt>.
|
|
24
|
+
attr_reader :provider
|
|
25
|
+
|
|
26
|
+
# The model family as a String, e.g. <tt>"gpt"</tt>, or +nil+.
|
|
27
|
+
attr_reader :family
|
|
28
|
+
|
|
29
|
+
# The model's release timestamp as a UTC Time, or +nil+.
|
|
30
|
+
attr_reader :created_at
|
|
31
|
+
|
|
32
|
+
# The maximum number of input tokens the model accepts, or +nil+.
|
|
33
|
+
attr_reader :context_window
|
|
34
|
+
|
|
35
|
+
# The maximum number of tokens the model can generate, or +nil+.
|
|
36
|
+
attr_reader :max_output_tokens
|
|
37
|
+
|
|
38
|
+
# The model's training data cutoff as a Date, or +nil+.
|
|
39
|
+
attr_reader :knowledge_cutoff
|
|
40
|
+
|
|
41
|
+
# The supported input and output modalities as a Model::Modalities object.
|
|
42
|
+
#
|
|
43
|
+
# model.modalities.input # => ["text", "image", "pdf"]
|
|
44
|
+
# model.modalities.output # => ["text"]
|
|
45
|
+
#
|
|
46
|
+
attr_reader :modalities
|
|
47
|
+
|
|
48
|
+
# The model's capability names as an array of Strings,
|
|
49
|
+
# e.g. <tt>["function_calling", "streaming"]</tt>.
|
|
50
|
+
attr_reader :capabilities
|
|
51
|
+
|
|
52
|
+
# The model's pricing as a Model::Pricing object, in USD per million tokens.
|
|
53
|
+
attr_reader :pricing
|
|
54
|
+
|
|
55
|
+
# Provider-specific metadata as a Hash.
|
|
56
|
+
attr_reader :metadata
|
|
57
|
+
|
|
58
|
+
# The time the configured provider stopped listing the model, as a UTC
|
|
59
|
+
# Time, or +nil+ while the provider still lists it. Only a store that keeps
|
|
60
|
+
# unlisted entries, such as the Rails model table, sets it.
|
|
61
|
+
attr_reader :unlisted_at
|
|
62
|
+
|
|
63
|
+
attr_reader :reasoning_options # :nodoc:
|
|
64
|
+
|
|
65
|
+
def self.default(model_id, provider) # :nodoc:
|
|
66
|
+
new(
|
|
67
|
+
id: model_id,
|
|
68
|
+
name: model_id.tr('-', ' ').capitalize,
|
|
69
|
+
provider: provider,
|
|
70
|
+
capabilities: %w[function_calling streaming vision structured_output],
|
|
71
|
+
modalities: { input: %w[text image], output: %w[text] },
|
|
72
|
+
metadata: { warning: 'Assuming model exists, capabilities may not be accurate' }
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
|
|
76
|
+
def initialize(data) # :nodoc:
|
|
77
|
+
@id = data[:id]
|
|
78
|
+
@name = data[:name]
|
|
79
|
+
@provider = data[:provider]
|
|
80
|
+
@family = data[:family]
|
|
81
|
+
@created_at = Support::Utils.to_time(data[:created_at])&.utc
|
|
82
|
+
@context_window = data[:context_window]
|
|
83
|
+
@max_output_tokens = data[:max_output_tokens]
|
|
84
|
+
@knowledge_cutoff = Support::Utils.to_date(data[:knowledge_cutoff])
|
|
85
|
+
@modalities = Modalities.new(data[:modalities] || {})
|
|
86
|
+
@capabilities = data[:capabilities] || []
|
|
87
|
+
@metadata = data[:metadata]&.dup || {}
|
|
88
|
+
@unlisted_at = Support::Utils.to_time(data[:unlisted_at])&.utc
|
|
89
|
+
@pricing = Pricing.new(pricing_data_with_long_context(data[:pricing] || {}))
|
|
90
|
+
@reasoning_options = normalize_reasoning_options(reasoning_options_from(data))
|
|
91
|
+
store_reasoning_options_metadata
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
# Returns whether #capabilities includes +capability+, given as a String
|
|
95
|
+
# or Symbol.
|
|
96
|
+
#
|
|
97
|
+
# model.supports?(:function_calling) # => true
|
|
98
|
+
#
|
|
99
|
+
def supports?(capability)
|
|
100
|
+
capabilities.include?(capability.to_s)
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
# Returns whether the configured provider has stopped listing the model.
|
|
104
|
+
# An unlisted model is kept only because application records still
|
|
105
|
+
# reference it, and it may already be failing at the provider.
|
|
106
|
+
#
|
|
107
|
+
# model.unlisted? # => false
|
|
108
|
+
#
|
|
109
|
+
def unlisted?
|
|
110
|
+
!unlisted_at.nil?
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
# Returns the provider display name and model name combined,
|
|
114
|
+
# e.g. <tt>"OpenAI - GPT-5.6"</tt>.
|
|
115
|
+
def label
|
|
116
|
+
provider_name = provider_class&.display_name || provider
|
|
117
|
+
"#{provider_name} - #{name}"
|
|
118
|
+
end
|
|
119
|
+
|
|
120
|
+
def reasoning_option(type) # :nodoc:
|
|
121
|
+
reasoning_options.find { |option| option[:type] == type.to_s }
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def reasoning_option_values(type) # :nodoc:
|
|
125
|
+
Array(reasoning_option(type)&.fetch(:values, nil))
|
|
126
|
+
end
|
|
127
|
+
|
|
128
|
+
# The standard text-token price columns, keyed by the symbol #price
|
|
129
|
+
# accepts.
|
|
130
|
+
PRICES = {
|
|
131
|
+
input: :input,
|
|
132
|
+
output: :output,
|
|
133
|
+
cache_read: :cache_read_input,
|
|
134
|
+
cache_write: :cache_write_input
|
|
135
|
+
}.freeze
|
|
136
|
+
private_constant :PRICES
|
|
137
|
+
|
|
138
|
+
# Returns the standard text-token price for +kind+ in USD per million
|
|
139
|
+
# tokens, or +nil+ if the registry has no such price. Valid kinds are
|
|
140
|
+
# +:input+, +:output+, +:cache_read+, and +:cache_write+.
|
|
141
|
+
#
|
|
142
|
+
# model.price(:input) # => 2.5
|
|
143
|
+
# model.price(:output) # => 10.0
|
|
144
|
+
#
|
|
145
|
+
def price(kind)
|
|
146
|
+
column = PRICES.fetch(kind) do
|
|
147
|
+
raise ArgumentError, "Unknown price kind: #{kind.inspect}. Valid kinds: #{PRICES.keys.join(', ')}"
|
|
148
|
+
end
|
|
149
|
+
pricing.text_tokens.public_send(column)
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Builds a Cost for +tokens+ (a Tokens object, or anything responding
|
|
153
|
+
# to +tokens+) using this model's pricing.
|
|
154
|
+
#
|
|
155
|
+
# cost = model.cost_for(response.tokens)
|
|
156
|
+
# puts cost.total
|
|
157
|
+
#
|
|
158
|
+
# Pass <tt>tier: :batch</tt> to use the model's explicit batch rates.
|
|
159
|
+
#
|
|
160
|
+
def cost_for(tokens, tier: :standard)
|
|
161
|
+
tokens = tokens.tokens if tokens.respond_to?(:tokens)
|
|
162
|
+
|
|
163
|
+
Cost.new(tokens:, model: self, tier:)
|
|
164
|
+
end
|
|
165
|
+
|
|
166
|
+
# Returns the Provider class registered for this model's provider slug,
|
|
167
|
+
# or +nil+ if no such provider is registered.
|
|
168
|
+
def provider_class
|
|
169
|
+
RubyLLM::Provider.resolve provider
|
|
170
|
+
end
|
|
171
|
+
|
|
172
|
+
# Returns the model's primary function, inferred from its output
|
|
173
|
+
# modalities: +:chat+, +:embedding+, +:moderation+, +:image+, +:audio+,
|
|
174
|
+
# +:video+, or +:rerank+.
|
|
175
|
+
def type
|
|
176
|
+
output = modalities.output
|
|
177
|
+
return :embedding if output.include?('embeddings')
|
|
178
|
+
return :moderation if output.include?('moderation')
|
|
179
|
+
return :image if output.include?('image')
|
|
180
|
+
return :audio if output.include?('audio')
|
|
181
|
+
return :video if output.include?('video')
|
|
182
|
+
return :rerank if output.include?('rerank')
|
|
183
|
+
|
|
184
|
+
:chat
|
|
185
|
+
end
|
|
186
|
+
|
|
187
|
+
def to_h # :nodoc:
|
|
188
|
+
data = {
|
|
189
|
+
id: id,
|
|
190
|
+
name: name,
|
|
191
|
+
provider: provider,
|
|
192
|
+
family: family,
|
|
193
|
+
created_at: created_at,
|
|
194
|
+
context_window: context_window,
|
|
195
|
+
max_output_tokens: max_output_tokens,
|
|
196
|
+
knowledge_cutoff: knowledge_cutoff,
|
|
197
|
+
modalities: modalities.to_h,
|
|
198
|
+
capabilities: capabilities,
|
|
199
|
+
pricing: pricing.to_h,
|
|
200
|
+
metadata: metadata
|
|
201
|
+
}
|
|
202
|
+
# A JSON registry drops unlisted models, so the key stays out of it.
|
|
203
|
+
data[:unlisted_at] = unlisted_at if unlisted_at
|
|
204
|
+
data
|
|
205
|
+
end
|
|
206
|
+
|
|
207
|
+
private
|
|
208
|
+
|
|
209
|
+
def pricing_data_with_long_context(pricing)
|
|
210
|
+
pricing = RubyLLM::Support::Utils.deep_symbolize_keys(RubyLLM::Support::Utils.deep_dup(pricing))
|
|
211
|
+
text = pricing[:text_tokens] || {}
|
|
212
|
+
return pricing if text[:long_context]
|
|
213
|
+
|
|
214
|
+
rates, threshold = PricingCategory.long_context_from_cost(metadata[:cost] || metadata['cost'])
|
|
215
|
+
return pricing unless rates
|
|
216
|
+
|
|
217
|
+
pricing[:text_tokens] = text.merge(long_context: rates)
|
|
218
|
+
pricing[:text_tokens][:long_context_threshold] = threshold if threshold
|
|
219
|
+
pricing
|
|
220
|
+
end
|
|
221
|
+
|
|
222
|
+
def reasoning_options_from(data)
|
|
223
|
+
data[:reasoning_options] || metadata[:reasoning_options] || metadata['reasoning_options']
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
def store_reasoning_options_metadata
|
|
227
|
+
return unless reasoning_options.any?
|
|
228
|
+
|
|
229
|
+
metadata.delete('reasoning_options')
|
|
230
|
+
metadata[:reasoning_options] = reasoning_options
|
|
231
|
+
end
|
|
232
|
+
|
|
233
|
+
def normalize_reasoning_options(options)
|
|
234
|
+
Array(options).filter_map do |option|
|
|
235
|
+
next unless option.is_a?(Hash)
|
|
236
|
+
|
|
237
|
+
normalized = option.to_h.transform_keys(&:to_sym)
|
|
238
|
+
normalized[:type] = normalized[:type].to_s if normalized[:type]
|
|
239
|
+
normalized[:values] = Array(normalized[:values]).map(&:to_s) if normalized.key?(:values)
|
|
240
|
+
normalized[:default] = normalized[:default].to_s if normalized[:default].is_a?(Symbol)
|
|
241
|
+
normalized
|
|
242
|
+
end
|
|
243
|
+
end
|
|
244
|
+
|
|
245
|
+
def inspect_attributes # :nodoc:
|
|
246
|
+
{ id: id, provider: provider, name: name }
|
|
247
|
+
end
|
|
6
248
|
end
|
|
7
249
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'json'
|
|
4
|
+
|
|
5
|
+
module RubyLLM
|
|
6
|
+
class Models
|
|
7
|
+
class Aliases # :nodoc:
|
|
8
|
+
class << self
|
|
9
|
+
def resolve(model_id, provider = nil)
|
|
10
|
+
return model_id unless aliases[model_id]
|
|
11
|
+
|
|
12
|
+
if provider
|
|
13
|
+
aliases[model_id][provider.to_s] || model_id
|
|
14
|
+
else
|
|
15
|
+
aliases[model_id].values.first || model_id
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def aliases
|
|
20
|
+
@aliases ||= load_aliases
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def aliases_file
|
|
24
|
+
File.expand_path('../aliases.json', __dir__)
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def load_aliases
|
|
28
|
+
if File.exist?(aliases_file)
|
|
29
|
+
JSON.parse(File.read(aliases_file))
|
|
30
|
+
else
|
|
31
|
+
{}
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
def reload
|
|
36
|
+
@aliases = load_aliases
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'fileutils'
|
|
4
|
+
require 'json'
|
|
5
|
+
require 'rbconfig'
|
|
6
|
+
require 'tempfile'
|
|
7
|
+
|
|
8
|
+
module RubyLLM
|
|
9
|
+
class Models
|
|
10
|
+
module Registry # :nodoc: all
|
|
11
|
+
PUBLISHED_URL = 'https://rubyllm.com/models.json'
|
|
12
|
+
|
|
13
|
+
class FileStore
|
|
14
|
+
attr_reader :path
|
|
15
|
+
|
|
16
|
+
def initialize(path)
|
|
17
|
+
@path = path.respond_to?(:path) ? path.path : path.to_s
|
|
18
|
+
raise ModelRegistryError, 'A model registry file path is required' if @path.empty?
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def read
|
|
22
|
+
Registry.read(path)
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def etag
|
|
26
|
+
return unless File.file?(path)
|
|
27
|
+
|
|
28
|
+
value = File.read(etag_path).strip
|
|
29
|
+
value unless value.empty?
|
|
30
|
+
rescue Errno::ENOENT
|
|
31
|
+
nil
|
|
32
|
+
rescue SystemCallError => e
|
|
33
|
+
raise ModelRegistryError, "Could not read the model registry ETag from #{etag_path}: #{e.message}"
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def write(models, etag: nil)
|
|
37
|
+
FileUtils.mkdir_p(File.dirname(path))
|
|
38
|
+
atomic_write(path, Registry.pretty_json(models))
|
|
39
|
+
write_etag(etag)
|
|
40
|
+
models
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
private
|
|
44
|
+
|
|
45
|
+
def etag_path
|
|
46
|
+
"#{path}.etag"
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def write_etag(etag)
|
|
50
|
+
if etag
|
|
51
|
+
atomic_write(etag_path, "#{etag}\n")
|
|
52
|
+
else
|
|
53
|
+
FileUtils.rm_f(etag_path)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
57
|
+
def atomic_write(destination, contents)
|
|
58
|
+
directory = File.dirname(destination)
|
|
59
|
+
basename = File.basename(destination)
|
|
60
|
+
Tempfile.create([basename, '.tmp'], directory) do |temporary|
|
|
61
|
+
temporary.binmode
|
|
62
|
+
temporary.write(contents)
|
|
63
|
+
temporary.flush
|
|
64
|
+
temporary.fsync
|
|
65
|
+
temporary.chmod(readable_mode(destination))
|
|
66
|
+
replace_file(temporary.path, destination)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
# Tempfile is 0600 and the rename carries that mode onto the registry,
|
|
71
|
+
# which then reads as unreadable to any other user on the machine.
|
|
72
|
+
def readable_mode(destination)
|
|
73
|
+
return File.stat(destination).mode & 0o7777 if File.exist?(destination)
|
|
74
|
+
|
|
75
|
+
0o666 & ~File.umask
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def replace_file(source, destination)
|
|
79
|
+
File.rename(source, destination)
|
|
80
|
+
rescue Errno::EACCES, Errno::EEXIST
|
|
81
|
+
FileUtils.rm_f(destination)
|
|
82
|
+
File.rename(source, destination)
|
|
83
|
+
end
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
class PublishedSource
|
|
87
|
+
Result = Struct.new(:models, :etag, :not_modified)
|
|
88
|
+
|
|
89
|
+
attr_reader :url
|
|
90
|
+
|
|
91
|
+
def initialize(url = PUBLISHED_URL)
|
|
92
|
+
@url = url
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def fetch(etag: nil)
|
|
96
|
+
connection = RubyLLM::Transport::Connection.basic do |faraday|
|
|
97
|
+
faraday.response :json, parser_options: { symbolize_names: true }
|
|
98
|
+
end
|
|
99
|
+
response = connection.get(url) do |request|
|
|
100
|
+
request.headers['If-None-Match'] = etag if etag
|
|
101
|
+
end
|
|
102
|
+
|
|
103
|
+
return Result.new(nil, response.headers['etag'] || etag, true) if response.status == 304
|
|
104
|
+
|
|
105
|
+
models = Registry.models_from_data(response.body, source: url)
|
|
106
|
+
raise ModelRegistryError, 'Published model registry is empty' if models.empty?
|
|
107
|
+
|
|
108
|
+
Result.new(models, response.headers['etag'], false)
|
|
109
|
+
rescue ModelRegistryError
|
|
110
|
+
raise
|
|
111
|
+
rescue StandardError => e
|
|
112
|
+
raise ModelRegistryError, "Could not refresh the model registry from #{url}: #{e.message}"
|
|
113
|
+
end
|
|
114
|
+
end
|
|
115
|
+
|
|
116
|
+
module_function
|
|
117
|
+
|
|
118
|
+
def read(file)
|
|
119
|
+
data = JSON.parse(File.read(file), symbolize_names: true)
|
|
120
|
+
models_from_data(data, source: file)
|
|
121
|
+
rescue Errno::ENOENT
|
|
122
|
+
nil
|
|
123
|
+
rescue JSON::ParserError => e
|
|
124
|
+
raise ModelRegistryError, "Invalid model registry JSON in #{file}: #{e.message}"
|
|
125
|
+
rescue SystemCallError => e
|
|
126
|
+
raise ModelRegistryError, "Could not read the model registry from #{file}: #{e.message}"
|
|
127
|
+
end
|
|
128
|
+
|
|
129
|
+
def models_from_data(data, source: nil)
|
|
130
|
+
unless data.is_a?(Array)
|
|
131
|
+
location = source ? " in #{source}" : ''
|
|
132
|
+
raise ModelRegistryError, "Model registry#{location} must be a JSON array"
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
data.map { |model| model.is_a?(RubyLLM::Model) ? model : RubyLLM::Model.new(model) }
|
|
136
|
+
end
|
|
137
|
+
|
|
138
|
+
def pretty_json(models)
|
|
139
|
+
"#{JSON.pretty_generate(Array(models).map(&:to_h))}\n"
|
|
140
|
+
end
|
|
141
|
+
|
|
142
|
+
def cache_path
|
|
143
|
+
home = Dir.home
|
|
144
|
+
host_os = RbConfig::CONFIG['host_os']
|
|
145
|
+
|
|
146
|
+
directory = if host_os.match?(/darwin/i)
|
|
147
|
+
File.join(home, 'Library', 'Caches', 'RubyLLM')
|
|
148
|
+
elsif host_os.match?(/mswin|mingw|cygwin/i)
|
|
149
|
+
local_app_data = ENV.fetch('LOCALAPPDATA', nil)
|
|
150
|
+
local_app_data = File.join(home, 'AppData', 'Local') if local_app_data.to_s.empty?
|
|
151
|
+
File.join(local_app_data, 'RubyLLM', 'Cache')
|
|
152
|
+
else
|
|
153
|
+
xdg_cache = ENV.fetch('XDG_CACHE_HOME', nil)
|
|
154
|
+
xdg_cache = File.join(home, '.cache') if xdg_cache.to_s.empty?
|
|
155
|
+
File.join(xdg_cache, 'ruby_llm')
|
|
156
|
+
end
|
|
157
|
+
|
|
158
|
+
File.join(directory, 'models.json')
|
|
159
|
+
rescue ArgumentError
|
|
160
|
+
# Dir.home raises when the home directory cannot be determined.
|
|
161
|
+
nil
|
|
162
|
+
end
|
|
163
|
+
end
|
|
164
|
+
end
|
|
165
|
+
end
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'schematist'
|
|
4
|
+
|
|
5
|
+
module RubyLLM
|
|
6
|
+
class Models
|
|
7
|
+
# Validation schema for entries in the model registry. The registry
|
|
8
|
+
# builder validates refreshed registries against it before publishing,
|
|
9
|
+
# and the spec suite validates the bundled models.json.
|
|
10
|
+
class Schema < Schematist::Schema # :nodoc:
|
|
11
|
+
CAPABILITIES = %w[
|
|
12
|
+
streaming function_calling tool_choice parallel_tool_calls
|
|
13
|
+
structured_output predicted_outputs
|
|
14
|
+
distillation fine_tuning batch realtime image_generation
|
|
15
|
+
speech_generation transcription translation citations reasoning
|
|
16
|
+
caching moderation json_mode vision video ocr
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
description 'A model entry in the RubyLLM model registry'
|
|
20
|
+
|
|
21
|
+
string :id, description: 'Unique identifier for the model'
|
|
22
|
+
string :name, description: 'Display name of the model'
|
|
23
|
+
string :provider, description: 'Provider of the model (e.g., openai, anthropic, mistral)'
|
|
24
|
+
|
|
25
|
+
any_of :family, required: false, description: 'Model family (e.g., gpt-4, claude-3)' do
|
|
26
|
+
string
|
|
27
|
+
null
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Ruby Time#to_s strings today, not ISO8601; normalize at write time
|
|
31
|
+
# before tightening this to format: 'date-time'.
|
|
32
|
+
any_of :created_at, required: false, description: 'Creation date of the model' do
|
|
33
|
+
string
|
|
34
|
+
null
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
any_of :context_window, description: 'Maximum context window size' do
|
|
38
|
+
integer minimum: 0
|
|
39
|
+
null
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
any_of :max_output_tokens, description: 'Maximum output tokens' do
|
|
43
|
+
integer minimum: 0
|
|
44
|
+
null
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
any_of :knowledge_cutoff, required: false, description: 'Knowledge cutoff date' do
|
|
48
|
+
string format: 'date'
|
|
49
|
+
null
|
|
50
|
+
end
|
|
51
|
+
|
|
52
|
+
object :modalities, required: false do
|
|
53
|
+
array :input, description: 'Supported input modalities' do
|
|
54
|
+
string enum: RubyLLM::Models::MODELS_DEV_INPUT_MODALITIES
|
|
55
|
+
end
|
|
56
|
+
array :output, description: 'Supported output modalities' do
|
|
57
|
+
string enum: RubyLLM::Models::MODELS_DEV_OUTPUT_MODALITIES
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
array :capabilities, required: false, description: 'Model capabilities' do
|
|
62
|
+
string enum: CAPABILITIES
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
object :pricing, required: false, description: 'Pricing information for the model' do
|
|
66
|
+
RubyLLM::Model::Pricing::CATEGORIES.each do |category|
|
|
67
|
+
object category, required: false do
|
|
68
|
+
RubyLLM::Model::PricingCategory::TIERS.each do |tier|
|
|
69
|
+
object tier, required: false do
|
|
70
|
+
RubyLLM::Model::PricingTier::ATTRIBUTES.each do |attribute|
|
|
71
|
+
number attribute, minimum: 0, required: false
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
|
75
|
+
integer :long_context_threshold,
|
|
76
|
+
minimum: 0,
|
|
77
|
+
required: false,
|
|
78
|
+
description: 'Prompt size above which long_context rates apply'
|
|
79
|
+
end
|
|
80
|
+
end
|
|
81
|
+
end
|
|
82
|
+
|
|
83
|
+
object :metadata, required: false, description: 'Additional metadata about the model' do
|
|
84
|
+
additional_properties true
|
|
85
|
+
end
|
|
86
|
+
|
|
87
|
+
any_of :unlisted_at, required: false, description: 'When the provider stopped listing the model' do
|
|
88
|
+
string
|
|
89
|
+
null
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# The Draft 2020-12 JSON Schema document for one model entry. Wrap it in an
|
|
93
|
+
# array schema to validate a whole registry.
|
|
94
|
+
def self.json_schema
|
|
95
|
+
new.to_json_schema
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|