ruby_llm 2.1.0 → 2.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +162 -120
- data/app/assets/javascripts/ruby_llm/chat_message_controller.js +163 -0
- data/app/assets/javascripts/ruby_llm/dompurify.js +9 -0
- data/app/assets/javascripts/ruby_llm/marked.js +71 -0
- data/config/importmap.rb +5 -0
- data/lib/generators/ruby_llm/agent/agent_generator.rb +11 -3
- data/lib/generators/ruby_llm/agent/templates/agent.rb.tt +6 -3
- data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +201 -55
- data/lib/generators/ruby_llm/chat_ui/templates/agents/chat_agent.rb.tt +15 -0
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/answers_controller.rb.tt +31 -0
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +16 -5
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +27 -9
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/controllers/queued_messages_controller.rb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/javascript/chat_message_controller.js +1 -0
- data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +5 -8
- data/lib/generators/ruby_llm/chat_ui/templates/jobs/resume_interrupted_chats_job.rb.tt +7 -0
- data/lib/generators/ruby_llm/chat_ui/templates/migrations/add_reply_error_migration.rb.tt +5 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_files.html.erb.tt +15 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +5 -7
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_model_select.html.erb.tt +7 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +32 -3
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +17 -2
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_attachments.html.erb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_error.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_form.html.erb.tt +28 -18
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_queued_message.html.erb.tt +18 -0
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +4 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +4 -2
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt +4 -2
- data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/questions/_question.html.erb.tt +101 -0
- data/lib/generators/ruby_llm/chat_ui/templates/test/agents/chat_agent_test.rb.tt +28 -0
- data/lib/generators/ruby_llm/chat_ui/templates/test/application_system_test_case.rb.tt +5 -0
- data/lib/generators/ruby_llm/chat_ui/templates/test/system/chats_test.rb.tt +240 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/answers/create.turbo_stream.erb.tt +6 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_files.html.erb.tt +15 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +5 -7
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_model_select.html.erb.tt +7 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_questions.html.erb.tt +13 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +29 -3
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +16 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_attachments.html.erb.tt +14 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_error.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +26 -16
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_queued_message.html.erb.tt +8 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_system.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +4 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_user.html.erb.tt +3 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +1 -0
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt +3 -2
- data/lib/generators/ruby_llm/chat_ui/templates/views/questions/_question.html.erb.tt +78 -0
- data/lib/generators/ruby_llm/evaluation/evaluation_generator.rb +43 -0
- data/lib/generators/ruby_llm/evaluation/templates/evaluation.rb.tt +9 -0
- data/lib/generators/ruby_llm/evaluation/templates/evaluation.yml.tt +4 -0
- data/lib/generators/ruby_llm/generator_helpers.rb +116 -3
- data/lib/generators/ruby_llm/install/install_generator.rb +5 -47
- data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +4 -0
- data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +5 -3
- data/lib/generators/ruby_llm/install/templates/create_ruby_llm_records_migration.rb.tt +41 -1
- data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +2 -1
- data/lib/generators/ruby_llm/install/templates/message_model.rb.tt +1 -2
- data/lib/generators/ruby_llm/judge/judge_generator.rb +35 -0
- data/lib/generators/ruby_llm/judge/templates/judge.rb.tt +2 -0
- data/lib/generators/ruby_llm/mcp/mcp_generator.rb +58 -0
- data/lib/generators/ruby_llm/mcp/templates/mcp.rb.tt +7 -0
- data/lib/generators/ruby_llm/provider/scaffold.rb +5 -12
- data/lib/generators/ruby_llm/provider/templates/core/provider.rb.erb +6 -0
- data/lib/generators/ruby_llm/provider/templates/gem/chat_spec.rb.erb +0 -1
- data/lib/generators/ruby_llm/provider/templates/gem/chat_streaming_spec.rb.erb +0 -1
- data/lib/generators/ruby_llm/provider/templates/gem/moderation_spec.rb.erb +1 -1
- data/lib/generators/ruby_llm/sandbox/sandbox_generator.rb +68 -0
- data/lib/generators/ruby_llm/sandbox/templates/sandbox.rb.tt +3 -0
- data/lib/generators/ruby_llm/skill/skill_generator.rb +48 -0
- data/lib/generators/ruby_llm/tool/templates/tailwind/tool_call.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/tool/templates/tailwind/tool_result.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +0 -3
- data/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt +1 -1
- data/lib/generators/ruby_llm/tool/tool_generator.rb +1 -5
- data/lib/generators/ruby_llm/upgrade/templates/upgrade_ruby_llm_to_2_2.rb.tt +164 -0
- data/lib/generators/ruby_llm/upgrade/upgrade_generator.rb +41 -3
- data/lib/ruby_llm/accounting/operation.rb +73 -0
- data/lib/ruby_llm/accounting/usage.rb +74 -24
- data/lib/ruby_llm/active_record/acts_as.rb +78 -11
- data/lib/ruby_llm/active_record/attachment_helpers.rb +10 -4
- data/lib/ruby_llm/active_record/batch.rb +67 -9
- data/lib/ruby_llm/active_record/chat_agent_hooks.rb +17 -0
- data/lib/ruby_llm/active_record/chat_approvals.rb +71 -0
- data/lib/ruby_llm/active_record/chat_cancellation.rb +106 -0
- data/lib/ruby_llm/active_record/chat_column_guard.rb +51 -0
- data/lib/ruby_llm/active_record/chat_file.rb +37 -0
- data/lib/ruby_llm/active_record/chat_files.rb +30 -0
- data/lib/ruby_llm/active_record/chat_helper.rb +73 -0
- data/lib/ruby_llm/active_record/chat_methods.rb +522 -277
- data/lib/ruby_llm/active_record/chat_placeholders.rb +67 -0
- data/lib/ruby_llm/active_record/chat_questions.rb +177 -0
- data/lib/ruby_llm/active_record/chat_queue.rb +120 -0
- data/lib/ruby_llm/active_record/chat_reply_error.rb +35 -0
- data/lib/ruby_llm/active_record/chat_run.rb +51 -0
- data/lib/ruby_llm/active_record/chat_tool_call_state.rb +77 -0
- data/lib/ruby_llm/active_record/chunk_methods.rb +49 -0
- data/lib/ruby_llm/active_record/claim_heartbeat.rb +149 -0
- data/lib/ruby_llm/active_record/file_store.rb +269 -0
- data/lib/ruby_llm/active_record/mcp_credential.rb +24 -9
- data/lib/ruby_llm/active_record/message_broadcasting.rb +85 -0
- data/lib/ruby_llm/active_record/message_methods.rb +83 -11
- data/lib/ruby_llm/active_record/model.rb +37 -9
- data/lib/ruby_llm/active_record/provider_file.rb +21 -4
- data/lib/ruby_llm/active_record/queued_message.rb +50 -0
- data/lib/ruby_llm/active_record/record.rb +3 -1
- data/lib/ruby_llm/active_record/run_claims.rb +608 -0
- data/lib/ruby_llm/active_record/solid_queue_shutdown.rb +33 -0
- data/lib/ruby_llm/active_record/system_instructions.rb +30 -0
- data/lib/ruby_llm/active_record/tool_call.rb +55 -4
- data/lib/ruby_llm/active_record/usage.rb +79 -9
- data/lib/ruby_llm/agent/callbacks.rb +98 -0
- data/lib/ruby_llm/agent/context_budget.rb +26 -0
- data/lib/ruby_llm/agent/join_callbacks.rb +28 -0
- data/lib/ruby_llm/agent/model_options.rb +56 -0
- data/lib/ruby_llm/agent/ownership.rb +22 -0
- data/lib/ruby_llm/agent/tool_conversion.rb +19 -0
- data/lib/ruby_llm/agent.rb +620 -236
- data/lib/ruby_llm/agent_tool.rb +91 -0
- data/lib/ruby_llm/aliases.json +84 -91
- data/lib/ruby_llm/attachment.rb +101 -20
- data/lib/ruby_llm/batch.rb +132 -46
- data/lib/ruby_llm/cached_content.rb +4 -4
- data/lib/ruby_llm/chat/agent_run.rb +22 -0
- data/lib/ruby_llm/chat/context_budget.rb +119 -0
- data/lib/ruby_llm/chat/file_references.rb +259 -0
- data/lib/ruby_llm/chat/message_queue.rb +56 -0
- data/lib/ruby_llm/chat/ownership.rb +18 -0
- data/lib/ruby_llm/chat/rescuable.rb +48 -0
- data/lib/ruby_llm/chat/run.rb +32 -0
- data/lib/ruby_llm/chat/tool_call_ids.rb +46 -0
- data/lib/ruby_llm/chat/tool_call_state.rb +115 -0
- data/lib/ruby_llm/chat/tool_concurrency.rb +90 -49
- data/lib/ruby_llm/chat/tool_search.rb +36 -1
- data/lib/ruby_llm/chat/tool_sequences.rb +84 -0
- data/lib/ruby_llm/chat.rb +1598 -332
- data/lib/ruby_llm/chunk.rb +27 -1
- data/lib/ruby_llm/citation.rb +2 -1
- data/lib/ruby_llm/code_execution/language.rb +28 -0
- data/lib/ruby_llm/code_execution/node_installation.rb +56 -0
- data/lib/ruby_llm/code_execution/packages.rb +241 -0
- data/lib/ruby_llm/code_execution/ruby_installation.rb +170 -0
- data/lib/ruby_llm/code_execution/workspace.rb +314 -0
- data/lib/ruby_llm/code_execution.rb +781 -0
- data/lib/ruby_llm/configuration.rb +151 -4
- data/lib/ruby_llm/content_filter.rb +68 -0
- data/lib/ruby_llm/cost.rb +68 -41
- data/lib/ruby_llm/downloaded_file.rb +10 -9
- data/lib/ruby_llm/embedding.rb +30 -45
- data/lib/ruby_llm/embedding_request.rb +1 -1
- data/lib/ruby_llm/error.rb +167 -14
- data/lib/ruby_llm/evaluation/dataset.rb +5 -4
- data/lib/ruby_llm/evaluation/evidence.rb +5 -3
- data/lib/ruby_llm/evaluation/report.rb +7 -3
- data/lib/ruby_llm/evaluation/result.rb +1 -1
- data/lib/ruby_llm/evaluation.rb +2 -1
- data/lib/ruby_llm/fallback.rb +32 -4
- data/lib/ruby_llm/file_store/converters/ocr.rb +28 -0
- data/lib/ruby_llm/file_store/converters/office/markup.rb +148 -0
- data/lib/ruby_llm/file_store/converters/office/outline.rb +193 -0
- data/lib/ruby_llm/file_store/converters/office/prose.rb +161 -0
- data/lib/ruby_llm/file_store/converters/office/workbook.rb +187 -0
- data/lib/ruby_llm/file_store/converters/office/zip.rb +149 -0
- data/lib/ruby_llm/file_store/converters/office.rb +106 -0
- data/lib/ruby_llm/file_store/converters/text.rb +27 -0
- data/lib/ruby_llm/file_store/converters.rb +84 -0
- data/lib/ruby_llm/file_store/directory.rb +190 -0
- data/lib/ruby_llm/file_store/memory.rb +75 -0
- data/lib/ruby_llm/file_store/tools/base.rb +99 -0
- data/lib/ruby_llm/file_store/tools/delete.rb +48 -0
- data/lib/ruby_llm/file_store/tools/edit.rb +155 -0
- data/lib/ruby_llm/file_store/tools/list.rb +57 -0
- data/lib/ruby_llm/file_store/tools/read.rb +120 -0
- data/lib/ruby_llm/file_store/tools/search.rb +208 -0
- data/lib/ruby_llm/file_store/tools/write.rb +40 -0
- data/lib/ruby_llm/file_store/tools.rb +39 -0
- data/lib/ruby_llm/file_store.rb +338 -0
- data/lib/ruby_llm/files/saving.rb +14 -0
- data/lib/ruby_llm/image.rb +67 -45
- data/lib/ruby_llm/input_required_error.rb +28 -0
- data/lib/ruby_llm/judge/builder.rb +6 -1
- data/lib/ruby_llm/judge.rb +1 -1
- data/lib/ruby_llm/judgment.rb +6 -24
- data/lib/ruby_llm/mcp/client.rb +67 -28
- data/lib/ruby_llm/mcp/collection.rb +39 -2
- data/lib/ruby_llm/mcp/content.rb +7 -2
- data/lib/ruby_llm/mcp/error.rb +8 -2
- data/lib/ruby_llm/mcp/http.rb +117 -37
- data/lib/ruby_llm/mcp/input_request.rb +3 -112
- data/lib/ruby_llm/mcp/input_required_error.rb +3 -21
- data/lib/ruby_llm/mcp/insufficient_scope_error.rb +27 -0
- data/lib/ruby_llm/mcp/listener.rb +7 -1
- data/lib/ruby_llm/mcp/oauth.rb +102 -39
- data/lib/ruby_llm/mcp/outages.rb +172 -0
- data/lib/ruby_llm/mcp/resource.rb +9 -6
- data/lib/ruby_llm/mcp/resource_template.rb +3 -2
- data/lib/ruby_llm/mcp/session_expired.rb +10 -2
- data/lib/ruby_llm/mcp/stdio.rb +66 -18
- data/lib/ruby_llm/mcp/task.rb +13 -40
- data/lib/ruby_llm/mcp/tool.rb +34 -11
- data/lib/ruby_llm/mcp.rb +123 -59
- data/lib/ruby_llm/message.rb +217 -23
- data/lib/ruby_llm/model/modalities.rb +1 -1
- data/lib/ruby_llm/model/pricing.rb +26 -4
- data/lib/ruby_llm/model/pricing_category.rb +38 -15
- data/lib/ruby_llm/model/pricing_tier.rb +3 -2
- data/lib/ruby_llm/model.rb +24 -6
- data/lib/ruby_llm/models/lookup.rb +37 -4
- data/lib/ruby_llm/models/registry.rb +4 -19
- data/lib/ruby_llm/models.json +36186 -27832
- data/lib/ruby_llm/models.rb +44 -39
- data/lib/ruby_llm/moderation.rb +10 -30
- data/lib/ruby_llm/ocr.rb +5 -22
- data/lib/ruby_llm/open_telemetry/attributes.rb +68 -19
- data/lib/ruby_llm/open_telemetry/content.rb +105 -0
- data/lib/ruby_llm/open_telemetry.rb +18 -4
- data/lib/ruby_llm/prompt/context.rb +2 -2
- data/lib/ruby_llm/prompt.rb +7 -5
- data/lib/ruby_llm/protocol/binary_streaming.rb +2 -8
- data/lib/ruby_llm/protocol/cache_lifetime.rb +22 -0
- data/lib/ruby_llm/protocol/native_content.rb +69 -0
- data/lib/ruby_llm/protocol/replayed_arguments.rb +28 -0
- data/lib/ruby_llm/protocol/stream_accumulator.rb +73 -33
- data/lib/ruby_llm/protocol/streaming.rb +105 -22
- data/lib/ruby_llm/protocol/tool_namespaces.rb +100 -0
- data/lib/ruby_llm/protocol/unsupported_operations.rb +42 -0
- data/lib/ruby_llm/protocol.rb +134 -109
- data/lib/ruby_llm/protocols/anthropic/batches.rb +41 -4
- data/lib/ruby_llm/protocols/anthropic/block_binding.rb +30 -0
- data/lib/ruby_llm/protocols/anthropic/chat.rb +135 -60
- data/lib/ruby_llm/protocols/anthropic/files.rb +9 -0
- data/lib/ruby_llm/protocols/anthropic/media.rb +14 -14
- data/lib/ruby_llm/protocols/anthropic/models.rb +16 -10
- data/lib/ruby_llm/protocols/anthropic/streaming.rb +43 -22
- data/lib/ruby_llm/protocols/anthropic/tools.rb +36 -24
- data/lib/ruby_llm/protocols/anthropic.rb +61 -6
- data/lib/ruby_llm/protocols/azure/speech/transcription.rb +125 -0
- data/lib/ruby_llm/protocols/azure/speech.rb +14 -0
- data/lib/ruby_llm/protocols/bedrock/batches.rb +26 -8
- data/lib/ruby_llm/protocols/bedrock/event_stream.rb +205 -0
- data/lib/ruby_llm/protocols/bedrock/files.rb +15 -6
- data/lib/ruby_llm/protocols/bedrock/guardrails.rb +1 -1
- data/lib/ruby_llm/protocols/bedrock/rerank.rb +2 -0
- data/lib/ruby_llm/protocols/chat_completions/chat.rb +158 -58
- data/lib/ruby_llm/protocols/chat_completions/content_filters.rb +45 -0
- data/lib/ruby_llm/protocols/chat_completions/embedding_batches.rb +23 -2
- data/lib/ruby_llm/protocols/chat_completions/embeddings.rb +26 -3
- data/lib/ruby_llm/protocols/chat_completions/images.rb +7 -9
- data/lib/ruby_llm/protocols/chat_completions/media.rb +20 -20
- data/lib/ruby_llm/protocols/chat_completions/moderation.rb +8 -5
- data/lib/ruby_llm/protocols/chat_completions/speech.rb +8 -2
- data/lib/ruby_llm/protocols/chat_completions/streaming.rb +38 -17
- data/lib/ruby_llm/protocols/chat_completions/think_tags.rb +148 -0
- data/lib/ruby_llm/protocols/chat_completions/tools.rb +15 -28
- data/lib/ruby_llm/protocols/chat_completions/transcription.rb +17 -2
- data/lib/ruby_llm/protocols/chat_completions.rb +4 -0
- data/lib/ruby_llm/protocols/cohere/batches.rb +15 -8
- data/lib/ruby_llm/protocols/cohere/chat.rb +47 -25
- data/lib/ruby_llm/protocols/cohere/datasets.rb +6 -2
- data/lib/ruby_llm/protocols/cohere/embeddings.rb +7 -5
- data/lib/ruby_llm/protocols/cohere/media.rb +7 -7
- data/lib/ruby_llm/protocols/cohere/rerank.rb +2 -1
- data/lib/ruby_llm/protocols/cohere/streaming.rb +26 -11
- data/lib/ruby_llm/protocols/cohere/tools.rb +17 -28
- data/lib/ruby_llm/protocols/cohere/transcription.rb +5 -4
- data/lib/ruby_llm/protocols/converse/batches.rb +2 -24
- data/lib/ruby_llm/protocols/converse/chat.rb +210 -75
- data/lib/ruby_llm/protocols/converse/media.rb +24 -22
- data/lib/ruby_llm/protocols/converse/reasoning.rb +23 -9
- data/lib/ruby_llm/protocols/converse/streaming.rb +68 -220
- data/lib/ruby_llm/protocols/converse.rb +17 -1
- data/lib/ruby_llm/protocols/deepgram/speech.rb +1 -2
- data/lib/ruby_llm/protocols/deepgram/streaming_transcription.rb +1 -3
- data/lib/ruby_llm/protocols/deepgram/transcription.rb +28 -19
- data/lib/ruby_llm/protocols/deepgram.rb +3 -2
- data/lib/ruby_llm/protocols/deepseek/files.rb +1 -1
- data/lib/ruby_llm/protocols/elevenlabs/assets.rb +6 -4
- data/lib/ruby_llm/protocols/elevenlabs/flows/images.rb +4 -0
- data/lib/ruby_llm/protocols/elevenlabs/speech.rb +3 -3
- data/lib/ruby_llm/protocols/elevenlabs/streaming_transcription.rb +5 -2
- data/lib/ruby_llm/protocols/elevenlabs/transcription.rb +6 -3
- data/lib/ruby_llm/protocols/files.rb +27 -12
- data/lib/ruby_llm/protocols/gemini/caches.rb +4 -4
- data/lib/ruby_llm/protocols/gemini/chat.rb +118 -51
- data/lib/ruby_llm/protocols/gemini/embedding_batches.rb +9 -5
- data/lib/ruby_llm/protocols/gemini/embeddings.rb +3 -3
- data/lib/ruby_llm/protocols/gemini/file_transcription.rb +4 -0
- data/lib/ruby_llm/protocols/gemini/files.rb +5 -4
- data/lib/ruby_llm/protocols/gemini/images.rb +11 -9
- data/lib/ruby_llm/protocols/gemini/live_transcription.rb +1 -1
- data/lib/ruby_llm/protocols/gemini/long_decode.rb +104 -0
- data/lib/ruby_llm/protocols/gemini/media.rb +31 -28
- data/lib/ruby_llm/protocols/gemini/retry_info.rb +29 -0
- data/lib/ruby_llm/protocols/gemini/schemas.rb +126 -0
- data/lib/ruby_llm/protocols/gemini/speech.rb +9 -3
- data/lib/ruby_llm/protocols/gemini/streaming.rb +37 -13
- data/lib/ruby_llm/protocols/gemini/tools.rb +18 -10
- data/lib/ruby_llm/protocols/gemini/transcription.rb +13 -9
- data/lib/ruby_llm/protocols/gemini/videos.rb +14 -3
- data/lib/ruby_llm/protocols/gemini.rb +16 -0
- data/lib/ruby_llm/protocols/gpustack/responses.rb +3 -3
- data/lib/ruby_llm/protocols/gpustack/tokenization.rb +14 -0
- data/lib/ruby_llm/protocols/interactions/chat.rb +29 -14
- data/lib/ruby_llm/protocols/interactions/content.rb +19 -14
- data/lib/ruby_llm/protocols/interactions/streaming.rb +1 -1
- data/lib/ruby_llm/protocols/interactions/tools.rb +10 -13
- data/lib/ruby_llm/protocols/interactions/videos.rb +99 -0
- data/lib/ruby_llm/protocols/interactions.rb +1 -0
- data/lib/ruby_llm/protocols/invoke_model/cohere_embeddings.rb +28 -10
- data/lib/ruby_llm/protocols/invoke_model/embedding_batches.rb +1 -1
- data/lib/ruby_llm/protocols/invoke_model/nova_embeddings.rb +45 -11
- data/lib/ruby_llm/protocols/invoke_model/stability_images.rb +5 -2
- data/lib/ruby_llm/protocols/invoke_model/titan_multimodal_embeddings.rb +11 -3
- data/lib/ruby_llm/protocols/invoke_model.rb +21 -2
- data/lib/ruby_llm/protocols/meta/transcription/stream.rb +108 -0
- data/lib/ruby_llm/protocols/meta/transcription.rb +76 -0
- data/lib/ruby_llm/protocols/minimax/files.rb +58 -0
- data/lib/ruby_llm/protocols/minimax/images.rb +54 -0
- data/lib/ruby_llm/protocols/minimax/speech.rb +53 -0
- data/lib/ruby_llm/protocols/minimax/transcription.rb +82 -0
- data/lib/ruby_llm/protocols/minimax/videos.rb +93 -0
- data/lib/ruby_llm/protocols/minimax.rb +21 -0
- data/lib/ruby_llm/protocols/mistral/conversations/chat.rb +44 -21
- data/lib/ruby_llm/protocols/mistral/files.rb +6 -4
- data/lib/ruby_llm/protocols/mistral/multi_completion.rb +15 -4
- data/lib/ruby_llm/protocols/moonshot/files.rb +48 -0
- data/lib/ruby_llm/protocols/openai/batches.rb +3 -2
- data/lib/ruby_llm/protocols/openai/decisions.rb +6 -4
- data/lib/ruby_llm/protocols/openai/files.rb +13 -6
- data/lib/ruby_llm/protocols/openrouter/batches.rb +13 -17
- data/lib/ruby_llm/protocols/openrouter/responses.rb +6 -10
- data/lib/ruby_llm/protocols/perplexity/agent.rb +19 -35
- data/lib/ruby_llm/protocols/perplexity/batches.rb +37 -0
- data/lib/ruby_llm/protocols/perplexity/files.rb +19 -9
- data/lib/ruby_llm/protocols/responses/approvals.rb +12 -11
- data/lib/ruby_llm/protocols/responses/chat.rb +165 -75
- data/lib/ruby_llm/protocols/responses/citation_markers.rb +167 -0
- data/lib/ruby_llm/protocols/responses/compaction.rb +2 -2
- data/lib/ruby_llm/protocols/responses/media.rb +17 -9
- data/lib/ruby_llm/protocols/responses/streaming.rb +71 -12
- data/lib/ruby_llm/protocols/responses/token_counting.rb +1 -1
- data/lib/ruby_llm/protocols/responses/tools.rb +48 -1
- data/lib/ruby_llm/protocols/responses.rb +21 -0
- data/lib/ruby_llm/protocols/system_one/responses.rb +9 -2
- data/lib/ruby_llm/protocols/vertexai/files.rb +4 -3
- data/lib/ruby_llm/protocols/vertexai/text_to_speech.rb +116 -0
- data/lib/ruby_llm/protocols/xai/files.rb +13 -6
- data/lib/ruby_llm/protocols/zai/files.rb +37 -0
- data/lib/ruby_llm/provider.rb +128 -31
- data/lib/ruby_llm/providers/anthropic.rb +5 -1
- data/lib/ruby_llm/providers/azure/chat.rb +17 -3
- data/lib/ruby_llm/providers/azure/chat_completions.rb +1 -0
- data/lib/ruby_llm/providers/azure/decisions.rb +17 -0
- data/lib/ruby_llm/providers/azure/embeddings.rb +1 -2
- data/lib/ruby_llm/providers/azure/media.rb +6 -6
- data/lib/ruby_llm/providers/azure/mistral.rb +17 -0
- data/lib/ruby_llm/providers/azure/models.rb +16 -2
- data/lib/ruby_llm/providers/azure/moderation.rb +16 -0
- data/lib/ruby_llm/providers/azure/responses.rb +9 -0
- data/lib/ruby_llm/providers/azure/videos.rb +2 -2
- data/lib/ruby_llm/providers/azure.rb +43 -1
- data/lib/ruby_llm/providers/bedrock/anthropic/batches.rb +32 -0
- data/lib/ruby_llm/providers/bedrock/anthropic.rb +134 -0
- data/lib/ruby_llm/providers/bedrock/auth.rb +12 -3
- data/lib/ruby_llm/providers/bedrock/connection.rb +19 -0
- data/lib/ruby_llm/providers/bedrock/mantle/anthropic.rb +5 -1
- data/lib/ruby_llm/providers/bedrock/mantle/chat_completions.rb +2 -2
- data/lib/ruby_llm/providers/bedrock/mantle/responses.rb +12 -2
- data/lib/ruby_llm/providers/bedrock/mantle/voxtral.rb +1 -1
- data/lib/ruby_llm/providers/bedrock/mantle.rb +7 -0
- data/lib/ruby_llm/providers/bedrock/models.rb +2 -9
- data/lib/ruby_llm/providers/bedrock.rb +51 -8
- data/lib/ruby_llm/providers/cohere.rb +5 -1
- data/lib/ruby_llm/providers/deepgram/chat.rb +25 -0
- data/lib/ruby_llm/providers/deepgram.rb +21 -2
- data/lib/ruby_llm/providers/deepseek/chat.rb +7 -16
- data/lib/ruby_llm/providers/deepseek/responses.rb +25 -8
- data/lib/ruby_llm/providers/deepseek.rb +5 -1
- data/lib/ruby_llm/providers/elevenlabs.rb +2 -1
- data/lib/ruby_llm/providers/gemini.rb +13 -1
- data/lib/ruby_llm/providers/gpustack/chat.rb +23 -3
- data/lib/ruby_llm/providers/gpustack/embeddings.rb +2 -2
- data/lib/ruby_llm/providers/gpustack/media.rb +8 -8
- data/lib/ruby_llm/providers/gpustack/transcription.rb +14 -6
- data/lib/ruby_llm/providers/gpustack.rb +1 -1
- data/lib/ruby_llm/providers/hetzner/chat.rb +23 -6
- data/lib/ruby_llm/providers/hetzner.rb +5 -1
- data/lib/ruby_llm/providers/meta/responses.rb +81 -0
- data/lib/ruby_llm/providers/meta.rb +37 -0
- data/lib/ruby_llm/providers/minimax/anthropic.rb +95 -0
- data/lib/ruby_llm/providers/minimax/chat_completions.rb +48 -0
- data/lib/ruby_llm/providers/minimax/responses.rb +52 -0
- data/lib/ruby_llm/providers/minimax.rb +104 -0
- data/lib/ruby_llm/providers/mistral/chat.rb +34 -4
- data/lib/ruby_llm/providers/mistral/chat_completions/batches.rb +8 -3
- data/lib/ruby_llm/providers/mistral/embeddings.rb +1 -12
- data/lib/ruby_llm/providers/mistral/input_tokens.rb +35 -0
- data/lib/ruby_llm/providers/mistral/media.rb +8 -8
- data/lib/ruby_llm/providers/mistral/transcription.rb +8 -6
- data/lib/ruby_llm/providers/mistral.rb +13 -1
- data/lib/ruby_llm/providers/moonshot/capabilities.rb +16 -0
- data/lib/ruby_llm/providers/moonshot/chat.rb +100 -0
- data/lib/ruby_llm/providers/moonshot/documents.rb +79 -0
- data/lib/ruby_llm/providers/moonshot/responses.rb +55 -0
- data/lib/ruby_llm/providers/moonshot.rb +54 -0
- data/lib/ruby_llm/providers/ollama/chat.rb +5 -6
- data/lib/ruby_llm/providers/ollama/embeddings.rb +41 -0
- data/lib/ruby_llm/providers/ollama/media.rb +6 -6
- data/lib/ruby_llm/providers/ollama/speech.rb +24 -0
- data/lib/ruby_llm/providers/ollama.rb +13 -1
- data/lib/ruby_llm/providers/ollama_cloud.rb +22 -1
- data/lib/ruby_llm/providers/openai/responses.rb +8 -0
- data/lib/ruby_llm/providers/openai.rb +7 -3
- data/lib/ruby_llm/providers/openrouter/chat.rb +15 -9
- data/lib/ruby_llm/providers/openrouter/embeddings.rb +12 -8
- data/lib/ruby_llm/providers/openrouter/images.rb +16 -10
- data/lib/ruby_llm/providers/openrouter/judgments.rb +47 -0
- data/lib/ruby_llm/providers/openrouter/media.rb +5 -5
- data/lib/ruby_llm/providers/openrouter/models.rb +19 -7
- data/lib/ruby_llm/providers/openrouter/streaming.rb +4 -4
- data/lib/ruby_llm/providers/openrouter/videos.rb +1 -1
- data/lib/ruby_llm/providers/openrouter.rb +32 -5
- data/lib/ruby_llm/providers/perplexity/chat.rb +3 -3
- data/lib/ruby_llm/providers/perplexity/embeddings.rb +1 -1
- data/lib/ruby_llm/providers/perplexity/media.rb +6 -6
- data/lib/ruby_llm/providers/perplexity.rb +13 -2
- data/lib/ruby_llm/providers/typesafe.rb +1 -1
- data/lib/ruby_llm/providers/vertexai/anthropic.rb +5 -1
- data/lib/ruby_llm/providers/vertexai/credentials.rb +27 -3
- data/lib/ruby_llm/providers/vertexai/embed_content.rb +1 -1
- data/lib/ruby_llm/providers/vertexai/embeddings.rb +2 -3
- data/lib/ruby_llm/providers/vertexai/gemini.rb +1 -1
- data/lib/ruby_llm/providers/vertexai/mistral.rb +5 -0
- data/lib/ruby_llm/providers/vertexai/models.rb +24 -2
- data/lib/ruby_llm/providers/vertexai/transcription.rb +2 -2
- data/lib/ruby_llm/providers/vertexai/videos.rb +1 -1
- data/lib/ruby_llm/providers/vertexai.rb +56 -9
- data/lib/ruby_llm/providers/xai/chat.rb +3 -3
- data/lib/ruby_llm/providers/xai/chat_completions/batches.rb +8 -3
- data/lib/ruby_llm/providers/xai/images.rb +9 -4
- data/lib/ruby_llm/providers/xai/models.rb +2 -2
- data/lib/ruby_llm/providers/xai/responses.rb +10 -0
- data/lib/ruby_llm/providers/xai/speech.rb +2 -2
- data/lib/ruby_llm/providers/xai/transcription.rb +14 -9
- data/lib/ruby_llm/providers/xai/videos.rb +1 -1
- data/lib/ruby_llm/providers/xai.rb +9 -1
- data/lib/ruby_llm/providers/zai/chat.rb +117 -0
- data/lib/ruby_llm/providers/zai/ocr.rb +65 -0
- data/lib/ruby_llm/providers/zai/token_counting.rb +25 -0
- data/lib/ruby_llm/providers/zai/videos.rb +67 -0
- data/lib/ruby_llm/providers/zai.rb +66 -0
- data/lib/ruby_llm/question.rb +390 -0
- data/lib/ruby_llm/queued_message.rb +74 -0
- data/lib/ruby_llm/railtie.rb +43 -0
- data/lib/ruby_llm/request_shape/part.rb +2 -2
- data/lib/ruby_llm/request_shape/problem.rb +2 -2
- data/lib/ruby_llm/request_shape/tool_round.rb +2 -2
- data/lib/ruby_llm/request_shape/turn.rb +2 -2
- data/lib/ruby_llm/request_shape.rb +1 -1
- data/lib/ruby_llm/rerank.rb +10 -37
- data/lib/ruby_llm/research_job.rb +26 -13
- data/lib/ruby_llm/sandbox/landlock.rb +78 -0
- data/lib/ruby_llm/sandbox/mxc.rb +137 -0
- data/lib/ruby_llm/sandbox.rb +141 -0
- data/lib/ruby_llm/search_results.rb +9 -4
- data/lib/ruby_llm/server_tool_call.rb +1 -1
- data/lib/ruby_llm/skill/loader.rb +131 -0
- data/lib/ruby_llm/skill.rb +263 -0
- data/lib/ruby_llm/speech.rb +25 -53
- data/lib/ruby_llm/stored_file.rb +94 -0
- data/lib/ruby_llm/support/cancellation.rb +4 -4
- data/lib/ruby_llm/support/deprecated_enumerable.rb +13 -0
- data/lib/ruby_llm/support/deprecated_job_predicates.rb +24 -0
- data/lib/ruby_llm/support/inspectable.rb +1 -1
- data/lib/ruby_llm/support/instrumentation.rb +6 -5
- data/lib/ruby_llm/support/rescuable.rb +84 -0
- data/lib/ruby_llm/support/utils.rb +26 -3
- data/lib/ruby_llm/task.rb +126 -0
- data/lib/ruby_llm/test_helper.rb +129 -0
- data/lib/ruby_llm/thinking.rb +3 -3
- data/lib/ruby_llm/tokens.rb +52 -12
- data/lib/ruby_llm/tool.rb +339 -5
- data/lib/ruby_llm/tool_call/collection.rb +132 -0
- data/lib/ruby_llm/tool_call.rb +68 -6
- data/lib/ruby_llm/tools/provider_tools.rb +23 -0
- data/lib/ruby_llm/tools/search.rb +118 -0
- data/lib/ruby_llm/transcription/segment.rb +67 -0
- data/lib/ruby_llm/transcription/wav_audio.rb +2 -1
- data/lib/ruby_llm/transcription.rb +25 -49
- data/lib/ruby_llm/transcription_chunk.rb +9 -9
- data/lib/ruby_llm/transport/connection.rb +146 -33
- data/lib/ruby_llm/transport/error_middleware.rb +94 -49
- data/lib/ruby_llm/transport/event_stream_parser.rb +18 -4
- data/lib/ruby_llm/transport/json_response.rb +9 -3
- data/lib/ruby_llm/transport/log_formatter.rb +20 -0
- data/lib/ruby_llm/transport/release_request.rb +35 -0
- data/lib/ruby_llm/transport/websocket_connection.rb +26 -30
- data/lib/ruby_llm/uploaded_file.rb +15 -6
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm/video.rb +34 -10
- data/lib/ruby_llm/video_job.rb +55 -42
- data/lib/ruby_llm/web_source.rb +38 -0
- data/lib/ruby_llm/workflow.rb +2 -2
- data/lib/ruby_llm.rb +63 -25
- data/lib/tasks/ruby_llm/evaluations.rb +1 -1
- data/skills/rubyllm/SKILL.md +128 -54
- data/skills/rubyllm/references/cost-and-tracing.md +8 -0
- data/skills/rubyllm/references/files-code-and-context.md +6 -0
- data/skills/rubyllm/references/operations.md +8 -0
- data/skills/rubyllm/references/rails-chats.md +22 -0
- data/skills/rubyllm/references/tools-and-agents.md +11 -0
- metadata +219 -14
- data/lib/generators/ruby_llm/chat_ui/templates/helpers/messages_helper.rb.tt +0 -25
- data/lib/generators/ruby_llm/upgrade/templates/upgrade_ruby_llm_to_2_1.rb.tt +0 -78
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Providers
|
|
5
|
+
# MiniMax API integration, through its Anthropic-compatible endpoint.
|
|
6
|
+
class MiniMax < Provider
|
|
7
|
+
protocol :anthropic, MiniMax::Anthropic
|
|
8
|
+
protocol :media, Protocols::MiniMax
|
|
9
|
+
protocol :files, Protocols::MiniMax::Files
|
|
10
|
+
protocol :chat_completions, MiniMax::ChatCompletions
|
|
11
|
+
protocol :responses, MiniMax::Responses
|
|
12
|
+
|
|
13
|
+
MEDIA_OPERATIONS = %i[paint animate speak transcribe].freeze
|
|
14
|
+
|
|
15
|
+
# The one MiniMax model that applies a response_format, on Chat
|
|
16
|
+
# Completion v2. The others ignore every structured output field.
|
|
17
|
+
STRUCTURED_OUTPUT_MODEL = 'MiniMax-Text-01'
|
|
18
|
+
|
|
19
|
+
# The HTTP status each MiniMax status code stands for on endpoints that
|
|
20
|
+
# answer 200 and report the failure in base_resp.
|
|
21
|
+
FAILURE_STATUSES = {
|
|
22
|
+
1000 => 500, 1001 => 504, 1002 => 429, 1004 => 401, 1008 => 402, 1039 => 429, 2049 => 401, 2056 => 429
|
|
23
|
+
}.freeze
|
|
24
|
+
|
|
25
|
+
def resolve_protocol(name, model, operation: nil, **options)
|
|
26
|
+
name ||= :media if MEDIA_OPERATIONS.include?(operation)
|
|
27
|
+
super
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def protocol_for(model, schema: nil, thinking: nil, tool_prefs: nil, provider_tools: [], messages: [], **)
|
|
31
|
+
return protocols[:chat_completions] if schema && model.id == STRUCTURED_OUTPUT_MODEL
|
|
32
|
+
return protocols[:responses] if web_search_route?(schema, thinking, tool_prefs, provider_tools, messages)
|
|
33
|
+
|
|
34
|
+
super
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def api_base
|
|
38
|
+
configured_api_base(:minimax_api_base, 'https://api.minimax.io/anthropic', endpoints: %r{/v1/messages\z})
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def media_base # :nodoc:
|
|
42
|
+
api_base.sub(%r{/anthropic/?\z}, '')
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def headers
|
|
46
|
+
{ 'Authorization' => "Bearer #{@config.minimax_api_key}" }
|
|
47
|
+
end
|
|
48
|
+
|
|
49
|
+
def account_identity
|
|
50
|
+
account_digest(api_base, @config.minimax_api_key)
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
def error_status(response)
|
|
54
|
+
code = failure_code(response)
|
|
55
|
+
FAILURE_STATUSES.fetch(code, 400) if code
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def parse_error(response)
|
|
59
|
+
return super unless failure_code(response)
|
|
60
|
+
|
|
61
|
+
parse_error_body(response).dig('base_resp', 'status_msg')
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
class << self
|
|
65
|
+
def models_dev_provider_ids # :nodoc:
|
|
66
|
+
%w[minimax]
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def configuration_options
|
|
70
|
+
%i[minimax_api_key minimax_api_base]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def configuration_requirements
|
|
74
|
+
%i[minimax_api_key]
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
private
|
|
79
|
+
|
|
80
|
+
# Only Responses cites web pages, but it ignores an attached PDF, so a
|
|
81
|
+
# chat that holds one stays on Messages, which reads it. Messages also
|
|
82
|
+
# raises on what MiniMax cannot carry and honours a thinking budget or
|
|
83
|
+
# off switch, all of which Responses would drop.
|
|
84
|
+
def web_search_route?(schema, thinking, tool_prefs, provider_tools, messages)
|
|
85
|
+
return false unless provider_tools.any? { |entry| entry[:name] == :web_search }
|
|
86
|
+
return false if messages_only_request?(schema, thinking, tool_prefs)
|
|
87
|
+
|
|
88
|
+
messages.none? { |message| message.attachments.any?(&:pdf?) }
|
|
89
|
+
end
|
|
90
|
+
|
|
91
|
+
def messages_only_request?(schema, thinking, tool_prefs)
|
|
92
|
+
schema || tool_prefs&.dig(:calls) == :one || (thinking && !thinking.effort)
|
|
93
|
+
end
|
|
94
|
+
|
|
95
|
+
def failure_code(response)
|
|
96
|
+
return unless response.body.to_s.include?('base_resp')
|
|
97
|
+
|
|
98
|
+
body = parse_error_body(response)
|
|
99
|
+
code = body.dig('base_resp', 'status_code') if body.is_a?(Hash)
|
|
100
|
+
code unless code.nil? || code.zero?
|
|
101
|
+
end
|
|
102
|
+
end
|
|
103
|
+
end
|
|
104
|
+
end
|
|
@@ -6,15 +6,43 @@ module RubyLLM
|
|
|
6
6
|
# Chat methods for Mistral API
|
|
7
7
|
module Chat
|
|
8
8
|
PROMPT_CACHE_OPTIONS = %i[key].freeze
|
|
9
|
+
FINISH_REASONS = Protocols::ChatCompletions::Chat::FINISH_REASONS.merge('model_length' => :max_tokens).freeze
|
|
9
10
|
|
|
10
11
|
module_function
|
|
11
12
|
|
|
12
|
-
def
|
|
13
|
+
def finish_reasons = FINISH_REASONS
|
|
14
|
+
|
|
15
|
+
# A tool that supplies numbered sources gets reference chunks back,
|
|
16
|
+
# whose reference_ids are the keys of that tool's own result.
|
|
17
|
+
def extract_citations(message_data, data, content)
|
|
18
|
+
references = reference_citations(message_data['content'])
|
|
19
|
+
references.any? ? references : super
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def parse_chunk_citations(delta, data)
|
|
23
|
+
references = reference_citations(delta['content'], offsets: false)
|
|
24
|
+
references.any? ? references : super
|
|
25
|
+
end
|
|
26
|
+
|
|
27
|
+
def reference_citations(content, offsets: true)
|
|
28
|
+
return [] unless content.is_a?(Array)
|
|
29
|
+
|
|
30
|
+
position = 0
|
|
31
|
+
content.flat_map do |part|
|
|
32
|
+
position += part['text'].to_s.length if part['type'] == 'text'
|
|
33
|
+
span = offsets ? { start_index: position, end_index: position } : {}
|
|
34
|
+
Array(part['reference_ids']).map { |id| Citation.new(source_index: id, **span) }
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def render_role(role)
|
|
13
39
|
role.to_s
|
|
14
40
|
end
|
|
15
41
|
|
|
16
42
|
def render_payload(messages, tools:, temperature:, model:, stream: false, max_output_tokens: nil,
|
|
17
43
|
schema: nil, thinking: nil, citations: false, caching: nil, tool_prefs: nil)
|
|
44
|
+
raise ArgumentError, 'Mistral has no thinking budget; use effort:' if thinking&.budget
|
|
45
|
+
|
|
18
46
|
payload = super
|
|
19
47
|
payload.delete(:stream_options)
|
|
20
48
|
normalize_required_tool_choice(payload)
|
|
@@ -42,6 +70,8 @@ module RubyLLM
|
|
|
42
70
|
raise ArgumentError, "Mistral prompt caching accepts :key, got #{format_cache_option_keys(unsupported)}"
|
|
43
71
|
end
|
|
44
72
|
|
|
73
|
+
def cache_option_names = PROMPT_CACHE_OPTIONS
|
|
74
|
+
|
|
45
75
|
def format_cache_option_keys(keys)
|
|
46
76
|
keys.map { |key| ":#{key}" }.join(', ')
|
|
47
77
|
end
|
|
@@ -49,7 +79,7 @@ module RubyLLM
|
|
|
49
79
|
# extra_content carries Gemini's thought signatures on Google's
|
|
50
80
|
# OpenAI-compatible endpoints, and Mistral refuses any field it does
|
|
51
81
|
# not define.
|
|
52
|
-
def
|
|
82
|
+
def render_tool_calls(tool_calls)
|
|
53
83
|
super&.map { |call| call.except(:extra_content) }
|
|
54
84
|
end
|
|
55
85
|
|
|
@@ -73,7 +103,7 @@ module RubyLLM
|
|
|
73
103
|
|
|
74
104
|
# Mistral carries reasoning in content blocks, not in the top-level
|
|
75
105
|
# reasoning fields the rest of the Chat Completions family uses.
|
|
76
|
-
def
|
|
106
|
+
def render_message_content(msg, **)
|
|
77
107
|
formatted_content = super
|
|
78
108
|
return formatted_content unless msg.role == :assistant && msg.thinking
|
|
79
109
|
|
|
@@ -83,7 +113,7 @@ module RubyLLM
|
|
|
83
113
|
content_blocks
|
|
84
114
|
end
|
|
85
115
|
|
|
86
|
-
def
|
|
116
|
+
def render_thinking(_msg)
|
|
87
117
|
{}
|
|
88
118
|
end
|
|
89
119
|
|
|
@@ -99,10 +99,15 @@ module RubyLLM
|
|
|
99
99
|
custom_id, shape = line['custom_id'].split(':', 2)
|
|
100
100
|
index = batch_result_index(custom_id)
|
|
101
101
|
response = line['response']
|
|
102
|
+
body = response['body'] if batch_response_succeeded?(response)
|
|
102
103
|
|
|
103
|
-
if
|
|
104
|
-
|
|
105
|
-
|
|
104
|
+
if body && body['data'].is_a?(Array) && !valid_embedding_positions?(body['data'])
|
|
105
|
+
return [index, nil, batch_failure(line['custom_id'], 'Invalid or duplicate embedding record positions')]
|
|
106
|
+
end
|
|
107
|
+
|
|
108
|
+
if body
|
|
109
|
+
billed = body unless body['data'].is_a?(Array)
|
|
110
|
+
parsed_batch_result(index, custom_id, billed) { parse_mistral_batch_body(body, shape:) }
|
|
106
111
|
else
|
|
107
112
|
[index, nil, batch_failure(line['custom_id'], batch_error_message(line))]
|
|
108
113
|
end
|
|
@@ -11,24 +11,13 @@ module RubyLLM
|
|
|
11
11
|
'embeddings'
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
|
|
15
|
-
def render_embedding_payload(text, model:, dimensions:, task_type: nil, title: nil, provider_options: {})
|
|
14
|
+
def render_embedding_payload(text, model:, dimensions:, provider_options: {}, **)
|
|
16
15
|
{
|
|
17
16
|
model: model,
|
|
18
17
|
input: text,
|
|
19
18
|
output_dimension: dimensions
|
|
20
19
|
}.compact.merge(provider_options)
|
|
21
20
|
end
|
|
22
|
-
|
|
23
|
-
def parse_embedding_response(response, model:, text:)
|
|
24
|
-
data = response.body
|
|
25
|
-
input_tokens = data.dig('usage', 'prompt_tokens')
|
|
26
|
-
vectors = data['data'].map { |d| d['embedding'] }
|
|
27
|
-
|
|
28
|
-
vectors = vectors.first if vectors.length == 1 && !text.is_a?(Array)
|
|
29
|
-
|
|
30
|
-
Embedding.new(vectors:, model:, input_tokens:)
|
|
31
|
-
end
|
|
32
21
|
end
|
|
33
22
|
end
|
|
34
23
|
end
|
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Providers
|
|
5
|
+
class Mistral
|
|
6
|
+
# Counts a request through Mistral's Responses input-token endpoint,
|
|
7
|
+
# the only token counting route Mistral offers, whichever protocol the
|
|
8
|
+
# chat itself runs on.
|
|
9
|
+
class InputTokens < Protocols::Responses
|
|
10
|
+
include Protocols::Responses::TokenCounting
|
|
11
|
+
|
|
12
|
+
def render_count_tokens_payload(messages, **)
|
|
13
|
+
payload = super(messages.flat_map { |message| countable_messages(message) }, **)
|
|
14
|
+
payload.merge(input: payload[:input].map { |item| typed_item(item) })
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
private
|
|
18
|
+
|
|
19
|
+
# Mistral rejects an input item that doesn't name its type.
|
|
20
|
+
def typed_item(item)
|
|
21
|
+
item.key?(:role) ? { type: 'message' }.merge(item) : item
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
# Native content from other protocols doesn't fit Responses input,
|
|
25
|
+
# and the endpoint can't take unsigned thinking, so thinking goes in
|
|
26
|
+
# as text to keep it in the count.
|
|
27
|
+
def countable_messages(message)
|
|
28
|
+
plain = message.without_native_content
|
|
29
|
+
text = message.thinking&.text
|
|
30
|
+
text ? [Message.new(role: :assistant, content: text), plain] : [plain]
|
|
31
|
+
end
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
35
|
+
end
|
|
@@ -11,31 +11,31 @@ module RubyLLM
|
|
|
11
11
|
%i[image audio pdf document text].include?(attachment.type)
|
|
12
12
|
end
|
|
13
13
|
|
|
14
|
-
def
|
|
15
|
-
Protocols::ChatCompletions::Media.
|
|
14
|
+
def render_content(content, attachments = [])
|
|
15
|
+
Protocols::ChatCompletions::Media.render_parts(content, attachments) do |attachment|
|
|
16
16
|
raise UnsupportedAttachmentError, attachment.mime_type unless supported_attachment?(attachment)
|
|
17
17
|
|
|
18
18
|
case attachment.type
|
|
19
19
|
when :image
|
|
20
|
-
|
|
20
|
+
render_image(attachment)
|
|
21
21
|
when :audio
|
|
22
|
-
Protocols::ChatCompletions::Media.
|
|
22
|
+
Protocols::ChatCompletions::Media.render_audio(attachment)
|
|
23
23
|
when :pdf, :document
|
|
24
|
-
|
|
24
|
+
render_document(attachment)
|
|
25
25
|
when :text
|
|
26
|
-
Protocols::ChatCompletions::Media.
|
|
26
|
+
Protocols::ChatCompletions::Media.render_text_file(attachment)
|
|
27
27
|
end
|
|
28
28
|
end
|
|
29
29
|
end
|
|
30
30
|
|
|
31
|
-
def
|
|
31
|
+
def render_image(image)
|
|
32
32
|
{
|
|
33
33
|
type: 'image_url',
|
|
34
34
|
image_url: image.url_or_data_uri
|
|
35
35
|
}
|
|
36
36
|
end
|
|
37
37
|
|
|
38
|
-
def
|
|
38
|
+
def render_document(document)
|
|
39
39
|
{
|
|
40
40
|
type: 'document_url',
|
|
41
41
|
document_url: document.url_or_data_uri
|
|
@@ -3,8 +3,8 @@
|
|
|
3
3
|
module RubyLLM
|
|
4
4
|
module Providers
|
|
5
5
|
class Mistral
|
|
6
|
-
# Transcription dialect for the Mistral audio API.
|
|
7
|
-
#
|
|
6
|
+
# Transcription dialect for the Mistral audio API. Requesting speaker
|
|
7
|
+
# labels turns on diarization, which labels each segment with a
|
|
8
8
|
# speaker id; a prompt rides along as context biasing terms.
|
|
9
9
|
module Transcription
|
|
10
10
|
STREAM_TYPES = {
|
|
@@ -20,6 +20,10 @@ module RubyLLM
|
|
|
20
20
|
{ timestamp_granularities: ['segment'] }
|
|
21
21
|
end
|
|
22
22
|
|
|
23
|
+
def rejected_options(operation)
|
|
24
|
+
operation == :transcribe ? %i[format speaker_references named_speakers] : super
|
|
25
|
+
end
|
|
26
|
+
|
|
23
27
|
module_function
|
|
24
28
|
|
|
25
29
|
def build_transcription_chunk(data)
|
|
@@ -39,10 +43,8 @@ module RubyLLM
|
|
|
39
43
|
usage['prompt_audio_seconds'] || super
|
|
40
44
|
end
|
|
41
45
|
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
speaker_references: nil, provider_options: {}, prompt: nil,
|
|
45
|
-
temperature: nil)
|
|
46
|
+
def render_transcription_payload(file_part, model:, language:, speaker_names: nil, provider_options: {},
|
|
47
|
+
prompt: nil, temperature: nil, **)
|
|
46
48
|
payload = {
|
|
47
49
|
model: model,
|
|
48
50
|
file: file_part,
|
|
@@ -14,8 +14,12 @@ module RubyLLM
|
|
|
14
14
|
super
|
|
15
15
|
end
|
|
16
16
|
|
|
17
|
+
def count_tokens(messages, model:, tools: {}, protocol: nil, **) # rubocop:disable Lint/UnusedMethodArgument
|
|
18
|
+
InputTokens.new(self, model).count_tokens(messages, tools:, **)
|
|
19
|
+
end
|
|
20
|
+
|
|
17
21
|
def api_base
|
|
18
|
-
|
|
22
|
+
configured_api_base(:mistral_api_base, 'https://api.mistral.ai/v1', endpoints: Protocols::ChatCompletions::ENDPOINT_PATHS)
|
|
19
23
|
end
|
|
20
24
|
|
|
21
25
|
def headers
|
|
@@ -27,6 +31,14 @@ module RubyLLM
|
|
|
27
31
|
def batch_cost_multiplier(**) = 0.5
|
|
28
32
|
|
|
29
33
|
class << self
|
|
34
|
+
def open_telemetry_name # :nodoc:
|
|
35
|
+
'mistral_ai'
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def models_dev_provider_ids # :nodoc:
|
|
39
|
+
%w[mistral]
|
|
40
|
+
end
|
|
41
|
+
|
|
30
42
|
def capabilities
|
|
31
43
|
Mistral::Capabilities
|
|
32
44
|
end
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Providers
|
|
5
|
+
class Moonshot
|
|
6
|
+
# Feature capability gaps not represented in upstream model catalogs.
|
|
7
|
+
module Capabilities
|
|
8
|
+
def self.augment(capabilities, **)
|
|
9
|
+
return capabilities unless capabilities.include?('function_calling')
|
|
10
|
+
|
|
11
|
+
capabilities | ['tool_choice']
|
|
12
|
+
end
|
|
13
|
+
end
|
|
14
|
+
end
|
|
15
|
+
end
|
|
16
|
+
end
|
|
@@ -0,0 +1,100 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Providers
|
|
5
|
+
class Moonshot
|
|
6
|
+
# Chat methods of the Moonshot AI API integration
|
|
7
|
+
# https://platform.kimi.ai/docs/api/chat
|
|
8
|
+
module Chat
|
|
9
|
+
module_function
|
|
10
|
+
|
|
11
|
+
def render_role(role)
|
|
12
|
+
role.to_s
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def max_output_tokens_field(_model)
|
|
16
|
+
:max_completion_tokens
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
def render_payload(messages, tools:, temperature:, model:, stream: false, max_output_tokens: nil,
|
|
20
|
+
schema: nil, thinking: nil, citations: false, caching: nil, tool_prefs: nil)
|
|
21
|
+
payload = super
|
|
22
|
+
raise ArgumentError, 'Moonshot does not accept calls: :one' if payload.delete(:parallel_tool_calls) == false
|
|
23
|
+
|
|
24
|
+
payload[:thinking] = { type: thinking.enabled ? 'enabled' : 'disabled' } unless thinking&.enabled.nil?
|
|
25
|
+
payload.merge!(prompt_cache_params(caching)) if caching
|
|
26
|
+
payload
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def count_tokens_url
|
|
30
|
+
'tokenizers/estimate-token-count'
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
def render_count_tokens_payload(messages, model:, **)
|
|
34
|
+
render_payload(messages, model:, temperature: nil, **).slice(:model, :messages, :tools, :response_format)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
def parse_count_tokens_response(response)
|
|
38
|
+
response.body.dig('data', 'total_tokens')
|
|
39
|
+
end
|
|
40
|
+
|
|
41
|
+
def apply_end_user(payload, identifier)
|
|
42
|
+
payload.merge(safety_identifier: identifier)
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
# Moonshot takes prompt_cache_key and prompt_cache_options, and
|
|
46
|
+
# refuses the explicit cache breakpoints OpenAI accepts.
|
|
47
|
+
def openai_prompt_caching?
|
|
48
|
+
false
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def render_thinking(msg)
|
|
52
|
+
text = msg.thinking&.text if msg.role == :assistant
|
|
53
|
+
text ? { reasoning_content: text } : {}
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def supported_attachment?(attachment)
|
|
57
|
+
return attachment.image? || own_video?(attachment) if attachment.provider_file?
|
|
58
|
+
|
|
59
|
+
attachment.video? || Protocols::ChatCompletions::Media.supported_attachment?(
|
|
60
|
+
attachment, document_attachments: :none, audio_attachments: false
|
|
61
|
+
)
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def render_content(content, attachments = [])
|
|
65
|
+
Protocols::ChatCompletions::Media.render_parts(content, attachments) do |attachment|
|
|
66
|
+
if attachment.image?
|
|
67
|
+
render_inline_image(attachment)
|
|
68
|
+
elsif attachment.video?
|
|
69
|
+
render_inline_video(attachment)
|
|
70
|
+
else
|
|
71
|
+
Protocols::ChatCompletions::Media.render_attachment(
|
|
72
|
+
attachment, document_attachments: :none, image_attachments: true, audio_attachments: false
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
def render_inline_image(image)
|
|
79
|
+
{ type: 'image_url', image_url: { url: inline_url(image) } }
|
|
80
|
+
end
|
|
81
|
+
|
|
82
|
+
def own_video?(attachment)
|
|
83
|
+
attachment.video? && attachment.source.provider == 'moonshot'
|
|
84
|
+
end
|
|
85
|
+
|
|
86
|
+
def render_inline_video(video)
|
|
87
|
+
raise UnsupportedAttachmentError, video.mime_type unless supported_attachment?(video)
|
|
88
|
+
|
|
89
|
+
{ type: 'video_url', video_url: { url: inline_url(video) } }
|
|
90
|
+
end
|
|
91
|
+
|
|
92
|
+
# Moonshot reads media only from data URIs and its own file references,
|
|
93
|
+
# so remote files travel inline.
|
|
94
|
+
def inline_url(attachment)
|
|
95
|
+
attachment.provider_file? ? "ms://#{attachment.provider_file_id}" : attachment.for_llm
|
|
96
|
+
end
|
|
97
|
+
end
|
|
98
|
+
end
|
|
99
|
+
end
|
|
100
|
+
end
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require 'stringio'
|
|
4
|
+
|
|
5
|
+
module RubyLLM
|
|
6
|
+
module Providers
|
|
7
|
+
class Moonshot
|
|
8
|
+
# Kimi models cannot read a document attached to a message. Kimi
|
|
9
|
+
# documents one way to ask about a file: extract its text through the
|
|
10
|
+
# Files API and send that text in the conversation.
|
|
11
|
+
# https://platform.kimi.ai/docs/guide/ask-questions-about-pdf-content
|
|
12
|
+
module Documents
|
|
13
|
+
EXTRACT_PURPOSE = 'file-extract'
|
|
14
|
+
|
|
15
|
+
def preprocess_message(message, unsupported_attachment: nil)
|
|
16
|
+
return super unless message.attachments.any? { |attachment| extracted_document?(attachment) }
|
|
17
|
+
|
|
18
|
+
super(message.with_attachments(message.attachments.map do |attachment|
|
|
19
|
+
extracted_document?(attachment) ? extracted_attachment(attachment) : attachment
|
|
20
|
+
end), unsupported_attachment:)
|
|
21
|
+
end
|
|
22
|
+
|
|
23
|
+
# Kimi extracts a whole document without a model, so +model:+ only
|
|
24
|
+
# labels the result. The upload exists only to read it back.
|
|
25
|
+
def ocr(file, model:, pages: nil, provider_options: {})
|
|
26
|
+
raise ArgumentError, "#{@provider.name} extracts whole documents; pages: is not available" if pages
|
|
27
|
+
|
|
28
|
+
track_usage(:ocr) do
|
|
29
|
+
upload = @provider.upload_file(file, purpose: EXTRACT_PURPOSE, provider_options:)
|
|
30
|
+
begin
|
|
31
|
+
text = @provider.download_file(upload.id)
|
|
32
|
+
RubyLLM::OCR.new(pages: [{ 'index' => 0, 'markdown' => text }], model:, raw: upload.metadata)
|
|
33
|
+
ensure
|
|
34
|
+
delete_upload(upload)
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
private
|
|
40
|
+
|
|
41
|
+
def provider_file_upload_options(_attachment)
|
|
42
|
+
{ purpose: EXTRACT_PURPOSE }
|
|
43
|
+
end
|
|
44
|
+
|
|
45
|
+
def extracted_document?(attachment)
|
|
46
|
+
return attachment.source.purpose == EXTRACT_PURPOSE if attachment.provider_file?
|
|
47
|
+
|
|
48
|
+
%i[pdf document].include?(attachment.type)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def extracted_attachment(attachment)
|
|
52
|
+
filename = "#{File.basename(attachment.filename.to_s, '.*')}.md"
|
|
53
|
+
Attachment.new(StringIO.new(extracted_text(attachment)), filename:, config: @config)
|
|
54
|
+
end
|
|
55
|
+
|
|
56
|
+
def extracted_text(attachment)
|
|
57
|
+
return @provider.download_file(attachment.provider_file_id) if attachment.provider_file?
|
|
58
|
+
|
|
59
|
+
download_upload(attachment)
|
|
60
|
+
rescue Error => e
|
|
61
|
+
message = Message.new(role: :user, content: nil, attachments: [attachment])
|
|
62
|
+
raise if attachment.provider_file? || discard_missing_uploads([message], e).empty?
|
|
63
|
+
|
|
64
|
+
download_upload(attachment)
|
|
65
|
+
end
|
|
66
|
+
|
|
67
|
+
def download_upload(attachment)
|
|
68
|
+
@provider.download_file(provider_upload(attachment).id)
|
|
69
|
+
end
|
|
70
|
+
|
|
71
|
+
def delete_upload(upload)
|
|
72
|
+
@connection.delete("files/#{upload.id}")
|
|
73
|
+
rescue Error
|
|
74
|
+
nil
|
|
75
|
+
end
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Providers
|
|
5
|
+
class Moonshot
|
|
6
|
+
# Moonshot's dialect of the OpenAI Responses API, the route to Kimi's
|
|
7
|
+
# server-side web search. It holds no state: store is always false.
|
|
8
|
+
# https://platform.kimi.ai/docs/api/responses
|
|
9
|
+
class Responses < Protocols::Responses
|
|
10
|
+
include Moonshot::Documents
|
|
11
|
+
|
|
12
|
+
SERVER_TOOL_ALIASES = {
|
|
13
|
+
web_search: { tool: { type: 'web_search' } }
|
|
14
|
+
}.freeze
|
|
15
|
+
|
|
16
|
+
def server_tool_aliases
|
|
17
|
+
SERVER_TOOL_ALIASES
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def web_search_sources?
|
|
21
|
+
true
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def apply_end_user(payload, identifier)
|
|
25
|
+
payload.merge(safety_identifier: identifier)
|
|
26
|
+
end
|
|
27
|
+
|
|
28
|
+
# Kimi reads images only from data URIs, so remote images travel inline.
|
|
29
|
+
def render_image(image, image_url: image.for_llm, **)
|
|
30
|
+
super
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def provider_file_references?
|
|
36
|
+
false
|
|
37
|
+
end
|
|
38
|
+
|
|
39
|
+
# Kimi silently drops the web_search tool for a message whose content
|
|
40
|
+
# is a bare string, so text always travels as input_text parts.
|
|
41
|
+
def render_item(msg, caching: nil)
|
|
42
|
+
item = super
|
|
43
|
+
return item unless item.is_a?(Hash) && item[:role] && item[:content].is_a?(String)
|
|
44
|
+
|
|
45
|
+
item.merge(content: [{ type: 'input_text', text: item[:content] }])
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def parse_server_tool_use(response)
|
|
49
|
+
searches = Array(response['output']).count { |item| item['type'] == 'web_search_call' }
|
|
50
|
+
searches.positive? ? { 'web_search_requests' => searches } : {}
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|
|
55
|
+
end
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
module Providers
|
|
5
|
+
# Moonshot AI API integration for the Kimi models.
|
|
6
|
+
class Moonshot < Provider
|
|
7
|
+
# Moonshot's dialect of the Chat Completions API.
|
|
8
|
+
class ChatCompletions < Protocols::ChatCompletions
|
|
9
|
+
include Moonshot::Chat
|
|
10
|
+
include Moonshot::Documents
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
protocol :chat_completions, ChatCompletions, batches: Protocols::ChatCompletions::Batches
|
|
14
|
+
protocol :responses, Moonshot::Responses
|
|
15
|
+
protocol :files, Protocols::Moonshot::Files
|
|
16
|
+
|
|
17
|
+
def api_base
|
|
18
|
+
configured_api_base(:moonshot_api_base, 'https://api.moonshot.ai/v1', endpoints: Protocols::ChatCompletions::ENDPOINT_PATHS)
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def headers
|
|
22
|
+
{ 'Authorization' => "Bearer #{@config.moonshot_api_key}" }
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def account_identity
|
|
26
|
+
account_digest(api_base, @config.moonshot_api_key)
|
|
27
|
+
end
|
|
28
|
+
|
|
29
|
+
def batch_cost_multiplier(**) = 0.6
|
|
30
|
+
|
|
31
|
+
class << self
|
|
32
|
+
def open_telemetry_name # :nodoc:
|
|
33
|
+
'moonshot_ai'
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def models_dev_provider_ids # :nodoc:
|
|
37
|
+
%w[moonshotai]
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def capabilities
|
|
41
|
+
Moonshot::Capabilities
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
def configuration_options
|
|
45
|
+
%i[moonshot_api_key moonshot_api_base]
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def configuration_requirements
|
|
49
|
+
%i[moonshot_api_key]
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
end
|
|
53
|
+
end
|
|
54
|
+
end
|