ruby_llm 2.1.0 → 2.2.0
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/README.md +162 -120
- data/app/assets/javascripts/ruby_llm/chat_message_controller.js +163 -0
- data/app/assets/javascripts/ruby_llm/dompurify.js +9 -0
- data/app/assets/javascripts/ruby_llm/marked.js +71 -0
- data/config/importmap.rb +5 -0
- data/lib/generators/ruby_llm/agent/agent_generator.rb +11 -3
- data/lib/generators/ruby_llm/agent/templates/agent.rb.tt +6 -3
- data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +201 -55
- data/lib/generators/ruby_llm/chat_ui/templates/agents/chat_agent.rb.tt +15 -0
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/answers_controller.rb.tt +31 -0
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +16 -5
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +27 -9
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/queued_messages_controller.rb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/javascript/chat_message_controller.js +1 -0
- data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +5 -8
- data/lib/generators/ruby_llm/chat_ui/templates/jobs/resume_interrupted_chats_job.rb.tt +7 -0
- data/lib/generators/ruby_llm/chat_ui/templates/migrations/add_reply_error_migration.rb.tt +5 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_files.html.erb.tt +15 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +5 -7
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_model_select.html.erb.tt +7 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +32 -3
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +17 -2
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_attachments.html.erb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_error.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_form.html.erb.tt +28 -18
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_queued_message.html.erb.tt +18 -0
- 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_calls.html.erb.tt +4 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +4 -2
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt +4 -2
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/questions/_question.html.erb.tt +101 -0
- data/lib/generators/ruby_llm/chat_ui/templates/test/agents/chat_agent_test.rb.tt +28 -0
- data/lib/generators/ruby_llm/chat_ui/templates/test/application_system_test_case.rb.tt +5 -0
- data/lib/generators/ruby_llm/chat_ui/templates/test/system/chats_test.rb.tt +240 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/answers/create.turbo_stream.erb.tt +6 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_files.html.erb.tt +15 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +5 -7
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_model_select.html.erb.tt +7 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_questions.html.erb.tt +13 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +29 -3
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +16 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_attachments.html.erb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_error.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +26 -16
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_queued_message.html.erb.tt +8 -0
- 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_calls.html.erb.tt +4 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_user.html.erb.tt +3 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +1 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt +3 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/questions/_question.html.erb.tt +78 -0
- data/lib/generators/ruby_llm/evaluation/evaluation_generator.rb +43 -0
- data/lib/generators/ruby_llm/evaluation/templates/evaluation.rb.tt +9 -0
- data/lib/generators/ruby_llm/evaluation/templates/evaluation.yml.tt +4 -0
- data/lib/generators/ruby_llm/generator_helpers.rb +116 -3
- data/lib/generators/ruby_llm/install/install_generator.rb +5 -47
- data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +4 -0
- data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +5 -3
- data/lib/generators/ruby_llm/install/templates/create_ruby_llm_records_migration.rb.tt +41 -1
- data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +2 -1
- data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +1 -2
- data/lib/generators/ruby_llm/judge/judge_generator.rb +35 -0
- data/lib/generators/ruby_llm/judge/templates/judge.rb.tt +2 -0
- data/lib/generators/ruby_llm/mcp/mcp_generator.rb +58 -0
- data/lib/generators/ruby_llm/mcp/templates/mcp.rb.tt +7 -0
- data/lib/generators/ruby_llm/provider/scaffold.rb +5 -12
- data/lib/generators/ruby_llm/provider/templates/core/provider.rb.erb +6 -0
- data/lib/generators/ruby_llm/provider/templates/gem/chat_spec.rb.erb +0 -1
- data/lib/generators/ruby_llm/provider/templates/gem/chat_streaming_spec.rb.erb +0 -1
- data/lib/generators/ruby_llm/provider/templates/gem/moderation_spec.rb.erb +1 -1
- data/lib/generators/ruby_llm/sandbox/sandbox_generator.rb +68 -0
- data/lib/generators/ruby_llm/sandbox/templates/sandbox.rb.tt +3 -0
- data/lib/generators/ruby_llm/skill/skill_generator.rb +48 -0
- data/lib/generators/ruby_llm/tool/templates/tailwind/tool_call.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/tool/templates/tailwind/tool_result.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +0 -3
- data/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/tool/tool_generator.rb +1 -5
- data/lib/generators/ruby_llm/upgrade/templates/upgrade_ruby_llm_to_2_2.rb.tt +164 -0
- data/lib/generators/ruby_llm/upgrade/upgrade_generator.rb +41 -3
- data/lib/ruby_llm/accounting/operation.rb +73 -0
- data/lib/ruby_llm/accounting/usage.rb +74 -24
- data/lib/ruby_llm/active_record/acts_as.rb +78 -11
- data/lib/ruby_llm/active_record/attachment_helpers.rb +10 -4
- data/lib/ruby_llm/active_record/batch.rb +67 -9
- data/lib/ruby_llm/active_record/chat_agent_hooks.rb +17 -0
- data/lib/ruby_llm/active_record/chat_approvals.rb +71 -0
- data/lib/ruby_llm/active_record/chat_cancellation.rb +106 -0
- data/lib/ruby_llm/active_record/chat_column_guard.rb +51 -0
- data/lib/ruby_llm/active_record/chat_file.rb +37 -0
- data/lib/ruby_llm/active_record/chat_files.rb +30 -0
- data/lib/ruby_llm/active_record/chat_helper.rb +73 -0
- data/lib/ruby_llm/active_record/chat_methods.rb +522 -277
- data/lib/ruby_llm/active_record/chat_placeholders.rb +67 -0
- data/lib/ruby_llm/active_record/chat_questions.rb +177 -0
- data/lib/ruby_llm/active_record/chat_queue.rb +120 -0
- data/lib/ruby_llm/active_record/chat_reply_error.rb +35 -0
- data/lib/ruby_llm/active_record/chat_run.rb +51 -0
- data/lib/ruby_llm/active_record/chat_tool_call_state.rb +77 -0
- data/lib/ruby_llm/active_record/chunk_methods.rb +49 -0
- data/lib/ruby_llm/active_record/claim_heartbeat.rb +149 -0
- data/lib/ruby_llm/active_record/file_store.rb +269 -0
- data/lib/ruby_llm/active_record/mcp_credential.rb +24 -9
- data/lib/ruby_llm/active_record/message_broadcasting.rb +85 -0
- data/lib/ruby_llm/active_record/message_methods.rb +83 -11
- data/lib/ruby_llm/active_record/model.rb +37 -9
- data/lib/ruby_llm/active_record/provider_file.rb +21 -4
- data/lib/ruby_llm/active_record/queued_message.rb +50 -0
- data/lib/ruby_llm/active_record/record.rb +3 -1
- data/lib/ruby_llm/active_record/run_claims.rb +608 -0
- data/lib/ruby_llm/active_record/solid_queue_shutdown.rb +33 -0
- data/lib/ruby_llm/active_record/system_instructions.rb +30 -0
- data/lib/ruby_llm/active_record/tool_call.rb +55 -4
- data/lib/ruby_llm/active_record/usage.rb +79 -9
- data/lib/ruby_llm/agent/callbacks.rb +98 -0
- data/lib/ruby_llm/agent/context_budget.rb +26 -0
- data/lib/ruby_llm/agent/join_callbacks.rb +28 -0
- data/lib/ruby_llm/agent/model_options.rb +56 -0
- data/lib/ruby_llm/agent/ownership.rb +22 -0
- data/lib/ruby_llm/agent/tool_conversion.rb +19 -0
- data/lib/ruby_llm/agent.rb +620 -236
- data/lib/ruby_llm/agent_tool.rb +91 -0
- data/lib/ruby_llm/aliases.json +84 -91
- data/lib/ruby_llm/attachment.rb +101 -20
- data/lib/ruby_llm/batch.rb +132 -46
- data/lib/ruby_llm/cached_content.rb +4 -4
- data/lib/ruby_llm/chat/agent_run.rb +22 -0
- data/lib/ruby_llm/chat/context_budget.rb +119 -0
- data/lib/ruby_llm/chat/file_references.rb +259 -0
- data/lib/ruby_llm/chat/message_queue.rb +56 -0
- data/lib/ruby_llm/chat/ownership.rb +18 -0
- data/lib/ruby_llm/chat/rescuable.rb +48 -0
- data/lib/ruby_llm/chat/run.rb +32 -0
- data/lib/ruby_llm/chat/tool_call_ids.rb +46 -0
- data/lib/ruby_llm/chat/tool_call_state.rb +115 -0
- data/lib/ruby_llm/chat/tool_concurrency.rb +90 -49
- data/lib/ruby_llm/chat/tool_search.rb +36 -1
- data/lib/ruby_llm/chat/tool_sequences.rb +84 -0
- data/lib/ruby_llm/chat.rb +1598 -332
- data/lib/ruby_llm/chunk.rb +27 -1
- data/lib/ruby_llm/citation.rb +2 -1
- data/lib/ruby_llm/code_execution/language.rb +28 -0
- data/lib/ruby_llm/code_execution/node_installation.rb +56 -0
- data/lib/ruby_llm/code_execution/packages.rb +241 -0
- data/lib/ruby_llm/code_execution/ruby_installation.rb +170 -0
- data/lib/ruby_llm/code_execution/workspace.rb +314 -0
- data/lib/ruby_llm/code_execution.rb +781 -0
- data/lib/ruby_llm/configuration.rb +151 -4
- data/lib/ruby_llm/content_filter.rb +68 -0
- data/lib/ruby_llm/cost.rb +68 -41
- data/lib/ruby_llm/downloaded_file.rb +10 -9
- data/lib/ruby_llm/embedding.rb +30 -45
- data/lib/ruby_llm/embedding_request.rb +1 -1
- data/lib/ruby_llm/error.rb +167 -14
- data/lib/ruby_llm/evaluation/dataset.rb +5 -4
- data/lib/ruby_llm/evaluation/evidence.rb +5 -3
- data/lib/ruby_llm/evaluation/report.rb +7 -3
- data/lib/ruby_llm/evaluation/result.rb +1 -1
- data/lib/ruby_llm/evaluation.rb +2 -1
- data/lib/ruby_llm/fallback.rb +32 -4
- data/lib/ruby_llm/file_store/converters/ocr.rb +28 -0
- data/lib/ruby_llm/file_store/converters/office/markup.rb +148 -0
- data/lib/ruby_llm/file_store/converters/office/outline.rb +193 -0
- data/lib/ruby_llm/file_store/converters/office/prose.rb +161 -0
- data/lib/ruby_llm/file_store/converters/office/workbook.rb +187 -0
- data/lib/ruby_llm/file_store/converters/office/zip.rb +149 -0
- data/lib/ruby_llm/file_store/converters/office.rb +106 -0
- data/lib/ruby_llm/file_store/converters/text.rb +27 -0
- data/lib/ruby_llm/file_store/converters.rb +84 -0
- data/lib/ruby_llm/file_store/directory.rb +190 -0
- data/lib/ruby_llm/file_store/memory.rb +75 -0
- data/lib/ruby_llm/file_store/tools/base.rb +99 -0
- data/lib/ruby_llm/file_store/tools/delete.rb +48 -0
- data/lib/ruby_llm/file_store/tools/edit.rb +155 -0
- data/lib/ruby_llm/file_store/tools/list.rb +57 -0
- data/lib/ruby_llm/file_store/tools/read.rb +120 -0
- data/lib/ruby_llm/file_store/tools/search.rb +208 -0
- data/lib/ruby_llm/file_store/tools/write.rb +40 -0
- data/lib/ruby_llm/file_store/tools.rb +39 -0
- data/lib/ruby_llm/file_store.rb +338 -0
- data/lib/ruby_llm/files/saving.rb +14 -0
- data/lib/ruby_llm/image.rb +67 -45
- data/lib/ruby_llm/input_required_error.rb +28 -0
- data/lib/ruby_llm/judge/builder.rb +6 -1
- data/lib/ruby_llm/judge.rb +1 -1
- data/lib/ruby_llm/judgment.rb +6 -24
- data/lib/ruby_llm/mcp/client.rb +67 -28
- data/lib/ruby_llm/mcp/collection.rb +39 -2
- data/lib/ruby_llm/mcp/content.rb +7 -2
- data/lib/ruby_llm/mcp/error.rb +8 -2
- data/lib/ruby_llm/mcp/http.rb +117 -37
- data/lib/ruby_llm/mcp/input_request.rb +3 -112
- data/lib/ruby_llm/mcp/input_required_error.rb +3 -21
- data/lib/ruby_llm/mcp/insufficient_scope_error.rb +27 -0
- data/lib/ruby_llm/mcp/listener.rb +7 -1
- data/lib/ruby_llm/mcp/oauth.rb +102 -39
- data/lib/ruby_llm/mcp/outages.rb +172 -0
- data/lib/ruby_llm/mcp/resource.rb +9 -6
- data/lib/ruby_llm/mcp/resource_template.rb +3 -2
- data/lib/ruby_llm/mcp/session_expired.rb +10 -2
- data/lib/ruby_llm/mcp/stdio.rb +66 -18
- data/lib/ruby_llm/mcp/task.rb +13 -40
- data/lib/ruby_llm/mcp/tool.rb +34 -11
- data/lib/ruby_llm/mcp.rb +123 -59
- data/lib/ruby_llm/message.rb +217 -23
- data/lib/ruby_llm/model/modalities.rb +1 -1
- data/lib/ruby_llm/model/pricing.rb +26 -4
- data/lib/ruby_llm/model/pricing_category.rb +38 -15
- data/lib/ruby_llm/model/pricing_tier.rb +3 -2
- data/lib/ruby_llm/model.rb +24 -6
- data/lib/ruby_llm/models/lookup.rb +37 -4
- data/lib/ruby_llm/models/registry.rb +4 -19
- data/lib/ruby_llm/models.json +36186 -27832
- data/lib/ruby_llm/models.rb +44 -39
- data/lib/ruby_llm/moderation.rb +10 -30
- data/lib/ruby_llm/ocr.rb +5 -22
- data/lib/ruby_llm/open_telemetry/attributes.rb +68 -19
- data/lib/ruby_llm/open_telemetry/content.rb +105 -0
- data/lib/ruby_llm/open_telemetry.rb +18 -4
- data/lib/ruby_llm/prompt/context.rb +2 -2
- data/lib/ruby_llm/prompt.rb +7 -5
- data/lib/ruby_llm/protocol/binary_streaming.rb +2 -8
- data/lib/ruby_llm/protocol/cache_lifetime.rb +22 -0
- data/lib/ruby_llm/protocol/native_content.rb +69 -0
- data/lib/ruby_llm/protocol/replayed_arguments.rb +28 -0
- data/lib/ruby_llm/protocol/stream_accumulator.rb +73 -33
- data/lib/ruby_llm/protocol/streaming.rb +105 -22
- data/lib/ruby_llm/protocol/tool_namespaces.rb +100 -0
- data/lib/ruby_llm/protocol/unsupported_operations.rb +42 -0
- data/lib/ruby_llm/protocol.rb +134 -109
- data/lib/ruby_llm/protocols/anthropic/batches.rb +41 -4
- data/lib/ruby_llm/protocols/anthropic/block_binding.rb +30 -0
- data/lib/ruby_llm/protocols/anthropic/chat.rb +135 -60
- data/lib/ruby_llm/protocols/anthropic/files.rb +9 -0
- data/lib/ruby_llm/protocols/anthropic/media.rb +14 -14
- data/lib/ruby_llm/protocols/anthropic/models.rb +16 -10
- data/lib/ruby_llm/protocols/anthropic/streaming.rb +43 -22
- data/lib/ruby_llm/protocols/anthropic/tools.rb +36 -24
- data/lib/ruby_llm/protocols/anthropic.rb +61 -6
- data/lib/ruby_llm/protocols/azure/speech/transcription.rb +125 -0
- data/lib/ruby_llm/protocols/azure/speech.rb +14 -0
- data/lib/ruby_llm/protocols/bedrock/batches.rb +26 -8
- data/lib/ruby_llm/protocols/bedrock/event_stream.rb +205 -0
- data/lib/ruby_llm/protocols/bedrock/files.rb +15 -6
- data/lib/ruby_llm/protocols/bedrock/guardrails.rb +1 -1
- data/lib/ruby_llm/protocols/bedrock/rerank.rb +2 -0
- data/lib/ruby_llm/protocols/chat_completions/chat.rb +158 -58
- data/lib/ruby_llm/protocols/chat_completions/content_filters.rb +45 -0
- data/lib/ruby_llm/protocols/chat_completions/embedding_batches.rb +23 -2
- data/lib/ruby_llm/protocols/chat_completions/embeddings.rb +26 -3
- data/lib/ruby_llm/protocols/chat_completions/images.rb +7 -9
- data/lib/ruby_llm/protocols/chat_completions/media.rb +20 -20
- data/lib/ruby_llm/protocols/chat_completions/moderation.rb +8 -5
- data/lib/ruby_llm/protocols/chat_completions/speech.rb +8 -2
- data/lib/ruby_llm/protocols/chat_completions/streaming.rb +38 -17
- data/lib/ruby_llm/protocols/chat_completions/think_tags.rb +148 -0
- data/lib/ruby_llm/protocols/chat_completions/tools.rb +15 -28
- data/lib/ruby_llm/protocols/chat_completions/transcription.rb +17 -2
- data/lib/ruby_llm/protocols/chat_completions.rb +4 -0
- data/lib/ruby_llm/protocols/cohere/batches.rb +15 -8
- data/lib/ruby_llm/protocols/cohere/chat.rb +47 -25
- data/lib/ruby_llm/protocols/cohere/datasets.rb +6 -2
- data/lib/ruby_llm/protocols/cohere/embeddings.rb +7 -5
- data/lib/ruby_llm/protocols/cohere/media.rb +7 -7
- data/lib/ruby_llm/protocols/cohere/rerank.rb +2 -1
- data/lib/ruby_llm/protocols/cohere/streaming.rb +26 -11
- data/lib/ruby_llm/protocols/cohere/tools.rb +17 -28
- data/lib/ruby_llm/protocols/cohere/transcription.rb +5 -4
- data/lib/ruby_llm/protocols/converse/batches.rb +2 -24
- data/lib/ruby_llm/protocols/converse/chat.rb +210 -75
- data/lib/ruby_llm/protocols/converse/media.rb +24 -22
- data/lib/ruby_llm/protocols/converse/reasoning.rb +23 -9
- data/lib/ruby_llm/protocols/converse/streaming.rb +68 -220
- data/lib/ruby_llm/protocols/converse.rb +17 -1
- data/lib/ruby_llm/protocols/deepgram/speech.rb +1 -2
- data/lib/ruby_llm/protocols/deepgram/streaming_transcription.rb +1 -3
- data/lib/ruby_llm/protocols/deepgram/transcription.rb +28 -19
- data/lib/ruby_llm/protocols/deepgram.rb +3 -2
- data/lib/ruby_llm/protocols/deepseek/files.rb +1 -1
- data/lib/ruby_llm/protocols/elevenlabs/assets.rb +6 -4
- data/lib/ruby_llm/protocols/elevenlabs/flows/images.rb +4 -0
- data/lib/ruby_llm/protocols/elevenlabs/speech.rb +3 -3
- data/lib/ruby_llm/protocols/elevenlabs/streaming_transcription.rb +5 -2
- data/lib/ruby_llm/protocols/elevenlabs/transcription.rb +6 -3
- data/lib/ruby_llm/protocols/files.rb +27 -12
- data/lib/ruby_llm/protocols/gemini/caches.rb +4 -4
- data/lib/ruby_llm/protocols/gemini/chat.rb +118 -51
- data/lib/ruby_llm/protocols/gemini/embedding_batches.rb +9 -5
- data/lib/ruby_llm/protocols/gemini/embeddings.rb +3 -3
- data/lib/ruby_llm/protocols/gemini/file_transcription.rb +4 -0
- data/lib/ruby_llm/protocols/gemini/files.rb +5 -4
- data/lib/ruby_llm/protocols/gemini/images.rb +11 -9
- data/lib/ruby_llm/protocols/gemini/live_transcription.rb +1 -1
- data/lib/ruby_llm/protocols/gemini/long_decode.rb +104 -0
- data/lib/ruby_llm/protocols/gemini/media.rb +31 -28
- data/lib/ruby_llm/protocols/gemini/retry_info.rb +29 -0
- data/lib/ruby_llm/protocols/gemini/schemas.rb +126 -0
- data/lib/ruby_llm/protocols/gemini/speech.rb +9 -3
- data/lib/ruby_llm/protocols/gemini/streaming.rb +37 -13
- data/lib/ruby_llm/protocols/gemini/tools.rb +18 -10
- data/lib/ruby_llm/protocols/gemini/transcription.rb +13 -9
- data/lib/ruby_llm/protocols/gemini/videos.rb +14 -3
- data/lib/ruby_llm/protocols/gemini.rb +16 -0
- data/lib/ruby_llm/protocols/gpustack/responses.rb +3 -3
- data/lib/ruby_llm/protocols/gpustack/tokenization.rb +14 -0
- data/lib/ruby_llm/protocols/interactions/chat.rb +29 -14
- data/lib/ruby_llm/protocols/interactions/content.rb +19 -14
- data/lib/ruby_llm/protocols/interactions/streaming.rb +1 -1
- data/lib/ruby_llm/protocols/interactions/tools.rb +10 -13
- data/lib/ruby_llm/protocols/interactions/videos.rb +99 -0
- data/lib/ruby_llm/protocols/interactions.rb +1 -0
- data/lib/ruby_llm/protocols/invoke_model/cohere_embeddings.rb +28 -10
- data/lib/ruby_llm/protocols/invoke_model/embedding_batches.rb +1 -1
- data/lib/ruby_llm/protocols/invoke_model/nova_embeddings.rb +45 -11
- data/lib/ruby_llm/protocols/invoke_model/stability_images.rb +5 -2
- data/lib/ruby_llm/protocols/invoke_model/titan_multimodal_embeddings.rb +11 -3
- data/lib/ruby_llm/protocols/invoke_model.rb +21 -2
- data/lib/ruby_llm/protocols/meta/transcription/stream.rb +108 -0
- data/lib/ruby_llm/protocols/meta/transcription.rb +76 -0
- data/lib/ruby_llm/protocols/minimax/files.rb +58 -0
- data/lib/ruby_llm/protocols/minimax/images.rb +54 -0
- data/lib/ruby_llm/protocols/minimax/speech.rb +53 -0
- data/lib/ruby_llm/protocols/minimax/transcription.rb +82 -0
- data/lib/ruby_llm/protocols/minimax/videos.rb +93 -0
- data/lib/ruby_llm/protocols/minimax.rb +21 -0
- data/lib/ruby_llm/protocols/mistral/conversations/chat.rb +44 -21
- data/lib/ruby_llm/protocols/mistral/files.rb +6 -4
- data/lib/ruby_llm/protocols/mistral/multi_completion.rb +15 -4
- data/lib/ruby_llm/protocols/moonshot/files.rb +48 -0
- data/lib/ruby_llm/protocols/openai/batches.rb +3 -2
- data/lib/ruby_llm/protocols/openai/decisions.rb +6 -4
- data/lib/ruby_llm/protocols/openai/files.rb +13 -6
- data/lib/ruby_llm/protocols/openrouter/batches.rb +13 -17
- data/lib/ruby_llm/protocols/openrouter/responses.rb +6 -10
- data/lib/ruby_llm/protocols/perplexity/agent.rb +19 -35
- data/lib/ruby_llm/protocols/perplexity/batches.rb +37 -0
- data/lib/ruby_llm/protocols/perplexity/files.rb +19 -9
- data/lib/ruby_llm/protocols/responses/approvals.rb +12 -11
- data/lib/ruby_llm/protocols/responses/chat.rb +165 -75
- data/lib/ruby_llm/protocols/responses/citation_markers.rb +167 -0
- data/lib/ruby_llm/protocols/responses/compaction.rb +2 -2
- data/lib/ruby_llm/protocols/responses/media.rb +17 -9
- data/lib/ruby_llm/protocols/responses/streaming.rb +71 -12
- data/lib/ruby_llm/protocols/responses/token_counting.rb +1 -1
- data/lib/ruby_llm/protocols/responses/tools.rb +48 -1
- data/lib/ruby_llm/protocols/responses.rb +21 -0
- data/lib/ruby_llm/protocols/system_one/responses.rb +9 -2
- data/lib/ruby_llm/protocols/vertexai/files.rb +4 -3
- data/lib/ruby_llm/protocols/vertexai/text_to_speech.rb +116 -0
- data/lib/ruby_llm/protocols/xai/files.rb +13 -6
- data/lib/ruby_llm/protocols/zai/files.rb +37 -0
- data/lib/ruby_llm/provider.rb +128 -31
- data/lib/ruby_llm/providers/anthropic.rb +5 -1
- data/lib/ruby_llm/providers/azure/chat.rb +17 -3
- data/lib/ruby_llm/providers/azure/chat_completions.rb +1 -0
- data/lib/ruby_llm/providers/azure/decisions.rb +17 -0
- data/lib/ruby_llm/providers/azure/embeddings.rb +1 -2
- data/lib/ruby_llm/providers/azure/media.rb +6 -6
- data/lib/ruby_llm/providers/azure/mistral.rb +17 -0
- data/lib/ruby_llm/providers/azure/models.rb +16 -2
- data/lib/ruby_llm/providers/azure/moderation.rb +16 -0
- data/lib/ruby_llm/providers/azure/responses.rb +9 -0
- data/lib/ruby_llm/providers/azure/videos.rb +2 -2
- data/lib/ruby_llm/providers/azure.rb +43 -1
- data/lib/ruby_llm/providers/bedrock/anthropic/batches.rb +32 -0
- data/lib/ruby_llm/providers/bedrock/anthropic.rb +134 -0
- data/lib/ruby_llm/providers/bedrock/auth.rb +12 -3
- data/lib/ruby_llm/providers/bedrock/connection.rb +19 -0
- data/lib/ruby_llm/providers/bedrock/mantle/anthropic.rb +5 -1
- data/lib/ruby_llm/providers/bedrock/mantle/chat_completions.rb +2 -2
- data/lib/ruby_llm/providers/bedrock/mantle/responses.rb +12 -2
- data/lib/ruby_llm/providers/bedrock/mantle/voxtral.rb +1 -1
- data/lib/ruby_llm/providers/bedrock/mantle.rb +7 -0
- data/lib/ruby_llm/providers/bedrock/models.rb +2 -9
- data/lib/ruby_llm/providers/bedrock.rb +51 -8
- data/lib/ruby_llm/providers/cohere.rb +5 -1
- data/lib/ruby_llm/providers/deepgram/chat.rb +25 -0
- data/lib/ruby_llm/providers/deepgram.rb +21 -2
- data/lib/ruby_llm/providers/deepseek/chat.rb +7 -16
- data/lib/ruby_llm/providers/deepseek/responses.rb +25 -8
- data/lib/ruby_llm/providers/deepseek.rb +5 -1
- data/lib/ruby_llm/providers/elevenlabs.rb +2 -1
- data/lib/ruby_llm/providers/gemini.rb +13 -1
- data/lib/ruby_llm/providers/gpustack/chat.rb +23 -3
- data/lib/ruby_llm/providers/gpustack/embeddings.rb +2 -2
- data/lib/ruby_llm/providers/gpustack/media.rb +8 -8
- data/lib/ruby_llm/providers/gpustack/transcription.rb +14 -6
- data/lib/ruby_llm/providers/gpustack.rb +1 -1
- data/lib/ruby_llm/providers/hetzner/chat.rb +23 -6
- data/lib/ruby_llm/providers/hetzner.rb +5 -1
- data/lib/ruby_llm/providers/meta/responses.rb +81 -0
- data/lib/ruby_llm/providers/meta.rb +37 -0
- data/lib/ruby_llm/providers/minimax/anthropic.rb +95 -0
- data/lib/ruby_llm/providers/minimax/chat_completions.rb +48 -0
- data/lib/ruby_llm/providers/minimax/responses.rb +52 -0
- data/lib/ruby_llm/providers/minimax.rb +104 -0
- data/lib/ruby_llm/providers/mistral/chat.rb +34 -4
- data/lib/ruby_llm/providers/mistral/chat_completions/batches.rb +8 -3
- data/lib/ruby_llm/providers/mistral/embeddings.rb +1 -12
- data/lib/ruby_llm/providers/mistral/input_tokens.rb +35 -0
- data/lib/ruby_llm/providers/mistral/media.rb +8 -8
- data/lib/ruby_llm/providers/mistral/transcription.rb +8 -6
- data/lib/ruby_llm/providers/mistral.rb +13 -1
- data/lib/ruby_llm/providers/moonshot/capabilities.rb +16 -0
- data/lib/ruby_llm/providers/moonshot/chat.rb +100 -0
- data/lib/ruby_llm/providers/moonshot/documents.rb +79 -0
- data/lib/ruby_llm/providers/moonshot/responses.rb +55 -0
- data/lib/ruby_llm/providers/moonshot.rb +54 -0
- data/lib/ruby_llm/providers/ollama/chat.rb +5 -6
- data/lib/ruby_llm/providers/ollama/embeddings.rb +41 -0
- data/lib/ruby_llm/providers/ollama/media.rb +6 -6
- data/lib/ruby_llm/providers/ollama/speech.rb +24 -0
- data/lib/ruby_llm/providers/ollama.rb +13 -1
- data/lib/ruby_llm/providers/ollama_cloud.rb +22 -1
- data/lib/ruby_llm/providers/openai/responses.rb +8 -0
- data/lib/ruby_llm/providers/openai.rb +7 -3
- data/lib/ruby_llm/providers/openrouter/chat.rb +15 -9
- data/lib/ruby_llm/providers/openrouter/embeddings.rb +12 -8
- data/lib/ruby_llm/providers/openrouter/images.rb +16 -10
- data/lib/ruby_llm/providers/openrouter/judgments.rb +47 -0
- data/lib/ruby_llm/providers/openrouter/media.rb +5 -5
- data/lib/ruby_llm/providers/openrouter/models.rb +19 -7
- data/lib/ruby_llm/providers/openrouter/streaming.rb +4 -4
- data/lib/ruby_llm/providers/openrouter/videos.rb +1 -1
- data/lib/ruby_llm/providers/openrouter.rb +32 -5
- data/lib/ruby_llm/providers/perplexity/chat.rb +3 -3
- data/lib/ruby_llm/providers/perplexity/embeddings.rb +1 -1
- data/lib/ruby_llm/providers/perplexity/media.rb +6 -6
- data/lib/ruby_llm/providers/perplexity.rb +13 -2
- data/lib/ruby_llm/providers/typesafe.rb +1 -1
- data/lib/ruby_llm/providers/vertexai/anthropic.rb +5 -1
- data/lib/ruby_llm/providers/vertexai/credentials.rb +27 -3
- data/lib/ruby_llm/providers/vertexai/embed_content.rb +1 -1
- data/lib/ruby_llm/providers/vertexai/embeddings.rb +2 -3
- data/lib/ruby_llm/providers/vertexai/gemini.rb +1 -1
- data/lib/ruby_llm/providers/vertexai/mistral.rb +5 -0
- data/lib/ruby_llm/providers/vertexai/models.rb +24 -2
- data/lib/ruby_llm/providers/vertexai/transcription.rb +2 -2
- data/lib/ruby_llm/providers/vertexai/videos.rb +1 -1
- data/lib/ruby_llm/providers/vertexai.rb +56 -9
- data/lib/ruby_llm/providers/xai/chat.rb +3 -3
- data/lib/ruby_llm/providers/xai/chat_completions/batches.rb +8 -3
- data/lib/ruby_llm/providers/xai/images.rb +9 -4
- data/lib/ruby_llm/providers/xai/models.rb +2 -2
- data/lib/ruby_llm/providers/xai/responses.rb +10 -0
- data/lib/ruby_llm/providers/xai/speech.rb +2 -2
- data/lib/ruby_llm/providers/xai/transcription.rb +14 -9
- data/lib/ruby_llm/providers/xai/videos.rb +1 -1
- data/lib/ruby_llm/providers/xai.rb +9 -1
- data/lib/ruby_llm/providers/zai/chat.rb +117 -0
- data/lib/ruby_llm/providers/zai/ocr.rb +65 -0
- data/lib/ruby_llm/providers/zai/token_counting.rb +25 -0
- data/lib/ruby_llm/providers/zai/videos.rb +67 -0
- data/lib/ruby_llm/providers/zai.rb +66 -0
- data/lib/ruby_llm/question.rb +390 -0
- data/lib/ruby_llm/queued_message.rb +74 -0
- data/lib/ruby_llm/railtie.rb +43 -0
- data/lib/ruby_llm/request_shape/part.rb +2 -2
- data/lib/ruby_llm/request_shape/problem.rb +2 -2
- data/lib/ruby_llm/request_shape/tool_round.rb +2 -2
- data/lib/ruby_llm/request_shape/turn.rb +2 -2
- data/lib/ruby_llm/request_shape.rb +1 -1
- data/lib/ruby_llm/rerank.rb +10 -37
- data/lib/ruby_llm/research_job.rb +26 -13
- data/lib/ruby_llm/sandbox/landlock.rb +78 -0
- data/lib/ruby_llm/sandbox/mxc.rb +137 -0
- data/lib/ruby_llm/sandbox.rb +141 -0
- data/lib/ruby_llm/search_results.rb +9 -4
- data/lib/ruby_llm/server_tool_call.rb +1 -1
- data/lib/ruby_llm/skill/loader.rb +131 -0
- data/lib/ruby_llm/skill.rb +263 -0
- data/lib/ruby_llm/speech.rb +25 -53
- data/lib/ruby_llm/stored_file.rb +94 -0
- data/lib/ruby_llm/support/cancellation.rb +4 -4
- data/lib/ruby_llm/support/deprecated_enumerable.rb +13 -0
- data/lib/ruby_llm/support/deprecated_job_predicates.rb +24 -0
- data/lib/ruby_llm/support/inspectable.rb +1 -1
- data/lib/ruby_llm/support/instrumentation.rb +6 -5
- data/lib/ruby_llm/support/rescuable.rb +84 -0
- data/lib/ruby_llm/support/utils.rb +26 -3
- data/lib/ruby_llm/task.rb +126 -0
- data/lib/ruby_llm/test_helper.rb +129 -0
- data/lib/ruby_llm/thinking.rb +3 -3
- data/lib/ruby_llm/tokens.rb +52 -12
- data/lib/ruby_llm/tool.rb +339 -5
- data/lib/ruby_llm/tool_call/collection.rb +132 -0
- data/lib/ruby_llm/tool_call.rb +68 -6
- data/lib/ruby_llm/tools/provider_tools.rb +23 -0
- data/lib/ruby_llm/tools/search.rb +118 -0
- data/lib/ruby_llm/transcription/segment.rb +67 -0
- data/lib/ruby_llm/transcription/wav_audio.rb +2 -1
- data/lib/ruby_llm/transcription.rb +25 -49
- data/lib/ruby_llm/transcription_chunk.rb +9 -9
- data/lib/ruby_llm/transport/connection.rb +146 -33
- data/lib/ruby_llm/transport/error_middleware.rb +94 -49
- data/lib/ruby_llm/transport/event_stream_parser.rb +18 -4
- data/lib/ruby_llm/transport/json_response.rb +9 -3
- data/lib/ruby_llm/transport/log_formatter.rb +20 -0
- data/lib/ruby_llm/transport/release_request.rb +35 -0
- data/lib/ruby_llm/transport/websocket_connection.rb +26 -30
- data/lib/ruby_llm/uploaded_file.rb +15 -6
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm/video.rb +34 -10
- data/lib/ruby_llm/video_job.rb +55 -42
- data/lib/ruby_llm/web_source.rb +38 -0
- data/lib/ruby_llm/workflow.rb +2 -2
- data/lib/ruby_llm.rb +63 -25
- data/lib/tasks/ruby_llm/evaluations.rb +1 -1
- data/skills/rubyllm/SKILL.md +128 -54
- data/skills/rubyllm/references/cost-and-tracing.md +8 -0
- data/skills/rubyllm/references/files-code-and-context.md +6 -0
- data/skills/rubyllm/references/operations.md +8 -0
- data/skills/rubyllm/references/rails-chats.md +22 -0
- data/skills/rubyllm/references/tools-and-agents.md +11 -0
- metadata +219 -14
- data/lib/generators/ruby_llm/chat_ui/templates/helpers/messages_helper.rb.tt +0 -25
- data/lib/generators/ruby_llm/upgrade/templates/upgrade_ruby_llm_to_2_1.rb.tt +0 -78
|
@@ -9,17 +9,16 @@ module RubyLLM
|
|
|
9
9
|
|
|
10
10
|
def render_payload(messages, tools:, temperature:, model:, stream: false, max_output_tokens: nil,
|
|
11
11
|
schema: nil, thinking: nil, citations: false, caching: nil, tool_prefs: nil)
|
|
12
|
-
if thinking&.budget
|
|
13
|
-
|
|
14
|
-
end
|
|
12
|
+
raise ArgumentError, 'Ollama has no thinking budget; use effort:' if thinking&.budget
|
|
13
|
+
|
|
15
14
|
super
|
|
16
15
|
end
|
|
17
16
|
|
|
18
|
-
def
|
|
19
|
-
Ollama::Media.
|
|
17
|
+
def render_content(content, attachments = [])
|
|
18
|
+
Ollama::Media.render_content(content, attachments)
|
|
20
19
|
end
|
|
21
20
|
|
|
22
|
-
def
|
|
21
|
+
def render_role(role)
|
|
23
22
|
role.to_s
|
|
24
23
|
end
|
|
25
24
|
end
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Providers
|
|
5
|
+
class Ollama
|
|
6
|
+
# Ollama embeds text, an image, and audio together when the input is
|
|
7
|
+
# one object holding each as a field.
|
|
8
|
+
module Embeddings
|
|
9
|
+
EMBEDDING_MEDIA_FIELDS = %i[image audio].freeze
|
|
10
|
+
|
|
11
|
+
module_function
|
|
12
|
+
|
|
13
|
+
def embedding_media?
|
|
14
|
+
true
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def render_embedding_payload(text, model:, dimensions:, task_type: nil, title: nil, with: [],
|
|
18
|
+
provider_options: {})
|
|
19
|
+
input = with.any? ? media_embedding_input(text, with) : text
|
|
20
|
+
super(input, model:, dimensions:, task_type:, title:, provider_options:)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def media_embedding_input(text, attachments)
|
|
24
|
+
raise ArgumentError, 'embed one text at a time when embedding attachments' if text.is_a?(Array)
|
|
25
|
+
|
|
26
|
+
input = text.to_s.empty? ? {} : { text: text }
|
|
27
|
+
attachments.each do |attachment|
|
|
28
|
+
unless EMBEDDING_MEDIA_FIELDS.include?(attachment.type)
|
|
29
|
+
raise UnsupportedAttachmentError,
|
|
30
|
+
attachment.mime_type
|
|
31
|
+
end
|
|
32
|
+
raise ArgumentError, "Ollama embeds one #{attachment.type} per input" if input.key?(attachment.type)
|
|
33
|
+
|
|
34
|
+
input[attachment.type] = attachment.encoded
|
|
35
|
+
end
|
|
36
|
+
input
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
end
|
|
@@ -11,22 +11,22 @@ module RubyLLM
|
|
|
11
11
|
%i[image audio text].include?(attachment.type)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def
|
|
15
|
-
Protocols::ChatCompletions::Media.
|
|
14
|
+
def render_content(content, attachments = [])
|
|
15
|
+
Protocols::ChatCompletions::Media.render_parts(content, attachments) do |attachment|
|
|
16
16
|
raise UnsupportedAttachmentError, attachment.mime_type unless supported_attachment?(attachment)
|
|
17
17
|
|
|
18
18
|
case attachment.type
|
|
19
19
|
when :image
|
|
20
|
-
|
|
20
|
+
render_image(attachment)
|
|
21
21
|
when :audio
|
|
22
|
-
Protocols::ChatCompletions::Media.
|
|
22
|
+
Protocols::ChatCompletions::Media.render_audio(attachment)
|
|
23
23
|
when :text
|
|
24
|
-
Protocols::ChatCompletions::Media.
|
|
24
|
+
Protocols::ChatCompletions::Media.render_text_file(attachment)
|
|
25
25
|
end
|
|
26
26
|
end
|
|
27
27
|
end
|
|
28
28
|
|
|
29
|
-
def
|
|
29
|
+
def render_image(image)
|
|
30
30
|
{
|
|
31
31
|
type: 'image_url',
|
|
32
32
|
image_url: {
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Providers
|
|
5
|
+
class Ollama
|
|
6
|
+
# Ollama's dialect of speech generation. Its voices come from each model,
|
|
7
|
+
# so a call with no voice leaves the choice to the model, and it answers
|
|
8
|
+
# with WAV unless asked for PCM.
|
|
9
|
+
module Speech
|
|
10
|
+
DEFAULT_VOICE = 'default'
|
|
11
|
+
DEFAULT_FORMAT = 'wav'
|
|
12
|
+
|
|
13
|
+
def render_speech_payload(input, model:, voice:, format:, provider_options: {})
|
|
14
|
+
{ model:, input:, voice:, response_format: format }.compact.merge(provider_options)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def parse_speech_response(response, model:, voice:, format:)
|
|
18
|
+
RubyLLM::Speech.new(data: response.body, model:, voice: voice || DEFAULT_VOICE,
|
|
19
|
+
format: (format || DEFAULT_FORMAT).to_s, characters: @speech_characters)
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
end
|
|
@@ -7,8 +7,10 @@ module RubyLLM
|
|
|
7
7
|
# Ollama's dialect of the Chat Completions API.
|
|
8
8
|
class ChatCompletions < Protocols::ChatCompletions
|
|
9
9
|
include Ollama::Chat
|
|
10
|
+
include Ollama::Embeddings
|
|
10
11
|
include Ollama::Media
|
|
11
12
|
include Ollama::Models
|
|
13
|
+
include Ollama::Speech
|
|
12
14
|
end
|
|
13
15
|
|
|
14
16
|
# Ollama's dialect of the System One API for decision models such as Clef.
|
|
@@ -16,11 +18,21 @@ module RubyLLM
|
|
|
16
18
|
include Ollama::Judgments
|
|
17
19
|
end
|
|
18
20
|
|
|
21
|
+
# Ollama's stateless Responses API, which serves manual compaction.
|
|
22
|
+
class Responses < Protocols::Responses
|
|
23
|
+
include Protocols::Responses::Compaction
|
|
24
|
+
|
|
25
|
+
def render_image(image, image_url: image.for_llm, **)
|
|
26
|
+
super
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
|
|
19
30
|
protocol :chat_completions, ChatCompletions
|
|
20
31
|
protocol :system_one, SystemOne
|
|
32
|
+
protocol :responses, Responses
|
|
21
33
|
|
|
22
34
|
def api_base
|
|
23
|
-
|
|
35
|
+
configured_api_base(:ollama_api_base, example: 'http://localhost:11434/v1', endpoints: Protocols::ChatCompletions::ENDPOINT_PATHS)
|
|
24
36
|
end
|
|
25
37
|
|
|
26
38
|
def resolve_protocol(name, model, operation: nil, **options)
|
|
@@ -7,12 +7,25 @@ module RubyLLM
|
|
|
7
7
|
# Ollama Cloud's dialect of the Chat Completions API.
|
|
8
8
|
class ChatCompletions < Ollama::ChatCompletions
|
|
9
9
|
include OllamaCloud::Models
|
|
10
|
+
|
|
11
|
+
abstract :embedding_url, :embedding_media?, :speech_url, :build_audio_file_part, :render_image_payload,
|
|
12
|
+
:render_moderation_payload
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
# Ollama Cloud's dialect of the System One API for decision models such as Clef.
|
|
16
|
+
class SystemOne < Ollama::SystemOne
|
|
10
17
|
end
|
|
11
18
|
|
|
12
19
|
protocol :chat_completions, ChatCompletions
|
|
20
|
+
protocol :system_one, SystemOne
|
|
13
21
|
|
|
14
22
|
def api_base
|
|
15
|
-
|
|
23
|
+
configured_api_base(:ollama_cloud_api_base, 'https://ollama.com/v1', endpoints: Protocols::ChatCompletions::ENDPOINT_PATHS)
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def resolve_protocol(name, model, operation: nil, **options)
|
|
27
|
+
name ||= :system_one if operation == :judge
|
|
28
|
+
super
|
|
16
29
|
end
|
|
17
30
|
|
|
18
31
|
def headers
|
|
@@ -20,6 +33,14 @@ module RubyLLM
|
|
|
20
33
|
end
|
|
21
34
|
|
|
22
35
|
class << self
|
|
36
|
+
def open_telemetry_name # :nodoc:
|
|
37
|
+
'ollama'
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def models_dev_provider_ids # :nodoc:
|
|
41
|
+
%w[ollama-cloud]
|
|
42
|
+
end
|
|
43
|
+
|
|
23
44
|
def configuration_options
|
|
24
45
|
%i[ollama_cloud_api_key ollama_cloud_api_base]
|
|
25
46
|
end
|
|
@@ -19,7 +19,7 @@ module RubyLLM
|
|
|
19
19
|
end
|
|
20
20
|
|
|
21
21
|
def api_base
|
|
22
|
-
|
|
22
|
+
configured_api_base(:openai_api_base, 'https://api.openai.com/v1', endpoints: Protocols::ChatCompletions::ENDPOINT_PATHS)
|
|
23
23
|
end
|
|
24
24
|
|
|
25
25
|
def resolve_protocol(name, model, operation: nil, **options)
|
|
@@ -29,9 +29,9 @@ module RubyLLM
|
|
|
29
29
|
|
|
30
30
|
# Audio, realtime, and dedicated search models only exist on Chat Completions.
|
|
31
31
|
def protocol_for(model, **)
|
|
32
|
-
return
|
|
32
|
+
return fetch_protocol(:chat_completions) if Capabilities::SEARCH_MODELS.include?(model.id)
|
|
33
33
|
|
|
34
|
-
model.id.match?(/audio|realtime/) ?
|
|
34
|
+
model.id.match?(/audio|realtime/) ? fetch_protocol(:chat_completions) : super
|
|
35
35
|
end
|
|
36
36
|
|
|
37
37
|
def headers
|
|
@@ -69,6 +69,10 @@ module RubyLLM
|
|
|
69
69
|
def batch_cost_multiplier(**) = 0.5
|
|
70
70
|
|
|
71
71
|
class << self
|
|
72
|
+
def models_dev_provider_ids # :nodoc:
|
|
73
|
+
%w[openai]
|
|
74
|
+
end
|
|
75
|
+
|
|
72
76
|
def capabilities
|
|
73
77
|
OpenAI::Capabilities
|
|
74
78
|
end
|
|
@@ -38,8 +38,8 @@ module RubyLLM
|
|
|
38
38
|
OpenRouter::Media.supported_attachment?(attachment)
|
|
39
39
|
end
|
|
40
40
|
|
|
41
|
-
def
|
|
42
|
-
OpenRouter::Media.
|
|
41
|
+
def render_content(content, attachments = [])
|
|
42
|
+
OpenRouter::Media.render_content(content, attachments)
|
|
43
43
|
end
|
|
44
44
|
|
|
45
45
|
def render_payload(messages, tools:, temperature:, model:, stream: false, max_output_tokens: nil, schema: nil,
|
|
@@ -85,7 +85,7 @@ module RubyLLM
|
|
|
85
85
|
reasoning[:enabled] = thinking.enabled
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
-
def
|
|
88
|
+
def render_thinking(msg)
|
|
89
89
|
return {} unless msg.role == :assistant
|
|
90
90
|
return { reasoning_details: msg.raw_reasoning } if msg.raw_reasoning.is_a?(Array)
|
|
91
91
|
|
|
@@ -103,7 +103,7 @@ module RubyLLM
|
|
|
103
103
|
end
|
|
104
104
|
end
|
|
105
105
|
|
|
106
|
-
def
|
|
106
|
+
def render_message_content(msg, caching: nil)
|
|
107
107
|
content = super
|
|
108
108
|
return content unless caching != false && msg.cache_until_here?
|
|
109
109
|
|
|
@@ -127,7 +127,7 @@ module RubyLLM
|
|
|
127
127
|
ttl ||= options[:ttl]
|
|
128
128
|
|
|
129
129
|
{ type: CACHE_CONTROL_TYPE }.tap do |control|
|
|
130
|
-
control[:ttl] = ttl if ttl
|
|
130
|
+
control[:ttl] = render_cache_lifetime(ttl) if ttl
|
|
131
131
|
end
|
|
132
132
|
end
|
|
133
133
|
|
|
@@ -142,6 +142,8 @@ module RubyLLM
|
|
|
142
142
|
"OpenRouter prompt caching accepts :ttl, got #{format_cache_option_keys(unsupported)}"
|
|
143
143
|
end
|
|
144
144
|
|
|
145
|
+
def cache_option_names = PROMPT_CACHE_OPTIONS
|
|
146
|
+
|
|
145
147
|
def format_cache_option_keys(keys)
|
|
146
148
|
keys.map { |key| ":#{key}" }.join(', ')
|
|
147
149
|
end
|
|
@@ -158,10 +160,14 @@ module RubyLLM
|
|
|
158
160
|
cost
|
|
159
161
|
end
|
|
160
162
|
|
|
161
|
-
def
|
|
163
|
+
def provider_file_references?
|
|
162
164
|
true
|
|
163
165
|
end
|
|
164
166
|
|
|
167
|
+
def provider_file_upload_options(_attachment)
|
|
168
|
+
{}
|
|
169
|
+
end
|
|
170
|
+
|
|
165
171
|
def default_large_file_upload_threshold
|
|
166
172
|
OPENROUTER_INLINE_FILE_THRESHOLD
|
|
167
173
|
end
|
|
@@ -170,7 +176,7 @@ module RubyLLM
|
|
|
170
176
|
attachment.pdf?
|
|
171
177
|
end
|
|
172
178
|
|
|
173
|
-
def
|
|
179
|
+
def parse_thinking_text(message_data)
|
|
174
180
|
candidate = message_data['reasoning']
|
|
175
181
|
return candidate if candidate.is_a?(String)
|
|
176
182
|
|
|
@@ -189,7 +195,7 @@ module RubyLLM
|
|
|
189
195
|
text.empty? ? nil : text
|
|
190
196
|
end
|
|
191
197
|
|
|
192
|
-
def
|
|
198
|
+
def parse_thinking_signature(message_data)
|
|
193
199
|
details = message_data['reasoning_details']
|
|
194
200
|
return nil unless details.is_a?(Array)
|
|
195
201
|
|
|
@@ -204,7 +210,7 @@ module RubyLLM
|
|
|
204
210
|
|
|
205
211
|
# OpenRouter requires the reasoning_details array back untouched for
|
|
206
212
|
# signed reasoning to survive multi-turn tool calls.
|
|
207
|
-
def
|
|
213
|
+
def parse_raw_reasoning(message_data)
|
|
208
214
|
details = message_data['reasoning_details']
|
|
209
215
|
details if details.is_a?(Array) && !details.empty?
|
|
210
216
|
end
|
|
@@ -7,9 +7,13 @@ module RubyLLM
|
|
|
7
7
|
module Embeddings
|
|
8
8
|
EMBEDDING_MEDIA_TYPES = { audio: :input_audio, video: :input_video, pdf: :input_file }.freeze
|
|
9
9
|
|
|
10
|
+
def rejected_options(operation)
|
|
11
|
+
operation == :embed ? %i[title] : super
|
|
12
|
+
end
|
|
13
|
+
|
|
10
14
|
module_function
|
|
11
15
|
|
|
12
|
-
def
|
|
16
|
+
def embedding_media?
|
|
13
17
|
true
|
|
14
18
|
end
|
|
15
19
|
|
|
@@ -18,7 +22,7 @@ module RubyLLM
|
|
|
18
22
|
input = if with.any?
|
|
19
23
|
raise ArgumentError, 'embed one text at a time when embedding attachments' if text.is_a?(Array)
|
|
20
24
|
|
|
21
|
-
[{ content:
|
|
25
|
+
[{ content: render_embedding_content(text, with) }]
|
|
22
26
|
else
|
|
23
27
|
text
|
|
24
28
|
end
|
|
@@ -28,21 +32,21 @@ module RubyLLM
|
|
|
28
32
|
payload.merge(provider_options)
|
|
29
33
|
end
|
|
30
34
|
|
|
31
|
-
def
|
|
32
|
-
Protocols::ChatCompletions::Media.
|
|
33
|
-
|
|
35
|
+
def render_embedding_content(text, attachments)
|
|
36
|
+
Protocols::ChatCompletions::Media.render_parts(text, attachments) do |attachment|
|
|
37
|
+
render_embedding_attachment(attachment)
|
|
34
38
|
end
|
|
35
39
|
end
|
|
36
40
|
|
|
37
|
-
def
|
|
41
|
+
def render_embedding_attachment(attachment)
|
|
38
42
|
raise UnsupportedAttachmentError, attachment.mime_type if attachment.provider_file?
|
|
39
43
|
|
|
40
44
|
if (type = EMBEDDING_MEDIA_TYPES[attachment.type])
|
|
41
45
|
{ type: type.to_s, type => { data: attachment.for_llm, format: attachment.format } }
|
|
42
46
|
elsif attachment.type == :image
|
|
43
|
-
Protocols::ChatCompletions::Media.
|
|
47
|
+
Protocols::ChatCompletions::Media.render_image(attachment)
|
|
44
48
|
else
|
|
45
|
-
Protocols::ChatCompletions::Media.
|
|
49
|
+
Protocols::ChatCompletions::Media.render_attachment(
|
|
46
50
|
attachment, document_attachments: :none, image_attachments: true, audio_attachments: false
|
|
47
51
|
)
|
|
48
52
|
end
|
|
@@ -7,34 +7,40 @@ module RubyLLM
|
|
|
7
7
|
# OpenRouter has a unified images endpoint that generates and edits
|
|
8
8
|
# images across providers and reports the exact cost of each call.
|
|
9
9
|
module Images
|
|
10
|
+
ASPECT_RATIO = /\A(?:\d+:\d+|auto)\z/
|
|
11
|
+
|
|
12
|
+
def rejected_options(operation)
|
|
13
|
+
operation == :paint ? %i[count] : super
|
|
14
|
+
end
|
|
15
|
+
|
|
10
16
|
module_function
|
|
11
17
|
|
|
12
|
-
def images_url(
|
|
18
|
+
def images_url(**)
|
|
13
19
|
'images'
|
|
14
20
|
end
|
|
15
21
|
|
|
16
|
-
def render_image_payload(prompt, model:, size:,
|
|
17
|
-
RubyLLM.logger.debug { "Ignoring size #{size}. Use aspect_ratio/resolution provider options instead." }
|
|
18
|
-
if count && count > 1
|
|
19
|
-
RubyLLM.logger.debug do
|
|
20
|
-
"Ignoring count #{count}. OpenRouter generates one image per request."
|
|
21
|
-
end
|
|
22
|
-
end
|
|
22
|
+
def render_image_payload(prompt, model:, size:, with: nil, provider_options: {}, **)
|
|
23
23
|
payload = { model: model, prompt: prompt }
|
|
24
24
|
references = build_input_references(with)
|
|
25
25
|
payload[:input_references] = references if references.any?
|
|
26
|
+
payload.merge!(render_image_size(size)) if size
|
|
26
27
|
payload.merge(provider_options)
|
|
27
28
|
end
|
|
28
29
|
|
|
30
|
+
def render_image_size(size)
|
|
31
|
+
value = size.to_s.strip
|
|
32
|
+
value.match?(ASPECT_RATIO) ? { aspect_ratio: value } : { size: value }
|
|
33
|
+
end
|
|
34
|
+
|
|
29
35
|
def build_input_references(with)
|
|
30
36
|
Attachment.wrap(with, config: @config).map do |attachment|
|
|
31
37
|
raise UnsupportedAttachmentError, attachment.mime_type unless attachment.image?
|
|
32
38
|
|
|
33
|
-
Protocols::ChatCompletions::Media.
|
|
39
|
+
Protocols::ChatCompletions::Media.render_image(attachment)
|
|
34
40
|
end
|
|
35
41
|
end
|
|
36
42
|
|
|
37
|
-
def validate_paint_inputs!(
|
|
43
|
+
def validate_paint_inputs!(mask:, **)
|
|
38
44
|
raise UnsupportedAttachmentError, 'image mask' unless mask.nil?
|
|
39
45
|
end
|
|
40
46
|
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Providers
|
|
5
|
+
class OpenRouter
|
|
6
|
+
# OpenRouter's dialect of the System One API: the Decisions route, media
|
|
7
|
+
# parts inside the state, and the exact cost of each judgment.
|
|
8
|
+
module Judgments
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def judgment_url
|
|
12
|
+
'../alpha/decisions'
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def render_judgment_payload(input, questions:, model:, with: [], provider_options: {})
|
|
16
|
+
parts = with.map { |attachment| render_judgment_part(attachment) }
|
|
17
|
+
super(render_judgment_state(input, parts), questions:, model:, provider_options:)
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def render_judgment_state(input, parts)
|
|
21
|
+
return input if parts.empty?
|
|
22
|
+
return parts if input.nil?
|
|
23
|
+
|
|
24
|
+
input.is_a?(Array) ? input + parts : [input, *parts]
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def render_judgment_part(attachment)
|
|
28
|
+
raise UnsupportedAttachmentError, attachment.mime_type if attachment.provider_file?
|
|
29
|
+
|
|
30
|
+
case attachment.type
|
|
31
|
+
when :image
|
|
32
|
+
Protocols::ChatCompletions::Media.with_image_detail(
|
|
33
|
+
{ type: 'image_url', image_url: { url: attachment.for_llm } }, attachment
|
|
34
|
+
)
|
|
35
|
+
when :audio then Protocols::ChatCompletions::Media.render_audio(attachment)
|
|
36
|
+
when :video then { type: 'video_url', video_url: { url: attachment.for_llm } }
|
|
37
|
+
else raise UnsupportedAttachmentError, attachment.mime_type
|
|
38
|
+
end
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def reported_cost(usage)
|
|
42
|
+
usage['cost']
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
end
|
|
@@ -12,19 +12,19 @@ module RubyLLM
|
|
|
12
12
|
attachment.video? || Protocols::ChatCompletions::Media.supported_attachment?(attachment)
|
|
13
13
|
end
|
|
14
14
|
|
|
15
|
-
def
|
|
16
|
-
Protocols::ChatCompletions::Media.
|
|
15
|
+
def render_content(content, attachments = [])
|
|
16
|
+
Protocols::ChatCompletions::Media.render_parts(content, attachments) do |attachment|
|
|
17
17
|
if attachment.type == :video
|
|
18
|
-
|
|
18
|
+
render_video(attachment)
|
|
19
19
|
else
|
|
20
|
-
Protocols::ChatCompletions::Media.
|
|
20
|
+
Protocols::ChatCompletions::Media.render_attachment(
|
|
21
21
|
attachment, document_attachments: :pdf, image_attachments: true, audio_attachments: true
|
|
22
22
|
)
|
|
23
23
|
end
|
|
24
24
|
end
|
|
25
25
|
end
|
|
26
26
|
|
|
27
|
-
def
|
|
27
|
+
def render_video(video)
|
|
28
28
|
{
|
|
29
29
|
type: 'video_url',
|
|
30
30
|
video_url: {
|
|
@@ -4,8 +4,8 @@ module RubyLLM
|
|
|
4
4
|
module Providers
|
|
5
5
|
class OpenRouter
|
|
6
6
|
# Models methods of the OpenRouter API integration. Chat models come
|
|
7
|
-
# from the main catalog; embedding, speech, transcription, and
|
|
8
|
-
# models live in separate catalogs that are merged in.
|
|
7
|
+
# from the main catalog; embedding, speech, transcription, image, and
|
|
8
|
+
# decision models live in separate catalogs that are merged in.
|
|
9
9
|
module Models
|
|
10
10
|
CATALOG_URLS = [
|
|
11
11
|
'models',
|
|
@@ -13,18 +13,26 @@ module RubyLLM
|
|
|
13
13
|
'models?output_modalities=speech',
|
|
14
14
|
'models?output_modalities=transcription',
|
|
15
15
|
'models?output_modalities=rerank',
|
|
16
|
+
'models?output_modalities=decisions',
|
|
16
17
|
'images/models'
|
|
17
18
|
].freeze
|
|
18
19
|
|
|
19
20
|
OUTPUT_MODALITY_MAP = {
|
|
20
21
|
'speech' => 'audio',
|
|
21
|
-
'transcription' => 'text'
|
|
22
|
+
'transcription' => 'text',
|
|
23
|
+
'decisions' => 'judgment'
|
|
22
24
|
}.freeze
|
|
23
25
|
|
|
24
26
|
CAPABILITY_BY_OUTPUT_MODALITY = {
|
|
25
27
|
'speech' => 'speech_generation',
|
|
26
28
|
'transcription' => 'transcription',
|
|
27
|
-
'image' => 'image_generation'
|
|
29
|
+
'image' => 'image_generation',
|
|
30
|
+
'decisions' => 'judgment'
|
|
31
|
+
}.freeze
|
|
32
|
+
|
|
33
|
+
CAPABILITIES_BY_INPUT_MODALITY = {
|
|
34
|
+
'image' => %w[vision],
|
|
35
|
+
'video' => %w[vision video]
|
|
28
36
|
}.freeze
|
|
29
37
|
|
|
30
38
|
CAPABILITY_PARAMETERS = {
|
|
@@ -71,7 +79,9 @@ module RubyLLM
|
|
|
71
79
|
end
|
|
72
80
|
|
|
73
81
|
capabilities = supported_parameters_to_capabilities(model_data['supported_parameters']) |
|
|
82
|
+
input_modality_capabilities(modalities[:input]) |
|
|
74
83
|
output_modalities.filter_map { |modality| CAPABILITY_BY_OUTPUT_MODALITY[modality] }
|
|
84
|
+
capabilities -= ['streaming'] if output_modalities.include?('decisions')
|
|
75
85
|
|
|
76
86
|
Model.new(
|
|
77
87
|
id: model_data['id'],
|
|
@@ -97,14 +107,16 @@ module RubyLLM
|
|
|
97
107
|
end
|
|
98
108
|
end
|
|
99
109
|
|
|
110
|
+
def input_modality_capabilities(input_modalities)
|
|
111
|
+
input_modalities.flat_map { |modality| CAPABILITIES_BY_INPUT_MODALITY.fetch(modality, []) }.uniq
|
|
112
|
+
end
|
|
113
|
+
|
|
100
114
|
def supported_parameters_to_capabilities(params)
|
|
101
115
|
return [] unless params
|
|
102
116
|
|
|
103
|
-
|
|
117
|
+
['streaming'] + CAPABILITY_PARAMETERS.filter_map do |capability, parameters|
|
|
104
118
|
capability if parameters.any? { |parameter| params.include?(parameter) }
|
|
105
119
|
end
|
|
106
|
-
capabilities << 'predicted_outputs' if params.include?('logit_bias') && params.include?('top_k')
|
|
107
|
-
capabilities
|
|
108
120
|
end
|
|
109
121
|
end
|
|
110
122
|
end
|
|
@@ -16,11 +16,11 @@ module RubyLLM
|
|
|
16
16
|
Chunk.new(
|
|
17
17
|
role: :assistant,
|
|
18
18
|
model: data['model'],
|
|
19
|
-
content: delta['content'],
|
|
20
|
-
citations:
|
|
19
|
+
content: delta['content'] || delta['refusal'],
|
|
20
|
+
citations: parse_chunk_citations(delta, data),
|
|
21
21
|
thinking: Thinking.build(
|
|
22
|
-
text:
|
|
23
|
-
signature:
|
|
22
|
+
text: parse_thinking_text(delta),
|
|
23
|
+
signature: parse_thinking_signature(delta)
|
|
24
24
|
),
|
|
25
25
|
raw_reasoning: accumulate_raw_reasoning(delta['reasoning_details']),
|
|
26
26
|
tool_calls: parse_tool_calls(delta['tool_calls'], parse_arguments: false, stream_keys: true),
|
|
@@ -36,7 +36,7 @@ module RubyLLM
|
|
|
36
36
|
"videos/#{job.id}"
|
|
37
37
|
end
|
|
38
38
|
|
|
39
|
-
def parse_video_job_status(response,
|
|
39
|
+
def parse_video_job_status(response, **)
|
|
40
40
|
body = response.body
|
|
41
41
|
status = job_status(body)
|
|
42
42
|
state = { status: status, raw: body, reported_cost: reported_cost(body['usage'] || {}) }
|