ruby_llm 2.1.1 → 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 +160 -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 +515 -274
- 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 +1593 -331
- 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 +50 -24
- 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 +127 -106
- 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 +15 -15
- 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 +1 -7
- 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 +5 -14
- 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 +213 -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
|
@@ -17,9 +17,23 @@ module RubyLLM
|
|
|
17
17
|
send(chat_association_name)
|
|
18
18
|
end
|
|
19
19
|
|
|
20
|
-
#
|
|
20
|
+
# Returns an assistant RubyLLM::Chunk with +content+ at position
|
|
21
|
+
# +index+ that streams into this message, as the chunks a persisted
|
|
22
|
+
# chat yields do, so it answers +message+ and +broadcast+. Use it to
|
|
23
|
+
# stand in for a streamed reply in tests:
|
|
24
|
+
#
|
|
25
|
+
# chunk = message.chunk("Hello", index: 0)
|
|
26
|
+
# chunk.broadcast
|
|
27
|
+
#
|
|
28
|
+
def chunk(content, index:)
|
|
29
|
+
ChunkMethods.attach(RubyLLM::Chunk.new(role: :assistant, content:, index:), self)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
# Converts this record to a RubyLLM::Message. The message carries the
|
|
33
|
+
# recorded cost, so RubyLLM::Message#to_h exports it.
|
|
21
34
|
def to_llm
|
|
22
35
|
entries = ruby_llm_usage_entries
|
|
36
|
+
succeeded = entries.reverse.find(&:succeeded?)
|
|
23
37
|
RubyLLM::Message.new(
|
|
24
38
|
role: role.to_sym,
|
|
25
39
|
content: extract_content,
|
|
@@ -29,17 +43,29 @@ module RubyLLM
|
|
|
29
43
|
server_tool_calls: server_tool_calls,
|
|
30
44
|
raw_content: optional_column(:raw_content),
|
|
31
45
|
raw_reasoning: optional_column(:raw_reasoning),
|
|
46
|
+
phase: optional_column(:phase),
|
|
47
|
+
continuation: optional_column(:continuation),
|
|
32
48
|
usage_entries: entries,
|
|
49
|
+
cost: (cost if entries.any?),
|
|
33
50
|
tool_calls: tool_calls,
|
|
34
51
|
tool_call_id: parent_tool_call&.id,
|
|
52
|
+
provider_call_id: parent_tool_call&.provider_call_id,
|
|
35
53
|
mcp_result: mcp_result,
|
|
36
54
|
finish_reason: optional_column(:finish_reason),
|
|
37
|
-
model:
|
|
55
|
+
model: succeeded&.model || stand_in_column(:model_id),
|
|
56
|
+
provider: (stand_in_column(:provider) unless succeeded),
|
|
38
57
|
cache_until_here: cache_until_here?,
|
|
39
58
|
cache_ttl: cache_ttl
|
|
40
59
|
)
|
|
41
60
|
end
|
|
42
61
|
|
|
62
|
+
# The fallback a reply with no successful attempt, such as a cancelled
|
|
63
|
+
# one, came from. On other roles these columns are the application's.
|
|
64
|
+
def stand_in_column(name)
|
|
65
|
+
optional_column(name) if role.to_s == 'assistant'
|
|
66
|
+
end
|
|
67
|
+
private :stand_in_column
|
|
68
|
+
|
|
43
69
|
def ruby_llm_usage_entries # :nodoc:
|
|
44
70
|
ruby_llm_usage_records.map(&:to_entry)
|
|
45
71
|
end
|
|
@@ -53,6 +79,20 @@ module RubyLLM
|
|
|
53
79
|
to_llm.model
|
|
54
80
|
end
|
|
55
81
|
|
|
82
|
+
# Returns the model ID from the last successful attempt, as #model
|
|
83
|
+
# does, or a +model_id+ column the application keeps, or +nil+.
|
|
84
|
+
def model_id
|
|
85
|
+
to_llm.model_id || optional_column(:model_id)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Returns the slug of the provider that produced the message, from its
|
|
89
|
+
# last successful attempt, or a +provider+ column the application keeps,
|
|
90
|
+
# or +nil+. RubyLLM writes +model_id+ and +provider+ columns, when the
|
|
91
|
+
# table has them, on replies a fallback model wrote.
|
|
92
|
+
def provider
|
|
93
|
+
to_llm.provider || optional_column(:provider)
|
|
94
|
+
end
|
|
95
|
+
|
|
56
96
|
# Returns the model from the registry, or +nil+ if it is unknown.
|
|
57
97
|
def model_info
|
|
58
98
|
to_llm.model_info
|
|
@@ -60,11 +100,13 @@ module RubyLLM
|
|
|
60
100
|
|
|
61
101
|
# Marks the message as a prompt cache boundary and returns it. Pass
|
|
62
102
|
# +ttl:+ to give the boundary its own cache lifetime, as
|
|
63
|
-
# RubyLLM::Message#cache_until_here describes.
|
|
103
|
+
# RubyLLM::Message#cache_until_here describes. The chat that returned
|
|
104
|
+
# the message sends the boundary with its next request.
|
|
64
105
|
def cache_until_here(ttl: nil)
|
|
65
106
|
attributes = { cache_until_here: true }
|
|
66
107
|
attributes[:cache_ttl] = ttl if ttl || has_attribute?(:cache_ttl)
|
|
67
108
|
update!(attributes)
|
|
109
|
+
association(chat_association_name).target&.refresh_messages
|
|
68
110
|
self
|
|
69
111
|
end
|
|
70
112
|
|
|
@@ -73,6 +115,12 @@ module RubyLLM
|
|
|
73
115
|
optional_column(:cache_until_here) || false
|
|
74
116
|
end
|
|
75
117
|
|
|
118
|
+
# Serialization reads each column through its method, and
|
|
119
|
+
# #cache_until_here marks a boundary instead of reading one.
|
|
120
|
+
def read_attribute_for_serialization(name) # :nodoc:
|
|
121
|
+
name.to_s == 'cache_until_here' ? cache_until_here? : super
|
|
122
|
+
end
|
|
123
|
+
|
|
76
124
|
# Returns the cache lifetime given to the message's boundary, or +nil+
|
|
77
125
|
# when the chat's caching policy applies.
|
|
78
126
|
def cache_ttl
|
|
@@ -97,6 +145,12 @@ module RubyLLM
|
|
|
97
145
|
Array(optional_column(:server_tool_calls)).map { |call| RubyLLM::ServerToolCall.from_h(call) }
|
|
98
146
|
end
|
|
99
147
|
|
|
148
|
+
# Returns the pages a hosted web search returned, as an array of
|
|
149
|
+
# RubyLLM::WebSource. See RubyLLM::Message#web_sources.
|
|
150
|
+
def web_sources
|
|
151
|
+
to_llm.web_sources
|
|
152
|
+
end
|
|
153
|
+
|
|
100
154
|
# Returns the token usage across every attempt that produced the message.
|
|
101
155
|
def tokens
|
|
102
156
|
RubyLLM::Tokens.aggregate(ruby_llm_usages.map(&:tokens))
|
|
@@ -104,14 +158,15 @@ module RubyLLM
|
|
|
104
158
|
|
|
105
159
|
# Returns the cost across every attempt that produced the message, as a RubyLLM::Cost.
|
|
106
160
|
def cost
|
|
107
|
-
records =
|
|
161
|
+
records = ruby_llm_usage_records
|
|
108
162
|
RubyLLM::Cost.aggregate(records.map(&:cost), complete: records.all?(&:cost_available?))
|
|
109
163
|
end
|
|
110
164
|
|
|
111
|
-
# Returns the tool calls as RubyLLM::
|
|
165
|
+
# Returns the tool calls, as RubyLLM::Message#tool_calls does: an
|
|
166
|
+
# Enumerable of RubyLLM::ToolCall objects, also readable by call ID.
|
|
112
167
|
def tool_calls
|
|
113
168
|
records = preloaded_records(:ruby_llm_tool_calls) || ruby_llm_tool_calls
|
|
114
|
-
records.to_h { |record| [record.tool_call_id, record.to_llm] }
|
|
169
|
+
RubyLLM::ToolCall::Collection.new(records.to_h { |record| [record.tool_call_id, record.to_llm] })
|
|
115
170
|
end
|
|
116
171
|
|
|
117
172
|
# Returns the tool call this message answers, or +nil+.
|
|
@@ -162,6 +217,24 @@ module RubyLLM
|
|
|
162
217
|
payload_error_message(extract_content)
|
|
163
218
|
end
|
|
164
219
|
|
|
220
|
+
# Returns whether a tool ended the turn with this result. See
|
|
221
|
+
# RubyLLM::Message#halted?.
|
|
222
|
+
def halted?
|
|
223
|
+
to_llm.halted?
|
|
224
|
+
end
|
|
225
|
+
|
|
226
|
+
# Returns the path of the file holding the whole of a tool result too
|
|
227
|
+
# long to send whole, or +nil+. See RubyLLM::Message#spilled_path.
|
|
228
|
+
def spilled_path
|
|
229
|
+
to_llm.spilled_path
|
|
230
|
+
end
|
|
231
|
+
|
|
232
|
+
# Returns whether this is the part of a streamed reply the chat kept
|
|
233
|
+
# when it was cancelled. See RubyLLM::Message#cancelled?.
|
|
234
|
+
def cancelled?
|
|
235
|
+
to_llm.cancelled?
|
|
236
|
+
end
|
|
237
|
+
|
|
165
238
|
# Returns why the model stopped, as the Symbol RubyLLM::Message#finish_reason reports.
|
|
166
239
|
def finish_reason
|
|
167
240
|
optional_column(:finish_reason)&.to_sym
|
|
@@ -176,6 +249,7 @@ module RubyLLM
|
|
|
176
249
|
def max_tokens?
|
|
177
250
|
to_llm.max_tokens?
|
|
178
251
|
end
|
|
252
|
+
alias truncated? max_tokens?
|
|
179
253
|
|
|
180
254
|
# Returns +true+ if the model stopped to request tool calls.
|
|
181
255
|
def tool_call_stop?
|
|
@@ -187,12 +261,10 @@ module RubyLLM
|
|
|
187
261
|
to_llm.content_filtered?
|
|
188
262
|
end
|
|
189
263
|
|
|
190
|
-
# Returns the content parsed as JSON, or +nil+ when the message has no
|
|
264
|
+
# Returns the content parsed as JSON, or +nil+ when the message has no
|
|
265
|
+
# text. Reads only the final answer, as RubyLLM::Message#parsed does.
|
|
191
266
|
def parsed
|
|
192
|
-
|
|
193
|
-
return if text.nil? || text.empty?
|
|
194
|
-
|
|
195
|
-
JSON.parse(text)
|
|
267
|
+
to_llm.parsed
|
|
196
268
|
end
|
|
197
269
|
|
|
198
270
|
private
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'active_support/core_ext/module/delegation'
|
|
4
|
+
require 'concurrent/set'
|
|
4
5
|
|
|
5
6
|
module RubyLLM
|
|
6
7
|
module ActiveRecord
|
|
@@ -18,6 +19,7 @@ module RubyLLM
|
|
|
18
19
|
scope :unlisted, -> { where.not(unlisted_at: nil) }
|
|
19
20
|
|
|
20
21
|
UNLISTED_WARNING_LIMIT = 5 # :nodoc:
|
|
22
|
+
STORED_REGISTRIES = Concurrent::Set.new # :nodoc:
|
|
21
23
|
|
|
22
24
|
class << self
|
|
23
25
|
def read # :nodoc:
|
|
@@ -42,16 +44,28 @@ module RubyLLM
|
|
|
42
44
|
end
|
|
43
45
|
|
|
44
46
|
def save_to_database(registry = RubyLLM.models) # :nodoc:
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
unlist(
|
|
47
|
+
rows = registry.all.index_by { |model_info| [model_info.provider, model_info.id] }
|
|
48
|
+
.transform_values { |model_info| attributes_from_llm(model_info) }
|
|
49
|
+
validate_incomplete(rows.values)
|
|
50
|
+
|
|
51
|
+
transaction(requires_new: true) do
|
|
52
|
+
upsert_all(rows.values, unique_by: registry_key) if rows.any?
|
|
53
|
+
unlist(pluck(:id, :provider, :model_id).reject { |_id, *key| rows.key?(key) }.map(&:first))
|
|
52
54
|
end
|
|
53
55
|
end
|
|
54
56
|
|
|
57
|
+
# A database stays marked once a chat that loaded the registry into it
|
|
58
|
+
# has committed. A transactional test rolls that load back after every
|
|
59
|
+
# example, and loading it again on each empty table would repeat the
|
|
60
|
+
# whole catalog in every test.
|
|
61
|
+
def registry_stored? # :nodoc:
|
|
62
|
+
STORED_REGISTRIES.include?(connection_db_config)
|
|
63
|
+
end
|
|
64
|
+
|
|
65
|
+
def registry_stored!(db_config) # :nodoc:
|
|
66
|
+
STORED_REGISTRIES << db_config
|
|
67
|
+
end
|
|
68
|
+
|
|
55
69
|
def from_llm(model_info) # :nodoc:
|
|
56
70
|
new(attributes_from_llm(model_info))
|
|
57
71
|
end
|
|
@@ -62,9 +76,9 @@ module RubyLLM
|
|
|
62
76
|
# away. A row an application record points at cannot: deleting it would
|
|
63
77
|
# dangle the reference. That row is stamped unlisted instead, keeping
|
|
64
78
|
# the chats that use it resolvable while the application migrates them.
|
|
65
|
-
def unlist(
|
|
79
|
+
def unlist(dropped_ids)
|
|
66
80
|
stayed = []
|
|
67
|
-
where
|
|
81
|
+
where(id: dropped_ids).find_each do |model|
|
|
68
82
|
transaction(requires_new: true) { model.destroy! }
|
|
69
83
|
rescue ::ActiveRecord::InvalidForeignKey
|
|
70
84
|
model.update!(unlisted_at: Time.current) unless model.unlisted_at
|
|
@@ -90,6 +104,20 @@ module RubyLLM
|
|
|
90
104
|
extra.positive? ? "#{listing}, and #{extra} more" : listing
|
|
91
105
|
end
|
|
92
106
|
|
|
107
|
+
# upsert_all skips validations. An entry missing a required value goes
|
|
108
|
+
# through them on its own, so it raises RecordInvalid as a save would.
|
|
109
|
+
def validate_incomplete(rows)
|
|
110
|
+
rows.each do |attributes|
|
|
111
|
+
new(attributes).validate! if attributes.values_at(:model_id, :provider, :name).any?(&:blank?)
|
|
112
|
+
end
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
# Active Record's MySQL adapter rejects a named conflict target;
|
|
116
|
+
# MySQL matches every unique index on its own.
|
|
117
|
+
def registry_key
|
|
118
|
+
%i[provider model_id] if connection_pool.with_connection(&:supports_insert_conflict_target?)
|
|
119
|
+
end
|
|
120
|
+
|
|
93
121
|
def attributes_from_llm(model_info)
|
|
94
122
|
{
|
|
95
123
|
model_id: model_info.id,
|
|
@@ -7,20 +7,37 @@ module RubyLLM
|
|
|
7
7
|
# already uploaded instead of uploading the blob again. Rows name the
|
|
8
8
|
# blob by its key, which Active Storage never hands out twice, so a row
|
|
9
9
|
# left by a deleted blob can never match a newer one.
|
|
10
|
-
|
|
10
|
+
#
|
|
11
|
+
# RubyLLM::ActiveRecord::ProviderFile.where(blob_key: document.file.key).pluck(:provider, :file_id)
|
|
12
|
+
#
|
|
13
|
+
# Its columns:
|
|
14
|
+
#
|
|
15
|
+
# [+blob_key+] The key of the Active Storage blob.
|
|
16
|
+
# [+provider+] The provider's slug.
|
|
17
|
+
# [+account+] A digest that identifies the provider account the file was uploaded to.
|
|
18
|
+
# [+file_id+] The provider's id for the uploaded file.
|
|
19
|
+
# [+expires_at+] When the provider deletes the file, or +nil+.
|
|
20
|
+
#
|
|
21
|
+
# RubyLLM writes these rows. Read and query them, and add scopes,
|
|
22
|
+
# columns, or encryption of your own, but do not create or update them.
|
|
23
|
+
# Encrypt +file_id+ deterministically, since RubyLLM queries it.
|
|
24
|
+
class ProviderFile < Record
|
|
11
25
|
self.table_name = 'ruby_llm_provider_files'
|
|
12
26
|
|
|
13
27
|
validates :blob_key, :provider, :account, :file_id, presence: true
|
|
14
28
|
|
|
15
29
|
# Returns a store for the blob whose key the block returns, or +nil+
|
|
16
30
|
# while the table has not been migrated.
|
|
17
|
-
def self.store(&)
|
|
31
|
+
def self.store(&) # :nodoc:
|
|
18
32
|
Store.new(&) if table_exists?
|
|
19
33
|
end
|
|
20
34
|
|
|
21
35
|
# Deletes the uploads recorded for +blob+ as Active Storage destroys it.
|
|
22
|
-
|
|
23
|
-
|
|
36
|
+
# Reads the stored key: Blob#key generates one when it is missing, and
|
|
37
|
+
# a destroyed blob is frozen.
|
|
38
|
+
def self.forget_blob(blob) # :nodoc:
|
|
39
|
+
key = blob[:key]
|
|
40
|
+
where(blob_key: key).delete_all if key && table_exists?
|
|
24
41
|
end
|
|
25
42
|
|
|
26
43
|
# The uploads of one blob. The block finds the blob the first time a
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module ActiveRecord
|
|
5
|
+
# A user message queued on a persisted chat with ChatMethods#queue,
|
|
6
|
+
# waiting in RubyLLM's +ruby_llm_queued_messages+ table until the
|
|
7
|
+
# conversation loop takes it. Any process can queue one, and the
|
|
8
|
+
# process that runs the chat takes it, so a request can add to a reply
|
|
9
|
+
# a job is running. Its attachments move to the message it becomes.
|
|
10
|
+
#
|
|
11
|
+
# chat.queued_messages.each { |queued| render queued }
|
|
12
|
+
# chat.queued_messages.find(params[:id]).withdraw
|
|
13
|
+
#
|
|
14
|
+
# It answers +content+, +attachments+, +sender+, +steer?+, and
|
|
15
|
+
# +withdraw+, like RubyLLM::QueuedMessage.
|
|
16
|
+
class QueuedMessage < Record
|
|
17
|
+
self.table_name = 'ruby_llm_queued_messages'
|
|
18
|
+
|
|
19
|
+
belongs_to :chat, polymorphic: true
|
|
20
|
+
belongs_to :sender, polymorphic: true, optional: true
|
|
21
|
+
|
|
22
|
+
# Declares the attachments when Active Storage is loaded. Called by
|
|
23
|
+
# +acts_as_chat+, since Active Storage loads after RubyLLM's records.
|
|
24
|
+
def self.declare_attachments # :nodoc:
|
|
25
|
+
return unless respond_to?(:has_many_attached) && !reflect_on_attachment(:attachments)
|
|
26
|
+
|
|
27
|
+
has_many_attached :attachments
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
# Returns when the message was queued.
|
|
31
|
+
def queued_at
|
|
32
|
+
created_at
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Deletes the record and its attachments before the conversation
|
|
36
|
+
# loop takes it, from any process. Returns +true+, or +false+ when it
|
|
37
|
+
# is no longer queued: the loop took it into the conversation, or it
|
|
38
|
+
# was withdrawn already.
|
|
39
|
+
#
|
|
40
|
+
# chat.queued_messages.find(params[:id]).withdraw # => true
|
|
41
|
+
#
|
|
42
|
+
def withdraw
|
|
43
|
+
return false unless self.class.where(id:).delete_all == 1
|
|
44
|
+
|
|
45
|
+
attachments.purge_later if respond_to?(:attachments) && attachments.attached?
|
|
46
|
+
true
|
|
47
|
+
end
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
end
|
|
@@ -2,7 +2,9 @@
|
|
|
2
2
|
|
|
3
3
|
module RubyLLM
|
|
4
4
|
module ActiveRecord
|
|
5
|
-
# Abstract base for
|
|
5
|
+
# Abstract base for the records of the tables RubyLLM owns: Model,
|
|
6
|
+
# ToolCall, Usage, Batch, MCPCredential, ProviderFile, QueuedMessage,
|
|
7
|
+
# and ChatFile.
|
|
6
8
|
# Inherits the application's default connection unless configured otherwise.
|
|
7
9
|
# Keep these records on the same database and connection pool as chats and
|
|
8
10
|
# messages to preserve foreign keys and shared transactions.
|