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/attachment.rb
CHANGED
|
@@ -30,6 +30,13 @@ module RubyLLM
|
|
|
30
30
|
# The detected MIME type string, such as <tt>"image/png"</tt>.
|
|
31
31
|
attr_reader :mime_type
|
|
32
32
|
|
|
33
|
+
# The requested media resolution: +:low+, +:medium+, +:high+,
|
|
34
|
+
# +:ultra_high+, +:original+, or +nil+ for the provider default.
|
|
35
|
+
attr_reader :resolution
|
|
36
|
+
|
|
37
|
+
# Media resolutions accepted by +resolution:+.
|
|
38
|
+
RESOLUTIONS = %i[low medium high ultra_high original].freeze
|
|
39
|
+
|
|
33
40
|
# File extensions recognized as document attachments when the MIME type
|
|
34
41
|
# alone is inconclusive.
|
|
35
42
|
DOCUMENT_EXTENSIONS = %w[
|
|
@@ -49,15 +56,25 @@ module RubyLLM
|
|
|
49
56
|
case sources
|
|
50
57
|
when nil then []
|
|
51
58
|
when Hash then sources.values.flat_map { |group| wrap(group, config:) }
|
|
52
|
-
else
|
|
53
|
-
Support::Utils.to_safe_array(sources).filter_map do |source|
|
|
54
|
-
next if source.nil? || (source.is_a?(String) && source.strip.empty?)
|
|
55
|
-
|
|
56
|
-
source.is_a?(Attachment) ? source : new(source, config:)
|
|
57
|
-
end
|
|
59
|
+
else Support::Utils.to_safe_array(sources).filter_map { |source| coerce(source, config:) }
|
|
58
60
|
end
|
|
59
61
|
end
|
|
60
62
|
|
|
63
|
+
def self.coerce(source, config:) # :nodoc:
|
|
64
|
+
return if source.nil? || (source.is_a?(String) && source.strip.empty?)
|
|
65
|
+
return source if source.is_a?(Attachment)
|
|
66
|
+
return source.to_attachment if source.respond_to?(:to_attachment)
|
|
67
|
+
return from_h(source, config:) if source.is_a?(Hash)
|
|
68
|
+
|
|
69
|
+
new(source, config:)
|
|
70
|
+
end
|
|
71
|
+
private_class_method :coerce
|
|
72
|
+
|
|
73
|
+
def self.from_h(data, config: nil) # :nodoc:
|
|
74
|
+
data = data.transform_keys(&:to_sym)
|
|
75
|
+
new(data.fetch(:source), filename: data[:filename], resolution: data[:resolution]&.to_sym, config:)
|
|
76
|
+
end
|
|
77
|
+
|
|
61
78
|
# Creates an attachment from +source+: a file path, URL, IO-like object,
|
|
62
79
|
# ActiveStorage object, or UploadedFile. Derives the filename from the
|
|
63
80
|
# source when +filename:+ is not given, then detects the MIME type.
|
|
@@ -65,6 +82,13 @@ module RubyLLM
|
|
|
65
82
|
# RubyLLM::Attachment.new("diagram.png")
|
|
66
83
|
# RubyLLM::Attachment.new(StringIO.new(data), filename: "report.pdf")
|
|
67
84
|
#
|
|
85
|
+
# +resolution:+ asks the provider to spend more or fewer tokens on an
|
|
86
|
+
# image, video, or PDF. +:original+ requests original image detail where
|
|
87
|
+
# supported, or the highest available resolution. Providers without
|
|
88
|
+
# the setting ignore it.
|
|
89
|
+
#
|
|
90
|
+
# RubyLLM::Attachment.new("page-3.png", resolution: :ultra_high)
|
|
91
|
+
#
|
|
68
92
|
# +config:+ is the Configuration a URL source is downloaded with, and
|
|
69
93
|
# defaults to the global one.
|
|
70
94
|
#
|
|
@@ -72,8 +96,13 @@ module RubyLLM
|
|
|
72
96
|
# They can be read or fetched during construction to detect the MIME type.
|
|
73
97
|
# Validate upload parameters before passing them here: an unchecked String
|
|
74
98
|
# can access local files or internal network endpoints.
|
|
75
|
-
def initialize(source, filename: nil, config: nil)
|
|
99
|
+
def initialize(source, filename: nil, resolution: nil, config: nil)
|
|
100
|
+
unless resolution.nil? || RESOLUTIONS.include?(resolution)
|
|
101
|
+
raise ArgumentError, "resolution must be one of #{RESOLUTIONS.join(', ')}, got #{resolution.inspect}"
|
|
102
|
+
end
|
|
103
|
+
|
|
76
104
|
@config = config
|
|
105
|
+
@resolution = resolution
|
|
77
106
|
@source = source
|
|
78
107
|
@source = source_type_cast
|
|
79
108
|
@filename = filename || source_filename
|
|
@@ -100,6 +129,13 @@ module RubyLLM
|
|
|
100
129
|
@provider_uploads ||= {}
|
|
101
130
|
end
|
|
102
131
|
|
|
132
|
+
# Where uploads of this attachment outlive the process: an object that
|
|
133
|
+
# responds to <tt>fetch(provider:, account:)</tt>,
|
|
134
|
+
# <tt>store(upload, provider:, account:)</tt>, and
|
|
135
|
+
# <tt>forget(id, provider:, account:)</tt>. The Rails integration sets
|
|
136
|
+
# one for files kept in Active Storage.
|
|
137
|
+
attr_accessor :provider_file_store # :nodoc:
|
|
138
|
+
|
|
103
139
|
def provider_file_id # :nodoc:
|
|
104
140
|
@source.id if provider_file?
|
|
105
141
|
end
|
|
@@ -144,10 +180,14 @@ module RubyLLM
|
|
|
144
180
|
when :text
|
|
145
181
|
"<file name='#{filename}' mime_type='#{mime_type}'>#{content}</file>"
|
|
146
182
|
else
|
|
147
|
-
|
|
183
|
+
data_uri
|
|
148
184
|
end
|
|
149
185
|
end
|
|
150
186
|
|
|
187
|
+
def url_or_data_uri # :nodoc:
|
|
188
|
+
url? ? @source.to_s : data_uri
|
|
189
|
+
end
|
|
190
|
+
|
|
151
191
|
# Returns the attachment category as a Symbol: +:image+, +:video+,
|
|
152
192
|
# +:audio+, +:pdf+, +:text+, +:document+, or +:unknown+.
|
|
153
193
|
def type
|
|
@@ -211,7 +251,7 @@ module RubyLLM
|
|
|
211
251
|
end
|
|
212
252
|
|
|
213
253
|
def to_h # :nodoc:
|
|
214
|
-
{ type: type, source: @source }
|
|
254
|
+
{ type: type, source: @source, filename: filename, resolution: resolution }.compact
|
|
215
255
|
end
|
|
216
256
|
|
|
217
257
|
def byte_size # :nodoc:
|
|
@@ -220,6 +260,10 @@ module RubyLLM
|
|
|
220
260
|
|
|
221
261
|
private
|
|
222
262
|
|
|
263
|
+
def data_uri
|
|
264
|
+
"data:#{mime_type};base64,#{encoded}"
|
|
265
|
+
end
|
|
266
|
+
|
|
223
267
|
def file_byte_size
|
|
224
268
|
return @source.byte_size if provider_file?
|
|
225
269
|
return File.size(@source) if path?
|
|
@@ -362,7 +406,7 @@ module RubyLLM
|
|
|
362
406
|
end
|
|
363
407
|
|
|
364
408
|
def inspect_attributes # :nodoc:
|
|
365
|
-
{ filename: filename, mime_type: mime_type, source: @source }
|
|
409
|
+
{ filename: filename, mime_type: mime_type, resolution: resolution, source: @source }
|
|
366
410
|
end
|
|
367
411
|
end
|
|
368
412
|
end
|
data/lib/ruby_llm/batch.rb
CHANGED
|
@@ -237,6 +237,7 @@ module RubyLLM
|
|
|
237
237
|
# puts message.content
|
|
238
238
|
# end
|
|
239
239
|
#
|
|
240
|
+
# Raises Error when the provider returns malformed result indices.
|
|
240
241
|
def messages
|
|
241
242
|
return @messages if @messages
|
|
242
243
|
|
|
@@ -287,6 +288,7 @@ module RubyLLM
|
|
|
287
288
|
|
|
288
289
|
def collect_results
|
|
289
290
|
results = @provider.batch_results(id, batch_protocol: @batch_protocol)
|
|
291
|
+
validate_result_indices(results)
|
|
290
292
|
slots = Array.new(result_slot_count(results))
|
|
291
293
|
|
|
292
294
|
results.each do |index, result, failure_status|
|
|
@@ -299,8 +301,22 @@ module RubyLLM
|
|
|
299
301
|
slots
|
|
300
302
|
end
|
|
301
303
|
|
|
304
|
+
def validate_result_indices(results)
|
|
305
|
+
count = known_request_count
|
|
306
|
+
indices = results.map(&:first)
|
|
307
|
+
invalid = indices.find { |index| index.negative? || (count && index >= count) }
|
|
308
|
+
raise Error, "Invalid batch result index: #{invalid}" if invalid
|
|
309
|
+
|
|
310
|
+
duplicate, = indices.tally.find { |_, occurrences| occurrences > 1 }
|
|
311
|
+
raise Error, "Duplicate batch result index: #{duplicate}" if duplicate
|
|
312
|
+
end
|
|
313
|
+
|
|
314
|
+
def known_request_count
|
|
315
|
+
chats&.size || requests&.size || @request_count
|
|
316
|
+
end
|
|
317
|
+
|
|
302
318
|
def result_slot_count(results)
|
|
303
|
-
|
|
319
|
+
known_request_count || ((results.map(&:first).max || -1) + 1)
|
|
304
320
|
end
|
|
305
321
|
|
|
306
322
|
def fill_missing_statuses(size)
|
|
@@ -364,10 +380,17 @@ module RubyLLM
|
|
|
364
380
|
status: :succeeded,
|
|
365
381
|
tokens: result.tokens,
|
|
366
382
|
cost: @provider.batch_cost(result.tokens, model:, category:),
|
|
367
|
-
message: result.is_a?(Message) ? result : nil
|
|
383
|
+
message: result.is_a?(Message) ? result : nil,
|
|
384
|
+
owner: Accounting::Usage.owner
|
|
368
385
|
)
|
|
369
386
|
result.ruby_llm_usage_entries = [entry]
|
|
370
|
-
|
|
387
|
+
return unless instrument
|
|
388
|
+
|
|
389
|
+
if operation == :chat
|
|
390
|
+
Accounting::Usage.instrument(entry, config: @provider.config)
|
|
391
|
+
else
|
|
392
|
+
Accounting::Usage.report(entry, config: @provider.config)
|
|
393
|
+
end
|
|
371
394
|
end
|
|
372
395
|
|
|
373
396
|
# A plain answer is the chat's last message once it arrives. A tool-call
|
|
@@ -18,13 +18,16 @@ module RubyLLM
|
|
|
18
18
|
end
|
|
19
19
|
|
|
20
20
|
def run_with_threads(tool_calls, on_result:, &execute)
|
|
21
|
-
|
|
21
|
+
caller_context = execution_context
|
|
22
22
|
workflow_context = Support::Instrumentation.current_workflow
|
|
23
|
+
instrumentation_context = Support::Instrumentation.capture_context
|
|
23
24
|
queue = Queue.new
|
|
24
25
|
threads = tool_calls.each_value.with_index.map do |tool_call, index|
|
|
25
26
|
thread = Thread.new do
|
|
26
27
|
Support::Instrumentation.with_workflow(workflow_context) do
|
|
27
|
-
|
|
28
|
+
Support::Instrumentation.with_context(instrumentation_context) do
|
|
29
|
+
queue << capture_result(index, tool_call, caller_context, execute)
|
|
30
|
+
end
|
|
28
31
|
end
|
|
29
32
|
end
|
|
30
33
|
thread.report_on_exception = false
|
|
@@ -48,22 +51,29 @@ module RubyLLM
|
|
|
48
51
|
raise LoadError, "The 'async' gem version 2.0 or newer is required for concurrent tool execution with fibers."
|
|
49
52
|
end
|
|
50
53
|
|
|
51
|
-
|
|
54
|
+
caller_context = execution_context
|
|
52
55
|
workflow_context = Support::Instrumentation.current_workflow
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
56
|
+
instrumentation_context = Support::Instrumentation.capture_context
|
|
57
|
+
# Inside a reactor, Sync runs the block in the calling fiber, so
|
|
58
|
+
# results persist with the caller's execution state and connection.
|
|
59
|
+
Sync do |task|
|
|
60
|
+
isolated(caller_context) do
|
|
61
|
+
queue = Async::Queue.new
|
|
62
|
+
tasks = tool_calls.each_value.with_index.map do |tool_call, index|
|
|
63
|
+
task.async do
|
|
64
|
+
Support::Instrumentation.with_workflow(workflow_context) do
|
|
65
|
+
Support::Instrumentation.with_context(instrumentation_context) do
|
|
66
|
+
queue << capture_result(index, tool_call, caller_context, execute)
|
|
67
|
+
end
|
|
68
|
+
end
|
|
59
69
|
end
|
|
60
70
|
end
|
|
61
|
-
end
|
|
62
71
|
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
72
|
+
collect_results(queue, tasks.size, on_result:)
|
|
73
|
+
ensure
|
|
74
|
+
tasks&.each(&:wait)
|
|
75
|
+
end
|
|
76
|
+
end
|
|
67
77
|
end
|
|
68
78
|
|
|
69
79
|
def collect_results(queue, count, on_result:)
|
|
@@ -85,27 +95,41 @@ module RubyLLM
|
|
|
85
95
|
results
|
|
86
96
|
end
|
|
87
97
|
|
|
88
|
-
def capture_result(index, tool_call,
|
|
89
|
-
|
|
98
|
+
def capture_result(index, tool_call, caller_context, execute)
|
|
99
|
+
value = isolated(caller_context) { execute.call(tool_call) }
|
|
90
100
|
Result.new(index:, tool_call:, value:)
|
|
91
101
|
rescue Exception => e # rubocop:disable Lint/RescueException
|
|
92
102
|
Result.new(index:, tool_call:, error: e)
|
|
93
103
|
end
|
|
94
104
|
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
105
|
+
# Rails gives a thread, and a fiber under fiber isolation, execution
|
|
106
|
+
# state of its own: the block runs as its own unit of work, so its
|
|
107
|
+
# connections return to the pool. A fiber sharing the caller's state
|
|
108
|
+
# must not start one, whose completion resets the caller's Current
|
|
109
|
+
# attributes. Unlike wrap, run! reports no errors: they reach the
|
|
110
|
+
# caller, whose own executor reports what nothing rescues.
|
|
111
|
+
def isolated(caller_context)
|
|
112
|
+
executor = rails_executor
|
|
113
|
+
return yield unless executor && !execution_context.equal?(caller_context)
|
|
114
|
+
|
|
115
|
+
execution = executor.run!
|
|
116
|
+
begin
|
|
117
|
+
yield
|
|
118
|
+
ensure
|
|
119
|
+
execution.complete!
|
|
100
120
|
end
|
|
101
121
|
end
|
|
102
122
|
|
|
123
|
+
def execution_context
|
|
124
|
+
defined?(ActiveSupport::IsolatedExecutionState) ? ActiveSupport::IsolatedExecutionState.context : Thread.current
|
|
125
|
+
end
|
|
126
|
+
|
|
103
127
|
def rails_executor
|
|
104
128
|
defined?(Rails) && Rails.respond_to?(:application) && Rails.application&.executor
|
|
105
129
|
end
|
|
106
130
|
|
|
107
|
-
private_class_method :run_with_threads, :run_with_fibers, :collect_results, :capture_result, :
|
|
108
|
-
:rails_executor
|
|
131
|
+
private_class_method :run_with_threads, :run_with_fibers, :collect_results, :capture_result, :isolated,
|
|
132
|
+
:execution_context, :rails_executor
|
|
109
133
|
end
|
|
110
134
|
end
|
|
111
135
|
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module RubyLLM
|
|
4
|
+
class Chat
|
|
5
|
+
# Deferred tool registration and the tool set rendered for the request.
|
|
6
|
+
module ToolSearch # :nodoc:
|
|
7
|
+
private
|
|
8
|
+
|
|
9
|
+
def deferred_tool_names
|
|
10
|
+
names = @tool_deferrals.filter_map { |name, deferred| name if deferred }
|
|
11
|
+
mcp.each do |server|
|
|
12
|
+
explicit = @mcp_deferrals[server.name.to_sym]
|
|
13
|
+
server.tools.each do |tool|
|
|
14
|
+
names << tool.name.to_sym if explicit.nil? ? server.defers?(tool) : explicit
|
|
15
|
+
end
|
|
16
|
+
end
|
|
17
|
+
names
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def request_tools
|
|
21
|
+
deferred = deferred_tool_names
|
|
22
|
+
tools.to_h { |name, tool| [name, deferred.include?(name) ? Tool::Deferred.new(tool) : tool] }
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|