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
|
@@ -6,182 +6,25 @@ require_relative '../generator_helpers'
|
|
|
6
6
|
|
|
7
7
|
module RubyLLM
|
|
8
8
|
module Generators
|
|
9
|
-
# Upgrades the schema
|
|
9
|
+
# Upgrades the schema of an application on RubyLLM 2.0 to 2.1.
|
|
10
10
|
class UpgradeGenerator < Rails::Generators::Base
|
|
11
11
|
include Rails::Generators::Migration
|
|
12
12
|
include RubyLLM::Generators::GeneratorHelpers
|
|
13
13
|
|
|
14
|
-
MODEL_MAPPING_DEFAULTS = {
|
|
15
|
-
chat: 'Chat',
|
|
16
|
-
message: 'Message',
|
|
17
|
-
model: 'Model',
|
|
18
|
-
tool_call: 'ToolCall'
|
|
19
|
-
}.freeze
|
|
20
|
-
MODEL_MAPPING_TYPES = MODEL_MAPPING_DEFAULTS.keys.map(&:to_s).freeze
|
|
21
|
-
|
|
22
14
|
namespace 'ruby_llm:upgrade'
|
|
23
15
|
source_root File.expand_path('templates', __dir__)
|
|
24
16
|
|
|
25
|
-
|
|
26
|
-
desc: 'Generate one upgrade phase; cleanup runs in a later deployment'
|
|
27
|
-
class_option :mode, type: :string, enum: %w[rename copy], default: 'rename',
|
|
28
|
-
desc: 'Copy prepares and backfills online, with protected 1.16 rollback until cleanup'
|
|
29
|
-
class_option :discard_incomplete_tool_calls, type: :boolean, default: false,
|
|
30
|
-
desc: 'Discard all incomplete legacy tool calls at copy finish'
|
|
31
|
-
|
|
32
|
-
argument :model_mappings,
|
|
33
|
-
type: :array,
|
|
34
|
-
default: [],
|
|
35
|
-
banner: 'chat:ChatName message:MessageName model:ModelName tool_call:ToolCallName'
|
|
17
|
+
argument :model_mappings, type: :array, default: [], banner: 'chat:ChatName message:MessageName'
|
|
36
18
|
|
|
37
|
-
desc 'Upgrades a RubyLLM
|
|
19
|
+
desc 'Upgrades a RubyLLM 2.0 Rails schema to 2.1\n' \
|
|
20
|
+
'Usage: bin/rails g ruby_llm:upgrade [chat:ChatName] [message:MessageName]'
|
|
38
21
|
|
|
39
22
|
def self.next_migration_number(dirname)
|
|
40
23
|
::ActiveRecord::Generators::Base.next_migration_number(dirname)
|
|
41
24
|
end
|
|
42
25
|
|
|
43
|
-
def
|
|
44
|
-
|
|
45
|
-
parse_model_mappings(allowed_types: MODEL_MAPPING_TYPES, defaults: MODEL_MAPPING_DEFAULTS)
|
|
46
|
-
say_status :models, resolved_model_mappings
|
|
47
|
-
|
|
48
|
-
if copy_mode? && !options[:phase].in?(%w[backfill finish cleanup])
|
|
49
|
-
template 'ruby_llm_upgrade.rb.tt', 'app/models/concerns/ruby_llm_upgrade.rb'
|
|
50
|
-
template 'upgrade_initializer.rb.tt', 'config/initializers/ruby_llm_upgrade.rb'
|
|
51
|
-
end
|
|
52
|
-
|
|
53
|
-
phases = options[:phase] ? [options[:phase]] : %w[prepare backfill finish]
|
|
54
|
-
phases.each do |phase|
|
|
55
|
-
template = phase == 'backfill' ? 'backfill_v2_data' : "#{phase}_v2_upgrade"
|
|
56
|
-
migration = phase == 'backfill' ? 'backfill_ruby_llm_v2_data' : "#{phase}_ruby_llm_v2_upgrade"
|
|
57
|
-
migration_template "#{template}.rb.tt", "db/migrate/#{migration}.rb"
|
|
58
|
-
end
|
|
59
|
-
end
|
|
60
|
-
|
|
61
|
-
def show_next_steps
|
|
62
|
-
say_status :success, 'Upgrade prepared!', :green
|
|
63
|
-
if copy_mode?
|
|
64
|
-
say <<~INSTRUCTIONS
|
|
65
|
-
|
|
66
|
-
Copy mode retains the legacy tables and a separate chat model reference.
|
|
67
|
-
Install the generated RubyLLMUpgrade concern and initializer in BOTH the
|
|
68
|
-
running 1.16 build and the 2.0 build BEFORE running prepare. Restart
|
|
69
|
-
all affected 1.16 processes so they load the guards and explicit column
|
|
70
|
-
selects. The guards protect whole conversations
|
|
71
|
-
changed by 2.0. Direct SQL, bulk updates/deletes and attachment purges
|
|
72
|
-
bypass these guards; review those application paths before upgrading.
|
|
73
|
-
|
|
74
|
-
Rehearse prepare, backfill and finish on a production database copy.
|
|
75
|
-
Run prepare and backfill from the 2.0 build while 1.16 serves traffic.
|
|
76
|
-
Stop affected traffic, workers, scheduled jobs and retries BEFORE finish.
|
|
77
|
-
Finish catches up intervening writes, validates and activates 2.0.
|
|
78
|
-
Then load models and restart the 2.0 application.
|
|
79
|
-
Keep the old model/tool-call classes in the 1.16 build.
|
|
80
|
-
|
|
81
|
-
PostgreSQL, MySQL and SQLite support this workflow. Schema changes can
|
|
82
|
-
block writes; SQLite also serializes backfill and app writes.
|
|
83
|
-
Use a direct connection or session-mode pool for copy migrations.
|
|
84
|
-
db:migrate runs ALL pending phases. Stop at the backfill timestamp to
|
|
85
|
-
defer finish, or pause AI before running all three together.
|
|
86
|
-
|
|
87
|
-
With all affected processes stopped, use the 2.0 build to run:
|
|
88
|
-
bin/rails ruby_llm:upgrade:rollback # activates the protected 1.16 view
|
|
89
|
-
bin/rails ruby_llm:upgrade:resume # reconciles 1.16 writes, activates 2.0
|
|
90
|
-
|
|
91
|
-
Finish pending tool calls, approvals and batches before switching.
|
|
92
|
-
Once rollback is no longer needed, run ruby_llm:upgrade:finalize, then
|
|
93
|
-
generate --mode copy --phase cleanup in a later deployment. Keep the
|
|
94
|
-
compatibility files until cleanup succeeds; remove them afterward.
|
|
95
|
-
|
|
96
|
-
Pass the same --mode and model mappings to every generator invocation.
|
|
97
|
-
Full guide: https://rubyllm.com/next/upgrading/
|
|
98
|
-
|
|
99
|
-
INSTRUCTIONS
|
|
100
|
-
return
|
|
101
|
-
end
|
|
102
|
-
say <<~INSTRUCTIONS
|
|
103
|
-
|
|
104
|
-
These migrations expect the schema generated by RubyLLM 1.16. They move
|
|
105
|
-
RubyLLM's model and tool-call records, preserve message content and usage,
|
|
106
|
-
and retain the 1.x message columns until a later cleanup deployment.
|
|
107
|
-
|
|
108
|
-
Next steps:
|
|
109
|
-
1. Review the generated migrations
|
|
110
|
-
2. Rehearse them on a recent production snapshot
|
|
111
|
-
3. Stop affected web processes, workers, scheduled jobs, and retries
|
|
112
|
-
4. Run preparation, backfill, and finish before reopening traffic
|
|
113
|
-
5. Run: bin/rails ruby_llm:load_models
|
|
114
|
-
6. Keep only the current declarations in your application models:
|
|
115
|
-
#{chat_model_path}: #{acts_as_chat_declaration}
|
|
116
|
-
#{message_model_path}: #{acts_as_message_declaration}
|
|
117
|
-
7. Remove the 1.x model and tool-call classes, config.model_registry_class,
|
|
118
|
-
and config.use_new_acts_as
|
|
119
|
-
8. Update 1.x API calls using the upgrade guide
|
|
120
|
-
|
|
121
|
-
Generate one phase with --phase prepare, backfill, or finish when the
|
|
122
|
-
backfill needs to run outside your deployment's release phase.
|
|
123
|
-
After verifying the upgrade, generate --phase cleanup in a later release.
|
|
124
|
-
Pass the same model mappings to every invocation.
|
|
125
|
-
|
|
126
|
-
The migrations are irreversible. Retained columns do not make a code
|
|
127
|
-
rollback safe; recovery requires the previous database and application.
|
|
128
|
-
|
|
129
|
-
Full upgrade guide:
|
|
130
|
-
https://rubyllm.com/next/upgrading/
|
|
131
|
-
|
|
132
|
-
INSTRUCTIONS
|
|
133
|
-
end
|
|
134
|
-
|
|
135
|
-
private
|
|
136
|
-
|
|
137
|
-
def copy_mode? = options[:mode] == 'copy'
|
|
138
|
-
def discard_incomplete_tool_calls? = options[:discard_incomplete_tool_calls]
|
|
139
|
-
|
|
140
|
-
def validate_cleanup_options
|
|
141
|
-
return unless discard_incomplete_tool_calls?
|
|
142
|
-
return if copy_mode? && options[:phase].in?([nil, 'finish'])
|
|
143
|
-
|
|
144
|
-
raise Thor::Error, 'Discard incomplete tool calls only with --mode copy and the finish phase'
|
|
145
|
-
end
|
|
146
|
-
|
|
147
|
-
def copy_upgrade_settings
|
|
148
|
-
{
|
|
149
|
-
chat_table: chat_table_name, message_table: message_table_name,
|
|
150
|
-
model_table: v1_model_table_name, tool_call_table: v1_tool_call_table_name,
|
|
151
|
-
chat_class: chat_model_name, message_class: message_model_name,
|
|
152
|
-
model_foreign_key: v1_model_foreign_key, tool_call_foreign_key: v1_tool_call_foreign_key,
|
|
153
|
-
chat_foreign_key: chat_foreign_key, message_foreign_key: message_foreign_key
|
|
154
|
-
}
|
|
155
|
-
end
|
|
156
|
-
|
|
157
|
-
def prepare_migration_class_name = 'prepare_ruby_llm_v2_upgrade'.camelize
|
|
158
|
-
def backfill_migration_class_name = 'backfill_ruby_llm_v2_data'.camelize
|
|
159
|
-
def finish_migration_class_name = 'finish_ruby_llm_v2_upgrade'.camelize
|
|
160
|
-
def cleanup_migration_class_name = 'cleanup_ruby_llm_v2_upgrade'.camelize
|
|
161
|
-
|
|
162
|
-
def resolved_model_mappings
|
|
163
|
-
@model_names.map do |type, model_name|
|
|
164
|
-
"#{type}:#{model_name} -> #{table_name_for(model_name)}"
|
|
165
|
-
end.join(', ')
|
|
166
|
-
end
|
|
167
|
-
|
|
168
|
-
def v1_model_name(type)
|
|
169
|
-
@model_names.fetch(type)
|
|
170
|
-
end
|
|
171
|
-
|
|
172
|
-
def v1_model_table_name = table_name_for(v1_model_name(:model))
|
|
173
|
-
def v1_tool_call_table_name = table_name_for(v1_model_name(:tool_call))
|
|
174
|
-
def v1_model_foreign_key = "#{v1_model_table_name.singularize}_id"
|
|
175
|
-
def v1_tool_call_foreign_key = "#{v1_tool_call_table_name.singularize}_id"
|
|
176
|
-
def message_foreign_key = "#{message_table_name.singularize}_id"
|
|
177
|
-
def chat_foreign_key = "#{chat_table_name.singularize}_id"
|
|
178
|
-
|
|
179
|
-
def chat_model_path
|
|
180
|
-
"app/models/#{chat_model_name.underscore}.rb"
|
|
181
|
-
end
|
|
182
|
-
|
|
183
|
-
def message_model_path
|
|
184
|
-
"app/models/#{message_model_name.underscore}.rb"
|
|
26
|
+
def create_migration_file
|
|
27
|
+
migration_template 'upgrade_ruby_llm_to_2_1.rb.tt', 'db/migrate/upgrade_ruby_llm_to_2_1.rb'
|
|
185
28
|
end
|
|
186
29
|
end
|
|
187
30
|
end
|
|
@@ -6,19 +6,55 @@ module RubyLLM
|
|
|
6
6
|
module Accounting # :nodoc:
|
|
7
7
|
# Internal accounting for physical provider attempts.
|
|
8
8
|
class Usage # :nodoc: all
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
9
|
+
OWNER_KEY = :ruby_llm_usage_owner
|
|
10
|
+
|
|
11
|
+
class << self
|
|
12
|
+
# Persists finished entries that no chat records, when the
|
|
13
|
+
# application provides a store, such as the Rails usage ledger.
|
|
14
|
+
attr_accessor :ledger
|
|
15
|
+
|
|
16
|
+
# The owner lives in fiber storage, which isolates concurrent
|
|
17
|
+
# fibers and threads and is inherited by those started inside it.
|
|
18
|
+
def owner
|
|
19
|
+
Fiber[OWNER_KEY]
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def with_owner(owner)
|
|
23
|
+
previous = Fiber[OWNER_KEY]
|
|
24
|
+
Fiber[OWNER_KEY] = owner
|
|
25
|
+
yield
|
|
26
|
+
ensure
|
|
27
|
+
Fiber[OWNER_KEY] = previous
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def owned_by(owner, &)
|
|
31
|
+
owner.nil? ? yield : with_owner(owner, &)
|
|
32
|
+
end
|
|
33
|
+
|
|
34
|
+
def record(entry)
|
|
35
|
+
ledger&.record(entry)
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def report(entry, config:)
|
|
39
|
+
record(entry)
|
|
40
|
+
instrument(entry, config:)
|
|
41
|
+
end
|
|
42
|
+
|
|
43
|
+
def instrument(entry, config:)
|
|
44
|
+
RubyLLM.instrument(
|
|
45
|
+
'usage.ruby_llm',
|
|
46
|
+
{
|
|
47
|
+
operation: entry.operation,
|
|
48
|
+
provider: entry.provider,
|
|
49
|
+
model: entry.model,
|
|
50
|
+
status: entry.status,
|
|
51
|
+
tokens: entry.tokens,
|
|
52
|
+
cost: entry.cost,
|
|
53
|
+
owner: entry.owner
|
|
54
|
+
},
|
|
55
|
+
config:
|
|
56
|
+
)
|
|
57
|
+
end
|
|
22
58
|
end
|
|
23
59
|
|
|
24
60
|
# Shared storage for result objects carrying attempt accounting.
|
|
@@ -48,10 +84,10 @@ module RubyLLM
|
|
|
48
84
|
|
|
49
85
|
# One physical provider request attempt and its accounting facts.
|
|
50
86
|
class Entry
|
|
51
|
-
OPERATIONS = %i[chat embedding moderation image speech transcription ocr rerank].freeze
|
|
87
|
+
OPERATIONS = %i[chat embedding moderation image speech transcription ocr rerank judgment video research].freeze
|
|
52
88
|
STATUSES = %i[pending succeeded failed cancelled].freeze
|
|
53
89
|
|
|
54
|
-
attr_reader :operation, :provider, :model, :status, :tokens, :
|
|
90
|
+
attr_reader :operation, :provider, :model, :status, :tokens, :owner
|
|
55
91
|
attr_accessor :message
|
|
56
92
|
|
|
57
93
|
include Support::Inspectable
|
|
@@ -60,17 +96,23 @@ module RubyLLM
|
|
|
60
96
|
{ operation: operation, provider: provider, model: model, status: status, tokens: tokens }
|
|
61
97
|
end
|
|
62
98
|
|
|
63
|
-
def initialize(operation:, provider:, model:, status: :pending, tokens: nil, cost: nil, message: nil
|
|
99
|
+
def initialize(operation:, provider:, model:, status: :pending, tokens: nil, cost: nil, message: nil,
|
|
100
|
+
owner: nil)
|
|
64
101
|
@operation = operation.to_sym
|
|
65
102
|
@provider = provider.to_s
|
|
66
103
|
@model = model&.to_s
|
|
67
104
|
@status = status.to_sym
|
|
68
105
|
@tokens = tokens || Tokens.new
|
|
69
|
-
@cost = cost
|
|
106
|
+
@cost = cost
|
|
70
107
|
@message = message
|
|
108
|
+
@owner = owner
|
|
71
109
|
validate!
|
|
72
110
|
end
|
|
73
111
|
|
|
112
|
+
def cost
|
|
113
|
+
@cost ||= Cost.new(tokens:)
|
|
114
|
+
end
|
|
115
|
+
|
|
74
116
|
def pending? = status == :pending
|
|
75
117
|
def succeeded? = status == :succeeded
|
|
76
118
|
def failed? = status == :failed
|
|
@@ -92,7 +134,7 @@ module RubyLLM
|
|
|
92
134
|
def finish(status:, tokens: nil, cost: nil) # :nodoc:
|
|
93
135
|
@status = status.to_sym
|
|
94
136
|
@tokens = tokens || Tokens.new
|
|
95
|
-
@cost = cost
|
|
137
|
+
@cost = cost
|
|
96
138
|
validate!
|
|
97
139
|
self
|
|
98
140
|
end
|
|
@@ -115,15 +157,17 @@ module RubyLLM
|
|
|
115
157
|
speech: :audio_tokens,
|
|
116
158
|
transcription: :audio_tokens,
|
|
117
159
|
ocr: :text_tokens,
|
|
118
|
-
rerank: :embeddings
|
|
160
|
+
rerank: :embeddings,
|
|
161
|
+
judgment: :text_tokens
|
|
119
162
|
}.freeze
|
|
120
163
|
|
|
121
164
|
attr_reader :entries
|
|
122
165
|
|
|
123
|
-
def initialize(operation:, provider:, model:, config:, on_finish: nil)
|
|
166
|
+
def initialize(operation:, provider:, model:, config:, on_finish: nil, model_id: nil)
|
|
124
167
|
@operation = operation.to_sym
|
|
125
168
|
@provider = provider
|
|
126
169
|
@model_info = model
|
|
170
|
+
@model_id = model_id
|
|
127
171
|
@config = config
|
|
128
172
|
@on_finish = on_finish
|
|
129
173
|
@entries = []
|
|
@@ -131,21 +175,25 @@ module RubyLLM
|
|
|
131
175
|
end
|
|
132
176
|
|
|
133
177
|
def start
|
|
134
|
-
entry = Entry.new(operation: @operation, provider: @provider.slug, model: @model_info&.id
|
|
178
|
+
entry = Entry.new(operation: @operation, provider: @provider.slug, model: @model_id || @model_info&.id,
|
|
179
|
+
owner: Usage.owner)
|
|
135
180
|
@entries << entry
|
|
136
181
|
@pending << entry
|
|
137
182
|
entry
|
|
138
183
|
end
|
|
139
184
|
|
|
140
185
|
def observe(chunk)
|
|
186
|
+
observe_tokens(chunk.tokens) if chunk.respond_to?(:tokens)
|
|
187
|
+
end
|
|
188
|
+
|
|
189
|
+
# Keeps the usage a response reported for the current attempt, so an
|
|
190
|
+
# attempt that fails after the provider answered, such as a blocked
|
|
191
|
+
# one, still records what it billed.
|
|
192
|
+
def observe_tokens(tokens)
|
|
141
193
|
entry = @pending.last
|
|
142
194
|
return unless entry
|
|
143
|
-
return unless chunk.respond_to?(:tokens)
|
|
144
195
|
|
|
145
|
-
entry.finish(
|
|
146
|
-
status: :pending,
|
|
147
|
-
tokens: merge_stream_tokens(entry.tokens, chunk.tokens)
|
|
148
|
-
)
|
|
196
|
+
entry.finish(status: :pending, tokens: entry.tokens.merge(tokens))
|
|
149
197
|
end
|
|
150
198
|
|
|
151
199
|
def fail_attempt(entry, error)
|
|
@@ -162,8 +210,9 @@ module RubyLLM
|
|
|
162
210
|
def succeed(result)
|
|
163
211
|
# A request that produced several results, like a multi-image
|
|
164
212
|
# generation, is billed once: the first result carries the call.
|
|
165
|
-
|
|
166
|
-
|
|
213
|
+
results = result.is_a?(Array) ? result : [result]
|
|
214
|
+
results.each { |item| item.model_info = result_model(item) if item.respond_to?(:model_info=) }
|
|
215
|
+
billed = results.first
|
|
167
216
|
pending = @pending.dup
|
|
168
217
|
if pending.empty?
|
|
169
218
|
attach_to_result(billed)
|
|
@@ -188,10 +237,10 @@ module RubyLLM
|
|
|
188
237
|
|
|
189
238
|
private
|
|
190
239
|
|
|
191
|
-
def
|
|
192
|
-
return @model_info if
|
|
240
|
+
def result_model(result)
|
|
241
|
+
return @model_info if result.model.nil? || result.model == @model_info&.id
|
|
193
242
|
|
|
194
|
-
RubyLLM.models.find(
|
|
243
|
+
RubyLLM.models.find(result.model, provider: @provider.slug, config: @config)
|
|
195
244
|
rescue ModelNotFoundError
|
|
196
245
|
@model_info
|
|
197
246
|
end
|
|
@@ -225,29 +274,17 @@ module RubyLLM
|
|
|
225
274
|
)
|
|
226
275
|
entry.finish(status:, tokens:, cost:)
|
|
227
276
|
@pending.delete(entry)
|
|
228
|
-
@on_finish
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
277
|
+
if @on_finish
|
|
278
|
+
@on_finish.call(entry)
|
|
279
|
+
Accounting::Usage.instrument(entry, config: @config)
|
|
280
|
+
else
|
|
281
|
+
Accounting::Usage.report(entry, config: @config)
|
|
282
|
+
end
|
|
234
283
|
end
|
|
235
284
|
|
|
236
285
|
def attach_to_result(result)
|
|
237
286
|
result&.ruby_llm_usage_entries = entries
|
|
238
287
|
end
|
|
239
|
-
|
|
240
|
-
def merge_stream_tokens(existing, incoming)
|
|
241
|
-
Tokens.new(
|
|
242
|
-
input: incoming.input.nil? ? existing.input : incoming.input,
|
|
243
|
-
output: incoming.output.nil? ? existing.output : incoming.output,
|
|
244
|
-
cache_read: incoming.cache_read.nil? ? existing.cache_read : incoming.cache_read,
|
|
245
|
-
cache_write: incoming.cache_write.nil? ? existing.cache_write : incoming.cache_write,
|
|
246
|
-
thinking: incoming.thinking.nil? ? existing.thinking : incoming.thinking,
|
|
247
|
-
server_tool_use: incoming.server_tool_use.nil? ? existing.server_tool_use : incoming.server_tool_use,
|
|
248
|
-
reported_cost: incoming.reported_cost.nil? ? existing.reported_cost : incoming.reported_cost
|
|
249
|
-
)
|
|
250
|
-
end
|
|
251
288
|
end
|
|
252
289
|
end
|
|
253
290
|
end
|
|
@@ -104,6 +104,7 @@ module RubyLLM
|
|
|
104
104
|
-> { chronological },
|
|
105
105
|
as: :chat,
|
|
106
106
|
class_name: 'RubyLLM::ActiveRecord::Usage',
|
|
107
|
+
inverse_of: :chat,
|
|
107
108
|
dependent: :destroy
|
|
108
109
|
end
|
|
109
110
|
|
|
@@ -120,6 +121,7 @@ module RubyLLM
|
|
|
120
121
|
touch: touch_chat
|
|
121
122
|
|
|
122
123
|
has_many :ruby_llm_tool_calls,
|
|
124
|
+
-> { order(:created_at, :id) },
|
|
123
125
|
as: :message,
|
|
124
126
|
class_name: 'RubyLLM::ActiveRecord::ToolCall',
|
|
125
127
|
dependent: :destroy
|
|
@@ -133,6 +135,7 @@ module RubyLLM
|
|
|
133
135
|
-> { chronological },
|
|
134
136
|
as: :message,
|
|
135
137
|
class_name: 'RubyLLM::ActiveRecord::Usage',
|
|
138
|
+
inverse_of: :message,
|
|
136
139
|
dependent: :nullify
|
|
137
140
|
end
|
|
138
141
|
end
|
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'active_support/core_ext/object/blank'
|
|
4
|
-
require 'tempfile'
|
|
5
4
|
|
|
6
5
|
module RubyLLM
|
|
7
6
|
module ActiveRecord
|
|
@@ -12,7 +11,27 @@ module RubyLLM
|
|
|
12
11
|
return unless message_record.respond_to?(:attachments)
|
|
13
12
|
|
|
14
13
|
attachables = prepare_for_active_storage(attachments)
|
|
15
|
-
|
|
14
|
+
return if attachables.empty?
|
|
15
|
+
|
|
16
|
+
message_record.attachments.attach(attachables)
|
|
17
|
+
Support::Utils.to_safe_array(attachments).grep(RubyLLM::Attachment).each do |attachment|
|
|
18
|
+
attachment.provider_file_store ||= ProviderFile.store { persisted_blob_key(message_record, attachment) }
|
|
19
|
+
end
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
# Active Storage records the size and MD5 of every blob, so the
|
|
23
|
+
# message's blob of the same size holds the attachment's bytes. Only
|
|
24
|
+
# attachments of equal size need the MD5 to tell them apart.
|
|
25
|
+
def persisted_blob_key(message_record, attachment)
|
|
26
|
+
blob = active_storage_blobs(attachment.source) if attachment.active_storage?
|
|
27
|
+
return blob.key if blob.is_a?(ActiveStorage::Blob)
|
|
28
|
+
|
|
29
|
+
content = attachment.content
|
|
30
|
+
candidates = message_record.attachments.blobs.where(byte_size: content.bytesize).to_a
|
|
31
|
+
return candidates.first&.key unless candidates.many?
|
|
32
|
+
|
|
33
|
+
checksum = OpenSSL::Digest::MD5.base64digest(content)
|
|
34
|
+
candidates.find { |candidate| candidate.checksum == checksum }&.key
|
|
16
35
|
end
|
|
17
36
|
|
|
18
37
|
def prepare_for_active_storage(attachments)
|
|
@@ -41,7 +60,8 @@ module RubyLLM
|
|
|
41
60
|
{
|
|
42
61
|
io: StringIO.new(attachment.content),
|
|
43
62
|
filename: attachment.filename,
|
|
44
|
-
content_type: attachment.mime_type
|
|
63
|
+
content_type: attachment.mime_type,
|
|
64
|
+
metadata: { resolution: attachment.resolution&.to_s }.compact
|
|
45
65
|
}
|
|
46
66
|
end
|
|
47
67
|
rescue StandardError => e
|
|
@@ -162,7 +182,23 @@ module RubyLLM
|
|
|
162
182
|
end
|
|
163
183
|
|
|
164
184
|
def active_storage_attachments?
|
|
165
|
-
respond_to?(:attachments)
|
|
185
|
+
return false unless respond_to?(:attachments)
|
|
186
|
+
|
|
187
|
+
preloaded = preloaded_attachments
|
|
188
|
+
preloaded ? preloaded.any? : attachments.attached?
|
|
189
|
+
end
|
|
190
|
+
|
|
191
|
+
def preloaded_attachments
|
|
192
|
+
return if pending_attachment_change || !self.class.reflect_on_association(:attachments_attachments)
|
|
193
|
+
|
|
194
|
+
preloaded_records(:attachments_attachments)
|
|
195
|
+
end
|
|
196
|
+
|
|
197
|
+
# Building an association proxy evaluates the association's scope,
|
|
198
|
+
# which costs more than reading the records a preload already loaded.
|
|
199
|
+
def preloaded_records(name)
|
|
200
|
+
association = association(name)
|
|
201
|
+
association.target if association.loaded?
|
|
166
202
|
end
|
|
167
203
|
|
|
168
204
|
def collect_attachments(action_text_attachments)
|
|
@@ -194,12 +230,13 @@ module RubyLLM
|
|
|
194
230
|
end
|
|
195
231
|
|
|
196
232
|
def stored_attachment(attachment, attachable)
|
|
197
|
-
if pending_upload_attachable?(attachable)
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
233
|
+
stored = if pending_upload_attachable?(attachable)
|
|
234
|
+
pending_upload_attachment(attachable)
|
|
235
|
+
else
|
|
236
|
+
RubyLLM::Attachment.new(attachment, resolution: attachment.metadata['resolution']&.to_sym)
|
|
237
|
+
end
|
|
238
|
+
stored.provider_file_store = ProviderFile.store { attachment.blob&.key }
|
|
239
|
+
stored
|
|
203
240
|
end
|
|
204
241
|
|
|
205
242
|
def pending_upload_attachable?(attachable)
|
|
@@ -225,7 +262,9 @@ module RubyLLM
|
|
|
225
262
|
|
|
226
263
|
def pending_upload_attachment(attachable)
|
|
227
264
|
if attachable.is_a?(Hash)
|
|
228
|
-
RubyLLM::Attachment.new(attachment_hash_io(attachable),
|
|
265
|
+
RubyLLM::Attachment.new(attachment_hash_io(attachable),
|
|
266
|
+
filename: attachment_hash_filename(attachable),
|
|
267
|
+
resolution: attachable.dig(:metadata, :resolution)&.to_sym)
|
|
229
268
|
else
|
|
230
269
|
RubyLLM::Attachment.new(attachable)
|
|
231
270
|
end
|
|
@@ -245,20 +284,6 @@ module RubyLLM
|
|
|
245
284
|
rescue NameError
|
|
246
285
|
false
|
|
247
286
|
end
|
|
248
|
-
|
|
249
|
-
def download_attachment(attachment)
|
|
250
|
-
ext = File.extname(attachment.filename.to_s)
|
|
251
|
-
basename = File.basename(attachment.filename.to_s, ext)
|
|
252
|
-
tempfile = Tempfile.new([basename, ext])
|
|
253
|
-
tempfile.binmode
|
|
254
|
-
|
|
255
|
-
attachment.download { |chunk| tempfile.write(chunk) }
|
|
256
|
-
|
|
257
|
-
tempfile.flush
|
|
258
|
-
tempfile.rewind
|
|
259
|
-
@_tempfiles << tempfile
|
|
260
|
-
tempfile
|
|
261
|
-
end
|
|
262
287
|
end
|
|
263
288
|
end
|
|
264
289
|
end
|
|
@@ -3,7 +3,7 @@
|
|
|
3
3
|
module RubyLLM
|
|
4
4
|
module ActiveRecord
|
|
5
5
|
# RubyLLM's private persistence for provider-side batches.
|
|
6
|
-
class Batch <
|
|
6
|
+
class Batch < Record # :nodoc:
|
|
7
7
|
self.table_name = 'ruby_llm_batches'
|
|
8
8
|
|
|
9
9
|
STATUSES = %w[pending succeeded failed cancelled].freeze
|