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
|
@@ -0,0 +1,390 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module RubyLLM
|
|
6
|
+
# A question a tool call waits on until a person answers it: the
|
|
7
|
+
# approval a tool declared with Tool.requires_approval asks before it
|
|
8
|
+
# runs, a question the tool asks itself with Tool#ask, or one an MCP
|
|
9
|
+
# server asks while it works on the call. Chat#pending_questions returns
|
|
10
|
+
# the questions a chat waits on.
|
|
11
|
+
#
|
|
12
|
+
# question = chat.pending_questions.first
|
|
13
|
+
# question.message # => "Window or aisle?"
|
|
14
|
+
# question.fields.first.choices # => ["window", "aisle"]
|
|
15
|
+
# question.answer "window"
|
|
16
|
+
# chat.complete
|
|
17
|
+
#
|
|
18
|
+
# An approval asks whether its call may run, so it is answered yes or no:
|
|
19
|
+
#
|
|
20
|
+
# question.approval? # => true
|
|
21
|
+
# question.tool_call.name # => "issue_refund"
|
|
22
|
+
# question.approve # or question.decline("Not on this account")
|
|
23
|
+
#
|
|
24
|
+
# A server can also ask the person to visit a page, such as to connect an
|
|
25
|
+
# account: #url? is +true+, and answering says they agreed to go. Before
|
|
26
|
+
# a server's question reaches the chat, MCP.before_input_request
|
|
27
|
+
# callbacks can answer it.
|
|
28
|
+
class Question
|
|
29
|
+
include Support::Inspectable
|
|
30
|
+
|
|
31
|
+
ACCEPTED = { 'action' => 'accept' }.freeze # :nodoc:
|
|
32
|
+
DECLINED = { 'action' => 'decline' }.freeze # :nodoc:
|
|
33
|
+
BOOLEANS = { 'true' => true, '1' => true, 'yes' => true, 'on' => true,
|
|
34
|
+
'false' => false, '0' => false, 'no' => false, 'off' => false }.freeze
|
|
35
|
+
TYPES = { 'string' => 'text', 'integer' => 'a whole number', 'number' => 'a number',
|
|
36
|
+
'boolean' => 'true or false', 'array' => 'a list' }.freeze
|
|
37
|
+
CLASSES = { 'string' => [String], 'integer' => [Integer], 'number' => [Numeric],
|
|
38
|
+
'boolean' => [TrueClass, FalseClass], 'array' => [Array] }.freeze
|
|
39
|
+
private_constant :BOOLEANS, :TYPES, :CLASSES
|
|
40
|
+
|
|
41
|
+
# A value a question asks for.
|
|
42
|
+
Field = Struct.new(:name, :type, :title, :description, :required, :choices, :default, keyword_init: true) do
|
|
43
|
+
# Returns whether the question needs this value.
|
|
44
|
+
def required?
|
|
45
|
+
required == true
|
|
46
|
+
end
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# The question, or +nil+ for an approval.
|
|
50
|
+
attr_reader :message
|
|
51
|
+
|
|
52
|
+
# The page the question asks the person to visit, or +nil+. When an MCP
|
|
53
|
+
# server asks, show the whole address before the person goes, since
|
|
54
|
+
# the server chose it. RubyLLM declines a server's request to visit
|
|
55
|
+
# anything but an +http+ or +https+ address (see MCP.input_requests).
|
|
56
|
+
attr_reader :url
|
|
57
|
+
|
|
58
|
+
# The values the question asks for, as Field objects: one for a question
|
|
59
|
+
# Tool#ask asks without a block, none for an approval or a page to visit.
|
|
60
|
+
attr_reader :fields
|
|
61
|
+
|
|
62
|
+
# The ToolCall that waits on the question, when it came from a chat.
|
|
63
|
+
attr_reader :tool_call
|
|
64
|
+
|
|
65
|
+
attr_reader :key, :response # :nodoc:
|
|
66
|
+
|
|
67
|
+
class << self
|
|
68
|
+
# Returns the question Tool#ask asks as its +index+th: one that takes
|
|
69
|
+
# free text or one of +choices+, a visit to +url+, or the fields
|
|
70
|
+
# +schema+ describes.
|
|
71
|
+
def asked(index, message, choices: nil, url: nil, schema: nil) # :nodoc:
|
|
72
|
+
params = { 'mode' => url ? 'url' : 'form', 'message' => message.to_s }
|
|
73
|
+
if url
|
|
74
|
+
params['url'] = url.to_s
|
|
75
|
+
else
|
|
76
|
+
params['requestedSchema'] = schema || answer_schema(choices)
|
|
77
|
+
end
|
|
78
|
+
new("ask_#{index}", params)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
# Returns the approval +chat+ waits on before it runs +tool_call+.
|
|
82
|
+
def approval(tool_call, chat:) # :nodoc:
|
|
83
|
+
new('approval', {}, id: tool_call.id, tool_call:, chat:, approval: true)
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def from_h(data, tool_call: nil, chat: nil) # :nodoc:
|
|
87
|
+
new(data['key'], data['params'], id: data['id'] || data['key'], response: data['response'], tool_call:, chat:)
|
|
88
|
+
end
|
|
89
|
+
|
|
90
|
+
# Returns the response that declines a question, keeping +note+ for
|
|
91
|
+
# the model unless it is blank.
|
|
92
|
+
def declined(note) # :nodoc:
|
|
93
|
+
note = note.to_s.strip unless note.is_a?(Hash)
|
|
94
|
+
note.empty? ? DECLINED : DECLINED.merge('note' => note)
|
|
95
|
+
end
|
|
96
|
+
|
|
97
|
+
# Returns the call +decision+ approves: the ToolCall of an approval,
|
|
98
|
+
# or +decision+ itself, such as a ToolCall or its id. Raises
|
|
99
|
+
# ArgumentError for any other Question, which takes +instead+.
|
|
100
|
+
def decided_call(decision, instead) # :nodoc:
|
|
101
|
+
return decision unless decision.is_a?(Question)
|
|
102
|
+
raise ArgumentError, "This question is not an approval: #{instead} it instead." unless decision.approval?
|
|
103
|
+
|
|
104
|
+
decision.tool_call
|
|
105
|
+
end
|
|
106
|
+
|
|
107
|
+
private
|
|
108
|
+
|
|
109
|
+
def answer_schema(choices)
|
|
110
|
+
raise ArgumentError, 'choices: needs at least one choice.' if choices && Array(choices).empty?
|
|
111
|
+
|
|
112
|
+
answer = { 'type' => 'string', 'enum' => choices&.map(&:to_s) }.compact
|
|
113
|
+
{ 'type' => 'object', 'properties' => { 'answer' => answer }, 'required' => ['answer'] }
|
|
114
|
+
end
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def initialize(key, params, id: SecureRandom.hex(8), response: nil, tool_call: nil, chat: nil, # :nodoc:
|
|
118
|
+
approval: false)
|
|
119
|
+
@key = key
|
|
120
|
+
@params = params
|
|
121
|
+
@nonce = id
|
|
122
|
+
@response = response
|
|
123
|
+
@tool_call = tool_call
|
|
124
|
+
@chat = chat
|
|
125
|
+
@approval = approval
|
|
126
|
+
@message = params['message']
|
|
127
|
+
@url = params['url'] if params['mode'] == 'url'
|
|
128
|
+
@fields = fields_from(params['requestedSchema'] || {})
|
|
129
|
+
end
|
|
130
|
+
|
|
131
|
+
# Returns a String that identifies the question among those of its
|
|
132
|
+
# chat. Chat#answer and the other methods that take a question take its
|
|
133
|
+
# id too, so a form can send it. An approval has its tool call's id.
|
|
134
|
+
def id
|
|
135
|
+
return @nonce if tool_call.nil? || approval?
|
|
136
|
+
|
|
137
|
+
"#{tool_call.id}/#{@nonce}"
|
|
138
|
+
end
|
|
139
|
+
|
|
140
|
+
# Returns whether the question asks whether its call may run: the
|
|
141
|
+
# approval of a tool declared with Tool.requires_approval. Answer it
|
|
142
|
+
# with #approve or #decline.
|
|
143
|
+
def approval?
|
|
144
|
+
@approval
|
|
145
|
+
end
|
|
146
|
+
|
|
147
|
+
# Returns whether the question asks the person to visit #url.
|
|
148
|
+
def url?
|
|
149
|
+
!url.nil?
|
|
150
|
+
end
|
|
151
|
+
|
|
152
|
+
# Returns whether the question asks for #fields.
|
|
153
|
+
def form?
|
|
154
|
+
!url? && !approval?
|
|
155
|
+
end
|
|
156
|
+
|
|
157
|
+
# Returns whether the question has been answered or declined.
|
|
158
|
+
def answered?
|
|
159
|
+
!response.nil?
|
|
160
|
+
end
|
|
161
|
+
|
|
162
|
+
# Answers the question and returns +self+. A question with one field,
|
|
163
|
+
# as Tool#ask asks without a block, takes its value. One with several
|
|
164
|
+
# takes them by name, as keywords or a Hash, and a field left out takes
|
|
165
|
+
# the default the question gives it. An approval or a page to visit
|
|
166
|
+
# takes no value: answering an approval approves its call, and
|
|
167
|
+
# answering a page says the person agreed to visit it.
|
|
168
|
+
#
|
|
169
|
+
# question.answer "window"
|
|
170
|
+
# question.answer environment: "staging", reason: "Release 2.2"
|
|
171
|
+
#
|
|
172
|
+
# A question from a chat records the answer there, as Chat#answer
|
|
173
|
+
# does, and the next Chat#complete carries the call on.
|
|
174
|
+
#
|
|
175
|
+
# Strings become the integers, numbers, and booleans the fields ask
|
|
176
|
+
# for, the way a form sends them, a Symbol becomes text, text and the
|
|
177
|
+
# items of a list lose the spaces around them, and a blank String
|
|
178
|
+
# counts as left out. The ActionController::Parameters a form sends
|
|
179
|
+
# for several fields work as a Hash, keeping only the fields the
|
|
180
|
+
# question asks for. Raises ArgumentError, with a sentence to show the
|
|
181
|
+
# person, for a value the question does not ask for, a choice it does
|
|
182
|
+
# not offer, a value of the wrong type, or a required value left out.
|
|
183
|
+
def answer(value = nil, **values)
|
|
184
|
+
if @chat
|
|
185
|
+
@chat.answer(self, value, **values)
|
|
186
|
+
else
|
|
187
|
+
respond(response_to(value, values))
|
|
188
|
+
end
|
|
189
|
+
self
|
|
190
|
+
end
|
|
191
|
+
|
|
192
|
+
# Declines the question and returns +self+. Declining an approval
|
|
193
|
+
# keeps its call from running. A +note+ tells the model why: an
|
|
194
|
+
# approval's call gets it as its result, in place of a plain refusal,
|
|
195
|
+
# and a tool that asked gets +nil+ from Tool#ask, and the model gets
|
|
196
|
+
# the note after the tool's result. A Hash +note+ is an approval's
|
|
197
|
+
# whole result, as given, and follows a tool's result as JSON. A question from a chat records it
|
|
198
|
+
# there, as Chat#decline does.
|
|
199
|
+
#
|
|
200
|
+
# question.decline
|
|
201
|
+
# question.decline("The order shipped already")
|
|
202
|
+
# question.decline(error: "Denied", reason: "Wrong account")
|
|
203
|
+
#
|
|
204
|
+
def decline(note = nil)
|
|
205
|
+
if @chat
|
|
206
|
+
@chat.decline(self, note)
|
|
207
|
+
else
|
|
208
|
+
respond(self.class.declined(note))
|
|
209
|
+
end
|
|
210
|
+
self
|
|
211
|
+
end
|
|
212
|
+
|
|
213
|
+
# Approves the call an approval asks about, as Chat#approve does, and
|
|
214
|
+
# returns +self+. With <tt>remember: true</tt>, later calls to the same
|
|
215
|
+
# tool run without asking for the rest of the chat.
|
|
216
|
+
#
|
|
217
|
+
# Raises ArgumentError for a question that is not an approval.
|
|
218
|
+
def approve(remember: false)
|
|
219
|
+
self.class.decided_call(self, 'answer')
|
|
220
|
+
@chat ? @chat.approve(self, remember:) : respond(ACCEPTED)
|
|
221
|
+
self
|
|
222
|
+
end
|
|
223
|
+
|
|
224
|
+
# Returns the response an answer of +value+ and +values+ makes, as
|
|
225
|
+
# #answer describes, raising ArgumentError for one it can't take.
|
|
226
|
+
def response_to(value, values) # :nodoc:
|
|
227
|
+
return response_without(value, values) unless form?
|
|
228
|
+
|
|
229
|
+
given = given_values(value, values)
|
|
230
|
+
unknown = given.keys - fields.map { |field| field.name.to_s }
|
|
231
|
+
raise ArgumentError, "The question does not ask for #{listed(unknown, 'or')}." if unknown.any?
|
|
232
|
+
|
|
233
|
+
ACCEPTED.merge('content' => content_from(given))
|
|
234
|
+
end
|
|
235
|
+
|
|
236
|
+
# Returns what Tool#ask returns for the question's answer: +nil+ when
|
|
237
|
+
# it was declined, +true+ for a page to visit, the values by name when
|
|
238
|
+
# +by_name+ (a block declared the fields), or else the one value.
|
|
239
|
+
def asked_answer(by_name) # :nodoc:
|
|
240
|
+
return unless response && response['action'] == 'accept'
|
|
241
|
+
return true if url?
|
|
242
|
+
|
|
243
|
+
content = response['content'].to_h
|
|
244
|
+
by_name ? content.transform_keys(&:to_sym) : content['answer']
|
|
245
|
+
end
|
|
246
|
+
|
|
247
|
+
# Records +response+ as the question's own, and returns +self+.
|
|
248
|
+
def respond(response) # :nodoc:
|
|
249
|
+
@response = response
|
|
250
|
+
self
|
|
251
|
+
end
|
|
252
|
+
|
|
253
|
+
# Returns +input+, the state the question's call paused on, with
|
|
254
|
+
# +response+ as its answer while it still waits for one.
|
|
255
|
+
def answered_input(input, response) # :nodoc:
|
|
256
|
+
requests = input['requests'].map do |data|
|
|
257
|
+
waiting = data['response'].nil? && self.class.from_h(data, tool_call:).id == id
|
|
258
|
+
waiting ? data.merge('response' => response) : data
|
|
259
|
+
end
|
|
260
|
+
input.merge('requests' => requests)
|
|
261
|
+
end
|
|
262
|
+
|
|
263
|
+
def to_h # :nodoc:
|
|
264
|
+
{ 'id' => @nonce, 'key' => key, 'params' => @params, 'response' => response }.compact
|
|
265
|
+
end
|
|
266
|
+
|
|
267
|
+
private
|
|
268
|
+
|
|
269
|
+
def response_without(value, values)
|
|
270
|
+
return ACCEPTED if blank?(value) && values.empty?
|
|
271
|
+
|
|
272
|
+
raise ArgumentError, "#{approval? ? 'An approval' : 'A page to visit'} takes no answer."
|
|
273
|
+
end
|
|
274
|
+
|
|
275
|
+
def given_values(value, values)
|
|
276
|
+
value = permitted(value) if value.respond_to?(:permit)
|
|
277
|
+
given = case value
|
|
278
|
+
when nil, Hash then value.to_h
|
|
279
|
+
else
|
|
280
|
+
unless fields.one?
|
|
281
|
+
raise ArgumentError, "The question asks for #{listed(fields.map(&:name), 'and')}: give each by name."
|
|
282
|
+
end
|
|
283
|
+
|
|
284
|
+
{ fields.first.name => value }
|
|
285
|
+
end
|
|
286
|
+
given.merge(values).transform_keys(&:to_s)
|
|
287
|
+
end
|
|
288
|
+
|
|
289
|
+
# The values of +params+, such as the ActionController::Parameters a
|
|
290
|
+
# form sends, that the question asks for.
|
|
291
|
+
def permitted(params)
|
|
292
|
+
params.permit(*fields.map { |field| field.type == 'array' ? { field.name => [] } : field.name }).to_h
|
|
293
|
+
end
|
|
294
|
+
|
|
295
|
+
def content_from(given)
|
|
296
|
+
fields.each_with_object({}) do |field, content|
|
|
297
|
+
answer = value_for(field, given[field.name.to_s])
|
|
298
|
+
answer = field.default if answer.nil?
|
|
299
|
+
raise ArgumentError, "#{label(field)} needs an answer." if answer.nil? && field.required?
|
|
300
|
+
|
|
301
|
+
content[field.name.to_s] = answer unless answer.nil?
|
|
302
|
+
end
|
|
303
|
+
end
|
|
304
|
+
|
|
305
|
+
def value_for(field, value)
|
|
306
|
+
value = trimmed(value)
|
|
307
|
+
return if blank?(value)
|
|
308
|
+
|
|
309
|
+
typed(field, value).tap { |typed| offered(field, typed) }
|
|
310
|
+
end
|
|
311
|
+
|
|
312
|
+
def typed(field, value)
|
|
313
|
+
value = parsed(field, value) if value.is_a?(String)
|
|
314
|
+
value = value.to_s if value.is_a?(Symbol) && field.type == 'string'
|
|
315
|
+
return value if CLASSES.fetch(field.type, [Object]).any? { |kind| value.is_a?(kind) }
|
|
316
|
+
|
|
317
|
+
raise ArgumentError, "#{label(field)} takes #{TYPES.fetch(field.type)}."
|
|
318
|
+
end
|
|
319
|
+
|
|
320
|
+
def parsed(field, text)
|
|
321
|
+
case field.type
|
|
322
|
+
when 'integer' then Integer(text, 10)
|
|
323
|
+
when 'number' then Float(text)
|
|
324
|
+
when 'boolean' then BOOLEANS.fetch(text.downcase, text)
|
|
325
|
+
when 'array' then [text]
|
|
326
|
+
else text
|
|
327
|
+
end
|
|
328
|
+
rescue ArgumentError
|
|
329
|
+
text
|
|
330
|
+
end
|
|
331
|
+
|
|
332
|
+
def offered(field, value)
|
|
333
|
+
return unless field.choices
|
|
334
|
+
|
|
335
|
+
Array(value).each do |choice|
|
|
336
|
+
next if field.choices.include?(choice)
|
|
337
|
+
|
|
338
|
+
raise ArgumentError, "#{choice.inspect} is not one of the choices: #{listed(field.choices, 'or')}."
|
|
339
|
+
end
|
|
340
|
+
end
|
|
341
|
+
|
|
342
|
+
def trimmed(value)
|
|
343
|
+
case value
|
|
344
|
+
when String then value.strip
|
|
345
|
+
when Array then value.map { |item| trimmed(item) }.reject { |item| blank?(item) }
|
|
346
|
+
else value
|
|
347
|
+
end
|
|
348
|
+
end
|
|
349
|
+
|
|
350
|
+
def blank?(value)
|
|
351
|
+
value.nil? || (value.respond_to?(:empty?) && value.empty?) || (value.is_a?(String) && value.strip.empty?)
|
|
352
|
+
end
|
|
353
|
+
|
|
354
|
+
def label(field)
|
|
355
|
+
field.title || (fields.one? ? 'The question' : humanized(field.name))
|
|
356
|
+
end
|
|
357
|
+
|
|
358
|
+
def humanized(name)
|
|
359
|
+
words = name.to_s.gsub(/([a-z\d])([A-Z])|([A-Z])([A-Z][a-z])/, '\\1\\3 \\2\\4').split(/[\s_-]+/)
|
|
360
|
+
text = words.map { |word| word == word.upcase ? word : word.downcase }.join(' ')
|
|
361
|
+
text.sub(/\A./, &:upcase)
|
|
362
|
+
end
|
|
363
|
+
|
|
364
|
+
def listed(words, conjunction)
|
|
365
|
+
return words.join(" #{conjunction} ") if words.size < 3
|
|
366
|
+
|
|
367
|
+
"#{words[0..-2].join(', ')}, #{conjunction} #{words.last}"
|
|
368
|
+
end
|
|
369
|
+
|
|
370
|
+
def fields_from(schema)
|
|
371
|
+
required = Array(schema['required'])
|
|
372
|
+
schema.fetch('properties', {}).map do |name, property|
|
|
373
|
+
Field.new(name: name.to_sym, type: property['type'], title: property['title'],
|
|
374
|
+
description: property['description'], required: required.include?(name),
|
|
375
|
+
choices: choices(property), default: property['default'])
|
|
376
|
+
end
|
|
377
|
+
end
|
|
378
|
+
|
|
379
|
+
def choices(property)
|
|
380
|
+
options = property['enum'] || property.dig('items', 'enum') ||
|
|
381
|
+
Array(property['oneOf'] || property.dig('items', 'anyOf')).map { |o| o['const'] }
|
|
382
|
+
options unless options.empty?
|
|
383
|
+
end
|
|
384
|
+
|
|
385
|
+
def inspect_attributes
|
|
386
|
+
{ id:, message:, url:, approval: (true if approval?), tool_call: (tool_call.name if approval?),
|
|
387
|
+
fields: fields.map(&:name) }
|
|
388
|
+
end
|
|
389
|
+
end
|
|
390
|
+
end
|
|
@@ -0,0 +1,74 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'securerandom'
|
|
4
|
+
|
|
5
|
+
module RubyLLM
|
|
6
|
+
# A user message waiting to join a conversation, from Chat#queue. The
|
|
7
|
+
# conversation loop takes it between two of its moves, where it becomes
|
|
8
|
+
# an ordinary user Message.
|
|
9
|
+
#
|
|
10
|
+
# queued = chat.queue "Use the EU file instead"
|
|
11
|
+
# chat.queued_messages # => [#<RubyLLM::QueuedMessage ...>]
|
|
12
|
+
# queued.withdraw # => true
|
|
13
|
+
#
|
|
14
|
+
class QueuedMessage
|
|
15
|
+
include Support::Inspectable
|
|
16
|
+
|
|
17
|
+
# A String that identifies this message among the chat's queued ones.
|
|
18
|
+
attr_reader :id
|
|
19
|
+
|
|
20
|
+
# The text of the message, as given to Chat#queue.
|
|
21
|
+
attr_reader :content
|
|
22
|
+
|
|
23
|
+
# The attachments given with +with:+, or +nil+.
|
|
24
|
+
attr_reader :attachments
|
|
25
|
+
|
|
26
|
+
# Whoever queued the message, as given with +sender:+, or +nil+.
|
|
27
|
+
# RubyLLM stores it and hands it back without interpreting it.
|
|
28
|
+
attr_reader :sender
|
|
29
|
+
|
|
30
|
+
# The Time the message was queued.
|
|
31
|
+
attr_reader :queued_at
|
|
32
|
+
|
|
33
|
+
def initialize(content:, attachments: nil, steer: true, sender: nil, queue: nil) # :nodoc:
|
|
34
|
+
@id = SecureRandom.uuid
|
|
35
|
+
@content = content
|
|
36
|
+
@attachments = attachments
|
|
37
|
+
@steer = steer
|
|
38
|
+
@sender = sender
|
|
39
|
+
@queue = queue
|
|
40
|
+
@queued_at = Time.now
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
# Returns whether the message steers the turn that is running, joining
|
|
44
|
+
# it at the next step, rather than waiting for the turn to end.
|
|
45
|
+
def steer?
|
|
46
|
+
@steer
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
# Takes the message out of the queue of the chat it was queued on, as
|
|
50
|
+
# Chat#withdraw does. Returns +true+, or +false+ when it is no longer
|
|
51
|
+
# queued: the loop took it into the conversation, or it was withdrawn
|
|
52
|
+
# already.
|
|
53
|
+
#
|
|
54
|
+
# queued.withdraw # => true
|
|
55
|
+
#
|
|
56
|
+
def withdraw
|
|
57
|
+
!@queue&.delete(self).nil?
|
|
58
|
+
end
|
|
59
|
+
|
|
60
|
+
# Returns a copy of the message that a copied chat's +queue+ holds, so
|
|
61
|
+
# #withdraw takes it out of that chat's queue.
|
|
62
|
+
def requeue(queue) # :nodoc:
|
|
63
|
+
copy = dup
|
|
64
|
+
copy.instance_variable_set(:@queue, queue)
|
|
65
|
+
copy
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
private
|
|
69
|
+
|
|
70
|
+
def inspect_attributes
|
|
71
|
+
{ id:, content:, steer: steer? }
|
|
72
|
+
end
|
|
73
|
+
end
|
|
74
|
+
end
|
data/lib/ruby_llm/railtie.rb
CHANGED
|
@@ -3,6 +3,9 @@
|
|
|
3
3
|
if defined?(Rails::Railtie)
|
|
4
4
|
module RubyLLM
|
|
5
5
|
class Railtie < Rails::Railtie # :nodoc:
|
|
6
|
+
ROOT = File.expand_path('../..', __dir__)
|
|
7
|
+
JAVASCRIPTS = %w[ruby_llm/chat_message_controller.js ruby_llm/marked.js ruby_llm/dompurify.js].freeze
|
|
8
|
+
|
|
6
9
|
initializer 'ruby_llm.inflections' do
|
|
7
10
|
ActiveSupport::Inflector.inflections(:en) do |inflect|
|
|
8
11
|
inflect.acronym 'RubyLLM'
|
|
@@ -13,6 +16,10 @@ if defined?(Rails::Railtie)
|
|
|
13
16
|
RubyLLM.config.instrumenter ||= ActiveSupport::Notifications
|
|
14
17
|
end
|
|
15
18
|
|
|
19
|
+
initializer 'ruby_llm.skills', before: :setup_main_autoloader do
|
|
20
|
+
Rails.autoloaders.main.ignore(*RubyLLM::Skill.roots)
|
|
21
|
+
end
|
|
22
|
+
|
|
16
23
|
initializer 'ruby_llm.active_record' do
|
|
17
24
|
ActiveSupport.on_load :active_record do
|
|
18
25
|
require 'ruby_llm/active_record/record'
|
|
@@ -23,8 +30,15 @@ if defined?(Rails::Railtie)
|
|
|
23
30
|
require 'ruby_llm/active_record/batch'
|
|
24
31
|
require 'ruby_llm/active_record/mcp_credential'
|
|
25
32
|
require 'ruby_llm/active_record/provider_file'
|
|
33
|
+
require 'ruby_llm/active_record/queued_message'
|
|
34
|
+
require 'ruby_llm/active_record/chat_file'
|
|
35
|
+
require 'ruby_llm/active_record/file_store'
|
|
26
36
|
require 'ruby_llm/active_record/chat_methods'
|
|
37
|
+
require 'ruby_llm/active_record/chat_files'
|
|
38
|
+
require 'ruby_llm/active_record/chat_column_guard'
|
|
27
39
|
require 'ruby_llm/active_record/message_methods'
|
|
40
|
+
require 'ruby_llm/active_record/message_broadcasting'
|
|
41
|
+
require 'ruby_llm/active_record/chunk_methods'
|
|
28
42
|
|
|
29
43
|
require 'ruby_llm/active_record/acts_as'
|
|
30
44
|
::ActiveRecord::Base.include RubyLLM::ActiveRecord::ActsAs
|
|
@@ -33,12 +47,41 @@ if defined?(Rails::Railtie)
|
|
|
33
47
|
end
|
|
34
48
|
end
|
|
35
49
|
|
|
50
|
+
initializer 'ruby_llm.solid_queue' do
|
|
51
|
+
ActiveSupport.on_load :solid_queue do
|
|
52
|
+
require 'ruby_llm/active_record/solid_queue_shutdown'
|
|
53
|
+
RubyLLM::ActiveRecord::SolidQueueShutdown.install(self)
|
|
54
|
+
end
|
|
55
|
+
end
|
|
56
|
+
|
|
36
57
|
initializer 'ruby_llm.active_storage' do
|
|
37
58
|
ActiveSupport.on_load :active_storage_blob do
|
|
38
59
|
after_destroy { RubyLLM::ActiveRecord::ProviderFile.forget_blob(self) }
|
|
39
60
|
end
|
|
40
61
|
end
|
|
41
62
|
|
|
63
|
+
initializer 'ruby_llm.helpers' do
|
|
64
|
+
ActiveSupport.on_load :action_view do
|
|
65
|
+
require 'ruby_llm/active_record/chat_helper'
|
|
66
|
+
include RubyLLM::ActiveRecord::ChatHelper
|
|
67
|
+
end
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
initializer 'ruby_llm.assets' do |app|
|
|
71
|
+
next unless app.config.respond_to?(:assets)
|
|
72
|
+
|
|
73
|
+
app.config.assets.paths << File.join(ROOT, 'app/assets/javascripts')
|
|
74
|
+
app.config.assets.precompile += JAVASCRIPTS
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
initializer 'ruby_llm.importmap', before: 'importmap' do |app|
|
|
78
|
+
app.config.importmap.paths << File.join(ROOT, 'config/importmap.rb') if app.config.respond_to?(:importmap)
|
|
79
|
+
end
|
|
80
|
+
|
|
81
|
+
config.after_initialize do
|
|
82
|
+
RubyLLM::Sandbox.verify(RubyLLM.sandbox) unless ENV['SECRET_KEY_BASE_DUMMY']
|
|
83
|
+
end
|
|
84
|
+
|
|
42
85
|
rake_tasks do
|
|
43
86
|
load 'tasks/ruby_llm.rake'
|
|
44
87
|
end
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module RubyLLM
|
|
4
|
-
class RequestShape
|
|
4
|
+
class RequestShape # :nodoc: all
|
|
5
5
|
# A Part describes one piece of a turn by its kind and size, never by
|
|
6
6
|
# its content.
|
|
7
7
|
#
|
|
@@ -12,7 +12,7 @@ module RubyLLM
|
|
|
12
12
|
# part.signed? # => true
|
|
13
13
|
# part.to_s # => "call find_tasks (args 42 chars), signed"
|
|
14
14
|
#
|
|
15
|
-
class Part
|
|
15
|
+
class Part # :nodoc:
|
|
16
16
|
include Support::Inspectable
|
|
17
17
|
|
|
18
18
|
TEXT_KINDS = %i[text thinking].freeze
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module RubyLLM
|
|
4
|
-
class RequestShape
|
|
4
|
+
class RequestShape # :nodoc: all
|
|
5
5
|
# A Problem is something in a request that providers are known to
|
|
6
6
|
# reject, found by the protocol that rendered it: a part with no data, a
|
|
7
7
|
# tool round whose calls and results do not pair up, a step that lacks
|
|
@@ -12,7 +12,7 @@ module RubyLLM
|
|
|
12
12
|
# problem.part # => 0
|
|
13
13
|
# problem.to_s # => "problem at #3, part 0: thinking part carries no data"
|
|
14
14
|
#
|
|
15
|
-
class Problem
|
|
15
|
+
class Problem # :nodoc:
|
|
16
16
|
include Support::Inspectable
|
|
17
17
|
|
|
18
18
|
# What is wrong: +:part_without_data+, +:empty_turn+,
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module RubyLLM
|
|
4
|
-
class RequestShape
|
|
4
|
+
class RequestShape # :nodoc: all
|
|
5
5
|
# A ToolRound describes a model turn that called tools and the results
|
|
6
6
|
# the request sends back before the next model turn: how many of each,
|
|
7
7
|
# and whether they pair up the way the provider matches them.
|
|
@@ -12,7 +12,7 @@ module RubyLLM
|
|
|
12
12
|
# round.paired? # => false
|
|
13
13
|
# round.to_s # => "tool round at #1: 2 calls, 1 result, not paired"
|
|
14
14
|
#
|
|
15
|
-
class ToolRound
|
|
15
|
+
class ToolRound # :nodoc:
|
|
16
16
|
include Support::Inspectable
|
|
17
17
|
|
|
18
18
|
# The index of the turn that made the calls.
|
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
module RubyLLM
|
|
4
|
-
class RequestShape
|
|
4
|
+
class RequestShape # :nodoc: all
|
|
5
5
|
# A Turn describes one entry of a request's conversation: a message, or
|
|
6
6
|
# one item where the provider sends a model's reasoning and tool calls as
|
|
7
7
|
# items of their own.
|
|
@@ -11,7 +11,7 @@ module RubyLLM
|
|
|
11
11
|
# turn.role # => "user"
|
|
12
12
|
# turn.to_s # => "#4 user: text (31 chars)"
|
|
13
13
|
#
|
|
14
|
-
class Turn
|
|
14
|
+
class Turn # :nodoc:
|
|
15
15
|
include Support::Inspectable
|
|
16
16
|
|
|
17
17
|
# The turn's 0-indexed position in the request, the position a
|
|
@@ -20,7 +20,7 @@ module RubyLLM
|
|
|
20
20
|
# file data, or setting values beyond thinking settings, so you can log it
|
|
21
21
|
# and send it to an error tracker. A long conversation keeps its first 10
|
|
22
22
|
# and last 50 turns.
|
|
23
|
-
class RequestShape
|
|
23
|
+
class RequestShape # :nodoc: all
|
|
24
24
|
include Support::Inspectable
|
|
25
25
|
|
|
26
26
|
# The provider's slug, such as <tt>"gemini"</tt>.
|