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
|
@@ -4,8 +4,30 @@ require 'ruby_llm/active_record/payload_helpers'
|
|
|
4
4
|
|
|
5
5
|
module RubyLLM
|
|
6
6
|
module ActiveRecord
|
|
7
|
-
#
|
|
8
|
-
|
|
7
|
+
# A tool call a message requested, in RubyLLM's +ruby_llm_tool_calls+
|
|
8
|
+
# table. An assistant message reaches its calls through
|
|
9
|
+
# +ruby_llm_tool_calls+, oldest first, and a tool message reaches the
|
|
10
|
+
# call it answers through +ruby_llm_parent_tool_call+.
|
|
11
|
+
#
|
|
12
|
+
# message.ruby_llm_tool_calls.pluck(:name) # => ["weather", "search"]
|
|
13
|
+
# RubyLLM::ActiveRecord::ToolCall.where(name: "weather").count
|
|
14
|
+
#
|
|
15
|
+
# Each row belongs to +message+, the assistant message that requested
|
|
16
|
+
# it, and to +result+, the tool message that answers it once there is
|
|
17
|
+
# one. Both associations are polymorphic. Its columns:
|
|
18
|
+
#
|
|
19
|
+
# [+tool_call_id+] The call's id, unique across the table.
|
|
20
|
+
# [+name+] The tool's name.
|
|
21
|
+
# [+namespace+] The namespace of a namespaced tool, or +nil+.
|
|
22
|
+
# [+arguments+] The arguments the model passed, as a Hash. Empty when
|
|
23
|
+
# the model's text was not a JSON object.
|
|
24
|
+
# [+approval+] <tt>"pending"</tt>, <tt>"approved"</tt>, <tt>"remembered"</tt>,
|
|
25
|
+
# or <tt>"denied"</tt>, or +nil+ when no approval was asked.
|
|
26
|
+
# [+remote+] Whether the provider ran the tool instead of RubyLLM.
|
|
27
|
+
#
|
|
28
|
+
# RubyLLM writes these rows. Read and query them, and add scopes and
|
|
29
|
+
# columns of your own, but do not create or update them.
|
|
30
|
+
class ToolCall < Record
|
|
9
31
|
self.table_name = 'ruby_llm_tool_calls'
|
|
10
32
|
|
|
11
33
|
include PayloadHelpers
|
|
@@ -15,17 +37,46 @@ module RubyLLM
|
|
|
15
37
|
|
|
16
38
|
validates :tool_call_id, :name, presence: true
|
|
17
39
|
|
|
40
|
+
# Returns this row as a RubyLLM::ToolCall.
|
|
18
41
|
def to_llm
|
|
19
42
|
RubyLLM::ToolCall.new(
|
|
20
43
|
id: tool_call_id,
|
|
44
|
+
provider_call_id: self[:provider_call_id],
|
|
21
45
|
name: name,
|
|
22
|
-
|
|
46
|
+
namespace: self[:namespace],
|
|
47
|
+
arguments: arguments,
|
|
48
|
+
malformed_arguments: malformed_arguments,
|
|
23
49
|
thought_signature: thought_signature,
|
|
24
50
|
remote: self[:remote]
|
|
25
51
|
)
|
|
26
52
|
end
|
|
27
53
|
|
|
28
|
-
|
|
54
|
+
# Returns the arguments the model passed, as a Hash, or an empty Hash
|
|
55
|
+
# when the model's text was not a JSON object.
|
|
56
|
+
def arguments
|
|
57
|
+
value = self[:arguments]
|
|
58
|
+
value.is_a?(Hash) ? value : {}
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def malformed_arguments # :nodoc:
|
|
62
|
+
value = self[:arguments]
|
|
63
|
+
value if value.is_a?(String)
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
# Returns whether a process is running this tool call: it has no
|
|
67
|
+
# result yet and its claim has not expired. The process running it
|
|
68
|
+
# keeps renewing the claim, which expires within a minute of that
|
|
69
|
+
# process dying.
|
|
70
|
+
def running?
|
|
71
|
+
(result_id.nil? && has_attribute?(:claimed_until) && claimed_until&.future?) || false
|
|
72
|
+
end
|
|
73
|
+
|
|
74
|
+
# Returns whether the call waits on a task or on questions it asked.
|
|
75
|
+
def paused?
|
|
76
|
+
has_attribute?(:mcp_state) && mcp_state.present?
|
|
77
|
+
end
|
|
78
|
+
|
|
79
|
+
def tool_error_message # :nodoc:
|
|
29
80
|
payload_error_message(arguments)
|
|
30
81
|
end
|
|
31
82
|
end
|
|
@@ -2,10 +2,43 @@
|
|
|
2
2
|
|
|
3
3
|
module RubyLLM
|
|
4
4
|
module ActiveRecord
|
|
5
|
-
# RubyLLM's
|
|
6
|
-
|
|
5
|
+
# One provider attempt in RubyLLM's usage ledger, the +ruby_llm_usages+
|
|
6
|
+
# table. RubyLLM writes a row for every billable attempt, including
|
|
7
|
+
# retries, cancelled streams, and one-shot operations without a chat.
|
|
8
|
+
# A write that fails is logged and skipped, so it never breaks the
|
|
9
|
+
# operation.
|
|
10
|
+
#
|
|
11
|
+
# chat.ruby_llm_usages.sum(:total_cost)
|
|
12
|
+
# RubyLLM::ActiveRecord::Usage.where(owner: current_user).group(:operation).sum(:total_cost)
|
|
13
|
+
#
|
|
14
|
+
# Each row has optional polymorphic +chat+, +message+, and +owner+
|
|
15
|
+
# associations. The +chronological+ scope orders rows oldest first.
|
|
16
|
+
# Its columns:
|
|
17
|
+
#
|
|
18
|
+
# [+operation+] What ran: <tt>"chat"</tt>, <tt>"embedding"</tt>, <tt>"image"</tt>,
|
|
19
|
+
# <tt>"speech"</tt>, <tt>"transcription"</tt>, and so on.
|
|
20
|
+
# [+provider+] The provider's slug.
|
|
21
|
+
# [+model+] The model the provider ran, or its own resource identifier.
|
|
22
|
+
# [+status+] <tt>"pending"</tt>, <tt>"succeeded"</tt>, <tt>"failed"</tt>, or <tt>"cancelled"</tt>.
|
|
23
|
+
# [+input_tokens+, +output_tokens+, +cache_read_tokens+, +cache_write_tokens+, +thinking_tokens+]
|
|
24
|
+
# Token counts, in the bucket the provider bills each at.
|
|
25
|
+
# [+characters+, +audio_seconds+, +search_units+] Units billed instead of tokens.
|
|
26
|
+
# [+server_tool_use+] Counts of provider-run tool calls, as a Hash.
|
|
27
|
+
# [+input_cost+, +output_cost+, +cache_read_cost+, +cache_write_cost+, +thinking_cost+]
|
|
28
|
+
# The cost of each token bucket, frozen when the attempt finished.
|
|
29
|
+
# [+characters_cost+, +audio_seconds_cost+, +search_units_cost+] The cost of each unit.
|
|
30
|
+
# [+total_cost+] The attempt's total cost, or +nil+ when it is unknown.
|
|
31
|
+
# [+workflow_id+, +workflow_name+, +workflow_step_id+, +workflow_step_name+]
|
|
32
|
+
# The workflow and step that made the attempt, if any.
|
|
33
|
+
#
|
|
34
|
+
# RubyLLM writes these rows. Read and query them, and add scopes and
|
|
35
|
+
# columns of your own, but do not create or update them.
|
|
36
|
+
class Usage < Record
|
|
7
37
|
self.table_name = 'ruby_llm_usages'
|
|
8
38
|
|
|
39
|
+
WORKFLOW_COLUMNS = %i[workflow_id workflow_name workflow_step_id workflow_step_name].freeze # :nodoc:
|
|
40
|
+
UNIT_COLUMNS = ::RubyLLM::Cost::UNITS.flat_map { |unit| [unit, :"#{unit}_cost"] }.freeze # :nodoc:
|
|
41
|
+
|
|
9
42
|
belongs_to :chat, polymorphic: true, optional: true
|
|
10
43
|
belongs_to :message, polymorphic: true, optional: true
|
|
11
44
|
belongs_to :owner, polymorphic: true, optional: true
|
|
@@ -22,7 +55,7 @@ module RubyLLM
|
|
|
22
55
|
# it. A savepoint keeps a failed insert from aborting the caller's
|
|
23
56
|
# transaction, and a thread that held no connection gives back the
|
|
24
57
|
# one it borrowed.
|
|
25
|
-
def self.record(entry)
|
|
58
|
+
def self.record(entry) # :nodoc:
|
|
26
59
|
return unless entry.model
|
|
27
60
|
|
|
28
61
|
connection_pool.with_connection do
|
|
@@ -35,11 +68,11 @@ module RubyLLM
|
|
|
35
68
|
nil
|
|
36
69
|
end
|
|
37
70
|
|
|
38
|
-
def self.ledger_available?
|
|
71
|
+
def self.ledger_available? # :nodoc:
|
|
39
72
|
table_exists? && columns_hash['chat_id']&.null
|
|
40
73
|
end
|
|
41
74
|
|
|
42
|
-
def self.attributes_for(entry)
|
|
75
|
+
def self.attributes_for(entry) # :nodoc:
|
|
43
76
|
tokens = entry.tokens
|
|
44
77
|
cost = entry.cost
|
|
45
78
|
attributes = {
|
|
@@ -60,10 +93,28 @@ module RubyLLM
|
|
|
60
93
|
total_cost: cost.total
|
|
61
94
|
}
|
|
62
95
|
attributes[:server_tool_use] = tokens.server_tool_use if column_names.include?('server_tool_use')
|
|
96
|
+
attributes.merge!(unit_attributes(tokens, cost)) if column_names.include?('search_units')
|
|
63
97
|
attributes[:owner] = entry.owner if entry.owner.is_a?(::ActiveRecord::Base) && column_names.include?('owner_id')
|
|
98
|
+
attributes.merge!(workflow_attributes(entry.workflow)) if column_names.include?('workflow_id')
|
|
64
99
|
attributes
|
|
65
100
|
end
|
|
66
101
|
|
|
102
|
+
def self.unit_attributes(tokens, cost) # :nodoc:
|
|
103
|
+
::RubyLLM::Cost::UNITS.each_with_object({}) do |unit, attributes|
|
|
104
|
+
attributes[unit] = tokens.public_send(unit)
|
|
105
|
+
attributes[:"#{unit}_cost"] = cost.public_send(unit)
|
|
106
|
+
end
|
|
107
|
+
end
|
|
108
|
+
|
|
109
|
+
def self.workflow_attributes(workflow) # :nodoc:
|
|
110
|
+
workflow.to_h.slice(*WORKFLOW_COLUMNS)
|
|
111
|
+
end
|
|
112
|
+
|
|
113
|
+
def self.owner_preload # :nodoc:
|
|
114
|
+
column_names.include?('owner_id') ? :owner : []
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
# Returns the attempt's counts as RubyLLM::Tokens.
|
|
67
118
|
def tokens
|
|
68
119
|
RubyLLM::Tokens.new(
|
|
69
120
|
input: input_tokens,
|
|
@@ -71,10 +122,12 @@ module RubyLLM
|
|
|
71
122
|
cache_read: cache_read_tokens,
|
|
72
123
|
cache_write: cache_write_tokens,
|
|
73
124
|
thinking: thinking_tokens,
|
|
125
|
+
**recorded_units,
|
|
74
126
|
server_tool_use: (self[:server_tool_use] if has_attribute?(:server_tool_use))
|
|
75
127
|
)
|
|
76
128
|
end
|
|
77
129
|
|
|
130
|
+
# Returns the attempt's frozen cost as RubyLLM::Cost.
|
|
78
131
|
def cost
|
|
79
132
|
recorded = {
|
|
80
133
|
input: input_cost,
|
|
@@ -82,20 +135,21 @@ module RubyLLM
|
|
|
82
135
|
cache_read: cache_read_cost,
|
|
83
136
|
cache_write: cache_write_cost,
|
|
84
137
|
thinking: thinking_cost,
|
|
138
|
+
**recorded_units(suffix: '_cost'),
|
|
85
139
|
total: total_cost
|
|
86
140
|
}.compact
|
|
87
141
|
RubyLLM::Cost.from_h(recorded, tokens: tokens)
|
|
88
142
|
end
|
|
89
143
|
|
|
90
|
-
def usage_available?
|
|
144
|
+
def usage_available? # :nodoc:
|
|
91
145
|
tokens.to_h.any?
|
|
92
146
|
end
|
|
93
147
|
|
|
94
|
-
def cost_available?
|
|
148
|
+
def cost_available? # :nodoc:
|
|
95
149
|
!total_cost.nil?
|
|
96
150
|
end
|
|
97
151
|
|
|
98
|
-
def to_entry
|
|
152
|
+
def to_entry # :nodoc:
|
|
99
153
|
::RubyLLM::Accounting::Usage::Entry.new(
|
|
100
154
|
operation: operation,
|
|
101
155
|
provider: provider,
|
|
@@ -103,9 +157,25 @@ module RubyLLM
|
|
|
103
157
|
status: status,
|
|
104
158
|
tokens: tokens,
|
|
105
159
|
cost: cost,
|
|
106
|
-
message: message
|
|
160
|
+
message: message,
|
|
161
|
+
owner: (owner if has_attribute?(:owner_id)),
|
|
162
|
+
workflow: workflow_context
|
|
107
163
|
)
|
|
108
164
|
end
|
|
165
|
+
|
|
166
|
+
private
|
|
167
|
+
|
|
168
|
+
def recorded_units(suffix: nil)
|
|
169
|
+
::RubyLLM::Cost::UNITS.each_with_object({}) do |unit, units|
|
|
170
|
+
column = :"#{unit}#{suffix}"
|
|
171
|
+
units[unit] = self[column] if has_attribute?(column)
|
|
172
|
+
end
|
|
173
|
+
end
|
|
174
|
+
|
|
175
|
+
def workflow_context
|
|
176
|
+
context = WORKFLOW_COLUMNS.select { |name| has_attribute?(name) }.index_with { |name| self[name] }.compact
|
|
177
|
+
context unless context.empty?
|
|
178
|
+
end
|
|
109
179
|
end
|
|
110
180
|
end
|
|
111
181
|
end
|
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
class Agent
|
|
5
|
+
# The Agent callback macros, such as Agent::after_message, which
|
|
6
|
+
# register the Chat callback of the same name on every chat an agent
|
|
7
|
+
# builds or finds. Each runs on the agent instance, like a Rails
|
|
8
|
+
# callback on its record. An agent that configures a chat again, as
|
|
9
|
+
# Agent.new(chat:) does for a chat from Agent::chat, takes over the
|
|
10
|
+
# callbacks already registered instead of adding them twice. As for
|
|
11
|
+
# Agent::rescue_from handlers, the agent behind a chat from Agent::chat
|
|
12
|
+
# or Agent::find is bound to it without running +initialize+.
|
|
13
|
+
module Callbacks # :nodoc:
|
|
14
|
+
HOOKS = %i[
|
|
15
|
+
before_request before_message after_message before_tool_call around_tool_call after_tool_result
|
|
16
|
+
after_tool_progress before_fallback after_fallback after_file_write
|
|
17
|
+
].freeze
|
|
18
|
+
|
|
19
|
+
# A declared callback: a method name or a block, with the +if:+ and
|
|
20
|
+
# +unless:+ conditions that guard it.
|
|
21
|
+
class Callback # :nodoc:
|
|
22
|
+
def initialize(hook, method_name, block, conditions)
|
|
23
|
+
unless method_name.nil? ^ block.nil?
|
|
24
|
+
raise ArgumentError, "#{hook} takes a method name or a block, not both or neither"
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
unknown = conditions.keys - %i[if unless]
|
|
28
|
+
raise ArgumentError, "#{hook} takes if: and unless:, got #{unknown.join(', ')}" if unknown.any?
|
|
29
|
+
|
|
30
|
+
@handler = method_name || block
|
|
31
|
+
@if = Array(conditions[:if])
|
|
32
|
+
@unless = Array(conditions[:unless])
|
|
33
|
+
end
|
|
34
|
+
|
|
35
|
+
# Runs the handler with +args+, passing +block+ on to it, as an
|
|
36
|
+
# around callback's +run+. A skipped around callback runs +block+.
|
|
37
|
+
def run(agent, args, &block)
|
|
38
|
+
return block&.call unless @if.all? { |condition| call(agent, condition, args) }
|
|
39
|
+
return block&.call if @unless.any? { |condition| call(agent, condition, args) }
|
|
40
|
+
|
|
41
|
+
call(agent, @handler, args, &block)
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
private
|
|
45
|
+
|
|
46
|
+
def call(agent, handler, args, &block)
|
|
47
|
+
if handler.is_a?(Symbol) || handler.is_a?(String)
|
|
48
|
+
method = agent.method(handler)
|
|
49
|
+
return method.arity.zero? ? method.call(&block) : method.call(*args, &block)
|
|
50
|
+
end
|
|
51
|
+
if block
|
|
52
|
+
method = bound(handler)
|
|
53
|
+
return method.arity.zero? ? method.bind_call(agent, &block) : method.bind_call(agent, *args, &block)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
handler.lambda? && handler.arity.zero? ? agent.instance_exec(&handler) : agent.instance_exec(*args, &handler)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
# instance_exec cannot pass a block on, so a handler that takes one
|
|
60
|
+
# runs as a method of the agent instead.
|
|
61
|
+
def bound(handler)
|
|
62
|
+
@bound ||= Module.new { define_method(:callback, &handler) }.instance_method(:callback)
|
|
63
|
+
end
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
HOOKS.each do |hook|
|
|
67
|
+
define_method(hook) do |method_name = nil, **conditions, &block|
|
|
68
|
+
chat_callbacks << [hook, Callback.new(hook, method_name, block, conditions)]
|
|
69
|
+
self
|
|
70
|
+
end
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def chat_callbacks
|
|
74
|
+
@chat_callbacks ||= []
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
def inherited(subclass)
|
|
78
|
+
super
|
|
79
|
+
subclass.instance_variable_set(:@chat_callbacks, chat_callbacks.dup)
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def apply_configuration(chat, input_values:, agent: nil, **)
|
|
83
|
+
super.tap do
|
|
84
|
+
next if chat_callbacks.empty?
|
|
85
|
+
|
|
86
|
+
receivers = (chat.respond_to?(:to_llm) ? chat.to_llm : chat).agent_callback_receivers
|
|
87
|
+
registered = receivers.key?(self)
|
|
88
|
+
receivers[self] = agent || allocate.bind(chat, input_values)
|
|
89
|
+
next if registered
|
|
90
|
+
|
|
91
|
+
chat_callbacks.each do |hook, callback|
|
|
92
|
+
chat.public_send(hook) { |*args, &run| callback.run(receivers[self], args, &run) }
|
|
93
|
+
end
|
|
94
|
+
end
|
|
95
|
+
end
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
class Agent
|
|
5
|
+
# Applies the Agent::context_budget macro to every chat an agent builds
|
|
6
|
+
# or finds.
|
|
7
|
+
module ContextBudget # :nodoc:
|
|
8
|
+
def apply_configuration(chat, input_values:, **)
|
|
9
|
+
super.tap do
|
|
10
|
+
next if @context_budget.nil?
|
|
11
|
+
|
|
12
|
+
chat.with_context_budget(evaluate(@context_budget, runtime_context(chat:, inputs: input_values)) || nil)
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
|
|
16
|
+
private
|
|
17
|
+
|
|
18
|
+
def context_budget_option(budget, **shares, &block)
|
|
19
|
+
return Chat::ContextBudget.normalize(budget, **shares) || false unless block
|
|
20
|
+
raise ArgumentError, 'context_budget takes options or a block, not both' if shares.any? || budget != {}
|
|
21
|
+
|
|
22
|
+
block
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
class Agent
|
|
5
|
+
# The Agent::around_join macro, which registers Chat#around_join
|
|
6
|
+
# callbacks on every chat an agent builds or finds.
|
|
7
|
+
module JoinCallbacks # :nodoc:
|
|
8
|
+
def around_join(&block)
|
|
9
|
+
raise ArgumentError, 'around_join needs a block' unless block
|
|
10
|
+
|
|
11
|
+
around_join_callbacks << block
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def around_join_callbacks
|
|
15
|
+
@around_join_callbacks ||= []
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def inherited(subclass)
|
|
19
|
+
super
|
|
20
|
+
subclass.instance_variable_set(:@around_join_callbacks, around_join_callbacks.dup)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
def apply_configuration(chat, **)
|
|
24
|
+
super.tap { around_join_callbacks.each { |callback| chat.around_join(&callback) } }
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
|
28
|
+
end
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
class Agent
|
|
5
|
+
# Decides what the Agent::model macro's options apply to. They belong to
|
|
6
|
+
# the declared model, so a caller's +model:+ replaces them along with it,
|
|
7
|
+
# and a record on another model does not take them. A record being created
|
|
8
|
+
# knows what its caller passed; a later one is judged by its saved model.
|
|
9
|
+
module ModelOptions # :nodoc:
|
|
10
|
+
DECLARED_WITH_MODEL = %i[provider protocol assume_model_exists].freeze
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def resolved_chat_kwargs(inputs:, options:)
|
|
15
|
+
replaced = options.key?(:model) && chat_kwargs.key?(:model)
|
|
16
|
+
return chat_kwargs.except(*DECLARED_WITH_MODEL).merge(options) if replaced
|
|
17
|
+
return chat_kwargs.merge(options) unless chat_kwargs[:model].is_a?(Proc)
|
|
18
|
+
|
|
19
|
+
chat_kwargs.merge(model: evaluate(chat_kwargs[:model], runtime_context(chat: nil, inputs:))).merge(options)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def apply_chat_options(chat, options = {})
|
|
23
|
+
return if options.key?(:model) || !rails_chat_record?(chat) || !declared_model_on?(chat)
|
|
24
|
+
|
|
25
|
+
declared = chat_kwargs.except(*options.keys)
|
|
26
|
+
chat.assume_model_exists = declared[:assume_model_exists] if declared.key?(:assume_model_exists)
|
|
27
|
+
chat.protocol ||= declared[:protocol] if declared.key?(:protocol)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def declared_model_on?(record)
|
|
31
|
+
return true unless record.model_id
|
|
32
|
+
|
|
33
|
+
declared_provider_on?(record) && declared_id_on?(record)
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def declared_provider_on?(record)
|
|
37
|
+
declared = chat_kwargs[:provider]
|
|
38
|
+
|
|
39
|
+
declared.nil? || record.provider.nil? || declared.to_s == record.provider.to_s
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def declared_id_on?(record)
|
|
43
|
+
declared = chat_kwargs[:model]
|
|
44
|
+
declared = declared.id if declared.is_a?(Model)
|
|
45
|
+
return true if declared.nil? || declared.is_a?(Proc) || declared == record.model_id
|
|
46
|
+
|
|
47
|
+
provider = record.provider || chat_kwargs[:provider]
|
|
48
|
+
return true if Models::Aliases.resolve(declared, provider) == record.model_id
|
|
49
|
+
|
|
50
|
+
Models.find(declared, provider:).id == record.model_id
|
|
51
|
+
rescue ModelNotFoundError, ConfigurationError
|
|
52
|
+
false
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
class Agent
|
|
5
|
+
# Applies the Agent::owner macro's owner to every chat an agent builds
|
|
6
|
+
# or finds, with Chat#with_owner, and hands it to subclasses. The owner
|
|
7
|
+
# is set once the chat has its context, and before the instructions
|
|
8
|
+
# and tools that may read it.
|
|
9
|
+
module Ownership # :nodoc:
|
|
10
|
+
def inherited(subclass)
|
|
11
|
+
super
|
|
12
|
+
subclass.instance_variable_set(:@owner, @owner)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
private
|
|
16
|
+
|
|
17
|
+
def apply_context(chat, runtime, **)
|
|
18
|
+
super.tap { chat.with_owner(evaluate(owner, runtime)) unless owner.nil? }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
class Agent
|
|
5
|
+
# The Agent::description and Agent::to_tool macros, which let an agent
|
|
6
|
+
# class go wherever tools go, as an AgentTool.
|
|
7
|
+
module ToolConversion # :nodoc:
|
|
8
|
+
def description(text = nil)
|
|
9
|
+
return @description unless text
|
|
10
|
+
|
|
11
|
+
@description = text
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
def to_tool(**inputs)
|
|
15
|
+
AgentTool.for(self, inputs).new(inputs)
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|
|
19
|
+
end
|