ruby_llm 2.0.0 → 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 +65 -8
- data/lib/generators/ruby_llm/generator_helpers.rb +68 -6
- data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +1 -0
- data/lib/generators/ruby_llm/install/templates/create_ruby_llm_records_migration.rb.tt +36 -3
- 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 +50 -25
- data/lib/ruby_llm/active_record/batch.rb +1 -1
- data/lib/ruby_llm/active_record/chat_methods.rb +136 -40
- 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 +198 -33
- 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 +488 -82
- 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 +34 -2
- 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 +41 -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 +192 -40
- 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 +9 -41
- data/lib/ruby_llm/protocols/xai/streaming_transcription.rb +2 -5
- data/lib/ruby_llm/provider.rb +87 -15
- 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 +20 -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 +21 -5
- 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 +10 -2
- 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/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 +95 -37
- 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
|
@@ -67,6 +67,11 @@ module RubyLLM
|
|
|
67
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,10 +137,11 @@ module RubyLLM
|
|
|
129
137
|
dup.tap { |message| message.instance_variable_set(:@attachments, wrapped) }
|
|
130
138
|
end
|
|
131
139
|
|
|
132
|
-
def
|
|
140
|
+
def without_native_content(raw_content: nil) # :nodoc:
|
|
133
141
|
dup.tap do |message|
|
|
134
142
|
message.instance_variable_set(:@thinking, nil)
|
|
135
143
|
message.instance_variable_set(:@raw_reasoning, nil)
|
|
144
|
+
message.instance_variable_set(:@raw_content, raw_content)
|
|
136
145
|
message.instance_variable_set(:@tool_calls, tool_calls_without_thought_signatures)
|
|
137
146
|
end
|
|
138
147
|
end
|
|
@@ -209,15 +218,32 @@ module RubyLLM
|
|
|
209
218
|
|
|
210
219
|
# Marks this message as an explicit prompt cache boundary. Providers
|
|
211
220
|
# with boundary controls use the conversation up to and including this
|
|
212
|
-
# 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+.
|
|
213
224
|
#
|
|
214
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")
|
|
215
227
|
#
|
|
216
|
-
def cache_until_here
|
|
228
|
+
def cache_until_here(ttl: nil)
|
|
217
229
|
@cache_until_here = true
|
|
230
|
+
@cache_ttl = ttl
|
|
218
231
|
self
|
|
219
232
|
end
|
|
220
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
|
+
|
|
221
247
|
# Returns +true+ if the message carries an explicit prompt cache
|
|
222
248
|
# boundary, +false+ otherwise.
|
|
223
249
|
def cache_until_here?
|
|
@@ -227,7 +253,8 @@ module RubyLLM
|
|
|
227
253
|
# Returns a Hash of the message's attributes, with token counts merged
|
|
228
254
|
# in as +:input_tokens+, +:output_tokens+, and related keys. Omits
|
|
229
255
|
# +nil+ values and empty attachment and citation lists. Includes +:cost+
|
|
230
|
-
# 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.
|
|
231
258
|
def to_h
|
|
232
259
|
{
|
|
233
260
|
role: role,
|
|
@@ -235,6 +262,7 @@ module RubyLLM
|
|
|
235
262
|
attachments: list_to_h(attachments),
|
|
236
263
|
model: model,
|
|
237
264
|
cost: @supplied_cost && cost.to_h,
|
|
265
|
+
reported_cost: tokens.reported_cost,
|
|
238
266
|
tool_calls: tool_calls&.transform_values(&:to_h),
|
|
239
267
|
tool_call_id: tool_call_id,
|
|
240
268
|
thinking: thinking&.text,
|
|
@@ -243,8 +271,10 @@ module RubyLLM
|
|
|
243
271
|
server_tool_calls: list_to_h(server_tool_calls),
|
|
244
272
|
raw_content: raw_content,
|
|
245
273
|
raw_reasoning: raw_reasoning,
|
|
274
|
+
mcp_result: mcp_result&.dump,
|
|
246
275
|
finish_reason: finish_reason,
|
|
247
|
-
cache_until_here: cache_until_here? || nil
|
|
276
|
+
cache_until_here: cache_until_here? || nil,
|
|
277
|
+
cache_ttl: cache_ttl
|
|
248
278
|
}.merge(tokens.to_h).compact
|
|
249
279
|
end
|
|
250
280
|
|
|
@@ -292,7 +322,8 @@ module RubyLLM
|
|
|
292
322
|
|
|
293
323
|
def coerce_thinking(thinking, signature)
|
|
294
324
|
case thinking
|
|
295
|
-
when
|
|
325
|
+
when Thinking then thinking
|
|
326
|
+
when nil then Thinking.build(signature: signature)
|
|
296
327
|
when Hash then Thinking.build(**thinking.transform_keys(&:to_sym).slice(:text, :signature))
|
|
297
328
|
else Thinking.build(text: thinking.to_s, signature: signature)
|
|
298
329
|
end
|
|
@@ -302,6 +333,10 @@ module RubyLLM
|
|
|
302
333
|
value.is_a?(Hash) ? klass.from_h(value) : value
|
|
303
334
|
end
|
|
304
335
|
|
|
336
|
+
def coerce_mcp_result(value)
|
|
337
|
+
value.is_a?(Hash) ? MCP::Result.load(value.transform_keys(&:to_s)) : value
|
|
338
|
+
end
|
|
339
|
+
|
|
305
340
|
def normalize_content(content)
|
|
306
341
|
return '' if role == :assistant && content.nil? && tool_calls && !tool_calls.empty?
|
|
307
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'
|