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
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
# The Tool an Agent class becomes when it goes wherever tools go, so a
|
|
5
|
+
# model can hand work to it: a subagent. Agent::to_tool returns one, and
|
|
6
|
+
# Chat#with_tools and Agent::tools convert an Agent class themselves.
|
|
7
|
+
#
|
|
8
|
+
# class ResearchAgent < RubyLLM::Agent
|
|
9
|
+
# description "Researches a question across the web and reports back"
|
|
10
|
+
# tools WebSearch, ReadPage
|
|
11
|
+
# end
|
|
12
|
+
#
|
|
13
|
+
# chat.with_tools(ResearchAgent).ask "What changed in Ruby 4.0?"
|
|
14
|
+
#
|
|
15
|
+
# The tool is named after the agent (+research_agent+, or
|
|
16
|
+
# +billing_research_agent+ for <tt>Billing::ResearchAgent</tt>) and takes
|
|
17
|
+
# a +task+, everything the agent needs written to stand alone, plus the
|
|
18
|
+
# agent's ::inputs that ::to_tool was not given. Each call runs a fresh
|
|
19
|
+
# instance of the agent on the task, under the owner of the chat that
|
|
20
|
+
# called it, and returns the content of its last answer.
|
|
21
|
+
#
|
|
22
|
+
# The agent lives only as long as its call, so it has to finish within
|
|
23
|
+
# it. When it stops to wait instead, for an approval, a question, or a
|
|
24
|
+
# task, the call raises ConfigurationError. To let a subagent wait for a
|
|
25
|
+
# person, give it a chat of its own, as the Agents guide shows.
|
|
26
|
+
class AgentTool < Tool
|
|
27
|
+
GUIDE = 'https://rubyllm.com/agents/#subagents-that-wait-for-a-person'
|
|
28
|
+
private_constant :GUIDE
|
|
29
|
+
|
|
30
|
+
class << self
|
|
31
|
+
# The Agent class this tool runs.
|
|
32
|
+
attr_reader :agent_class
|
|
33
|
+
|
|
34
|
+
# The agent's ::inputs that the model supplies, as parameters.
|
|
35
|
+
attr_reader :input_parameters # :nodoc:
|
|
36
|
+
|
|
37
|
+
# Returns a Tool class for +agent_class+, with the inputs in +given+
|
|
38
|
+
# left out of its parameters.
|
|
39
|
+
def for(agent_class, given) # :nodoc:
|
|
40
|
+
raise ArgumentError, 'An agent needs a class name to become a tool' unless agent_class.name
|
|
41
|
+
if agent_class.inputs.include?(:task)
|
|
42
|
+
raise ArgumentError, "#{agent_class.name} has an input named task, which its tool takes for the prompt"
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
missing = agent_class.inputs - given.keys.map(&:to_sym)
|
|
46
|
+
Class.new(self) do
|
|
47
|
+
@agent_class = agent_class
|
|
48
|
+
@input_parameters = missing
|
|
49
|
+
description(agent_class.description || "Hands a task to the #{agent_class.name} agent, which reports back.")
|
|
50
|
+
parameter :task, description: 'Everything the agent needs to do the work, written to stand alone'
|
|
51
|
+
missing.each { |input| parameter input, description: "The agent's #{input}" }
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
|
|
55
|
+
# Returns the agent's name in snake case, such as +research_agent+.
|
|
56
|
+
def tool_name
|
|
57
|
+
Support::Utils.underscore(agent_class.name.gsub('::', '_'))
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def initialize(inputs = {}) # :nodoc:
|
|
62
|
+
super()
|
|
63
|
+
@inputs = inputs
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Runs a fresh instance of the agent on +task+, with the inputs given to
|
|
67
|
+
# Agent::to_tool and those the model chose, and returns the content of
|
|
68
|
+
# its last answer. Raises ConfigurationError, naming what the agent
|
|
69
|
+
# waits for, when it stops to wait instead of answering.
|
|
70
|
+
def execute(task:, **inputs)
|
|
71
|
+
agent = self.class.agent_class.new(**@inputs, **inputs.slice(*self.class.input_parameters))
|
|
72
|
+
owner = Accounting::Usage.owner
|
|
73
|
+
agent.with_owner(owner) unless owner.nil?
|
|
74
|
+
message = agent.ask(task)
|
|
75
|
+
raise ConfigurationError, stopped(agent) if agent.waiting?
|
|
76
|
+
|
|
77
|
+
message&.content
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
private
|
|
81
|
+
|
|
82
|
+
def stopped(agent)
|
|
83
|
+
people = agent.pending_questions.map do |question|
|
|
84
|
+
"a person to #{question.approval? ? 'approve' : 'answer'} #{question.tool_call.name}"
|
|
85
|
+
end
|
|
86
|
+
tasks = agent.pending_tasks.map { |task| "the task of #{task.tool_call.name}" }
|
|
87
|
+
"#{self.class.agent_class.name} stopped to wait for #{(people + tasks).join(' and ')}, but an agent used " \
|
|
88
|
+
"as a tool has to finish within its call. To let it wait, give it a chat of its own: #{GUIDE}"
|
|
89
|
+
end
|
|
90
|
+
end
|
|
91
|
+
end
|
data/lib/ruby_llm/aliases.json
CHANGED
|
@@ -1,4 +1,7 @@
|
|
|
1
1
|
{
|
|
2
|
+
"antigravity-preview": {
|
|
3
|
+
"gemini": "antigravity-preview-latest"
|
|
4
|
+
},
|
|
2
5
|
"claude-fable-5": {
|
|
3
6
|
"anthropic": "claude-fable-5",
|
|
4
7
|
"openrouter": "anthropic/claude-fable-5",
|
|
@@ -13,6 +16,13 @@
|
|
|
13
16
|
"vertexai": "claude-fable-5-1",
|
|
14
17
|
"azure": "claude-fable-5-1"
|
|
15
18
|
},
|
|
19
|
+
"claude-fable-5-2": {
|
|
20
|
+
"anthropic": "claude-fable-5-2",
|
|
21
|
+
"openrouter": "anthropic/claude-fable-5.2",
|
|
22
|
+
"bedrock": "anthropic.claude-fable-5-2",
|
|
23
|
+
"vertexai": "claude-fable-5-2",
|
|
24
|
+
"azure": "claude-fable-5-2"
|
|
25
|
+
},
|
|
16
26
|
"claude-haiku-4-5": {
|
|
17
27
|
"anthropic": "claude-haiku-4-5-20251001",
|
|
18
28
|
"openrouter": "anthropic/claude-haiku-4.5",
|
|
@@ -20,6 +30,13 @@
|
|
|
20
30
|
"vertexai": "claude-haiku-4-5",
|
|
21
31
|
"azure": "claude-haiku-4-5-20251001"
|
|
22
32
|
},
|
|
33
|
+
"claude-haiku-5-5": {
|
|
34
|
+
"anthropic": "claude-haiku-5-5",
|
|
35
|
+
"openrouter": "anthropic/claude-haiku-5.5",
|
|
36
|
+
"bedrock": "anthropic.claude-haiku-5-5",
|
|
37
|
+
"vertexai": "claude-haiku-5-5",
|
|
38
|
+
"azure": "claude-haiku-5-5"
|
|
39
|
+
},
|
|
23
40
|
"claude-opus-4-1": {
|
|
24
41
|
"bedrock": "anthropic.claude-opus-4-1-20250805-v1:0",
|
|
25
42
|
"vertexai": "claude-opus-4-1"
|
|
@@ -94,13 +111,6 @@
|
|
|
94
111
|
"vertexai": "claude-sonnet-5-5",
|
|
95
112
|
"azure": "claude-sonnet-5-5"
|
|
96
113
|
},
|
|
97
|
-
"deepseek-ocr": {
|
|
98
|
-
"vertexai": "deepseek-ai/deepseek-ocr-maas"
|
|
99
|
-
},
|
|
100
|
-
"deepseek-r1-0528": {
|
|
101
|
-
"vertexai": "deepseek-ai/deepseek-r1-0528-maas",
|
|
102
|
-
"openrouter": "deepseek/deepseek-r1-0528"
|
|
103
|
-
},
|
|
104
114
|
"deepseek-v3.1": {
|
|
105
115
|
"vertexai": "deepseek-ai/deepseek-v3.1-maas"
|
|
106
116
|
},
|
|
@@ -120,6 +130,10 @@
|
|
|
120
130
|
"deepseek": "deepseek-v4-pro",
|
|
121
131
|
"openrouter": "deepseek/deepseek-v4-pro"
|
|
122
132
|
},
|
|
133
|
+
"deepseek-v4.1-flash": {
|
|
134
|
+
"vertexai": "deepseek-ai/deepseek-v4.1-flash-maas",
|
|
135
|
+
"openrouter": "deepseek/deepseek-v4.1-flash"
|
|
136
|
+
},
|
|
123
137
|
"enhanced": {
|
|
124
138
|
"deepgram": "enhanced-general"
|
|
125
139
|
},
|
|
@@ -142,6 +156,9 @@
|
|
|
142
156
|
"openrouter": "google/gemini-2.5-flash-lite",
|
|
143
157
|
"vertexai": "gemini-2.5-flash-lite"
|
|
144
158
|
},
|
|
159
|
+
"gemini-2.5-flash-native-audio": {
|
|
160
|
+
"gemini": "gemini-2.5-flash-native-audio-latest"
|
|
161
|
+
},
|
|
145
162
|
"gemini-2.5-pro": {
|
|
146
163
|
"gemini": "gemini-2.5-pro",
|
|
147
164
|
"openrouter": "google/gemini-2.5-pro",
|
|
@@ -183,8 +200,7 @@
|
|
|
183
200
|
},
|
|
184
201
|
"gemini-3.1-flash-lite-preview": {
|
|
185
202
|
"gemini": "gemini-3.1-flash-lite-preview",
|
|
186
|
-
"openrouter": "google/gemini-3.1-flash-lite-preview"
|
|
187
|
-
"vertexai": "gemini-3.1-flash-lite-preview"
|
|
203
|
+
"openrouter": "google/gemini-3.1-flash-lite-preview"
|
|
188
204
|
},
|
|
189
205
|
"gemini-3.1-flash-tts-preview": {
|
|
190
206
|
"gemini": "gemini-3.1-flash-tts-preview",
|
|
@@ -217,7 +233,12 @@
|
|
|
217
233
|
},
|
|
218
234
|
"gemini-3.5-transcribe": {
|
|
219
235
|
"gemini": "gemini-3.5-transcribe",
|
|
220
|
-
"openrouter": "google/gemini-3.5-transcribe"
|
|
236
|
+
"openrouter": "google/gemini-3.5-transcribe",
|
|
237
|
+
"vertexai": "gemini-3.5-transcribe"
|
|
238
|
+
},
|
|
239
|
+
"gemini-3.5-transcribe-live": {
|
|
240
|
+
"gemini": "gemini-3.5-transcribe-live",
|
|
241
|
+
"vertexai": "gemini-3.5-transcribe-live"
|
|
221
242
|
},
|
|
222
243
|
"gemini-3.6-flash": {
|
|
223
244
|
"gemini": "gemini-3.6-flash",
|
|
@@ -236,16 +257,28 @@
|
|
|
236
257
|
},
|
|
237
258
|
"gemini-3.8-flash-lite-tts": {
|
|
238
259
|
"gemini": "gemini-3.8-flash-lite-tts",
|
|
239
|
-
"openrouter": "google/gemini-3.8-flash-lite-tts"
|
|
260
|
+
"openrouter": "google/gemini-3.8-flash-lite-tts",
|
|
261
|
+
"vertexai": "gemini-3.8-flash-lite-tts"
|
|
240
262
|
},
|
|
241
263
|
"gemini-3.8-flash-tts": {
|
|
242
264
|
"gemini": "gemini-3.8-flash-tts",
|
|
243
|
-
"openrouter": "google/gemini-3.8-flash-tts"
|
|
265
|
+
"openrouter": "google/gemini-3.8-flash-tts",
|
|
266
|
+
"vertexai": "gemini-3.8-flash-tts"
|
|
244
267
|
},
|
|
245
268
|
"gemini-3.8-live": {
|
|
246
269
|
"gemini": "gemini-3.8-live",
|
|
247
270
|
"vertexai": "gemini-3.8-live"
|
|
248
271
|
},
|
|
272
|
+
"gemini-4-argon": {
|
|
273
|
+
"gemini": "gemini-4-argon",
|
|
274
|
+
"openrouter": "google/gemini-4-argon",
|
|
275
|
+
"vertexai": "gemini-4-argon"
|
|
276
|
+
},
|
|
277
|
+
"gemini-4-carbon": {
|
|
278
|
+
"gemini": "gemini-4-carbon",
|
|
279
|
+
"openrouter": "google/gemini-4-carbon",
|
|
280
|
+
"vertexai": "gemini-4-carbon"
|
|
281
|
+
},
|
|
249
282
|
"gemini-embedding-001": {
|
|
250
283
|
"gemini": "gemini-embedding-001",
|
|
251
284
|
"openrouter": "google/gemini-embedding-001",
|
|
@@ -261,22 +294,36 @@
|
|
|
261
294
|
"openrouter": "google/gemini-embedding-2-preview"
|
|
262
295
|
},
|
|
263
296
|
"gemini-flash": {
|
|
264
|
-
"gemini": "gemini-flash-latest"
|
|
265
|
-
"vertexai": "gemini-flash-latest"
|
|
297
|
+
"gemini": "gemini-flash-latest"
|
|
266
298
|
},
|
|
267
299
|
"gemini-flash-lite": {
|
|
268
|
-
"gemini": "gemini-flash-lite-latest"
|
|
269
|
-
"vertexai": "gemini-flash-lite-latest"
|
|
300
|
+
"gemini": "gemini-flash-lite-latest"
|
|
270
301
|
},
|
|
271
302
|
"gemini-nano-banana-2.1": {
|
|
272
303
|
"gemini": "gemini-nano-banana-2.1",
|
|
273
304
|
"openrouter": "google/gemini-nano-banana-2.1",
|
|
274
305
|
"vertexai": "gemini-nano-banana-2.1"
|
|
275
306
|
},
|
|
307
|
+
"gemini-omni-1.1-flash": {
|
|
308
|
+
"gemini": "gemini-omni-1.1-flash",
|
|
309
|
+
"openrouter": "google/gemini-omni-1.1-flash"
|
|
310
|
+
},
|
|
311
|
+
"gemini-omni-1.2-flash": {
|
|
312
|
+
"gemini": "gemini-omni-1.2-flash",
|
|
313
|
+
"openrouter": "google/gemini-omni-1.2-flash",
|
|
314
|
+
"vertexai": "gemini-omni-1.2-flash"
|
|
315
|
+
},
|
|
276
316
|
"gemini-omni-flash-preview": {
|
|
277
317
|
"gemini": "gemini-omni-flash-preview",
|
|
278
318
|
"vertexai": "gemini-omni-flash-preview"
|
|
279
319
|
},
|
|
320
|
+
"gemini-pro": {
|
|
321
|
+
"gemini": "gemini-pro-latest"
|
|
322
|
+
},
|
|
323
|
+
"gemini-robotics-er-2-preview": {
|
|
324
|
+
"gemini": "gemini-robotics-er-2-preview",
|
|
325
|
+
"vertexai": "gemini-robotics-er-2-preview"
|
|
326
|
+
},
|
|
280
327
|
"gemma-4-26b-a4b-it": {
|
|
281
328
|
"gemini": "gemma-4-26b-a4b-it",
|
|
282
329
|
"openrouter": "google/gemma-4-26b-a4b-it",
|
|
@@ -298,28 +345,10 @@
|
|
|
298
345
|
"vertexai": "zai-org/glm-5.2-maas",
|
|
299
346
|
"openrouter": "z-ai/glm-5.2"
|
|
300
347
|
},
|
|
301
|
-
"gpt-3.5-turbo": {
|
|
302
|
-
"openai": "gpt-3.5-turbo",
|
|
303
|
-
"openrouter": "openai/gpt-3.5-turbo"
|
|
304
|
-
},
|
|
305
|
-
"gpt-3.5-turbo-16k": {
|
|
306
|
-
"openai": "gpt-3.5-turbo-16k",
|
|
307
|
-
"openrouter": "openai/gpt-3.5-turbo-16k"
|
|
308
|
-
},
|
|
309
|
-
"gpt-3.5-turbo-instruct": {
|
|
310
|
-
"openai": "gpt-3.5-turbo-instruct",
|
|
311
|
-
"openrouter": "openai/gpt-3.5-turbo-instruct",
|
|
312
|
-
"azure": "gpt-3.5-turbo-instruct"
|
|
313
|
-
},
|
|
314
348
|
"gpt-4": {
|
|
315
349
|
"openai": "gpt-4",
|
|
316
350
|
"openrouter": "openai/gpt-4"
|
|
317
351
|
},
|
|
318
|
-
"gpt-4-turbo": {
|
|
319
|
-
"openai": "gpt-4-turbo",
|
|
320
|
-
"openrouter": "openai/gpt-4-turbo",
|
|
321
|
-
"azure": "gpt-4-turbo"
|
|
322
|
-
},
|
|
323
352
|
"gpt-4.1": {
|
|
324
353
|
"openai": "gpt-4.1",
|
|
325
354
|
"openrouter": "openai/gpt-4.1",
|
|
@@ -400,31 +429,11 @@
|
|
|
400
429
|
"openrouter": "openai/gpt-5.1",
|
|
401
430
|
"azure": "gpt-5.1"
|
|
402
431
|
},
|
|
403
|
-
"gpt-5.1-codex": {
|
|
404
|
-
"openai": "gpt-5.1-codex",
|
|
405
|
-
"openrouter": "openai/gpt-5.1-codex",
|
|
406
|
-
"azure": "gpt-5.1-codex"
|
|
407
|
-
},
|
|
408
|
-
"gpt-5.1-codex-max": {
|
|
409
|
-
"openai": "gpt-5.1-codex-max",
|
|
410
|
-
"openrouter": "openai/gpt-5.1-codex-max",
|
|
411
|
-
"azure": "gpt-5.1-codex-max"
|
|
412
|
-
},
|
|
413
|
-
"gpt-5.1-codex-mini": {
|
|
414
|
-
"openai": "gpt-5.1-codex-mini",
|
|
415
|
-
"openrouter": "openai/gpt-5.1-codex-mini",
|
|
416
|
-
"azure": "gpt-5.1-codex-mini"
|
|
417
|
-
},
|
|
418
432
|
"gpt-5.2": {
|
|
419
433
|
"openai": "gpt-5.2",
|
|
420
434
|
"openrouter": "openai/gpt-5.2",
|
|
421
435
|
"azure": "gpt-5.2"
|
|
422
436
|
},
|
|
423
|
-
"gpt-5.2-codex": {
|
|
424
|
-
"openai": "gpt-5.2-codex",
|
|
425
|
-
"openrouter": "openai/gpt-5.2-codex",
|
|
426
|
-
"azure": "gpt-5.2-codex"
|
|
427
|
-
},
|
|
428
437
|
"gpt-5.2-pro": {
|
|
429
438
|
"openai": "gpt-5.2-pro",
|
|
430
439
|
"openrouter": "openai/gpt-5.2-pro"
|
|
@@ -498,6 +507,11 @@
|
|
|
498
507
|
"openrouter": "openai/gpt-6.1-sol",
|
|
499
508
|
"azure": "gpt-6.1-sol"
|
|
500
509
|
},
|
|
510
|
+
"gpt-6.1-terra": {
|
|
511
|
+
"openai": "gpt-6.1-terra",
|
|
512
|
+
"openrouter": "openai/gpt-6.1-terra",
|
|
513
|
+
"azure": "gpt-6.1-terra"
|
|
514
|
+
},
|
|
501
515
|
"gpt-audio": {
|
|
502
516
|
"openai": "gpt-audio",
|
|
503
517
|
"openrouter": "openai/gpt-audio",
|
|
@@ -508,11 +522,6 @@
|
|
|
508
522
|
"openrouter": "openai/gpt-audio-mini",
|
|
509
523
|
"azure": "gpt-audio-mini"
|
|
510
524
|
},
|
|
511
|
-
"gpt-image-1": {
|
|
512
|
-
"openai": "gpt-image-1",
|
|
513
|
-
"openrouter": "openai/gpt-image-1",
|
|
514
|
-
"azure": "gpt-image-1"
|
|
515
|
-
},
|
|
516
525
|
"gpt-image-1-mini": {
|
|
517
526
|
"openai": "gpt-image-1-mini",
|
|
518
527
|
"openrouter": "openai/gpt-image-1-mini",
|
|
@@ -533,6 +542,14 @@
|
|
|
533
542
|
"openrouter": "openai/gpt-image-2.5-sunburst",
|
|
534
543
|
"azure": "gpt-image-2.5-sunburst"
|
|
535
544
|
},
|
|
545
|
+
"gpt-image-2.6-flare": {
|
|
546
|
+
"openai": "gpt-image-2.6-flare",
|
|
547
|
+
"openrouter": "openai/gpt-image-2.6-flare"
|
|
548
|
+
},
|
|
549
|
+
"gpt-image-2.6-sunburst": {
|
|
550
|
+
"openai": "gpt-image-2.6-sunburst",
|
|
551
|
+
"openrouter": "openai/gpt-image-2.6-sunburst"
|
|
552
|
+
},
|
|
536
553
|
"gpt-oss-120b": {
|
|
537
554
|
"vertexai": "openai/gpt-oss-120b-maas",
|
|
538
555
|
"openrouter": "openai/gpt-oss-120b"
|
|
@@ -612,6 +629,9 @@
|
|
|
612
629
|
"grok-4.6": {
|
|
613
630
|
"vertexai": "xai/grok-4.6"
|
|
614
631
|
},
|
|
632
|
+
"grok-4.7": {
|
|
633
|
+
"vertexai": "xai/grok-4.7"
|
|
634
|
+
},
|
|
615
635
|
"grok-latest": {
|
|
616
636
|
"xai": "grok-4.3"
|
|
617
637
|
},
|
|
@@ -626,6 +646,9 @@
|
|
|
626
646
|
"llama-4-maverick-17b-128e-instruct": {
|
|
627
647
|
"vertexai": "meta/llama-4-maverick-17b-128e-instruct-maas"
|
|
628
648
|
},
|
|
649
|
+
"llama-4-scout-17b-16e-instruct": {
|
|
650
|
+
"vertexai": "meta/llama-4-scout-17b-16e-instruct-maas"
|
|
651
|
+
},
|
|
629
652
|
"lyria-3-clip-preview": {
|
|
630
653
|
"gemini": "lyria-3-clip-preview",
|
|
631
654
|
"openrouter": "google/lyria-3-clip-preview"
|
|
@@ -648,49 +671,19 @@
|
|
|
648
671
|
"nova-3": {
|
|
649
672
|
"deepgram": "nova-3-general"
|
|
650
673
|
},
|
|
651
|
-
"o1": {
|
|
652
|
-
"openai": "o1",
|
|
653
|
-
"openrouter": "openai/o1",
|
|
654
|
-
"azure": "o1"
|
|
655
|
-
},
|
|
656
|
-
"o1-pro": {
|
|
657
|
-
"openai": "o1-pro",
|
|
658
|
-
"openrouter": "openai/o1-pro",
|
|
659
|
-
"azure": "o1-pro"
|
|
660
|
-
},
|
|
661
674
|
"o3": {
|
|
662
675
|
"openai": "o3",
|
|
663
676
|
"openrouter": "openai/o3",
|
|
664
677
|
"azure": "o3"
|
|
665
678
|
},
|
|
666
|
-
"o3-mini": {
|
|
667
|
-
"openai": "o3-mini",
|
|
668
|
-
"openrouter": "openai/o3-mini",
|
|
669
|
-
"azure": "o3-mini"
|
|
670
|
-
},
|
|
671
679
|
"o3-pro": {
|
|
672
680
|
"openai": "o3-pro",
|
|
673
|
-
"openrouter": "openai/o3-pro"
|
|
674
|
-
|
|
675
|
-
"o4-mini": {
|
|
676
|
-
"openai": "o4-mini",
|
|
677
|
-
"openrouter": "openai/o4-mini",
|
|
678
|
-
"azure": "o4-mini"
|
|
681
|
+
"openrouter": "openai/o3-pro",
|
|
682
|
+
"azure": "o3-pro"
|
|
679
683
|
},
|
|
680
684
|
"qwen3-235b-a22b-instruct-2507": {
|
|
681
685
|
"vertexai": "qwen/qwen3-235b-a22b-instruct-2507-maas"
|
|
682
686
|
},
|
|
683
|
-
"qwen3-coder-480b-a35b-instruct": {
|
|
684
|
-
"vertexai": "qwen/qwen3-coder-480b-a35b-instruct-maas"
|
|
685
|
-
},
|
|
686
|
-
"qwen3-next-80b-a3b-instruct": {
|
|
687
|
-
"vertexai": "qwen/qwen3-next-80b-a3b-instruct-maas",
|
|
688
|
-
"openrouter": "qwen/qwen3-next-80b-a3b-instruct"
|
|
689
|
-
},
|
|
690
|
-
"qwen3-next-80b-a3b-thinking": {
|
|
691
|
-
"vertexai": "qwen/qwen3-next-80b-a3b-thinking-maas",
|
|
692
|
-
"openrouter": "qwen/qwen3-next-80b-a3b-thinking"
|
|
693
|
-
},
|
|
694
687
|
"text-embedding-3-large": {
|
|
695
688
|
"openai": "text-embedding-3-large",
|
|
696
689
|
"openrouter": "openai/text-embedding-3-large",
|
data/lib/ruby_llm/attachment.rb
CHANGED
|
@@ -37,6 +37,18 @@ module RubyLLM
|
|
|
37
37
|
# Media resolutions accepted by +resolution:+.
|
|
38
38
|
RESOLUTIONS = %i[low medium high ultra_high original].freeze
|
|
39
39
|
|
|
40
|
+
# How the model reads a video: +:static+, +:agentic+, or +nil+ for the
|
|
41
|
+
# provider default.
|
|
42
|
+
attr_reader :processing
|
|
43
|
+
|
|
44
|
+
# Video processing modes accepted by +processing:+.
|
|
45
|
+
PROCESSING_MODES = %i[static agentic].freeze
|
|
46
|
+
|
|
47
|
+
ATTRIBUTE_ESCAPES = { # :nodoc:
|
|
48
|
+
'&' => '&', '<' => '<', '>' => '>', "'" => ''', '"' => '"'
|
|
49
|
+
}.freeze
|
|
50
|
+
CLOSING_FILE_TAG = %r{</(file)}i # :nodoc:
|
|
51
|
+
|
|
40
52
|
# File extensions recognized as document attachments when the MIME type
|
|
41
53
|
# alone is inconclusive.
|
|
42
54
|
DOCUMENT_EXTENSIONS = %w[
|
|
@@ -62,7 +74,7 @@ module RubyLLM
|
|
|
62
74
|
|
|
63
75
|
def self.coerce(source, config:) # :nodoc:
|
|
64
76
|
return if source.nil? || (source.is_a?(String) && source.strip.empty?)
|
|
65
|
-
return source if source.is_a?(Attachment)
|
|
77
|
+
return source.with_default_config(config) if source.is_a?(Attachment)
|
|
66
78
|
return source.to_attachment if source.respond_to?(:to_attachment)
|
|
67
79
|
return from_h(source, config:) if source.is_a?(Hash)
|
|
68
80
|
|
|
@@ -72,7 +84,9 @@ module RubyLLM
|
|
|
72
84
|
|
|
73
85
|
def self.from_h(data, config: nil) # :nodoc:
|
|
74
86
|
data = data.transform_keys(&:to_sym)
|
|
75
|
-
new(data.fetch(:source), filename: data[:filename],
|
|
87
|
+
new(data.fetch(:source), filename: data[:filename], mime_type: data[:mime_type],
|
|
88
|
+
resolution: data[:resolution]&.to_sym,
|
|
89
|
+
processing: data[:processing]&.to_sym, config:)
|
|
76
90
|
end
|
|
77
91
|
|
|
78
92
|
# Creates an attachment from +source+: a file path, URL, IO-like object,
|
|
@@ -89,31 +103,53 @@ module RubyLLM
|
|
|
89
103
|
#
|
|
90
104
|
# RubyLLM::Attachment.new("page-3.png", resolution: :ultra_high)
|
|
91
105
|
#
|
|
92
|
-
# +
|
|
93
|
-
#
|
|
106
|
+
# +processing:+ chooses how the model reads a video. +:static+ samples
|
|
107
|
+
# frames across the whole video up front. +:agentic+ lets the model
|
|
108
|
+
# navigate a long video and load only the parts it needs. Providers
|
|
109
|
+
# without the setting ignore it.
|
|
110
|
+
#
|
|
111
|
+
# RubyLLM::Attachment.new("lecture.mp4", processing: :agentic)
|
|
112
|
+
#
|
|
113
|
+
# +mime_type:+ states the file's type, for a source RubyLLM cannot tell
|
|
114
|
+
# it from, such as an IO, instead of detecting it. A <tt>charset</tt>
|
|
115
|
+
# says how to read binary text; #mime_type leaves parameters out. A
|
|
116
|
+
# Rails attachment's type comes from its blob, so set it there for one.
|
|
117
|
+
#
|
|
118
|
+
# RubyLLM::Attachment.new(io, filename: "export.csv", mime_type: "text/csv")
|
|
119
|
+
#
|
|
120
|
+
# +config:+ is the Configuration a URL source is downloaded with. It
|
|
121
|
+
# defaults to that of the chat or operation the attachment is given to,
|
|
122
|
+
# then to the global one.
|
|
94
123
|
#
|
|
95
124
|
# Paths and URLs must be trusted and authorized by the application.
|
|
96
125
|
# They can be read or fetched during construction to detect the MIME type.
|
|
97
126
|
# Validate upload parameters before passing them here: an unchecked String
|
|
98
127
|
# can access local files or internal network endpoints.
|
|
99
|
-
def initialize(source, filename: nil, resolution: nil, config: nil)
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
end
|
|
128
|
+
def initialize(source, filename: nil, mime_type: nil, resolution: nil, processing: nil, config: nil)
|
|
129
|
+
check_option(:resolution, resolution, RESOLUTIONS)
|
|
130
|
+
check_option(:processing, processing, PROCESSING_MODES)
|
|
103
131
|
|
|
104
132
|
@config = config
|
|
105
133
|
@resolution = resolution
|
|
134
|
+
@processing = processing
|
|
106
135
|
@source = source
|
|
107
136
|
@source = source_type_cast
|
|
108
137
|
@filename = filename || source_filename
|
|
109
138
|
|
|
110
|
-
|
|
139
|
+
@given_mime_type = mime_type
|
|
140
|
+
@mime_type = media_type(mime_type)
|
|
141
|
+
determine_mime_type unless @mime_type
|
|
111
142
|
end
|
|
112
143
|
|
|
113
144
|
def config # :nodoc:
|
|
114
145
|
@config || RubyLLM.config
|
|
115
146
|
end
|
|
116
147
|
|
|
148
|
+
def with_default_config(config) # :nodoc:
|
|
149
|
+
@config ||= config
|
|
150
|
+
self
|
|
151
|
+
end
|
|
152
|
+
|
|
117
153
|
def url? # :nodoc:
|
|
118
154
|
@source.is_a?(URI) || (@source.is_a?(String) && @source.match?(%r{\Ahttps?://}i))
|
|
119
155
|
end
|
|
@@ -149,7 +185,7 @@ module RubyLLM
|
|
|
149
185
|
end
|
|
150
186
|
|
|
151
187
|
def io_like? # :nodoc:
|
|
152
|
-
@source.respond_to?(:read) && !path? && !active_storage? && !provider_file?
|
|
188
|
+
@source.respond_to?(:read) && !path? && !url? && !active_storage? && !provider_file?
|
|
153
189
|
end
|
|
154
190
|
|
|
155
191
|
def active_storage? # :nodoc:
|
|
@@ -178,7 +214,8 @@ module RubyLLM
|
|
|
178
214
|
def for_llm # :nodoc:
|
|
179
215
|
case type
|
|
180
216
|
when :text
|
|
181
|
-
"<file name='#{filename}' mime_type='#{mime_type}'
|
|
217
|
+
"<file name='#{escape_attribute(filename)}' mime_type='#{escape_attribute(mime_type)}'>" \
|
|
218
|
+
"#{content.to_s.gsub(CLOSING_FILE_TAG, '<\/\1')}</file>"
|
|
182
219
|
else
|
|
183
220
|
data_uri
|
|
184
221
|
end
|
|
@@ -250,8 +287,23 @@ module RubyLLM
|
|
|
250
287
|
RubyLLM::Files::MimeType.text? mime_type
|
|
251
288
|
end
|
|
252
289
|
|
|
290
|
+
# Returns whether the attachment holds only the start of a text, the
|
|
291
|
+
# way a chat with a file store sends a file longer than its inline
|
|
292
|
+
# limit (see Chat#with_file_store). The whole file stays in the chat's
|
|
293
|
+
# files, and the text says where. An attachment you make is never
|
|
294
|
+
# partial.
|
|
295
|
+
def partial?
|
|
296
|
+
@partial == true
|
|
297
|
+
end
|
|
298
|
+
|
|
299
|
+
def mark_partial # :nodoc:
|
|
300
|
+
@partial = true
|
|
301
|
+
self
|
|
302
|
+
end
|
|
303
|
+
|
|
253
304
|
def to_h # :nodoc:
|
|
254
|
-
{ type: type, source: @source, filename: filename, resolution: resolution
|
|
305
|
+
{ type: type, source: @source, filename: filename, mime_type: @given_mime_type, resolution: resolution,
|
|
306
|
+
processing: processing }.compact
|
|
255
307
|
end
|
|
256
308
|
|
|
257
309
|
def byte_size # :nodoc:
|
|
@@ -260,6 +312,16 @@ module RubyLLM
|
|
|
260
312
|
|
|
261
313
|
private
|
|
262
314
|
|
|
315
|
+
def escape_attribute(value)
|
|
316
|
+
value.to_s.gsub(/[&<>'"]/, ATTRIBUTE_ESCAPES)
|
|
317
|
+
end
|
|
318
|
+
|
|
319
|
+
def check_option(name, value, allowed)
|
|
320
|
+
return if value.nil? || allowed.include?(value)
|
|
321
|
+
|
|
322
|
+
raise ArgumentError, "#{name} must be one of #{allowed.join(', ')}, got #{value.inspect}"
|
|
323
|
+
end
|
|
324
|
+
|
|
263
325
|
def data_uri
|
|
264
326
|
"data:#{mime_type};base64,#{encoded}"
|
|
265
327
|
end
|
|
@@ -309,12 +371,26 @@ module RubyLLM
|
|
|
309
371
|
end
|
|
310
372
|
end
|
|
311
373
|
|
|
312
|
-
# Text content
|
|
374
|
+
# Text content loaded as binary is retagged so payloads serialize as
|
|
375
|
+
# UTF-8. An IO can return text already tagged, with bytes its encoding
|
|
376
|
+
# does not allow.
|
|
313
377
|
def normalize_text_encoding
|
|
314
|
-
return unless text? && @content.is_a?(String)
|
|
378
|
+
return unless text? && @content.is_a?(String)
|
|
315
379
|
|
|
316
|
-
|
|
317
|
-
@content =
|
|
380
|
+
@content = decoded_text(@content) if @content.encoding == Encoding::ASCII_8BIT
|
|
381
|
+
@content = @content.scrub unless @content.valid_encoding?
|
|
382
|
+
end
|
|
383
|
+
|
|
384
|
+
def decoded_text(bytes)
|
|
385
|
+
charset = @given_mime_type.to_s[/charset="?([^";\s]+)/i, 1] || Encoding::UTF_8
|
|
386
|
+
bytes.dup.force_encoding(charset).encode(Encoding::UTF_8, invalid: :replace, undef: :replace)
|
|
387
|
+
rescue ArgumentError, EncodingError
|
|
388
|
+
bytes.dup.force_encoding(Encoding::UTF_8)
|
|
389
|
+
end
|
|
390
|
+
|
|
391
|
+
def media_type(mime_type)
|
|
392
|
+
type = mime_type.to_s.split(';').first.to_s.strip.downcase
|
|
393
|
+
type unless type.empty?
|
|
318
394
|
end
|
|
319
395
|
|
|
320
396
|
def determine_mime_type
|
|
@@ -324,13 +400,18 @@ module RubyLLM
|
|
|
324
400
|
return @mime_type = content_type unless content_type.to_s.empty?
|
|
325
401
|
|
|
326
402
|
@mime_type = RubyLLM::Files::MimeType.for(url? ? nil : @source, name: @filename)
|
|
327
|
-
@mime_type =
|
|
403
|
+
@mime_type = content_mime_type if @mime_type == 'application/octet-stream'
|
|
328
404
|
@mime_type = 'audio/wav' if @mime_type == 'audio/x-wav'
|
|
329
405
|
end
|
|
330
406
|
|
|
407
|
+
# An IO's bytes were examined already, so it isn't read in full for a
|
|
408
|
+
# second look.
|
|
409
|
+
def content_mime_type
|
|
410
|
+
io_like? ? @mime_type : RubyLLM::Files::MimeType.for(content)
|
|
411
|
+
end
|
|
412
|
+
|
|
331
413
|
def fetch_content
|
|
332
|
-
|
|
333
|
-
@content = response.body
|
|
414
|
+
@content = Transport::Connection.download(@source, config)
|
|
334
415
|
end
|
|
335
416
|
|
|
336
417
|
def load_content_from_path
|
|
@@ -406,7 +487,7 @@ module RubyLLM
|
|
|
406
487
|
end
|
|
407
488
|
|
|
408
489
|
def inspect_attributes # :nodoc:
|
|
409
|
-
{ filename: filename, mime_type: mime_type, resolution: resolution, source: @source }
|
|
490
|
+
{ filename: filename, mime_type: mime_type, resolution: resolution, processing: processing, source: @source }
|
|
410
491
|
end
|
|
411
492
|
end
|
|
412
493
|
end
|