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
|
@@ -1,468 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
|
|
3
|
-
<% if copy_mode? -%>
|
|
4
|
-
require 'generators/ruby_llm/upgrade/upgrade_migration'
|
|
5
|
-
<% else -%>
|
|
6
|
-
require 'generators/ruby_llm/upgrade/legacy_content_sql'
|
|
7
|
-
<% end -%>
|
|
8
|
-
|
|
9
|
-
class <%= backfill_migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
|
|
10
|
-
disable_ddl_transaction!
|
|
11
|
-
|
|
12
|
-
<% unless copy_mode? -%>
|
|
13
|
-
BATCH_SIZE = 10_000
|
|
14
|
-
<% end -%>
|
|
15
|
-
PROGRESS_TABLE = :ruby_llm_v2_backfills
|
|
16
|
-
|
|
17
|
-
def up
|
|
18
|
-
<% if copy_mode? -%>
|
|
19
|
-
RubyLLM::Generators::UpgradeMigration.for.verify_settings(<%= copy_upgrade_settings.inspect %>)
|
|
20
|
-
verify_upgrade_in_progress
|
|
21
|
-
RubyLLM::Generators::UpgradeMigration.for.backfill
|
|
22
|
-
<% else -%>
|
|
23
|
-
raise 'Generate this migration with --mode copy' if table_exists?(:ruby_llm_v2_upgrades)
|
|
24
|
-
verify_upgrade_in_progress
|
|
25
|
-
create_progress_table
|
|
26
|
-
backfill_required_defaults
|
|
27
|
-
backfill_message_content
|
|
28
|
-
backfill_tool_results
|
|
29
|
-
backfill_usages
|
|
30
|
-
verify_backfills
|
|
31
|
-
<% end -%>
|
|
32
|
-
end
|
|
33
|
-
|
|
34
|
-
def down
|
|
35
|
-
raise ActiveRecord::IrreversibleMigration, 'RubyLLM 2.0 changes persisted record ownership'
|
|
36
|
-
end
|
|
37
|
-
|
|
38
|
-
private
|
|
39
|
-
|
|
40
|
-
def verify_upgrade_in_progress
|
|
41
|
-
legacy_columns = %i[content_raw input_tokens output_tokens <%= v1_model_foreign_key %> <%= v1_tool_call_foreign_key %>]
|
|
42
|
-
unless legacy_columns.any? { |column| column_exists?(:<%= message_table_name %>, column) }
|
|
43
|
-
raise 'The RubyLLM 2.0 schema has already been cleaned. Do not run the backfill again.'
|
|
44
|
-
end
|
|
45
|
-
return unless table_exists?(PROGRESS_TABLE)
|
|
46
|
-
return unless progress_records.where(task: 'finished', completed: true).exists?
|
|
47
|
-
|
|
48
|
-
raise 'The RubyLLM 2.0 upgrade is already finished. Do not run the backfill again.'
|
|
49
|
-
end
|
|
50
|
-
|
|
51
|
-
<% unless copy_mode? -%>
|
|
52
|
-
def backfill_required_defaults
|
|
53
|
-
{
|
|
54
|
-
<%= chat_table_name %>: {cancelled: false},
|
|
55
|
-
<%= message_table_name %>: {cache_until_here: false},
|
|
56
|
-
ruby_llm_tool_calls: {message_type: '<%= message_model_name %>'}
|
|
57
|
-
}.each do |table, attributes|
|
|
58
|
-
column, value = attributes.first
|
|
59
|
-
migration_record(table).where(column => nil).in_batches(of: BATCH_SIZE) do |batch|
|
|
60
|
-
batch.update_all(column => value)
|
|
61
|
-
end
|
|
62
|
-
end
|
|
63
|
-
end
|
|
64
|
-
|
|
65
|
-
def backfill_message_content
|
|
66
|
-
return mark_completed('message_content') unless column_exists?(:<%= message_table_name %>, :content_raw)
|
|
67
|
-
|
|
68
|
-
each_message_range('message_content') do |range|
|
|
69
|
-
content_raw = message_value(:content_raw)
|
|
70
|
-
raw_content = message_value(:raw_content)
|
|
71
|
-
content = message_value(:content)
|
|
72
|
-
<% if postgresql? -%>
|
|
73
|
-
structured_content = "#{content_raw}::jsonb"
|
|
74
|
-
<% elsif mysql? -%>
|
|
75
|
-
structured_content = content_raw
|
|
76
|
-
<% else -%>
|
|
77
|
-
structured_content = content_raw
|
|
78
|
-
<% end -%>
|
|
79
|
-
rendered_content = RubyLLM::Generators::LegacyContentSQL.new(connection).render(content: content, raw: content_raw)
|
|
80
|
-
execute <<~SQL
|
|
81
|
-
UPDATE #{quote_table(:<%= message_table_name %>)} AS legacy_messages
|
|
82
|
-
SET #{quote_column(:raw_content)} = COALESCE(#{raw_content}, #{structured_content}),
|
|
83
|
-
#{quote_column(:content)} = #{rendered_content}
|
|
84
|
-
WHERE #{range}
|
|
85
|
-
AND #{content_raw} IS NOT NULL
|
|
86
|
-
SQL
|
|
87
|
-
end
|
|
88
|
-
end
|
|
89
|
-
|
|
90
|
-
def backfill_tool_results
|
|
91
|
-
result_reference = :<%= v1_tool_call_foreign_key %>
|
|
92
|
-
return mark_completed('tool_results') unless column_exists?(:<%= message_table_name %>, result_reference)
|
|
93
|
-
|
|
94
|
-
each_message_range('tool_results') do |range|
|
|
95
|
-
messages = quote_table(:<%= message_table_name %>)
|
|
96
|
-
tool_calls = quote_table(:ruby_llm_tool_calls)
|
|
97
|
-
message_id = quoted_primary_key(:<%= message_table_name %>)
|
|
98
|
-
tool_call_id = quoted_primary_key(:ruby_llm_tool_calls)
|
|
99
|
-
result_id = message_value(result_reference)
|
|
100
|
-
result_type = connection.quote('<%= message_model_name %>')
|
|
101
|
-
<% if mysql? -%>
|
|
102
|
-
execute <<~SQL
|
|
103
|
-
UPDATE #{tool_calls}
|
|
104
|
-
INNER JOIN (
|
|
105
|
-
SELECT #{result_id} AS matched_tool_call_id,
|
|
106
|
-
MIN(legacy_messages.#{message_id}) AS matched_result_id
|
|
107
|
-
FROM #{messages} legacy_messages
|
|
108
|
-
WHERE #{range}
|
|
109
|
-
AND #{result_id} IS NOT NULL
|
|
110
|
-
GROUP BY #{result_id}
|
|
111
|
-
) matches ON matches.matched_tool_call_id = #{tool_calls}.#{tool_call_id}
|
|
112
|
-
SET #{tool_calls}.result_id = matches.matched_result_id,
|
|
113
|
-
#{tool_calls}.result_type = #{result_type}
|
|
114
|
-
SQL
|
|
115
|
-
<% elsif postgresql? -%>
|
|
116
|
-
execute <<~SQL
|
|
117
|
-
UPDATE #{tool_calls}
|
|
118
|
-
SET result_id = legacy_messages.#{message_id},
|
|
119
|
-
result_type = #{result_type}
|
|
120
|
-
FROM #{messages} legacy_messages
|
|
121
|
-
WHERE #{result_id} = #{tool_calls}.#{tool_call_id}
|
|
122
|
-
AND #{range}
|
|
123
|
-
AND #{result_id} IS NOT NULL
|
|
124
|
-
SQL
|
|
125
|
-
<% else -%>
|
|
126
|
-
execute <<~SQL
|
|
127
|
-
UPDATE #{tool_calls}
|
|
128
|
-
SET result_id = (
|
|
129
|
-
SELECT legacy_messages.#{message_id}
|
|
130
|
-
FROM #{messages} legacy_messages
|
|
131
|
-
WHERE #{range}
|
|
132
|
-
AND #{result_id} = #{tool_calls}.#{tool_call_id}
|
|
133
|
-
ORDER BY legacy_messages.#{message_id}
|
|
134
|
-
LIMIT 1
|
|
135
|
-
),
|
|
136
|
-
result_type = #{result_type}
|
|
137
|
-
WHERE EXISTS (
|
|
138
|
-
SELECT 1
|
|
139
|
-
FROM #{messages} legacy_messages
|
|
140
|
-
WHERE #{range}
|
|
141
|
-
AND #{result_id} = #{tool_calls}.#{tool_call_id}
|
|
142
|
-
)
|
|
143
|
-
SQL
|
|
144
|
-
<% end -%>
|
|
145
|
-
end
|
|
146
|
-
end
|
|
147
|
-
|
|
148
|
-
def backfill_usages
|
|
149
|
-
joins, provider, model = usage_identity_sql
|
|
150
|
-
conditions = legacy_usage_conditions
|
|
151
|
-
return mark_completed('usages') if conditions.empty?
|
|
152
|
-
|
|
153
|
-
each_message_range('usages') do |range|
|
|
154
|
-
candidate = usage_candidate_sql(conditions, joins, range)
|
|
155
|
-
execute usage_insert_sql(candidate, provider, model)
|
|
156
|
-
end
|
|
157
|
-
end
|
|
158
|
-
|
|
159
|
-
def each_message_range(task)
|
|
160
|
-
return if completed?(task)
|
|
161
|
-
|
|
162
|
-
primary_key = connection.primary_key(:<%= message_table_name %>)
|
|
163
|
-
records = migration_record(:<%= message_table_name %>)
|
|
164
|
-
last_id = progress_value(task)
|
|
165
|
-
loop do
|
|
166
|
-
relation = records.order(primary_key => :asc)
|
|
167
|
-
relation = relation.where("#{quote_column(primary_key)} > ?", last_id) if last_id
|
|
168
|
-
ids = relation.limit(BATCH_SIZE).pluck(primary_key)
|
|
169
|
-
break mark_completed(task) if ids.empty?
|
|
170
|
-
|
|
171
|
-
upper_id = ids.last
|
|
172
|
-
range = message_range(primary_key, last_id, upper_id)
|
|
173
|
-
records.transaction do
|
|
174
|
-
with_upgrade_safety { yield range }
|
|
175
|
-
record_progress(task, upper_id)
|
|
176
|
-
end
|
|
177
|
-
say "#{task}: migrated through #{primary_key}=#{upper_id} (#{ids.length} messages)", true
|
|
178
|
-
last_id = upper_id
|
|
179
|
-
end
|
|
180
|
-
end
|
|
181
|
-
|
|
182
|
-
def message_range(primary_key, lower_id, upper_id)
|
|
183
|
-
upper = "#{message_value(primary_key)} <= #{connection.quote(upper_id)}"
|
|
184
|
-
return upper unless lower_id
|
|
185
|
-
|
|
186
|
-
"#{message_value(primary_key)} > #{connection.quote(lower_id)} AND #{upper}"
|
|
187
|
-
end
|
|
188
|
-
|
|
189
|
-
def verify_backfills
|
|
190
|
-
<% if postgresql? -%>
|
|
191
|
-
tables = %i[<%= chat_table_name %> <%= message_table_name %> ruby_llm_models ruby_llm_tool_calls ruby_llm_usages]
|
|
192
|
-
with_upgrade_safety { execute "ANALYZE #{tables.map { |table| quote_table(table) }.join(', ')}" }
|
|
193
|
-
<% end -%>
|
|
194
|
-
verify_message_content
|
|
195
|
-
verify_tool_results
|
|
196
|
-
verify_usages
|
|
197
|
-
end
|
|
198
|
-
|
|
199
|
-
def verify_message_content
|
|
200
|
-
return unless column_exists?(:<%= message_table_name %>, :content_raw)
|
|
201
|
-
|
|
202
|
-
missing = migration_record(:<%= message_table_name %>)
|
|
203
|
-
.where.not(content_raw: nil)
|
|
204
|
-
.where('raw_content IS NULL OR content IS NULL')
|
|
205
|
-
.limit(1)
|
|
206
|
-
.pick(connection.primary_key(:<%= message_table_name %>))
|
|
207
|
-
raise "Message #{missing} did not preserve content_raw" if missing
|
|
208
|
-
end
|
|
209
|
-
|
|
210
|
-
def verify_tool_results
|
|
211
|
-
result_reference = :<%= v1_tool_call_foreign_key %>
|
|
212
|
-
return unless column_exists?(:<%= message_table_name %>, result_reference)
|
|
213
|
-
|
|
214
|
-
messages = quote_table(:<%= message_table_name %>)
|
|
215
|
-
tool_calls = quote_table(:ruby_llm_tool_calls)
|
|
216
|
-
missing = select_value(<<~SQL)
|
|
217
|
-
SELECT legacy_messages.#{quoted_primary_key(:<%= message_table_name %>)}
|
|
218
|
-
FROM #{messages} legacy_messages
|
|
219
|
-
<% if copy_mode? -%>
|
|
220
|
-
LEFT JOIN #{quote_table(:<%= v1_tool_call_table_name %>)} legacy_tool_calls
|
|
221
|
-
ON #{message_value(result_reference)} = legacy_tool_calls.#{quoted_primary_key(:<%= v1_tool_call_table_name %>)}
|
|
222
|
-
LEFT JOIN #{tool_calls} migrated_tool_calls
|
|
223
|
-
ON legacy_tool_calls.tool_call_id = migrated_tool_calls.tool_call_id
|
|
224
|
-
AND legacy_tool_calls.#{quote_column(:<%= message_foreign_key %>)} = migrated_tool_calls.message_id
|
|
225
|
-
<% else -%>
|
|
226
|
-
LEFT JOIN #{tool_calls} migrated_tool_calls
|
|
227
|
-
ON #{message_value(result_reference)} = migrated_tool_calls.#{quoted_primary_key(:ruby_llm_tool_calls)}
|
|
228
|
-
<% end -%>
|
|
229
|
-
AND migrated_tool_calls.result_type = #{connection.quote('<%= message_model_name %>')}
|
|
230
|
-
AND migrated_tool_calls.result_id = legacy_messages.#{quoted_primary_key(:<%= message_table_name %>)}
|
|
231
|
-
WHERE #{message_value(result_reference)} IS NOT NULL
|
|
232
|
-
AND migrated_tool_calls.#{quoted_primary_key(:ruby_llm_tool_calls)} IS NULL
|
|
233
|
-
LIMIT 1
|
|
234
|
-
SQL
|
|
235
|
-
raise "Message #{missing} did not preserve its tool result association" if missing
|
|
236
|
-
end
|
|
237
|
-
|
|
238
|
-
def verify_usages
|
|
239
|
-
joins, provider, model = usage_identity_sql
|
|
240
|
-
conditions = legacy_usage_conditions
|
|
241
|
-
return if conditions.empty?
|
|
242
|
-
|
|
243
|
-
candidate = usage_candidate_sql(conditions, joins, '1 = 1')
|
|
244
|
-
missing = select_value(<<~SQL)
|
|
245
|
-
SELECT #{message_value(connection.primary_key(:<%= message_table_name %>))}
|
|
246
|
-
#{candidate}
|
|
247
|
-
LIMIT 1
|
|
248
|
-
SQL
|
|
249
|
-
return unless missing
|
|
250
|
-
|
|
251
|
-
raise "Message #{missing} did not receive its RubyLLM usage entry (provider: #{provider}, model: #{model})"
|
|
252
|
-
end
|
|
253
|
-
|
|
254
|
-
def usage_identity_sql
|
|
255
|
-
providers = []
|
|
256
|
-
models = []
|
|
257
|
-
joins = []
|
|
258
|
-
registry = quote_table(:ruby_llm_models)
|
|
259
|
-
registry_key = quoted_primary_key(:ruby_llm_models)
|
|
260
|
-
|
|
261
|
-
if column_exists?(:<%= message_table_name %>, :<%= v1_model_foreign_key %>)
|
|
262
|
-
explicit_model = column_definition(:<%= message_table_name %>, :<%= v1_model_foreign_key %>)
|
|
263
|
-
if explicit_model.type == :string
|
|
264
|
-
models << message_value(:<%= v1_model_foreign_key %>)
|
|
265
|
-
providers << message_value(:provider) if column_exists?(:<%= message_table_name %>, :provider)
|
|
266
|
-
else
|
|
267
|
-
joins << "LEFT JOIN #{registry} legacy_message_models " \
|
|
268
|
-
"ON #{message_value(:<%= v1_model_foreign_key %>)} = legacy_message_models.#{registry_key}"
|
|
269
|
-
providers << "legacy_message_models.#{quote_column(:provider)}"
|
|
270
|
-
models << "legacy_message_models.#{quote_column(:model_id)}"
|
|
271
|
-
end
|
|
272
|
-
end
|
|
273
|
-
|
|
274
|
-
if column_exists?(:<%= chat_table_name %>, :ruby_llm_model_id)
|
|
275
|
-
chats = quote_table(:<%= chat_table_name %>)
|
|
276
|
-
joins << "LEFT JOIN #{chats} legacy_chats " \
|
|
277
|
-
"ON #{message_value(:<%= chat_foreign_key %>)} = legacy_chats.#{quoted_primary_key(:<%= chat_table_name %>)}"
|
|
278
|
-
joins << "LEFT JOIN #{registry} legacy_chat_models " \
|
|
279
|
-
"ON legacy_chats.#{quote_column(:ruby_llm_model_id)} = legacy_chat_models.#{registry_key}"
|
|
280
|
-
providers << "legacy_chat_models.#{quote_column(:provider)}"
|
|
281
|
-
models << "legacy_chat_models.#{quote_column(:model_id)}"
|
|
282
|
-
end
|
|
283
|
-
|
|
284
|
-
[joins.join("\n"), coalesce_sql(providers), coalesce_sql(models)]
|
|
285
|
-
end
|
|
286
|
-
|
|
287
|
-
def legacy_usage_conditions
|
|
288
|
-
token_columns = %i[
|
|
289
|
-
input_tokens output_tokens cached_tokens cache_creation_tokens cache_read_tokens cache_write_tokens
|
|
290
|
-
thinking_tokens total_cost
|
|
291
|
-
]
|
|
292
|
-
conditions = token_columns.filter_map do |column|
|
|
293
|
-
"#{message_value(column)} IS NOT NULL" if column_exists?(:<%= message_table_name %>, column)
|
|
294
|
-
end
|
|
295
|
-
if column_exists?(:<%= message_table_name %>, :cost_details)
|
|
296
|
-
costs = %w[input output cache_read cache_write thinking total].map do |key|
|
|
297
|
-
"#{legacy_cost_detail(key)} IS NOT NULL"
|
|
298
|
-
end
|
|
299
|
-
conditions << "(#{costs.join(' OR ')})"
|
|
300
|
-
end
|
|
301
|
-
conditions << "#{message_value(:role)} = 'assistant'" if column_exists?(:<%= message_table_name %>, :role)
|
|
302
|
-
conditions
|
|
303
|
-
end
|
|
304
|
-
|
|
305
|
-
def usage_candidate_sql(conditions, joins, range)
|
|
306
|
-
usages = quote_table(:ruby_llm_usages)
|
|
307
|
-
usage_range = range.gsub(message_value(connection.primary_key(:<%= message_table_name %>)), 'existing_usages.message_id')
|
|
308
|
-
<<~SQL
|
|
309
|
-
FROM #{quote_table(:<%= message_table_name %>)} legacy_messages
|
|
310
|
-
#{joins}
|
|
311
|
-
WHERE (#{conditions.join(' OR ')})
|
|
312
|
-
AND #{range}
|
|
313
|
-
AND NOT EXISTS (
|
|
314
|
-
SELECT 1
|
|
315
|
-
FROM #{usages} existing_usages
|
|
316
|
-
WHERE existing_usages.message_type = #{connection.quote('<%= message_model_name %>')}
|
|
317
|
-
AND (#{usage_range})
|
|
318
|
-
AND existing_usages.message_id = #{message_value(connection.primary_key(:<%= message_table_name %>))}
|
|
319
|
-
AND existing_usages.operation = 'chat'
|
|
320
|
-
AND existing_usages.status = 'succeeded'
|
|
321
|
-
)
|
|
322
|
-
SQL
|
|
323
|
-
end
|
|
324
|
-
|
|
325
|
-
def usage_insert_sql(candidate, provider, model)
|
|
326
|
-
columns = %i[
|
|
327
|
-
chat_type chat_id message_type message_id operation provider model status
|
|
328
|
-
input_tokens output_tokens cache_read_tokens cache_write_tokens thinking_tokens
|
|
329
|
-
input_cost output_cost cache_read_cost cache_write_cost thinking_cost total_cost
|
|
330
|
-
created_at updated_at
|
|
331
|
-
].map { |column| quote_column(column) }.join(', ')
|
|
332
|
-
values = [
|
|
333
|
-
connection.quote('<%= chat_model_name %>'),
|
|
334
|
-
message_value(:<%= chat_foreign_key %>),
|
|
335
|
-
connection.quote('<%= message_model_name %>'),
|
|
336
|
-
message_value(connection.primary_key(:<%= message_table_name %>)),
|
|
337
|
-
connection.quote('chat'),
|
|
338
|
-
provider,
|
|
339
|
-
model,
|
|
340
|
-
connection.quote('succeeded'),
|
|
341
|
-
legacy_message_value(:input_tokens),
|
|
342
|
-
legacy_message_value(:output_tokens),
|
|
343
|
-
preferred_message_value(:cache_read_tokens, :cached_tokens),
|
|
344
|
-
preferred_message_value(:cache_write_tokens, :cache_creation_tokens),
|
|
345
|
-
legacy_message_value(:thinking_tokens),
|
|
346
|
-
legacy_cost_detail('input'),
|
|
347
|
-
legacy_cost_detail('output'),
|
|
348
|
-
legacy_cost_detail('cache_read'),
|
|
349
|
-
legacy_cost_detail('cache_write'),
|
|
350
|
-
legacy_cost_detail('thinking'),
|
|
351
|
-
legacy_total_cost,
|
|
352
|
-
legacy_message_value(:created_at, fallback: 'CURRENT_TIMESTAMP'),
|
|
353
|
-
legacy_message_value(:updated_at, fallback: 'CURRENT_TIMESTAMP')
|
|
354
|
-
].join(', ')
|
|
355
|
-
<<~SQL
|
|
356
|
-
INSERT INTO #{quote_table(:ruby_llm_usages)} (#{columns})
|
|
357
|
-
SELECT #{values}
|
|
358
|
-
#{candidate}
|
|
359
|
-
SQL
|
|
360
|
-
end
|
|
361
|
-
|
|
362
|
-
def preferred_message_value(preferred, fallback)
|
|
363
|
-
return message_value(preferred) if column_exists?(:<%= message_table_name %>, preferred)
|
|
364
|
-
|
|
365
|
-
legacy_message_value(fallback)
|
|
366
|
-
end
|
|
367
|
-
|
|
368
|
-
def legacy_message_value(column, fallback: 'NULL')
|
|
369
|
-
return fallback unless column_exists?(:<%= message_table_name %>, column)
|
|
370
|
-
|
|
371
|
-
message_value(column)
|
|
372
|
-
end
|
|
373
|
-
|
|
374
|
-
def legacy_cost_detail(key)
|
|
375
|
-
return 'NULL' unless column_exists?(:<%= message_table_name %>, :cost_details)
|
|
376
|
-
|
|
377
|
-
details = message_value(:cost_details)
|
|
378
|
-
<% if postgresql? -%>
|
|
379
|
-
"NULLIF(#{details}::jsonb ->> '#{key}', '')::numeric"
|
|
380
|
-
<% elsif mysql? -%>
|
|
381
|
-
"JSON_UNQUOTE(JSON_EXTRACT(#{details}, '$.#{key}'))"
|
|
382
|
-
<% else -%>
|
|
383
|
-
"json_extract(#{details}, '$.#{key}')"
|
|
384
|
-
<% end -%>
|
|
385
|
-
end
|
|
386
|
-
|
|
387
|
-
def legacy_total_cost
|
|
388
|
-
direct = legacy_message_value(:total_cost)
|
|
389
|
-
detailed = legacy_cost_detail('total')
|
|
390
|
-
return 'NULL' if direct == 'NULL' && detailed == 'NULL'
|
|
391
|
-
return detailed if direct == 'NULL'
|
|
392
|
-
return direct if detailed == 'NULL'
|
|
393
|
-
|
|
394
|
-
"COALESCE(#{direct}, #{detailed})"
|
|
395
|
-
end
|
|
396
|
-
|
|
397
|
-
def create_progress_table
|
|
398
|
-
return if table_exists?(PROGRESS_TABLE)
|
|
399
|
-
|
|
400
|
-
create_table PROGRESS_TABLE, id: false do |table|
|
|
401
|
-
table.string :task, null: false
|
|
402
|
-
table.column :last_id, reference_type_for(:<%= message_table_name %>)
|
|
403
|
-
table.boolean :completed, null: false, default: false
|
|
404
|
-
table.index :task, unique: true
|
|
405
|
-
end
|
|
406
|
-
end
|
|
407
|
-
|
|
408
|
-
def progress_value(task)
|
|
409
|
-
progress_records.where(task: task).pick(:last_id)
|
|
410
|
-
end
|
|
411
|
-
|
|
412
|
-
def completed?(task)
|
|
413
|
-
progress_records.where(task: task, completed: true).exists?
|
|
414
|
-
end
|
|
415
|
-
|
|
416
|
-
def record_progress(task, last_id)
|
|
417
|
-
row = progress_records.where(task: task)
|
|
418
|
-
return row.update_all(last_id: last_id, completed: false) if row.exists?
|
|
419
|
-
|
|
420
|
-
progress_records.insert_all!([{ task: task, last_id: last_id, completed: false }], returning: false)
|
|
421
|
-
end
|
|
422
|
-
|
|
423
|
-
def mark_completed(task)
|
|
424
|
-
row = progress_records.where(task: task)
|
|
425
|
-
return row.update_all(completed: true) if row.exists?
|
|
426
|
-
|
|
427
|
-
progress_records.insert_all!([{ task: task, completed: true }], returning: false)
|
|
428
|
-
end
|
|
429
|
-
|
|
430
|
-
def coalesce_sql(values)
|
|
431
|
-
values = Array(values).compact
|
|
432
|
-
return 'NULL' if values.empty?
|
|
433
|
-
return values.first if values.one?
|
|
434
|
-
|
|
435
|
-
"COALESCE(#{values.join(', ')})"
|
|
436
|
-
end
|
|
437
|
-
|
|
438
|
-
def message_value(column) = "legacy_messages.#{quote_column(column)}"
|
|
439
|
-
def column_definition(table, column) = connection.columns(table).find { |candidate| candidate.name == column.to_s }
|
|
440
|
-
def quoted_primary_key(table) = quote_column(connection.primary_key(table))
|
|
441
|
-
def quote_table(table) = connection.quote_table_name(table)
|
|
442
|
-
def quote_column(column) = connection.quote_column_name(column)
|
|
443
|
-
|
|
444
|
-
def reference_type_for(table)
|
|
445
|
-
column = column_definition(table, connection.primary_key(table))
|
|
446
|
-
raise "Could not determine the primary key type for #{table}" unless column
|
|
447
|
-
|
|
448
|
-
return :bigint if column.type == :integer && column.limit == 8
|
|
449
|
-
|
|
450
|
-
column.type
|
|
451
|
-
end
|
|
452
|
-
|
|
453
|
-
def with_upgrade_safety(&)
|
|
454
|
-
return safety_assured(&) if respond_to?(:safety_assured, true)
|
|
455
|
-
|
|
456
|
-
yield
|
|
457
|
-
end
|
|
458
|
-
|
|
459
|
-
<% end -%>
|
|
460
|
-
def progress_records = migration_record(PROGRESS_TABLE)
|
|
461
|
-
|
|
462
|
-
def migration_record(table)
|
|
463
|
-
Class.new(ActiveRecord::Base) do
|
|
464
|
-
self.table_name = table.to_s
|
|
465
|
-
self.inheritance_column = :_type_disabled
|
|
466
|
-
end
|
|
467
|
-
end
|
|
468
|
-
end
|
|
@@ -1,101 +0,0 @@
|
|
|
1
|
-
# frozen_string_literal: true
|
|
2
|
-
<% if copy_mode? -%>
|
|
3
|
-
|
|
4
|
-
require 'generators/ruby_llm/upgrade/upgrade_migration'
|
|
5
|
-
<% end -%>
|
|
6
|
-
|
|
7
|
-
class <%= cleanup_migration_class_name %> < ActiveRecord::Migration<%= migration_version %>
|
|
8
|
-
disable_ddl_transaction!
|
|
9
|
-
|
|
10
|
-
PROGRESS_TABLE = :ruby_llm_v2_backfills
|
|
11
|
-
|
|
12
|
-
def up
|
|
13
|
-
<% if copy_mode? -%>
|
|
14
|
-
RubyLLM::Generators::UpgradeMigration.for.verify_settings(<%= copy_upgrade_settings.inspect %>)
|
|
15
|
-
RubyLLM::Generators::UpgradeMigration.for.verify_cleanup
|
|
16
|
-
<% else -%>
|
|
17
|
-
raise 'Generate cleanup with --mode copy for this database' if table_exists?(:ruby_llm_v2_upgrades)
|
|
18
|
-
<% end -%>
|
|
19
|
-
unless table_exists?(PROGRESS_TABLE)
|
|
20
|
-
<% if copy_mode? -%>
|
|
21
|
-
RubyLLM::Generators::UpgradeMigration.for.cleanup { remove_legacy_message_columns }
|
|
22
|
-
return
|
|
23
|
-
<% else -%>
|
|
24
|
-
return unless legacy_columns.any?
|
|
25
|
-
|
|
26
|
-
raise 'RubyLLM 2.0 upgrade progress is missing. Run the finish migration before cleanup.'
|
|
27
|
-
<% end -%>
|
|
28
|
-
end
|
|
29
|
-
unless migration_record(PROGRESS_TABLE).where(task: 'finished', completed: true).exists?
|
|
30
|
-
raise 'Run <%= finish_migration_class_name %> before removing the legacy message columns'
|
|
31
|
-
end
|
|
32
|
-
|
|
33
|
-
<% if copy_mode? -%>
|
|
34
|
-
RubyLLM::Generators::UpgradeMigration.for.cleanup { remove_legacy_message_columns }
|
|
35
|
-
<% else -%>
|
|
36
|
-
remove_legacy_message_columns
|
|
37
|
-
<% end -%>
|
|
38
|
-
drop_table PROGRESS_TABLE
|
|
39
|
-
end
|
|
40
|
-
|
|
41
|
-
def down
|
|
42
|
-
raise ActiveRecord::IrreversibleMigration, 'RubyLLM 2.0 removes legacy message data'
|
|
43
|
-
end
|
|
44
|
-
|
|
45
|
-
private
|
|
46
|
-
|
|
47
|
-
def remove_legacy_message_columns
|
|
48
|
-
table = :<%= message_table_name %>
|
|
49
|
-
remove_matching_indexes(table, :role)
|
|
50
|
-
reference_columns = %i[<%= v1_model_foreign_key %> <%= v1_tool_call_foreign_key %>]
|
|
51
|
-
reference_columns.each do |column|
|
|
52
|
-
remove_column_foreign_keys(table, column)
|
|
53
|
-
remove_matching_indexes(table, column)
|
|
54
|
-
end
|
|
55
|
-
|
|
56
|
-
columns = legacy_columns
|
|
57
|
-
with_upgrade_safety { remove_columns table, *columns } if columns.any?
|
|
58
|
-
end
|
|
59
|
-
|
|
60
|
-
def remove_column_foreign_keys(table, column)
|
|
61
|
-
connection.foreign_keys(table).each do |key|
|
|
62
|
-
remove_foreign_key(table, key.to_table, column: column) if key.column == column.to_s
|
|
63
|
-
end
|
|
64
|
-
end
|
|
65
|
-
|
|
66
|
-
def remove_matching_indexes(table, columns)
|
|
67
|
-
connection.indexes(table).each do |index|
|
|
68
|
-
remove_upgrade_index(table, index.name) if index.columns == Array(columns).map(&:to_s)
|
|
69
|
-
end
|
|
70
|
-
end
|
|
71
|
-
|
|
72
|
-
def remove_upgrade_index(table, name)
|
|
73
|
-
options = { name: name }
|
|
74
|
-
<% if postgresql? -%>
|
|
75
|
-
options[:algorithm] = :concurrently
|
|
76
|
-
<% end -%>
|
|
77
|
-
remove_index table, **options
|
|
78
|
-
end
|
|
79
|
-
|
|
80
|
-
def with_upgrade_safety(&)
|
|
81
|
-
return safety_assured(&) if respond_to?(:safety_assured, true)
|
|
82
|
-
|
|
83
|
-
yield
|
|
84
|
-
end
|
|
85
|
-
|
|
86
|
-
def legacy_columns
|
|
87
|
-
candidates = %i[
|
|
88
|
-
<%= v1_model_foreign_key %> <%= v1_tool_call_foreign_key %>
|
|
89
|
-
content_raw input_tokens output_tokens cached_tokens cache_creation_tokens cache_read_tokens
|
|
90
|
-
cache_write_tokens thinking_tokens total_cost cost_details
|
|
91
|
-
]
|
|
92
|
-
candidates.uniq.select { |column| column_exists?(:<%= message_table_name %>, column) }
|
|
93
|
-
end
|
|
94
|
-
|
|
95
|
-
def migration_record(table)
|
|
96
|
-
Class.new(ActiveRecord::Base) do
|
|
97
|
-
self.table_name = table.to_s
|
|
98
|
-
self.inheritance_column = :_type_disabled
|
|
99
|
-
end
|
|
100
|
-
end
|
|
101
|
-
end
|