ruby_llm 2.0.0.rc4 → 2.1.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 +66 -9
- data/lib/generators/ruby_llm/generator_helpers.rb +72 -7
- data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +1 -1
- data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +2 -1
- data/lib/generators/ruby_llm/install/templates/create_ruby_llm_records_migration.rb.tt +41 -8
- data/lib/generators/ruby_llm/provider/scaffold.rb +32 -22
- data/lib/generators/ruby_llm/provider/templates/gem/ci.yml.erb +1 -5
- data/lib/generators/ruby_llm/provider/templates/gem/gemfile.erb +1 -1
- data/lib/generators/ruby_llm/provider/templates/gem/gemspec.erb +1 -1
- data/lib/generators/ruby_llm/provider/templates/gem/rubocop.yml.erb +1 -1
- data/lib/generators/ruby_llm/upgrade/templates/upgrade_ruby_llm_to_2_1.rb.tt +78 -0
- data/lib/generators/ruby_llm/upgrade/upgrade_generator.rb +6 -163
- data/lib/ruby_llm/accounting/usage.rb +86 -49
- data/lib/ruby_llm/active_record/acts_as.rb +3 -0
- data/lib/ruby_llm/active_record/attachment_helpers.rb +131 -28
- data/lib/ruby_llm/active_record/batch.rb +1 -1
- data/lib/ruby_llm/active_record/chat_methods.rb +145 -46
- data/lib/ruby_llm/active_record/mcp_credential.rb +41 -0
- data/lib/ruby_llm/active_record/message_methods.rb +32 -7
- data/lib/ruby_llm/active_record/model.rb +1 -1
- data/lib/ruby_llm/active_record/provider_file.rb +64 -0
- data/lib/ruby_llm/active_record/record.rb +19 -0
- data/lib/ruby_llm/active_record/tool_call.rb +1 -1
- data/lib/ruby_llm/active_record/usage.rb +53 -3
- data/lib/ruby_llm/agent.rb +213 -48
- data/lib/ruby_llm/aliases.json +66 -8
- data/lib/ruby_llm/attachment.rb +54 -10
- data/lib/ruby_llm/batch.rb +26 -3
- data/lib/ruby_llm/chat/tool_concurrency.rb +47 -23
- data/lib/ruby_llm/chat/tool_search.rb +26 -0
- data/lib/ruby_llm/chat.rb +502 -96
- data/lib/ruby_llm/choice.rb +38 -0
- data/lib/ruby_llm/configuration.rb +36 -11
- data/lib/ruby_llm/context.rb +45 -32
- data/lib/ruby_llm/cost.rb +42 -1
- data/lib/ruby_llm/embedding.rb +9 -2
- data/lib/ruby_llm/error.rb +35 -3
- data/lib/ruby_llm/evaluation/assertions.rb +40 -0
- data/lib/ruby_llm/evaluation/case.rb +52 -0
- data/lib/ruby_llm/evaluation/dataset.rb +65 -0
- data/lib/ruby_llm/evaluation/evaluator.rb +126 -0
- data/lib/ruby_llm/evaluation/evidence.rb +125 -0
- data/lib/ruby_llm/evaluation/minitest.rb +26 -0
- data/lib/ruby_llm/evaluation/report.rb +103 -0
- data/lib/ruby_llm/evaluation/result.rb +75 -0
- data/lib/ruby_llm/evaluation/reviewer.rb +20 -0
- data/lib/ruby_llm/evaluation/rspec.rb +23 -0
- data/lib/ruby_llm/evaluation/runner.rb +93 -0
- data/lib/ruby_llm/evaluation/trial.rb +101 -0
- data/lib/ruby_llm/evaluation.rb +289 -0
- data/lib/ruby_llm/image.rb +12 -4
- data/lib/ruby_llm/judge/builder.rb +51 -0
- data/lib/ruby_llm/judge/data.rb +43 -0
- data/lib/ruby_llm/judge/question.rb +101 -0
- data/lib/ruby_llm/judge.rb +179 -0
- data/lib/ruby_llm/judgment.rb +125 -0
- data/lib/ruby_llm/mcp/apps.rb +25 -0
- data/lib/ruby_llm/mcp/client.rb +213 -0
- data/lib/ruby_llm/mcp/collection.rb +56 -0
- data/lib/ruby_llm/mcp/content.rb +44 -0
- data/lib/ruby_llm/mcp/error.rb +27 -0
- data/lib/ruby_llm/mcp/http.rb +379 -0
- data/lib/ruby_llm/mcp/input_request.rb +118 -0
- data/lib/ruby_llm/mcp/input_required_error.rb +29 -0
- data/lib/ruby_llm/mcp/listener.rb +168 -0
- data/lib/ruby_llm/mcp/oauth/key.rb +78 -0
- data/lib/ruby_llm/mcp/oauth/memory_store.rb +28 -0
- data/lib/ruby_llm/mcp/oauth/proofs.rb +39 -0
- data/lib/ruby_llm/mcp/oauth.rb +653 -0
- data/lib/ruby_llm/mcp/param_headers.rb +83 -0
- data/lib/ruby_llm/mcp/prompt.rb +71 -0
- data/lib/ruby_llm/mcp/resource.rb +84 -0
- data/lib/ruby_llm/mcp/resource_template.rb +99 -0
- data/lib/ruby_llm/mcp/result.rb +76 -0
- data/lib/ruby_llm/mcp/session_expired.rb +10 -0
- data/lib/ruby_llm/mcp/stdio.rb +250 -0
- data/lib/ruby_llm/mcp/task.rb +151 -0
- data/lib/ruby_llm/mcp/tool.rb +130 -0
- data/lib/ruby_llm/mcp.rb +1101 -0
- data/lib/ruby_llm/message.rb +55 -6
- data/lib/ruby_llm/model.rb +2 -1
- data/lib/ruby_llm/models/aliases.rb +5 -2
- data/lib/ruby_llm/models/lookup.rb +80 -0
- data/lib/ruby_llm/models/registry.rb +2 -2
- data/lib/ruby_llm/models/schema.rb +1 -1
- data/lib/ruby_llm/models.json +23082 -10158
- data/lib/ruby_llm/models.rb +20 -69
- data/lib/ruby_llm/moderation.rb +7 -2
- data/lib/ruby_llm/ocr.rb +7 -2
- data/lib/ruby_llm/open_telemetry/attributes.rb +111 -0
- data/lib/ruby_llm/open_telemetry.rb +82 -0
- data/lib/ruby_llm/probability.rb +30 -0
- data/lib/ruby_llm/progress.rb +44 -0
- data/lib/ruby_llm/prompt/context.rb +55 -0
- data/lib/ruby_llm/prompt.rb +1 -1
- data/lib/ruby_llm/protocol/binary_streaming.rb +1 -1
- data/lib/ruby_llm/protocol/request_shapes/analysis.rb +190 -0
- data/lib/ruby_llm/protocol/request_shapes.rb +183 -0
- data/lib/ruby_llm/protocol/stored_uploads.rb +62 -0
- data/lib/ruby_llm/protocol/stream_accumulator.rb +9 -25
- data/lib/ruby_llm/protocol/streaming.rb +38 -31
- data/lib/ruby_llm/protocol.rb +217 -42
- data/lib/ruby_llm/protocols/anthropic/chat.rb +39 -32
- data/lib/ruby_llm/protocols/anthropic/media.rb +6 -2
- data/lib/ruby_llm/protocols/anthropic/models.rb +16 -1
- data/lib/ruby_llm/protocols/anthropic/request_shapes.rb +70 -0
- data/lib/ruby_llm/protocols/anthropic/streaming.rb +23 -9
- data/lib/ruby_llm/protocols/anthropic/tools.rb +43 -0
- data/lib/ruby_llm/protocols/anthropic.rb +20 -1
- data/lib/ruby_llm/protocols/bedrock/async_videos.rb +9 -17
- data/lib/ruby_llm/protocols/bedrock/guardrails.rb +8 -9
- data/lib/ruby_llm/protocols/bedrock/rerank.rb +1 -17
- data/lib/ruby_llm/protocols/chat_completions/chat.rb +5 -7
- data/lib/ruby_llm/protocols/chat_completions/media.rb +34 -8
- data/lib/ruby_llm/protocols/chat_completions/request_shapes.rb +121 -0
- data/lib/ruby_llm/protocols/chat_completions/streaming.rb +3 -0
- data/lib/ruby_llm/protocols/chat_completions.rb +1 -0
- data/lib/ruby_llm/protocols/cohere/batch_requests.rb +0 -5
- data/lib/ruby_llm/protocols/cohere/batches.rb +3 -7
- data/lib/ruby_llm/protocols/cohere/chat.rb +8 -0
- data/lib/ruby_llm/protocols/cohere/embeddings.rb +7 -4
- data/lib/ruby_llm/protocols/cohere/media.rb +6 -2
- data/lib/ruby_llm/protocols/cohere/ocr.rb +1 -1
- data/lib/ruby_llm/protocols/cohere/streaming.rb +1 -0
- data/lib/ruby_llm/protocols/cohere/tools.rb +5 -1
- data/lib/ruby_llm/protocols/cohere.rb +1 -0
- data/lib/ruby_llm/protocols/converse/chat.rb +22 -131
- data/lib/ruby_llm/protocols/converse/media.rb +16 -2
- data/lib/ruby_llm/protocols/converse/reasoning.rb +194 -0
- data/lib/ruby_llm/protocols/converse/request_shapes.rb +99 -0
- data/lib/ruby_llm/protocols/converse/streaming.rb +2 -2
- data/lib/ruby_llm/protocols/converse/thinking_stream.rb +2 -1
- data/lib/ruby_llm/protocols/converse.rb +3 -5
- data/lib/ruby_llm/protocols/deepseek/files.rb +2 -9
- data/lib/ruby_llm/protocols/elevenlabs/flows/images.rb +1 -7
- data/lib/ruby_llm/protocols/elevenlabs/flows/videos.rb +11 -17
- data/lib/ruby_llm/protocols/elevenlabs/streaming_transcription.rb +1 -4
- data/lib/ruby_llm/protocols/gemini/chat.rb +50 -21
- data/lib/ruby_llm/protocols/gemini/embeddings.rb +2 -2
- data/lib/ruby_llm/protocols/gemini/files.rb +1 -1
- data/lib/ruby_llm/protocols/gemini/images.rb +5 -1
- data/lib/ruby_llm/protocols/gemini/media.rb +15 -3
- data/lib/ruby_llm/protocols/gemini/request_shapes.rb +111 -0
- data/lib/ruby_llm/protocols/gemini/streaming.rb +2 -1
- data/lib/ruby_llm/protocols/gemini/tools.rb +17 -11
- data/lib/ruby_llm/protocols/gemini/transcription.rb +13 -0
- data/lib/ruby_llm/protocols/gemini/videos.rb +7 -2
- data/lib/ruby_llm/protocols/gemini.rb +1 -0
- data/lib/ruby_llm/protocols/gpustack/videos.rb +7 -9
- data/lib/ruby_llm/protocols/interactions/chat.rb +46 -18
- data/lib/ruby_llm/protocols/interactions/content.rb +9 -2
- data/lib/ruby_llm/protocols/interactions/request_shapes.rb +62 -0
- data/lib/ruby_llm/protocols/interactions/streaming.rb +15 -0
- data/lib/ruby_llm/protocols/interactions/tools.rb +28 -4
- data/lib/ruby_llm/protocols/interactions/transcription.rb +1 -11
- data/lib/ruby_llm/protocols/interactions.rb +1 -0
- data/lib/ruby_llm/protocols/invoke_model/cohere_embeddings.rb +2 -11
- data/lib/ruby_llm/protocols/invoke_model/embedding_batches.rb +2 -2
- data/lib/ruby_llm/protocols/invoke_model/stability_images.rb +2 -6
- data/lib/ruby_llm/protocols/invoke_model/titan_text_embeddings.rb +2 -8
- data/lib/ruby_llm/protocols/invoke_model.rb +1 -4
- data/lib/ruby_llm/protocols/mistral/content.rb +13 -0
- data/lib/ruby_llm/protocols/mistral/conversations/chat.rb +11 -1
- data/lib/ruby_llm/protocols/mistral/conversations/images.rb +5 -1
- data/lib/ruby_llm/protocols/mistral/conversations/request_shapes.rb +45 -0
- data/lib/ruby_llm/protocols/mistral/conversations.rb +1 -0
- data/lib/ruby_llm/protocols/mistral/multi_completion.rb +11 -1
- data/lib/ruby_llm/protocols/openai/decisions.rb +135 -0
- data/lib/ruby_llm/protocols/openai/files.rb +0 -7
- data/lib/ruby_llm/protocols/openrouter/batches.rb +13 -8
- data/lib/ruby_llm/protocols/openrouter/responses.rb +5 -1
- data/lib/ruby_llm/protocols/perplexity/agent.rb +116 -0
- data/lib/ruby_llm/protocols/perplexity/router.rb +2 -43
- data/lib/ruby_llm/protocols/responses/chat.rb +56 -22
- data/lib/ruby_llm/protocols/responses/media.rb +11 -7
- data/lib/ruby_llm/protocols/responses/request_shapes.rb +70 -0
- data/lib/ruby_llm/protocols/responses/streaming.rb +23 -7
- data/lib/ruby_llm/protocols/responses/token_counting.rb +2 -2
- data/lib/ruby_llm/protocols/responses/tools.rb +27 -0
- data/lib/ruby_llm/protocols/responses.rb +1 -0
- data/lib/ruby_llm/protocols/system_one/judgments.rb +42 -0
- data/lib/ruby_llm/protocols/system_one/models.rb +27 -0
- data/lib/ruby_llm/protocols/system_one/responses.rb +55 -0
- data/lib/ruby_llm/protocols/system_one.rb +27 -0
- data/lib/ruby_llm/protocols/vertexai/embedding_prediction/requests.rb +2 -2
- data/lib/ruby_llm/protocols/vertexai/files.rb +34 -6
- data/lib/ruby_llm/protocols/vertexai/ranking.rb +9 -30
- data/lib/ruby_llm/protocols/vertexai/research.rb +17 -49
- data/lib/ruby_llm/protocols/xai/streaming_transcription.rb +2 -5
- data/lib/ruby_llm/provider.rb +91 -19
- data/lib/ruby_llm/providers/anthropic.rb +23 -0
- data/lib/ruby_llm/providers/azure/chat.rb +4 -0
- data/lib/ruby_llm/providers/azure/images.rb +2 -2
- data/lib/ruby_llm/providers/azure/media.rb +8 -3
- data/lib/ruby_llm/providers/azure/videos.rb +7 -2
- data/lib/ruby_llm/providers/azure.rb +24 -4
- data/lib/ruby_llm/providers/bedrock/auth.rb +1 -1
- data/lib/ruby_llm/providers/bedrock/mantle/anthropic.rb +1 -1
- data/lib/ruby_llm/providers/bedrock/mantle/voxtral.rb +1 -3
- data/lib/ruby_llm/providers/bedrock/mantle.rb +5 -9
- data/lib/ruby_llm/providers/bedrock.rb +38 -6
- data/lib/ruby_llm/providers/deepseek/chat.rb +6 -1
- data/lib/ruby_llm/providers/deepseek/responses.rb +6 -0
- data/lib/ruby_llm/providers/deepseek.rb +4 -0
- data/lib/ruby_llm/providers/gemini.rb +4 -0
- data/lib/ruby_llm/providers/gpustack/media.rb +8 -5
- data/lib/ruby_llm/providers/hetzner/chat.rb +39 -0
- data/lib/ruby_llm/providers/hetzner.rb +41 -0
- data/lib/ruby_llm/providers/mistral/chat.rb +16 -12
- data/lib/ruby_llm/providers/mistral/media.rb +8 -4
- data/lib/ruby_llm/providers/mistral/ocr.rb +1 -5
- data/lib/ruby_llm/providers/ollama/judgments.rb +26 -0
- data/lib/ruby_llm/providers/ollama/media.rb +6 -2
- data/lib/ruby_llm/providers/ollama/models.rb +2 -0
- data/lib/ruby_llm/providers/ollama.rb +11 -0
- data/lib/ruby_llm/providers/openai/capabilities.rb +3 -1
- data/lib/ruby_llm/providers/openai.rb +14 -0
- data/lib/ruby_llm/providers/openrouter/chat.rb +24 -24
- data/lib/ruby_llm/providers/openrouter/embeddings.rb +2 -0
- data/lib/ruby_llm/providers/openrouter/media.rb +5 -1
- data/lib/ruby_llm/providers/openrouter/streaming.rb +4 -1
- data/lib/ruby_llm/providers/openrouter/videos.rb +10 -5
- data/lib/ruby_llm/providers/openrouter.rb +4 -0
- data/lib/ruby_llm/providers/perplexity/chat.rb +4 -0
- data/lib/ruby_llm/providers/perplexity/media.rb +5 -11
- data/lib/ruby_llm/providers/perplexity/models.rb +8 -4
- data/lib/ruby_llm/providers/perplexity.rb +11 -0
- data/lib/ruby_llm/providers/typesafe.rb +32 -0
- data/lib/ruby_llm/providers/vertexai/credentials.rb +63 -0
- data/lib/ruby_llm/providers/vertexai/gemini.rb +2 -2
- data/lib/ruby_llm/providers/vertexai/transcription.rb +1 -0
- data/lib/ruby_llm/providers/vertexai.rb +27 -28
- data/lib/ruby_llm/providers/xai/chat.rb +6 -0
- data/lib/ruby_llm/providers/xai/images.rb +9 -10
- data/lib/ruby_llm/providers/xai/responses.rb +10 -8
- data/lib/ruby_llm/providers/xai/videos.rb +11 -9
- data/lib/ruby_llm/providers/xai.rb +4 -0
- data/lib/ruby_llm/railtie.rb +11 -0
- data/lib/ruby_llm/request_shape/part.rb +117 -0
- data/lib/ruby_llm/request_shape/problem.rb +63 -0
- data/lib/ruby_llm/request_shape/tool_round.rb +64 -0
- data/lib/ruby_llm/request_shape/turn.rb +54 -0
- data/lib/ruby_llm/request_shape.rb +160 -0
- data/lib/ruby_llm/rerank.rb +9 -2
- data/lib/ruby_llm/research_job.rb +23 -7
- data/lib/ruby_llm/score.rb +42 -0
- data/lib/ruby_llm/search_results.rb +2 -2
- data/lib/ruby_llm/server_tool_call.rb +12 -4
- data/lib/ruby_llm/speech.rb +8 -1
- data/lib/ruby_llm/support/cancellation.rb +39 -0
- data/lib/ruby_llm/support/inspectable.rb +3 -2
- data/lib/ruby_llm/support/instrumentation.rb +38 -1
- data/lib/ruby_llm/support/process_cache.rb +60 -0
- data/lib/ruby_llm/support/progress_reporter.rb +33 -0
- data/lib/ruby_llm/thinking.rb +2 -1
- data/lib/ruby_llm/tokens.rb +57 -14
- data/lib/ruby_llm/tool.rb +50 -3
- data/lib/ruby_llm/tool_call.rb +17 -2
- data/lib/ruby_llm/tools/{server_tools.rb → provider_tools.rb} +7 -7
- data/lib/ruby_llm/transcription.rb +10 -3
- data/lib/ruby_llm/transport/connection.rb +127 -44
- data/lib/ruby_llm/transport/error_middleware.rb +90 -24
- data/lib/ruby_llm/transport/event_stream_parser.rb +121 -0
- data/lib/ruby_llm/transport/json_response.rb +33 -0
- data/lib/ruby_llm/transport/signing_middleware.rb +29 -0
- data/lib/ruby_llm/version.rb +1 -1
- data/lib/ruby_llm/video.rb +5 -3
- data/lib/ruby_llm/video_job.rb +48 -5
- data/lib/ruby_llm.rb +106 -5
- data/lib/tasks/ruby_llm/evaluations.rb +43 -0
- data/lib/tasks/ruby_llm.rake +6 -9
- data/skills/rubyllm/SKILL.md +33 -12
- metadata +96 -38
- data/lib/generators/ruby_llm/upgrade/legacy_content_sql.rb +0 -34
- data/lib/generators/ruby_llm/upgrade/online_copy_migration/data.rb +0 -341
- data/lib/generators/ruby_llm/upgrade/online_copy_migration/journal.rb +0 -171
- data/lib/generators/ruby_llm/upgrade/online_copy_migration/verification.rb +0 -197
- data/lib/generators/ruby_llm/upgrade/online_copy_migration.rb +0 -174
- data/lib/generators/ruby_llm/upgrade/templates/backfill_v2_data.rb.tt +0 -468
- data/lib/generators/ruby_llm/upgrade/templates/cleanup_v2_upgrade.rb.tt +0 -101
- data/lib/generators/ruby_llm/upgrade/templates/finish_v2_upgrade.rb.tt +0 -247
- data/lib/generators/ruby_llm/upgrade/templates/prepare_v2_upgrade.rb.tt +0 -662
- data/lib/generators/ruby_llm/upgrade/templates/ruby_llm_upgrade.rb.tt +0 -251
- data/lib/generators/ruby_llm/upgrade/templates/upgrade_initializer.rb.tt +0 -13
- data/lib/generators/ruby_llm/upgrade/upgrade_migration.rb +0 -359
data/lib/ruby_llm/message.rb
CHANGED
|
@@ -64,9 +64,14 @@ module RubyLLM
|
|
|
64
64
|
|
|
65
65
|
# The provider-executed tool steps in this response, as an array of
|
|
66
66
|
# ServerToolCall objects. Empty unless the chat enabled tools with
|
|
67
|
-
# Chat#
|
|
67
|
+
# Chat#with_provider_tools and the model used one.
|
|
68
68
|
attr_reader :server_tool_calls
|
|
69
69
|
|
|
70
|
+
# The MCP::Result behind a tool result message, when the MCP tool that
|
|
71
|
+
# returned it has a UI and did not fail, so your app can render the UI
|
|
72
|
+
# again with the result. +nil+ otherwise.
|
|
73
|
+
attr_reader :mcp_result
|
|
74
|
+
|
|
70
75
|
# The provider-shaped content blocks of this assistant message, kept
|
|
71
76
|
# verbatim when the response used server tools so later requests can
|
|
72
77
|
# replay the turn exactly. +nil+ otherwise.
|
|
@@ -95,6 +100,7 @@ module RubyLLM
|
|
|
95
100
|
output: options[:output_tokens],
|
|
96
101
|
cache_read: options[:cache_read_tokens],
|
|
97
102
|
cache_write: options[:cache_write_tokens],
|
|
103
|
+
cache_write_by_ttl: options[:cache_write_tokens_by_ttl],
|
|
98
104
|
thinking: options[:thinking_tokens],
|
|
99
105
|
server_tool_use: options[:server_tool_use],
|
|
100
106
|
reported_cost: options[:reported_cost]
|
|
@@ -105,9 +111,11 @@ module RubyLLM
|
|
|
105
111
|
@server_tool_calls = Array(options[:server_tool_calls]).map { |call| coerce_value(call, ServerToolCall) }
|
|
106
112
|
@raw_content = options[:raw_content]
|
|
107
113
|
@raw_reasoning = options[:raw_reasoning]
|
|
114
|
+
@mcp_result = coerce_mcp_result(options[:mcp_result])
|
|
108
115
|
@finish_reason = options[:finish_reason]&.to_sym
|
|
109
116
|
self.ruby_llm_usage_entries = options[:usage_entries] if options[:usage_entries]
|
|
110
117
|
@cache_until_here = options.fetch(:cache_until_here, false)
|
|
118
|
+
@cache_ttl = options[:cache_ttl] if @cache_until_here
|
|
111
119
|
|
|
112
120
|
ensure_valid_role
|
|
113
121
|
end
|
|
@@ -129,6 +137,15 @@ module RubyLLM
|
|
|
129
137
|
dup.tap { |message| message.instance_variable_set(:@attachments, wrapped) }
|
|
130
138
|
end
|
|
131
139
|
|
|
140
|
+
def without_native_content(raw_content: nil) # :nodoc:
|
|
141
|
+
dup.tap do |message|
|
|
142
|
+
message.instance_variable_set(:@thinking, nil)
|
|
143
|
+
message.instance_variable_set(:@raw_reasoning, nil)
|
|
144
|
+
message.instance_variable_set(:@raw_content, raw_content)
|
|
145
|
+
message.instance_variable_set(:@tool_calls, tool_calls_without_thought_signatures)
|
|
146
|
+
end
|
|
147
|
+
end
|
|
148
|
+
|
|
132
149
|
# Returns +true+ if the assistant requested one or more tool calls,
|
|
133
150
|
# +false+ otherwise.
|
|
134
151
|
def tool_call?
|
|
@@ -201,15 +218,32 @@ module RubyLLM
|
|
|
201
218
|
|
|
202
219
|
# Marks this message as an explicit prompt cache boundary. Providers
|
|
203
220
|
# with boundary controls use the conversation up to and including this
|
|
204
|
-
# message as the cacheable prefix.
|
|
221
|
+
# message as the cacheable prefix. Pass +ttl:+ to give this boundary its
|
|
222
|
+
# own cache lifetime instead of the +ttl:+ from Chat#with_caching, on
|
|
223
|
+
# providers that set a lifetime per boundary. Returns +self+.
|
|
205
224
|
#
|
|
206
225
|
# chat.add_message(role: :user, content: long_context).cache_until_here
|
|
226
|
+
# chat.add_message(role: :user, content: handbook).cache_until_here(ttl: "1h")
|
|
207
227
|
#
|
|
208
|
-
def cache_until_here
|
|
228
|
+
def cache_until_here(ttl: nil)
|
|
209
229
|
@cache_until_here = true
|
|
230
|
+
@cache_ttl = ttl
|
|
210
231
|
self
|
|
211
232
|
end
|
|
212
233
|
|
|
234
|
+
# Returns the cache lifetime given to this message's boundary, such as
|
|
235
|
+
# <tt>"1h"</tt>, or +nil+ when the chat's caching policy applies.
|
|
236
|
+
attr_reader :cache_ttl
|
|
237
|
+
|
|
238
|
+
def self.cache_boundary_options(value) # :nodoc:
|
|
239
|
+
return {} if value == true
|
|
240
|
+
|
|
241
|
+
options = value.transform_keys(&:to_sym) if value.is_a?(Hash)
|
|
242
|
+
return options if options && (options.keys - %i[ttl]).empty?
|
|
243
|
+
|
|
244
|
+
raise ArgumentError, "cache_until_here accepts true, false, or ttl:, got #{value.inspect}"
|
|
245
|
+
end
|
|
246
|
+
|
|
213
247
|
# Returns +true+ if the message carries an explicit prompt cache
|
|
214
248
|
# boundary, +false+ otherwise.
|
|
215
249
|
def cache_until_here?
|
|
@@ -219,7 +253,8 @@ module RubyLLM
|
|
|
219
253
|
# Returns a Hash of the message's attributes, with token counts merged
|
|
220
254
|
# in as +:input_tokens+, +:output_tokens+, and related keys. Omits
|
|
221
255
|
# +nil+ values and empty attachment and citation lists. Includes +:cost+
|
|
222
|
-
# only when supplied explicitly, preserving unknown costs on round-trip
|
|
256
|
+
# only when supplied explicitly, preserving unknown costs on round-trip,
|
|
257
|
+
# and +:reported_cost+ when the provider reported its own price.
|
|
223
258
|
def to_h
|
|
224
259
|
{
|
|
225
260
|
role: role,
|
|
@@ -227,6 +262,7 @@ module RubyLLM
|
|
|
227
262
|
attachments: list_to_h(attachments),
|
|
228
263
|
model: model,
|
|
229
264
|
cost: @supplied_cost && cost.to_h,
|
|
265
|
+
reported_cost: tokens.reported_cost,
|
|
230
266
|
tool_calls: tool_calls&.transform_values(&:to_h),
|
|
231
267
|
tool_call_id: tool_call_id,
|
|
232
268
|
thinking: thinking&.text,
|
|
@@ -235,8 +271,10 @@ module RubyLLM
|
|
|
235
271
|
server_tool_calls: list_to_h(server_tool_calls),
|
|
236
272
|
raw_content: raw_content,
|
|
237
273
|
raw_reasoning: raw_reasoning,
|
|
274
|
+
mcp_result: mcp_result&.dump,
|
|
238
275
|
finish_reason: finish_reason,
|
|
239
|
-
cache_until_here: cache_until_here? || nil
|
|
276
|
+
cache_until_here: cache_until_here? || nil,
|
|
277
|
+
cache_ttl: cache_ttl
|
|
240
278
|
}.merge(tokens.to_h).compact
|
|
241
279
|
end
|
|
242
280
|
|
|
@@ -276,9 +314,16 @@ module RubyLLM
|
|
|
276
314
|
end
|
|
277
315
|
end
|
|
278
316
|
|
|
317
|
+
def tool_calls_without_thought_signatures
|
|
318
|
+
tool_calls&.transform_values do |call|
|
|
319
|
+
call.dup.tap { |copy| copy.thought_signature = nil }
|
|
320
|
+
end
|
|
321
|
+
end
|
|
322
|
+
|
|
279
323
|
def coerce_thinking(thinking, signature)
|
|
280
324
|
case thinking
|
|
281
|
-
when
|
|
325
|
+
when Thinking then thinking
|
|
326
|
+
when nil then Thinking.build(signature: signature)
|
|
282
327
|
when Hash then Thinking.build(**thinking.transform_keys(&:to_sym).slice(:text, :signature))
|
|
283
328
|
else Thinking.build(text: thinking.to_s, signature: signature)
|
|
284
329
|
end
|
|
@@ -288,6 +333,10 @@ module RubyLLM
|
|
|
288
333
|
value.is_a?(Hash) ? klass.from_h(value) : value
|
|
289
334
|
end
|
|
290
335
|
|
|
336
|
+
def coerce_mcp_result(value)
|
|
337
|
+
value.is_a?(Hash) ? MCP::Result.load(value.transform_keys(&:to_s)) : value
|
|
338
|
+
end
|
|
339
|
+
|
|
291
340
|
def normalize_content(content)
|
|
292
341
|
return '' if role == :assistant && content.nil? && tool_calls && !tool_calls.empty?
|
|
293
342
|
return content if content.nil? || content.is_a?(String)
|
data/lib/ruby_llm/model.rb
CHANGED
|
@@ -171,7 +171,7 @@ module RubyLLM
|
|
|
171
171
|
|
|
172
172
|
# Returns the model's primary function, inferred from its output
|
|
173
173
|
# modalities: +:chat+, +:embedding+, +:moderation+, +:image+, +:audio+,
|
|
174
|
-
# +:video+, or +:
|
|
174
|
+
# +:video+, +:rerank+, or +:judgment+.
|
|
175
175
|
def type
|
|
176
176
|
output = modalities.output
|
|
177
177
|
return :embedding if output.include?('embeddings')
|
|
@@ -180,6 +180,7 @@ module RubyLLM
|
|
|
180
180
|
return :audio if output.include?('audio')
|
|
181
181
|
return :video if output.include?('video')
|
|
182
182
|
return :rerank if output.include?('rerank')
|
|
183
|
+
return :judgment if output.include?('judgment')
|
|
183
184
|
|
|
184
185
|
:chat
|
|
185
186
|
end
|
|
@@ -5,6 +5,9 @@ require 'json'
|
|
|
5
5
|
module RubyLLM
|
|
6
6
|
class Models
|
|
7
7
|
class Aliases # :nodoc:
|
|
8
|
+
LOAD_LOCK = Mutex.new
|
|
9
|
+
private_constant :LOAD_LOCK
|
|
10
|
+
|
|
8
11
|
class << self
|
|
9
12
|
def resolve(model_id, provider = nil)
|
|
10
13
|
return model_id unless aliases[model_id]
|
|
@@ -17,7 +20,7 @@ module RubyLLM
|
|
|
17
20
|
end
|
|
18
21
|
|
|
19
22
|
def aliases
|
|
20
|
-
@aliases ||= load_aliases
|
|
23
|
+
@aliases || LOAD_LOCK.synchronize { @aliases ||= load_aliases }
|
|
21
24
|
end
|
|
22
25
|
|
|
23
26
|
def aliases_file
|
|
@@ -26,7 +29,7 @@ module RubyLLM
|
|
|
26
29
|
|
|
27
30
|
def load_aliases
|
|
28
31
|
if File.exist?(aliases_file)
|
|
29
|
-
JSON.parse(File.read(aliases_file))
|
|
32
|
+
JSON.parse(File.read(aliases_file, encoding: Encoding::UTF_8))
|
|
30
33
|
else
|
|
31
34
|
{}
|
|
32
35
|
end
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
class Models
|
|
5
|
+
# Finds registry entries by model id, resolving aliases, provider
|
|
6
|
+
# registry ids, and deployments, and preferring first-party providers.
|
|
7
|
+
module Lookup # :nodoc:
|
|
8
|
+
private
|
|
9
|
+
|
|
10
|
+
def models_by_id
|
|
11
|
+
models = @models
|
|
12
|
+
index = @model_index
|
|
13
|
+
index = @model_index = [models, models.group_by(&:id)] unless index && index.first.equal?(models)
|
|
14
|
+
index.last
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def find_with_provider(model_id, provider, config = nil)
|
|
18
|
+
deployed_id = Provider.resolve(provider)&.deployed_model_id(model_id, config || RubyLLM.config)
|
|
19
|
+
return find_registered(model_id, provider, config) unless deployed_id
|
|
20
|
+
|
|
21
|
+
Model.new(find_deployed_model(model_id, deployed_id, provider, config).to_h.merge(id: model_id))
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
def find_deployed_model(deployment, model_id, provider, config)
|
|
25
|
+
find_registered(model_id, provider, config)
|
|
26
|
+
rescue ModelNotFoundError
|
|
27
|
+
raise ConfigurationError, "Deployment #{deployment.inspect} points to unknown model #{model_id.inspect} " \
|
|
28
|
+
"for provider: #{provider.inspect}. #{refresh_registry_guidance}"
|
|
29
|
+
end
|
|
30
|
+
|
|
31
|
+
def find_registered(model_id, provider, config)
|
|
32
|
+
resolved_id = Aliases.resolve(model_id, provider)
|
|
33
|
+
resolved_id = resolve_provider_registry_id(resolved_id, provider, config)
|
|
34
|
+
index = models_by_id
|
|
35
|
+
Array(index[resolved_id]).find { |m| m.provider == provider.to_s } ||
|
|
36
|
+
Array(index[model_id]).find { |m| m.provider == provider.to_s } ||
|
|
37
|
+
raise_model_not_found(model_id, provider: provider)
|
|
38
|
+
end
|
|
39
|
+
|
|
40
|
+
def resolve_provider_registry_id(model_id, provider, config = nil)
|
|
41
|
+
provider_class = Provider.resolve(provider)
|
|
42
|
+
return model_id unless provider_class
|
|
43
|
+
|
|
44
|
+
provider_class.resolve_registry_id(model_id, self, config || RubyLLM.config)
|
|
45
|
+
end
|
|
46
|
+
|
|
47
|
+
# A name can be one provider's exact id and another's alias:
|
|
48
|
+
# claude-opus-4 is exact on vertexai, an alias on anthropic.
|
|
49
|
+
# Provider preference settles it, not the kind of match.
|
|
50
|
+
def find_without_provider(model_id)
|
|
51
|
+
resolved_id = Aliases.resolve(model_id)
|
|
52
|
+
index = models_by_id
|
|
53
|
+
matches = Array(index[model_id])
|
|
54
|
+
matches += Array(index[resolved_id]) unless resolved_id == model_id
|
|
55
|
+
|
|
56
|
+
preferred_match(matches) || raise_model_not_found(model_id)
|
|
57
|
+
end
|
|
58
|
+
|
|
59
|
+
def raise_model_not_found(model_id, provider: nil)
|
|
60
|
+
message = "Unknown model: #{model_id.inspect}"
|
|
61
|
+
message = "#{message} for provider: #{provider.inspect}" if provider
|
|
62
|
+
|
|
63
|
+
raise ModelNotFoundError, "#{message}. #{refresh_registry_guidance}"
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def refresh_registry_guidance
|
|
67
|
+
'If the model exists at the provider, refresh the registry with `RubyLLM.models.refresh`.'
|
|
68
|
+
end
|
|
69
|
+
|
|
70
|
+
def preferred_match(candidates)
|
|
71
|
+
return candidates.first if candidates.size == 1
|
|
72
|
+
|
|
73
|
+
candidates.min_by do |model|
|
|
74
|
+
index = PROVIDER_PREFERENCE.index(model.provider)
|
|
75
|
+
[model.unlisted? ? 1 : 0, index || PROVIDER_PREFERENCE.length]
|
|
76
|
+
end
|
|
77
|
+
end
|
|
78
|
+
end
|
|
79
|
+
end
|
|
80
|
+
end
|
|
@@ -94,7 +94,7 @@ module RubyLLM
|
|
|
94
94
|
|
|
95
95
|
def fetch(etag: nil)
|
|
96
96
|
connection = RubyLLM::Transport::Connection.basic do |faraday|
|
|
97
|
-
faraday.
|
|
97
|
+
faraday.use Transport::JsonResponse, parser_options: { symbolize_names: true }
|
|
98
98
|
end
|
|
99
99
|
response = connection.get(url) do |request|
|
|
100
100
|
request.headers['If-None-Match'] = etag if etag
|
|
@@ -116,7 +116,7 @@ module RubyLLM
|
|
|
116
116
|
module_function
|
|
117
117
|
|
|
118
118
|
def read(file)
|
|
119
|
-
data = JSON.parse(File.read(file), symbolize_names: true)
|
|
119
|
+
data = JSON.parse(File.read(file, encoding: Encoding::UTF_8), symbolize_names: true)
|
|
120
120
|
models_from_data(data, source: file)
|
|
121
121
|
rescue Errno::ENOENT
|
|
122
122
|
nil
|
|
@@ -13,7 +13,7 @@ module RubyLLM
|
|
|
13
13
|
structured_output predicted_outputs
|
|
14
14
|
distillation fine_tuning batch realtime image_generation
|
|
15
15
|
speech_generation transcription translation citations reasoning
|
|
16
|
-
caching moderation json_mode vision video ocr
|
|
16
|
+
caching moderation json_mode vision video ocr judgment
|
|
17
17
|
].freeze
|
|
18
18
|
|
|
19
19
|
description 'A model entry in the RubyLLM model registry'
|