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
data/lib/ruby_llm/models.rb
CHANGED
|
@@ -18,34 +18,18 @@ module RubyLLM
|
|
|
18
18
|
include Enumerable
|
|
19
19
|
include Lookup
|
|
20
20
|
|
|
21
|
-
MODELS_DEV_PROVIDER_MAP = { # :nodoc:
|
|
22
|
-
'openai' => 'openai',
|
|
23
|
-
'anthropic' => 'anthropic',
|
|
24
|
-
'azure' => 'azure',
|
|
25
|
-
'google' => 'gemini',
|
|
26
|
-
'google-vertex' => 'vertexai',
|
|
27
|
-
'amazon-bedrock' => 'bedrock',
|
|
28
|
-
'cohere' => 'cohere',
|
|
29
|
-
'deepseek' => 'deepseek',
|
|
30
|
-
'hetzner' => 'hetzner',
|
|
31
|
-
'mistral' => 'mistral',
|
|
32
|
-
'ollama-cloud' => 'ollama_cloud',
|
|
33
|
-
'openrouter' => 'openrouter',
|
|
34
|
-
'perplexity' => 'perplexity',
|
|
35
|
-
'perplexity-agent' => 'perplexity',
|
|
36
|
-
'xai' => 'xai'
|
|
37
|
-
}.freeze
|
|
38
21
|
MODELS_DEV_INPUT_MODALITIES = %w[text image audio pdf video file].freeze # :nodoc:
|
|
39
22
|
MODELS_DEV_OUTPUT_MODALITIES = %w[text image audio video embeddings moderation rerank judgment].freeze # :nodoc:
|
|
40
23
|
# First-party providers outrank the aggregators that resell their models.
|
|
41
24
|
PROVIDER_PREFERENCE = %w[
|
|
42
|
-
openai anthropic gemini deepseek mistral cohere typesafe perplexity xai
|
|
25
|
+
openai anthropic gemini deepseek mistral cohere typesafe perplexity xai moonshot minimax zai meta
|
|
43
26
|
vertexai bedrock openrouter azure hetzner ollama_cloud ollama gpustack
|
|
44
27
|
].freeze # :nodoc:
|
|
45
28
|
INSTANCE_DELEGATES = (Enumerable.instance_methods(false) + %i[
|
|
46
29
|
all
|
|
47
30
|
each
|
|
48
31
|
find
|
|
32
|
+
find_configured
|
|
49
33
|
listed
|
|
50
34
|
unlisted
|
|
51
35
|
chat_models
|
|
@@ -149,9 +133,7 @@ module RubyLLM
|
|
|
149
133
|
existing_models = read_existing_models
|
|
150
134
|
|
|
151
135
|
provider_fetch = fetch_provider_models(remote_only: remote_only)
|
|
152
|
-
|
|
153
|
-
log_provider_fetch(provider_fetch)
|
|
154
|
-
payload[:failed_providers] = provider_fetch[:failed].map { |failure| failure[:slug] }
|
|
136
|
+
record_provider_fetch(provider_fetch, payload)
|
|
155
137
|
|
|
156
138
|
models_dev_fetch = fetch_models_dev_models(existing_models)
|
|
157
139
|
log_models_dev_fetch(models_dev_fetch)
|
|
@@ -162,6 +144,12 @@ module RubyLLM
|
|
|
162
144
|
end
|
|
163
145
|
end
|
|
164
146
|
|
|
147
|
+
def record_provider_fetch(provider_fetch, payload) # :nodoc:
|
|
148
|
+
@last_provider_failures = provider_fetch[:failed]
|
|
149
|
+
log_provider_fetch(provider_fetch)
|
|
150
|
+
payload[:failed_providers] = provider_fetch[:failed].map { |failure| failure[:slug] }
|
|
151
|
+
end
|
|
152
|
+
|
|
165
153
|
def fetch_provider_models(remote_only: true) # :nodoc:
|
|
166
154
|
config = RubyLLM.config
|
|
167
155
|
providers = remote_only ? Provider.configured_remote_providers(config) : Provider.configured_providers(config)
|
|
@@ -210,7 +198,7 @@ module RubyLLM
|
|
|
210
198
|
|
|
211
199
|
model ||= Model.default(model_id, provider_class.slug)
|
|
212
200
|
else
|
|
213
|
-
model = Models.
|
|
201
|
+
model = Models.find_configured model_id, provider: provider, config: config
|
|
214
202
|
provider_class = Provider.resolve!(model.provider)
|
|
215
203
|
end
|
|
216
204
|
[model, provider_class.new(config)]
|
|
@@ -237,18 +225,26 @@ module RubyLLM
|
|
|
237
225
|
def parse_models_dev_catalog(body) # :nodoc:
|
|
238
226
|
raise ModelRegistryError, "models.dev returned #{body.class} instead of a catalog" unless body.is_a?(Hash)
|
|
239
227
|
|
|
240
|
-
|
|
228
|
+
slugs = models_dev_provider_slugs
|
|
229
|
+
models = body.flat_map do |provider_key, data|
|
|
230
|
+
models_dev_provider_models(provider_key.to_s, data, slugs[provider_key.to_s])
|
|
231
|
+
end
|
|
241
232
|
raise ModelRegistryError, 'models.dev returned no models RubyLLM knows a provider for' if models.empty?
|
|
242
233
|
|
|
243
234
|
models
|
|
244
235
|
end
|
|
245
236
|
|
|
246
|
-
def
|
|
247
|
-
|
|
237
|
+
def models_dev_provider_slugs # :nodoc:
|
|
238
|
+
Provider.providers.each_with_object({}) do |(slug, provider), slugs|
|
|
239
|
+
provider.models_dev_provider_ids.each { |id| slugs[id] ||= slug.to_s }
|
|
240
|
+
end
|
|
241
|
+
end
|
|
242
|
+
|
|
243
|
+
def models_dev_provider_models(provider_key, provider_data, provider_slug) # :nodoc:
|
|
248
244
|
return [] unless provider_slug
|
|
249
245
|
|
|
250
246
|
(provider_data[:models] || {}).values.filter_map do |model_data|
|
|
251
|
-
model = Model.new(models_dev_model_attributes(model_data, provider_slug, provider_key
|
|
247
|
+
model = Model.new(models_dev_model_attributes(model_data, provider_slug, provider_key))
|
|
252
248
|
model unless model.provider.nil? || model.id.nil?
|
|
253
249
|
end
|
|
254
250
|
end
|
|
@@ -285,13 +281,7 @@ module RubyLLM
|
|
|
285
281
|
.flatten
|
|
286
282
|
|
|
287
283
|
provider_models = provider_fetch[:models] + preserved_models
|
|
288
|
-
|
|
289
|
-
models_dev_fetch[:models]
|
|
290
|
-
else
|
|
291
|
-
existing_models.select { |model| model.metadata[:source] == 'models.dev' }
|
|
292
|
-
end
|
|
293
|
-
|
|
294
|
-
merge_models(provider_models, models_dev_models)
|
|
284
|
+
merge_models(provider_models, models_dev_fetch[:models])
|
|
295
285
|
end
|
|
296
286
|
|
|
297
287
|
def merge_models(provider_models, models_dev_models) # :nodoc:
|
|
@@ -472,7 +462,13 @@ module RubyLLM
|
|
|
472
462
|
text_tokens = models_dev_text_tokens_pricing(text_standard, cost)
|
|
473
463
|
pricing[:text_tokens] = text_tokens if text_tokens
|
|
474
464
|
pricing[:audio_tokens] = { standard: audio_standard } if audio_standard.any?
|
|
475
|
-
pricing
|
|
465
|
+
pricing.merge(models_dev_unit_pricing(cost))
|
|
466
|
+
end
|
|
467
|
+
|
|
468
|
+
def models_dev_unit_pricing(cost) # :nodoc:
|
|
469
|
+
%i[characters audio_seconds search_units].each_with_object({}) do |unit, pricing|
|
|
470
|
+
pricing[unit] = { standard: { input_per_million: cost[unit] } } unless cost[unit].nil?
|
|
471
|
+
end
|
|
476
472
|
end
|
|
477
473
|
|
|
478
474
|
def models_dev_text_tokens_pricing(text_standard, cost) # :nodoc:
|
|
@@ -603,7 +599,7 @@ module RubyLLM
|
|
|
603
599
|
# the model, first-party providers before aggregators. Raises
|
|
604
600
|
# RubyLLM::ModelNotFoundError if no model matches.
|
|
605
601
|
#
|
|
606
|
-
# RubyLLM.models.find 'gpt-
|
|
602
|
+
# RubyLLM.models.find 'gpt-6.1-sol'
|
|
607
603
|
# RubyLLM.models.find 'claude-sonnet-5', provider: :bedrock
|
|
608
604
|
#
|
|
609
605
|
def find(model_id, provider: nil, config: nil)
|
|
@@ -614,6 +610,15 @@ module RubyLLM
|
|
|
614
610
|
end
|
|
615
611
|
end
|
|
616
612
|
|
|
613
|
+
# Returns the Model that resolution uses for +model_id+. Without
|
|
614
|
+
# +provider+, it prefers providers configured in +config+, falling back
|
|
615
|
+
# to #find's choice when none of them carries the model.
|
|
616
|
+
def find_configured(model_id, provider: nil, config: nil) # :nodoc:
|
|
617
|
+
return find_with_provider(model_id, provider, config) if provider
|
|
618
|
+
|
|
619
|
+
find_on_configured_providers(model_id, config || RubyLLM.config)
|
|
620
|
+
end
|
|
621
|
+
|
|
617
622
|
# Returns a new Models registry containing only chat models.
|
|
618
623
|
def chat_models
|
|
619
624
|
select_models { |m| m.type == :chat }
|
|
@@ -638,7 +643,7 @@ module RubyLLM
|
|
|
638
643
|
|
|
639
644
|
# Returns a new Models registry containing only models in +family+.
|
|
640
645
|
#
|
|
641
|
-
# RubyLLM.models.by_family('
|
|
646
|
+
# RubyLLM.models.by_family('claude-sonnet')
|
|
642
647
|
#
|
|
643
648
|
def by_family(family)
|
|
644
649
|
select_models { |m| m.family == family.to_s }
|
|
@@ -668,7 +673,7 @@ module RubyLLM
|
|
|
668
673
|
def refresh(remote_only: false)
|
|
669
674
|
RubyLLM.instrument('models.refresh.ruby_llm', remote_only:) do |payload|
|
|
670
675
|
published = fetch_published_models
|
|
671
|
-
main_models = merge_discovered_models(published.models, remote_only:)
|
|
676
|
+
main_models = merge_discovered_models(published.models, payload, remote_only:)
|
|
672
677
|
merged_models = self.class.merge_models(self.class.models_from_provider_gems, main_models)
|
|
673
678
|
persisted_models = RubyLLM.config.model_registry_store ? merged_models : main_models
|
|
674
679
|
persist_registry!(persisted_models, published:)
|
|
@@ -728,9 +733,9 @@ module RubyLLM
|
|
|
728
733
|
nil
|
|
729
734
|
end
|
|
730
735
|
|
|
731
|
-
def merge_discovered_models(published, remote_only:)
|
|
736
|
+
def merge_discovered_models(published, payload, remote_only:)
|
|
732
737
|
provider_fetch = self.class.fetch_provider_models(remote_only: remote_only)
|
|
733
|
-
self.class.
|
|
738
|
+
self.class.record_provider_fetch(provider_fetch, payload)
|
|
734
739
|
preserved = preserved_providers(provider_fetch, published)
|
|
735
740
|
preserved_models = all.select { |model| preserved.include?(model.provider) }
|
|
736
741
|
self.class.merge_models(provider_fetch[:models] + preserved_models, published)
|
data/lib/ruby_llm/moderation.rb
CHANGED
|
@@ -57,8 +57,9 @@ module RubyLLM
|
|
|
57
57
|
# The provider-assigned identifier of the moderation request.
|
|
58
58
|
attr_reader :id
|
|
59
59
|
|
|
60
|
-
# The id of the model that performed the moderation
|
|
61
|
-
#
|
|
60
|
+
# The id of the model that performed the moderation. A moderation run
|
|
61
|
+
# by a provider resource rather than a model names that resource, such
|
|
62
|
+
# as a Bedrock guardrail's <tt>"id:version"</tt>, as its usage does.
|
|
62
63
|
attr_reader :model
|
|
63
64
|
|
|
64
65
|
# The per-input verdicts, as an array of Result objects, one per
|
|
@@ -82,7 +83,7 @@ module RubyLLM
|
|
|
82
83
|
# to use a model that is not in the registry. An explicitly selected
|
|
83
84
|
# provider may instead use a configured resource without a model.
|
|
84
85
|
# +owner:+ attributes the usage to a record, such as a user, and wins
|
|
85
|
-
# over RubyLLM.
|
|
86
|
+
# over RubyLLM.with_owner.
|
|
86
87
|
#
|
|
87
88
|
# RubyLLM.moderate("User message")
|
|
88
89
|
# RubyLLM.moderate(["First comment", "Second comment"]).results
|
|
@@ -100,33 +101,12 @@ module RubyLLM
|
|
|
100
101
|
attachments = Attachment.wrap(with)
|
|
101
102
|
raise ArgumentError, 'must provide input text, image attachment, or both' if input.nil? && attachments.empty?
|
|
102
103
|
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
provider: provider_instance.slug,
|
|
110
|
-
provider_class: provider_instance.name,
|
|
111
|
-
model: model&.id,
|
|
112
|
-
model_info: model,
|
|
113
|
-
input: input,
|
|
114
|
-
attachment_count: attachments.size,
|
|
115
|
-
provider_options: provider_options,
|
|
116
|
-
metadata: metadata,
|
|
117
|
-
tokens: empty_tokens,
|
|
118
|
-
cost: Cost.new(tokens: empty_tokens, model:)
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
RubyLLM.instrument('moderation.ruby_llm', payload, config: config) do |event|
|
|
122
|
-
result = Accounting::Usage.owned_by(owner) do
|
|
123
|
-
provider_instance.moderate(input, model:, with: attachments, provider_options:)
|
|
124
|
-
end
|
|
125
|
-
event[:result] = result
|
|
126
|
-
event[:flagged] = result.flagged?
|
|
127
|
-
event[:tokens] = result.tokens
|
|
128
|
-
event[:cost] = result.cost
|
|
129
|
-
result
|
|
104
|
+
Accounting::Operation.new(
|
|
105
|
+
'moderation', model, operation: :moderate, default: :default_moderation_model, provider:, assume_model_exists:,
|
|
106
|
+
context:, owner:,
|
|
107
|
+
details: ->(event, result) { event[:flagged] = result.flagged? }
|
|
108
|
+
).run(input:, attachment_count: attachments.size, provider_options:, metadata:) do |provider_instance, model|
|
|
109
|
+
provider_instance.moderate(input, model:, with: attachments, provider_options:)
|
|
130
110
|
end
|
|
131
111
|
end
|
|
132
112
|
|
data/lib/ruby_llm/ocr.rb
CHANGED
|
@@ -42,7 +42,7 @@ module RubyLLM
|
|
|
42
42
|
# into the request in the provider's own vocabulary, such as Mistral's
|
|
43
43
|
# +include_image_base64:+ or +table_format:+.
|
|
44
44
|
# +owner:+ attributes the usage to a record, such as a user, and wins
|
|
45
|
-
# over RubyLLM.
|
|
45
|
+
# over RubyLLM.with_owner.
|
|
46
46
|
#
|
|
47
47
|
# RubyLLM.ocr("report.pdf")
|
|
48
48
|
# RubyLLM.ocr("https://example.com/scan.png")
|
|
@@ -59,27 +59,10 @@ module RubyLLM
|
|
|
59
59
|
provider_options: {},
|
|
60
60
|
metadata: nil,
|
|
61
61
|
owner: nil)
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
payload = {
|
|
67
|
-
provider: provider_instance.slug,
|
|
68
|
-
provider_class: provider_instance.name,
|
|
69
|
-
model: model.id,
|
|
70
|
-
model_info: model,
|
|
71
|
-
pages: pages,
|
|
72
|
-
provider_options: provider_options,
|
|
73
|
-
metadata: metadata
|
|
74
|
-
}
|
|
75
|
-
|
|
76
|
-
RubyLLM.instrument('ocr.ruby_llm', payload, config: config) do |event|
|
|
77
|
-
result = Accounting::Usage.owned_by(owner) do
|
|
78
|
-
provider_instance.ocr(file, model:, pages:, provider_options:)
|
|
79
|
-
end
|
|
80
|
-
event[:result] = result
|
|
81
|
-
event[:response_model] = result.model
|
|
82
|
-
result
|
|
62
|
+
Accounting::Operation.new(
|
|
63
|
+
'ocr', model, default: :default_ocr_model, usage: false, provider:, assume_model_exists:, context:, owner:
|
|
64
|
+
).run(pages:, provider_options:, metadata:) do |provider_instance, model|
|
|
65
|
+
provider_instance.ocr(file, model:, pages:, provider_options:)
|
|
83
66
|
end
|
|
84
67
|
end
|
|
85
68
|
|
|
@@ -4,6 +4,7 @@ module RubyLLM
|
|
|
4
4
|
class OpenTelemetry
|
|
5
5
|
module Attributes # :nodoc:
|
|
6
6
|
OPERATIONS = {
|
|
7
|
+
'agent.ruby_llm' => 'invoke_agent',
|
|
7
8
|
'chat.ruby_llm' => 'chat',
|
|
8
9
|
'embedding.ruby_llm' => 'embeddings',
|
|
9
10
|
'image.ruby_llm' => 'generate_content',
|
|
@@ -14,51 +15,60 @@ module RubyLLM
|
|
|
14
15
|
'moderation.ruby_llm' => 'moderation',
|
|
15
16
|
'judgment.ruby_llm' => 'judgment',
|
|
16
17
|
'tool_call.ruby_llm' => 'execute_tool',
|
|
18
|
+
'code_execution.ruby_llm' => 'ruby_llm.code_execution',
|
|
17
19
|
'workflow.ruby_llm' => 'invoke_workflow',
|
|
18
20
|
'workflow_step.ruby_llm' => 'ruby_llm.workflow_step'
|
|
19
21
|
}.freeze
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
'gemini' => 'gcp.gen_ai',
|
|
24
|
-
'vertexai' => 'gcp.vertex_ai',
|
|
25
|
-
'mistral' => 'mistral_ai',
|
|
26
|
-
'xai' => 'x_ai'
|
|
27
|
-
}.freeze
|
|
28
|
-
INTERNAL_OPERATIONS = %w[execute_tool invoke_workflow ruby_llm.workflow_step].freeze
|
|
22
|
+
INTERNAL_OPERATIONS = %w[
|
|
23
|
+
invoke_agent execute_tool invoke_workflow ruby_llm.workflow_step ruby_llm.code_execution
|
|
24
|
+
].freeze
|
|
29
25
|
|
|
30
26
|
module_function
|
|
31
27
|
|
|
32
|
-
def span_name(operation, payload)
|
|
28
|
+
def span_name(operation, payload, content: false)
|
|
33
29
|
target = case operation
|
|
34
|
-
when '
|
|
30
|
+
when 'invoke_agent' then payload[:agent_name]
|
|
31
|
+
when 'execute_tool' then [payload[:tool_name], *skill(payload, content:).values]
|
|
35
32
|
when 'invoke_workflow' then payload[:workflow_name]
|
|
36
33
|
when 'ruby_llm.workflow_step' then payload[:workflow_step_name]
|
|
34
|
+
when 'ruby_llm.code_execution' then payload[:language]
|
|
37
35
|
else payload[:model]
|
|
38
36
|
end
|
|
39
|
-
[operation, target].compact.join(' ')
|
|
37
|
+
[operation, *target].compact.join(' ')
|
|
40
38
|
end
|
|
41
39
|
|
|
42
40
|
def span_kind(operation)
|
|
43
41
|
INTERNAL_OPERATIONS.include?(operation) ? :internal : :client
|
|
44
42
|
end
|
|
45
43
|
|
|
46
|
-
def request(operation, payload, event:)
|
|
44
|
+
def request(operation, payload, event:, content: false)
|
|
47
45
|
return workflow(operation, payload) if %w[invoke_workflow ruby_llm.workflow_step].include?(operation)
|
|
48
|
-
return
|
|
46
|
+
return agent(payload) if operation == 'invoke_agent'
|
|
47
|
+
return tool(payload, content:) if operation == 'execute_tool'
|
|
48
|
+
return code_execution_request(payload) if operation == 'ruby_llm.code_execution'
|
|
49
49
|
|
|
50
50
|
{
|
|
51
51
|
'gen_ai.operation.name' => operation,
|
|
52
|
-
'gen_ai.provider.name' =>
|
|
52
|
+
'gen_ai.provider.name' => provider_name(payload[:provider]),
|
|
53
53
|
'gen_ai.request.model' => payload[:model],
|
|
54
54
|
'gen_ai.request.temperature' => payload[:temperature],
|
|
55
55
|
'gen_ai.request.max_tokens' => payload[:max_output_tokens],
|
|
56
56
|
'gen_ai.request.stream' => (true if payload[:streaming]),
|
|
57
|
+
'gen_ai.request.reasoning.level' => reasoning_level(payload[:thinking]),
|
|
57
58
|
'gen_ai.embeddings.dimension.count' => payload[:dimensions],
|
|
58
59
|
'gen_ai.output.type' => output_type(event, payload)
|
|
59
60
|
}.compact
|
|
60
61
|
end
|
|
61
62
|
|
|
63
|
+
def provider_name(slug)
|
|
64
|
+
provider = Provider.resolve(slug) if slug
|
|
65
|
+
provider ? provider.open_telemetry_name : slug
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
def reasoning_level(thinking)
|
|
69
|
+
thinking.effort.to_s if thinking.respond_to?(:effort) && thinking.effort
|
|
70
|
+
end
|
|
71
|
+
|
|
62
72
|
def output_type(event, payload)
|
|
63
73
|
return 'json' if payload[:schema]
|
|
64
74
|
return 'image' if event == 'image.ruby_llm'
|
|
@@ -69,10 +79,10 @@ module RubyLLM
|
|
|
69
79
|
|
|
70
80
|
def response(payload)
|
|
71
81
|
response = payload[:response]
|
|
72
|
-
attributes =
|
|
82
|
+
attributes = code_execution(payload).merge(
|
|
73
83
|
'gen_ai.response.model' => payload[:response_model],
|
|
74
84
|
'gen_ai.response.finish_reasons' => ([response.finish_reason.to_s] if response&.finish_reason)
|
|
75
|
-
|
|
85
|
+
).compact
|
|
76
86
|
attributes.merge!(tokens(payload[:response_tokens] || payload[:tokens])) if payload[:tokens]
|
|
77
87
|
attributes
|
|
78
88
|
end
|
|
@@ -88,12 +98,51 @@ module RubyLLM
|
|
|
88
98
|
}.compact
|
|
89
99
|
end
|
|
90
100
|
|
|
91
|
-
def
|
|
101
|
+
def code_execution_request(payload)
|
|
102
|
+
{ 'ruby_llm.code_execution.language' => payload[:language].to_s }
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def code_execution(payload)
|
|
106
|
+
return {} unless payload.key?(:timed_out)
|
|
107
|
+
|
|
108
|
+
{
|
|
109
|
+
'ruby_llm.code_execution.exit_code' => payload[:exit_code],
|
|
110
|
+
'ruby_llm.code_execution.timed_out' => payload[:timed_out],
|
|
111
|
+
'ruby_llm.code_execution.duration' => payload[:duration]
|
|
112
|
+
}
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
def tool(payload, content:)
|
|
92
116
|
{
|
|
93
117
|
'gen_ai.operation.name' => 'execute_tool',
|
|
94
118
|
'gen_ai.tool.name' => payload[:tool_name],
|
|
95
119
|
'gen_ai.tool.call.id' => payload[:tool_call_id],
|
|
96
|
-
'gen_ai.tool.
|
|
120
|
+
'gen_ai.tool.description' => payload[:tool]&.description,
|
|
121
|
+
'gen_ai.tool.type' => 'function',
|
|
122
|
+
'gen_ai.agent.name' => payload[:agent_name]
|
|
123
|
+
}.merge(skill(payload, content:)).compact
|
|
124
|
+
end
|
|
125
|
+
|
|
126
|
+
# A skill name the chat does not have, or the file a call asks for,
|
|
127
|
+
# is a model's argument, so it is content.
|
|
128
|
+
def skill(payload, content:)
|
|
129
|
+
loader = payload[:tool]
|
|
130
|
+
arguments = payload[:tool_arguments].to_h.transform_keys(&:to_s)
|
|
131
|
+
name = arguments['skill'].to_s
|
|
132
|
+
return {} unless loader.is_a?(Skill::Loader) && loader.skill?(name)
|
|
133
|
+
|
|
134
|
+
{
|
|
135
|
+
'gen_ai.skill.name' => name,
|
|
136
|
+
'gen_ai.skill.resource.name' => (Skill::Loader.resource_name(name, arguments['file']) if content)
|
|
137
|
+
}.compact
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
def agent(payload)
|
|
141
|
+
{
|
|
142
|
+
'gen_ai.operation.name' => 'invoke_agent',
|
|
143
|
+
'gen_ai.agent.name' => payload[:agent_name],
|
|
144
|
+
'gen_ai.agent.description' => payload[:agent_description],
|
|
145
|
+
'gen_ai.conversation.id' => payload[:chat_id]&.to_s
|
|
97
146
|
}.compact
|
|
98
147
|
end
|
|
99
148
|
|
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
class OpenTelemetry
|
|
5
|
+
# The opt-in content attributes, in the GenAI semantic conventions'
|
|
6
|
+
# message format. Attachments are described, never sent as bytes.
|
|
7
|
+
module Content # :nodoc:
|
|
8
|
+
module_function
|
|
9
|
+
|
|
10
|
+
def request(operation, payload)
|
|
11
|
+
case operation
|
|
12
|
+
when 'chat' then chat_request(payload[:input_messages] || [])
|
|
13
|
+
when 'execute_tool' then { 'gen_ai.tool.call.arguments' => json(payload[:tool_arguments]) }
|
|
14
|
+
else {}
|
|
15
|
+
end.compact
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def response(operation, payload)
|
|
19
|
+
case operation
|
|
20
|
+
when 'chat' then chat_response(payload[:response])
|
|
21
|
+
when 'execute_tool' then { 'gen_ai.tool.call.result' => tool_result(payload[:result]) }
|
|
22
|
+
else {}
|
|
23
|
+
end.compact
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def chat_request(messages)
|
|
27
|
+
system, conversation = messages.partition { |message| message.role == :system }
|
|
28
|
+
{
|
|
29
|
+
'gen_ai.system_instructions' => (json(system.flat_map { |message| parts(message) }) if system.any?),
|
|
30
|
+
'gen_ai.input.messages' => (json(conversation.map { |message| message(message) }) if conversation.any?)
|
|
31
|
+
}
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def chat_response(response)
|
|
35
|
+
return {} unless response.is_a?(Message)
|
|
36
|
+
|
|
37
|
+
output = message(response)
|
|
38
|
+
output[:finish_reason] = response.finish_reason.to_s if response.finish_reason
|
|
39
|
+
{ 'gen_ai.output.messages' => json([output]) }
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def message(message)
|
|
43
|
+
{ role: message.role.to_s, parts: parts(message) }
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
def parts(message)
|
|
47
|
+
return tool_parts(message) if message.role == :tool
|
|
48
|
+
|
|
49
|
+
thinking = message.thinking&.text
|
|
50
|
+
[
|
|
51
|
+
({ type: 'reasoning', content: thinking } if present?(thinking)),
|
|
52
|
+
({ type: 'text', content: message.content } if present?(message.content)),
|
|
53
|
+
*message.attachments.map { |attachment| attachment(attachment) },
|
|
54
|
+
*message.tool_calls.map { |call| tool_call(call) }
|
|
55
|
+
].compact
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def tool_parts(message)
|
|
59
|
+
[{ type: 'tool_call_response', id: message.tool_call_id, response: message.content },
|
|
60
|
+
*message.attachments.map { |attachment| attachment(attachment) }]
|
|
61
|
+
end
|
|
62
|
+
|
|
63
|
+
def tool_call(call)
|
|
64
|
+
{ type: 'tool_call', id: call.id, name: call.name, arguments: call.arguments }
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def attachment(attachment)
|
|
68
|
+
described = { modality: attachment.type.to_s, mime_type: attachment.mime_type }
|
|
69
|
+
if attachment.provider_file?
|
|
70
|
+
{ type: 'file', **described, file_id: attachment.provider_file_id }
|
|
71
|
+
elsif attachment.url?
|
|
72
|
+
{ type: 'uri', **described, uri: uri_without_credentials(attachment.source) }
|
|
73
|
+
else
|
|
74
|
+
{ type: 'attachment', **described, filename: attachment.filename }
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
# What the model receives from the tool, with attachments described.
|
|
79
|
+
def tool_result(result)
|
|
80
|
+
return if result.nil?
|
|
81
|
+
|
|
82
|
+
content, attachments = Tool.split_result(result)
|
|
83
|
+
return content if attachments.empty?
|
|
84
|
+
|
|
85
|
+
json([({ type: 'text', content: } if present?(content)), *attachments.map { |item| attachment(item) }].compact)
|
|
86
|
+
end
|
|
87
|
+
|
|
88
|
+
# Signed URLs carry access credentials in their query string.
|
|
89
|
+
def uri_without_credentials(source)
|
|
90
|
+
uri = URI(source.to_s)
|
|
91
|
+
uri.class.build(host: uri.host, port: uri.port, path: uri.path).to_s
|
|
92
|
+
end
|
|
93
|
+
|
|
94
|
+
def present?(text)
|
|
95
|
+
text.is_a?(String) && !text.empty?
|
|
96
|
+
end
|
|
97
|
+
|
|
98
|
+
def json(value)
|
|
99
|
+
return if value.nil?
|
|
100
|
+
|
|
101
|
+
value.is_a?(String) ? value : JSON.generate(value)
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
end
|
|
@@ -10,9 +10,16 @@ module RubyLLM
|
|
|
10
10
|
# Enables tracing without replacing the configured instrumenter.
|
|
11
11
|
# Requires the optional opentelemetry-api gem. Repeated calls are harmless.
|
|
12
12
|
# The application owns the tracer provider, SDK, and exporters.
|
|
13
|
-
|
|
13
|
+
# With <tt>content: true</tt>, spans also carry instructions, messages,
|
|
14
|
+
# model output, and tool arguments and results.
|
|
15
|
+
#
|
|
16
|
+
# RubyLLM::OpenTelemetry.enable
|
|
17
|
+
# RubyLLM::OpenTelemetry.enable(content: true)
|
|
18
|
+
#
|
|
19
|
+
def enable(content: false)
|
|
14
20
|
require 'opentelemetry-api'
|
|
15
21
|
@mutex.synchronize do
|
|
22
|
+
@content = content
|
|
16
23
|
@subscriber ||= new
|
|
17
24
|
Support::Instrumentation.subscribe(@subscriber)
|
|
18
25
|
end
|
|
@@ -27,16 +34,22 @@ module RubyLLM
|
|
|
27
34
|
@mutex.synchronize { Support::Instrumentation.unsubscribe(@subscriber) if @subscriber }
|
|
28
35
|
nil
|
|
29
36
|
end
|
|
37
|
+
|
|
38
|
+
def content? # :nodoc:
|
|
39
|
+
@content == true
|
|
40
|
+
end
|
|
30
41
|
end
|
|
31
42
|
|
|
32
43
|
def instrument(name, payload) # :nodoc:
|
|
33
44
|
operation = Attributes::OPERATIONS[name]
|
|
34
45
|
return yield unless operation
|
|
35
46
|
|
|
47
|
+
content = self.class.content?
|
|
36
48
|
span = safely do
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
49
|
+
attributes = Attributes.request(operation, payload, event: name, content:)
|
|
50
|
+
attributes.merge!(Content.request(operation, payload)) if content
|
|
51
|
+
span_name = Attributes.span_name(operation, payload, content:)
|
|
52
|
+
tracer.start_span(span_name, kind: Attributes.span_kind(operation), attributes:)
|
|
40
53
|
end
|
|
41
54
|
return yield unless span
|
|
42
55
|
|
|
@@ -47,6 +60,7 @@ module RubyLLM
|
|
|
47
60
|
raise
|
|
48
61
|
ensure
|
|
49
62
|
safely { span.add_attributes(Attributes.response(payload)) }
|
|
63
|
+
safely { span.add_attributes(Content.response(operation, payload)) } if content
|
|
50
64
|
safely { span.finish }
|
|
51
65
|
end
|
|
52
66
|
end
|
|
@@ -7,7 +7,7 @@
|
|
|
7
7
|
# RubyLLM lexical scope: +Chat+ in a template is the application's model,
|
|
8
8
|
# not RubyLLM::Chat.
|
|
9
9
|
class RubyLLM::Prompt::Context # rubocop:disable Style/ClassAndModuleChildren
|
|
10
|
-
LOCAL_VARIABLE_NAME = /\A(?![
|
|
10
|
+
LOCAL_VARIABLE_NAME = /\A(?![[:upper:]\p{Lt}0-9])[[:alnum:]_[:^ascii:]]+\z/
|
|
11
11
|
private_constant :LOCAL_VARIABLE_NAME
|
|
12
12
|
|
|
13
13
|
# Returns the locals passed to the current prompt or partial. Use it to
|
|
@@ -46,7 +46,7 @@ class RubyLLM::Prompt::Context # rubocop:disable Style/ClassAndModuleChildren
|
|
|
46
46
|
|
|
47
47
|
def partial(name)
|
|
48
48
|
name = name.to_s
|
|
49
|
-
partial = name.sub(%r{([^/]+)\z}, '
|
|
49
|
+
partial = name.sub(%r{(\A|/)([^/]+)\z}, '\\1_\\2')
|
|
50
50
|
directory = File.dirname(@prompt.name)
|
|
51
51
|
return RubyLLM::Prompt.new(partial) if name.include?('/') || directory == '.'
|
|
52
52
|
|
data/lib/ruby_llm/prompt.rb
CHANGED
|
@@ -6,13 +6,15 @@ require 'pathname'
|
|
|
6
6
|
module RubyLLM
|
|
7
7
|
# Resolves prompt templates from the application and registered engine directories.
|
|
8
8
|
class Prompt
|
|
9
|
-
# The ordered directories searched for prompt files
|
|
10
|
-
#
|
|
11
|
-
#
|
|
12
|
-
# Engines append their own
|
|
9
|
+
# The ordered directories searched for prompt files, and for skills
|
|
10
|
+
# through Skill.roots. The application's directory is always first, so
|
|
11
|
+
# an application can override any prompt or skill an engine ships by
|
|
12
|
+
# placing its own at the same relative path. Engines append their own
|
|
13
|
+
# directories in an initializer:
|
|
13
14
|
#
|
|
14
15
|
# initializer "my_engine.prompts" do
|
|
15
16
|
# RubyLLM::Prompt.roots << MyEngine::Engine.root.join("app/prompts")
|
|
17
|
+
# RubyLLM::Skill.roots << MyEngine::Engine.root.join("app/skills")
|
|
16
18
|
# end
|
|
17
19
|
#
|
|
18
20
|
class Roots
|
|
@@ -50,7 +52,7 @@ module RubyLLM
|
|
|
50
52
|
def render(**locals) # :nodoc:
|
|
51
53
|
raise PromptNotFoundError, "Prompt file not found: #{path}" unless File.exist?(path)
|
|
52
54
|
|
|
53
|
-
ERB.new(File.read(path)).result(Context.new(self, locals).scope)
|
|
55
|
+
ERB.new(File.read(path, encoding: Encoding::UTF_8), trim_mode: '-').result(Context.new(self, locals).scope)
|
|
54
56
|
end
|
|
55
57
|
|
|
56
58
|
def self.render(name, **locals) # :nodoc:
|
|
@@ -8,14 +8,8 @@ module RubyLLM
|
|
|
8
8
|
fallback = Streaming::StreamState.new
|
|
9
9
|
on_data = binary_on_data(fallback, progress, &)
|
|
10
10
|
|
|
11
|
-
response =
|
|
12
|
-
|
|
13
|
-
if faraday_1?
|
|
14
|
-
request.options[:on_data] = on_data
|
|
15
|
-
else
|
|
16
|
-
request.options.on_data = on_data
|
|
17
|
-
end
|
|
18
|
-
end
|
|
11
|
+
response = post_stream(url, payload, on_data:, progress:, on_reset: fallback.method(:reset),
|
|
12
|
+
on_failure: stream_failure(fallback))
|
|
19
13
|
state = response.env[:streaming_state] || fallback
|
|
20
14
|
validate_binary_response(response, state.buffer)
|
|
21
15
|
yield state.buffer.dup unless progress[:started] || state.buffer.empty?
|