ruby_llm 1.13.2 → 1.16.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.
Files changed (171) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +16 -13
  3. data/lib/generators/ruby_llm/agent/agent_generator.rb +36 -0
  4. data/lib/generators/ruby_llm/agent/templates/agent.rb.tt +6 -0
  5. data/lib/generators/ruby_llm/agent/templates/instructions.txt.erb.tt +0 -0
  6. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +110 -41
  7. data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +14 -15
  8. data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +8 -11
  9. data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +2 -2
  10. data/lib/generators/ruby_llm/chat_ui/templates/helpers/messages_helper.rb.tt +25 -0
  11. data/lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt +1 -1
  12. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt +16 -0
  13. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +31 -0
  14. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt +31 -0
  15. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/new.html.erb.tt +9 -0
  16. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +27 -0
  17. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +14 -0
  18. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_content.html.erb.tt +1 -0
  19. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_error.html.erb.tt +13 -0
  20. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_form.html.erb.tt +23 -0
  21. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +10 -0
  22. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool.html.erb.tt +2 -0
  23. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +4 -0
  24. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +14 -0
  25. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +13 -0
  26. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt +21 -0
  27. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt +17 -0
  28. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt +40 -0
  29. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt +27 -0
  30. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +2 -2
  31. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +2 -2
  32. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +19 -7
  33. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/new.html.erb.tt +1 -1
  34. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +5 -3
  35. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +9 -0
  36. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_content.html.erb.tt +1 -1
  37. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_error.html.erb.tt +8 -0
  38. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt +1 -1
  39. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_system.html.erb.tt +6 -0
  40. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool.html.erb.tt +2 -0
  41. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +4 -7
  42. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_user.html.erb.tt +9 -0
  43. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +5 -7
  44. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +8 -0
  45. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt +16 -0
  46. data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +11 -12
  47. data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +27 -17
  48. data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +3 -4
  49. data/lib/generators/ruby_llm/generator_helpers.rb +41 -17
  50. data/lib/generators/ruby_llm/install/install_generator.rb +21 -18
  51. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +3 -4
  52. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +1 -1
  53. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +3 -3
  54. data/lib/generators/ruby_llm/schema/schema_generator.rb +26 -0
  55. data/lib/generators/ruby_llm/schema/templates/schema.rb.tt +2 -0
  56. data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +9 -0
  57. data/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt +13 -0
  58. data/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt +13 -0
  59. data/lib/generators/ruby_llm/tool/tool_generator.rb +96 -0
  60. data/lib/generators/ruby_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +1 -1
  61. data/lib/generators/ruby_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +7 -0
  62. data/lib/generators/ruby_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +49 -0
  63. data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +5 -7
  64. data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +1 -1
  65. data/lib/ruby_llm/active_record/acts_as.rb +6 -26
  66. data/lib/ruby_llm/active_record/acts_as_legacy.rb +125 -29
  67. data/lib/ruby_llm/active_record/chat_methods.rb +50 -26
  68. data/lib/ruby_llm/active_record/message_methods.rb +104 -4
  69. data/lib/ruby_llm/active_record/model_methods.rb +8 -10
  70. data/lib/ruby_llm/active_record/payload_helpers.rb +29 -0
  71. data/lib/ruby_llm/active_record/tool_call_methods.rb +18 -0
  72. data/lib/ruby_llm/agent.rb +15 -2
  73. data/lib/ruby_llm/aliases.json +141 -89
  74. data/lib/ruby_llm/aliases.rb +3 -0
  75. data/lib/ruby_llm/attachment.rb +44 -40
  76. data/lib/ruby_llm/chat.rb +229 -59
  77. data/lib/ruby_llm/configuration.rb +69 -75
  78. data/lib/ruby_llm/connection.rb +37 -10
  79. data/lib/ruby_llm/content.rb +15 -1
  80. data/lib/ruby_llm/cost.rb +224 -0
  81. data/lib/ruby_llm/deprecator.rb +24 -0
  82. data/lib/ruby_llm/embedding.rb +31 -1
  83. data/lib/ruby_llm/error.rb +16 -75
  84. data/lib/ruby_llm/error_middleware.rb +81 -0
  85. data/lib/ruby_llm/image.rb +39 -4
  86. data/lib/ruby_llm/instrumentation.rb +36 -0
  87. data/lib/ruby_llm/message.rb +20 -0
  88. data/lib/ruby_llm/mime_type.rb +25 -0
  89. data/lib/ruby_llm/model/info.rb +67 -14
  90. data/lib/ruby_llm/model/pricing.rb +19 -9
  91. data/lib/ruby_llm/model/pricing_category.rb +13 -2
  92. data/lib/ruby_llm/model/pricing_tier.rb +20 -9
  93. data/lib/ruby_llm/model_registry.rb +39 -0
  94. data/lib/ruby_llm/models.json +21796 -20601
  95. data/lib/ruby_llm/models.rb +107 -34
  96. data/lib/ruby_llm/models_schema.json +3 -0
  97. data/lib/ruby_llm/provider.rb +25 -4
  98. data/lib/ruby_llm/providers/anthropic/capabilities.rb +1 -133
  99. data/lib/ruby_llm/providers/anthropic/chat.rb +49 -15
  100. data/lib/ruby_llm/providers/anthropic/models.rb +4 -8
  101. data/lib/ruby_llm/providers/anthropic/streaming.rb +2 -0
  102. data/lib/ruby_llm/providers/anthropic/tools.rb +32 -3
  103. data/lib/ruby_llm/providers/anthropic.rb +4 -0
  104. data/lib/ruby_llm/providers/azure/media.rb +1 -1
  105. data/lib/ruby_llm/providers/azure.rb +4 -0
  106. data/lib/ruby_llm/providers/bedrock/auth.rb +1 -0
  107. data/lib/ruby_llm/providers/bedrock/chat.rb +26 -13
  108. data/lib/ruby_llm/providers/bedrock/media.rb +21 -3
  109. data/lib/ruby_llm/providers/bedrock/models.rb +1 -1
  110. data/lib/ruby_llm/providers/bedrock/streaming.rb +10 -1
  111. data/lib/ruby_llm/providers/bedrock.rb +5 -1
  112. data/lib/ruby_llm/providers/deepseek/capabilities.rb +28 -103
  113. data/lib/ruby_llm/providers/deepseek/chat.rb +9 -0
  114. data/lib/ruby_llm/providers/deepseek.rb +4 -0
  115. data/lib/ruby_llm/providers/gemini/capabilities.rb +45 -215
  116. data/lib/ruby_llm/providers/gemini/chat.rb +10 -4
  117. data/lib/ruby_llm/providers/gemini/images.rb +2 -2
  118. data/lib/ruby_llm/providers/gemini/media.rb +16 -9
  119. data/lib/ruby_llm/providers/gemini/models.rb +2 -4
  120. data/lib/ruby_llm/providers/gemini/streaming.rb +6 -1
  121. data/lib/ruby_llm/providers/gemini/tools.rb +5 -1
  122. data/lib/ruby_llm/providers/gemini.rb +4 -0
  123. data/lib/ruby_llm/providers/gpustack/chat.rb +8 -1
  124. data/lib/ruby_llm/providers/gpustack/models.rb +2 -0
  125. data/lib/ruby_llm/providers/gpustack.rb +4 -0
  126. data/lib/ruby_llm/providers/mistral/capabilities.rb +7 -2
  127. data/lib/ruby_llm/providers/mistral/chat.rb +56 -5
  128. data/lib/ruby_llm/providers/mistral/media.rb +55 -0
  129. data/lib/ruby_llm/providers/mistral/models.rb +2 -0
  130. data/lib/ruby_llm/providers/mistral.rb +5 -1
  131. data/lib/ruby_llm/providers/ollama/chat.rb +8 -1
  132. data/lib/ruby_llm/providers/ollama.rb +4 -0
  133. data/lib/ruby_llm/providers/openai/capabilities.rb +157 -195
  134. data/lib/ruby_llm/providers/openai/chat.rb +61 -7
  135. data/lib/ruby_llm/providers/openai/images.rb +58 -6
  136. data/lib/ruby_llm/providers/openai/media.rb +40 -16
  137. data/lib/ruby_llm/providers/openai/models.rb +2 -4
  138. data/lib/ruby_llm/providers/openai/streaming.rb +7 -6
  139. data/lib/ruby_llm/providers/openai/tools.rb +2 -0
  140. data/lib/ruby_llm/providers/openai/transcription.rb +1 -0
  141. data/lib/ruby_llm/providers/openai.rb +10 -0
  142. data/lib/ruby_llm/providers/openrouter/chat.rb +36 -8
  143. data/lib/ruby_llm/providers/openrouter/images.rb +3 -3
  144. data/lib/ruby_llm/providers/openrouter/models.rb +1 -1
  145. data/lib/ruby_llm/providers/openrouter/streaming.rb +5 -6
  146. data/lib/ruby_llm/providers/openrouter.rb +4 -0
  147. data/lib/ruby_llm/providers/perplexity/capabilities.rb +34 -99
  148. data/lib/ruby_llm/providers/perplexity/chat.rb +11 -0
  149. data/lib/ruby_llm/providers/perplexity/media.rb +62 -0
  150. data/lib/ruby_llm/providers/perplexity/models.rb +12 -14
  151. data/lib/ruby_llm/providers/perplexity.rb +5 -1
  152. data/lib/ruby_llm/providers/vertexai.rb +8 -0
  153. data/lib/ruby_llm/providers/xai/chat.rb +9 -0
  154. data/lib/ruby_llm/providers/xai/models.rb +15 -27
  155. data/lib/ruby_llm/providers/xai.rb +5 -1
  156. data/lib/ruby_llm/railtie.rb +11 -1
  157. data/lib/ruby_llm/stream_accumulator.rb +45 -30
  158. data/lib/ruby_llm/streaming.rb +4 -0
  159. data/lib/ruby_llm/tokens.rb +8 -0
  160. data/lib/ruby_llm/tool.rb +24 -7
  161. data/lib/ruby_llm/tool_concurrency.rb +105 -0
  162. data/lib/ruby_llm/transcription.rb +2 -1
  163. data/lib/ruby_llm/utils.rb +39 -0
  164. data/lib/ruby_llm/version.rb +1 -1
  165. data/lib/ruby_llm.rb +11 -6
  166. data/lib/tasks/models.rake +45 -16
  167. data/lib/tasks/release.rake +51 -24
  168. data/lib/tasks/ruby_llm.rake +6 -5
  169. data/lib/tasks/vcr.rake +1 -1
  170. metadata +81 -21
  171. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_message.html.erb.tt +0 -13
@@ -329,25 +329,26 @@ end
329
329
 
330
330
  def standard_pricing_display(model)
331
331
  pricing_data = model.pricing.to_h[:text_tokens]&.dig(:standard) || {}
332
+ parts = [
333
+ pricing_part(pricing_data, :input_per_million, 'In'),
334
+ pricing_part(pricing_data, :output_per_million, 'Out'),
335
+ pricing_part(pricing_data, %i[cache_read_input_per_million cached_input_per_million], 'Cache Read'),
336
+ pricing_part(pricing_data, %i[cache_write_input_per_million cache_creation_input_per_million], 'Cache Write')
337
+ ].compact
332
338
 
333
- if pricing_data.any?
334
- parts = []
339
+ return parts.join(', ') if parts.any?
335
340
 
336
- parts << "In: $#{format('%.2f', pricing_data[:input_per_million])}" if pricing_data[:input_per_million]
337
-
338
- parts << "Out: $#{format('%.2f', pricing_data[:output_per_million])}" if pricing_data[:output_per_million]
339
-
340
- if pricing_data[:cached_input_per_million]
341
- parts << "Cache: $#{format('%.2f', pricing_data[:cached_input_per_million])}"
342
- end
341
+ '-'
342
+ end
343
343
 
344
- return parts.join(', ') if parts.any?
345
- end
344
+ def pricing_part(pricing_data, key, label)
345
+ key = Array(key).find { |candidate| pricing_data[candidate] }
346
+ return unless key
346
347
 
347
- '-'
348
+ "#{label}: $#{format('%.2f', pricing_data[key])}"
348
349
  end
349
350
 
350
- def generate_aliases # rubocop:disable Metrics/PerceivedComplexity
351
+ def generate_aliases # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
351
352
  models = Hash.new { |h, k| h[k] = [] }
352
353
 
353
354
  RubyLLM.models.all.each do |model|
@@ -466,12 +467,40 @@ def generate_aliases # rubocop:disable Metrics/PerceivedComplexity
466
467
  }
467
468
  end
468
469
 
470
+ add_xai_aliases(aliases, models['xai'])
471
+
469
472
  sorted_aliases = aliases.sort.to_h
470
473
  File.write(RubyLLM::Aliases.aliases_file, JSON.pretty_generate(sorted_aliases))
471
474
 
472
475
  puts "Generated #{sorted_aliases.size} aliases"
473
476
  end
474
477
 
478
+ def add_xai_aliases(aliases, xai_models)
479
+ return unless xai_models.include?('grok-4.3')
480
+
481
+ %w[
482
+ grok-latest
483
+ grok-3
484
+ grok-3-latest
485
+ grok-3-mini
486
+ grok-3-mini-latest
487
+ grok-4
488
+ grok-4-latest
489
+ grok-4-fast
490
+ grok-4-fast-reasoning
491
+ grok-4-fast-reasoning-latest
492
+ grok-4-fast-non-reasoning
493
+ grok-4-fast-non-reasoning-latest
494
+ grok-4-1-fast
495
+ grok-4-1-fast-reasoning
496
+ grok-4-1-fast-reasoning-latest
497
+ grok-4-1-fast-non-reasoning
498
+ grok-4-1-fast-non-reasoning-latest
499
+ ].each do |alias_key|
500
+ aliases[alias_key] ||= { 'xai' => 'grok-4.3' }
501
+ end
502
+ end
503
+
475
504
  def group_anthropic_models_by_base_name(anthropic_models)
476
505
  grouped = Hash.new { |h, k| h[k] = [] }
477
506
 
@@ -518,12 +547,12 @@ def find_best_bedrock_model(anthropic_model, bedrock_models) # rubocop:disable M
518
547
  when 'claude-instant-v1', 'claude-instant'
519
548
  'claude-instant'
520
549
  else
521
- extract_base_name(anthropic_model)
550
+ anthropic_model
522
551
  end
523
552
 
524
553
  matching_models = bedrock_models.select do |bedrock_model|
525
- model_without_prefix = bedrock_model.sub(/^(?:us\.)?anthropic\./, '')
526
- model_without_prefix.start_with?(base_pattern)
554
+ model_without_prefix = bedrock_model.sub(/^(?:(?:[a-z]{2}|global)\.)?anthropic\./, '')
555
+ model_without_prefix.match?(/\A#{Regexp.escape(base_pattern)}(?:-v\d+|:\d+k|$)/)
527
556
  end
528
557
 
529
558
  return nil if matching_models.empty?
@@ -1,5 +1,48 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'time'
4
+ require 'yaml'
5
+
6
+ # Shared helpers for release-related Rake tasks.
7
+ module ReleaseTasks
8
+ module_function
9
+
10
+ def cassette_recorded_at_times(cassette)
11
+ data = YAML.safe_load_file(cassette, aliases: true)
12
+
13
+ Array(data['http_interactions']).filter_map do |interaction|
14
+ Time.parse(interaction['recorded_at'])
15
+ rescue ArgumentError, TypeError
16
+ nil
17
+ end
18
+ rescue Psych::Exception => e
19
+ abort "Could not parse VCR cassette #{cassette}: #{e.message}"
20
+ end
21
+
22
+ def cassette_recorded_at(cassette)
23
+ recorded_at_times = cassette_recorded_at_times(cassette)
24
+
25
+ abort "No recorded_at timestamps found in VCR cassette #{cassette}" if recorded_at_times.empty?
26
+
27
+ recorded_at_times.min
28
+ end
29
+
30
+ def find_stale_cassettes(cassette_dir, max_age_days)
31
+ Dir.glob("#{cassette_dir}/**/*.yml").filter_map do |cassette|
32
+ recorded_at = cassette_recorded_at(cassette)
33
+ age_days = (Time.now - recorded_at) / 86_400
34
+
35
+ next unless age_days > max_age_days
36
+
37
+ {
38
+ path: cassette,
39
+ file: File.basename(cassette),
40
+ age: age_days.round(1)
41
+ }
42
+ end
43
+ end
44
+ end
45
+
3
46
  namespace :release do # rubocop:disable Metrics/BlockLength
4
47
  desc 'Prepare for release'
5
48
  task :prepare do
@@ -13,20 +56,17 @@ namespace :release do # rubocop:disable Metrics/BlockLength
13
56
  max_age_days = 1
14
57
  cassette_dir = 'spec/fixtures/vcr_cassettes'
15
58
 
16
- stale_count = 0
17
- Dir.glob("#{cassette_dir}/**/*.yml").each do |cassette|
18
- age_days = (Time.now - File.mtime(cassette)) / 86_400
19
- next unless age_days > max_age_days
59
+ stale_cassettes = ReleaseTasks.find_stale_cassettes(cassette_dir, max_age_days)
20
60
 
21
- puts "Removing stale cassette: #{File.basename(cassette)} (#{age_days.round(1)} days old)"
22
- File.delete(cassette)
23
- stale_count += 1
61
+ stale_cassettes.each do |cassette|
62
+ puts "Removing stale cassette: #{cassette[:file]} (#{cassette[:age]} days old)"
63
+ File.delete(cassette[:path])
24
64
  end
25
65
 
26
- if stale_count.positive?
27
- puts "\n🗑️ Removed #{stale_count} stale cassettes"
66
+ if stale_cassettes.any?
67
+ puts "\n🗑️ Removed #{stale_cassettes.size} stale cassettes"
28
68
  puts '🔄 Re-recording cassettes...'
29
- run_parallel_rspec || exit(1)
69
+ run_test_queue_rspec || exit(1)
30
70
  puts '✅ Cassettes refreshed!'
31
71
  else
32
72
  puts '✅ No stale cassettes found'
@@ -37,25 +77,12 @@ namespace :release do # rubocop:disable Metrics/BlockLength
37
77
  task :verify_cassettes do
38
78
  max_age_days = 1
39
79
  cassette_dir = 'spec/fixtures/vcr_cassettes'
40
- stale_cassettes = []
41
-
42
- Dir.glob("#{cassette_dir}/**/*.yml").each do |cassette|
43
- age_days = (Time.now - File.mtime(cassette)) / 86_400
44
-
45
- next unless age_days > max_age_days
46
-
47
- stale_cassettes << {
48
- file: File.basename(cassette),
49
- age: age_days.round(1)
50
- }
51
- end
80
+ stale_cassettes = ReleaseTasks.find_stale_cassettes(cassette_dir, max_age_days)
52
81
 
53
82
  if stale_cassettes.any?
54
83
  puts "\n❌ Found stale cassettes (older than #{max_age_days} days):"
55
- stale_files = []
56
84
  stale_cassettes.each do |c|
57
85
  puts " - #{c[:file]} (#{c[:age]} days old)"
58
- stale_files << File.join(cassette_dir, '**', c[:file])
59
86
  end
60
87
 
61
88
  puts "\nRun locally: bundle exec rake release:refresh_stale_cassettes"
@@ -1,10 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- def run_parallel_rspec
3
+ def run_test_queue_rspec
4
4
  workers = ENV.fetch('RSPEC_WORKERS', nil)
5
- cmd = %w[bundle exec parallel_rspec]
6
- cmd += ['-n', workers] if workers && !workers.empty?
7
- system(*cmd)
5
+ env = {}
6
+ env['TEST_QUEUE_WORKERS'] = workers if workers && !workers.empty? && ENV.fetch('TEST_QUEUE_WORKERS', '').empty?
7
+
8
+ system(env, 'bundle', 'exec', 'bin/rspec-queue')
8
9
  end
9
10
 
10
11
  namespace :ruby_llm do
@@ -16,7 +17,7 @@ namespace :ruby_llm do
16
17
  model_class.save_to_database
17
18
  puts "✅ Loaded #{model_class.count} models into database"
18
19
  else
19
- puts 'Model registry not configured. Run rails generate ruby_llm:install'
20
+ puts 'Model registry not configured. Run bin/rails generate ruby_llm:install'
20
21
  end
21
22
  end
22
23
  end
data/lib/tasks/vcr.rake CHANGED
@@ -71,7 +71,7 @@ def delete_cassettes(cassettes)
71
71
  end
72
72
 
73
73
  def run_tests
74
- run_parallel_rspec || abort('Tests failed')
74
+ run_test_queue_rspec || abort('Tests failed')
75
75
  end
76
76
 
77
77
  def retimestamp_cassettes(cassette_dir)
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby_llm
3
3
  version: !ruby/object:Gem::Version
4
- version: 1.13.2
4
+ version: 1.16.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Carmine Paolino
@@ -43,30 +43,30 @@ dependencies:
43
43
  requirements:
44
44
  - - ">="
45
45
  - !ruby/object:Gem::Version
46
- version: '2.0'
46
+ version: 1.10.0
47
47
  type: :runtime
48
48
  prerelease: false
49
49
  version_requirements: !ruby/object:Gem::Requirement
50
50
  requirements:
51
51
  - - ">="
52
52
  - !ruby/object:Gem::Version
53
- version: '2.0'
53
+ version: 1.10.0
54
54
  - !ruby/object:Gem::Dependency
55
- name: faraday-retry
55
+ name: faraday-multipart
56
56
  requirement: !ruby/object:Gem::Requirement
57
57
  requirements:
58
58
  - - ">="
59
59
  - !ruby/object:Gem::Version
60
- version: '2.0'
60
+ version: '1'
61
61
  type: :runtime
62
62
  prerelease: false
63
63
  version_requirements: !ruby/object:Gem::Requirement
64
64
  requirements:
65
65
  - - ">="
66
66
  - !ruby/object:Gem::Version
67
- version: '2.0'
67
+ version: '1'
68
68
  - !ruby/object:Gem::Dependency
69
- name: faraday-multipart
69
+ name: faraday-net_http
70
70
  requirement: !ruby/object:Gem::Requirement
71
71
  requirements:
72
72
  - - ">="
@@ -80,7 +80,7 @@ dependencies:
80
80
  - !ruby/object:Gem::Version
81
81
  version: '1'
82
82
  - !ruby/object:Gem::Dependency
83
- name: faraday-net_http
83
+ name: faraday-retry
84
84
  requirement: !ruby/object:Gem::Requirement
85
85
  requirements:
86
86
  - - ">="
@@ -135,13 +135,13 @@ dependencies:
135
135
  - - "~>"
136
136
  - !ruby/object:Gem::Version
137
137
  version: '2'
138
- description: One beautiful Ruby API for GPT, Claude, Gemini, and more. Easily build
139
- chatbots, AI agents, RAG applications, and content generators. Features chat (text,
140
- images, audio, PDFs), image generation, embeddings, tools (function calling), structured
141
- output, Rails integration, and streaming. Works with OpenAI, Anthropic, Google Gemini,
142
- AWS Bedrock, DeepSeek, Mistral, Ollama (local models), OpenRouter, Perplexity, GPUStack,
143
- and any OpenAI-compatible API. Minimal dependencies - just Faraday, Zeitwerk, and
144
- Marcel.
138
+ description: A single, beautiful Ruby framework for all major AI providers. Easily
139
+ build chatbots, AI agents, RAG applications, content generators, and every AI workflow
140
+ you can think of. Features chat (text, images, audio, PDFs), image generation, embeddings,
141
+ tools (function calling), structured output, Rails integration, and streaming. Works
142
+ with OpenAI, Anthropic, Google Gemini, AWS Bedrock, DeepSeek, Mistral, Ollama (local
143
+ models), OpenRouter, Perplexity, GPUStack, and any OpenAI-compatible API. Minimal
144
+ dependencies - just Faraday, Zeitwerk, and Marcel.
145
145
  email:
146
146
  - carmine@paolino.me
147
147
  executables: []
@@ -150,21 +150,49 @@ extra_rdoc_files: []
150
150
  files:
151
151
  - LICENSE
152
152
  - README.md
153
+ - lib/generators/ruby_llm/agent/agent_generator.rb
154
+ - lib/generators/ruby_llm/agent/templates/agent.rb.tt
155
+ - lib/generators/ruby_llm/agent/templates/instructions.txt.erb.tt
153
156
  - lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb
154
157
  - lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt
155
158
  - lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt
156
159
  - lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt
160
+ - lib/generators/ruby_llm/chat_ui/templates/helpers/messages_helper.rb.tt
157
161
  - lib/generators/ruby_llm/chat_ui/templates/jobs/chat_response_job.rb.tt
162
+ - lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt
163
+ - lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt
164
+ - lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt
165
+ - lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/new.html.erb.tt
166
+ - lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt
167
+ - lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt
168
+ - lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_content.html.erb.tt
169
+ - lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_error.html.erb.tt
170
+ - lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_form.html.erb.tt
171
+ - lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt
172
+ - lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool.html.erb.tt
173
+ - lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt
174
+ - lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt
175
+ - lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt
176
+ - lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_results/_default.html.erb.tt
177
+ - lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt
178
+ - lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt
179
+ - lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt
158
180
  - lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt
159
181
  - lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt
160
182
  - lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt
161
183
  - lib/generators/ruby_llm/chat_ui/templates/views/chats/new.html.erb.tt
162
184
  - lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt
185
+ - lib/generators/ruby_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt
163
186
  - lib/generators/ruby_llm/chat_ui/templates/views/messages/_content.html.erb.tt
187
+ - lib/generators/ruby_llm/chat_ui/templates/views/messages/_error.html.erb.tt
164
188
  - lib/generators/ruby_llm/chat_ui/templates/views/messages/_form.html.erb.tt
165
- - lib/generators/ruby_llm/chat_ui/templates/views/messages/_message.html.erb.tt
189
+ - lib/generators/ruby_llm/chat_ui/templates/views/messages/_system.html.erb.tt
190
+ - lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool.html.erb.tt
166
191
  - lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt
192
+ - lib/generators/ruby_llm/chat_ui/templates/views/messages/_user.html.erb.tt
167
193
  - lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt
194
+ - lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt
195
+ - lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_results/_default.html.erb.tt
168
196
  - lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt
169
197
  - lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt
170
198
  - lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt
@@ -180,8 +208,16 @@ files:
180
208
  - lib/generators/ruby_llm/install/templates/message_model.rb.tt
181
209
  - lib/generators/ruby_llm/install/templates/model_model.rb.tt
182
210
  - lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt
211
+ - lib/generators/ruby_llm/schema/schema_generator.rb
212
+ - lib/generators/ruby_llm/schema/templates/schema.rb.tt
213
+ - lib/generators/ruby_llm/tool/templates/tool.rb.tt
214
+ - lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt
215
+ - lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt
216
+ - lib/generators/ruby_llm/tool/tool_generator.rb
183
217
  - lib/generators/ruby_llm/upgrade_to_v1_10/templates/add_v1_10_message_columns.rb.tt
184
218
  - lib/generators/ruby_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb
219
+ - lib/generators/ruby_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt
220
+ - lib/generators/ruby_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb
185
221
  - lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt
186
222
  - lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb
187
223
  - lib/generators/ruby_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt
@@ -192,6 +228,8 @@ files:
192
228
  - lib/ruby_llm/active_record/chat_methods.rb
193
229
  - lib/ruby_llm/active_record/message_methods.rb
194
230
  - lib/ruby_llm/active_record/model_methods.rb
231
+ - lib/ruby_llm/active_record/payload_helpers.rb
232
+ - lib/ruby_llm/active_record/tool_call_methods.rb
195
233
  - lib/ruby_llm/agent.rb
196
234
  - lib/ruby_llm/aliases.json
197
235
  - lib/ruby_llm/aliases.rb
@@ -202,9 +240,13 @@ files:
202
240
  - lib/ruby_llm/connection.rb
203
241
  - lib/ruby_llm/content.rb
204
242
  - lib/ruby_llm/context.rb
243
+ - lib/ruby_llm/cost.rb
244
+ - lib/ruby_llm/deprecator.rb
205
245
  - lib/ruby_llm/embedding.rb
206
246
  - lib/ruby_llm/error.rb
247
+ - lib/ruby_llm/error_middleware.rb
207
248
  - lib/ruby_llm/image.rb
249
+ - lib/ruby_llm/instrumentation.rb
208
250
  - lib/ruby_llm/message.rb
209
251
  - lib/ruby_llm/mime_type.rb
210
252
  - lib/ruby_llm/model.rb
@@ -213,6 +255,7 @@ files:
213
255
  - lib/ruby_llm/model/pricing.rb
214
256
  - lib/ruby_llm/model/pricing_category.rb
215
257
  - lib/ruby_llm/model/pricing_tier.rb
258
+ - lib/ruby_llm/model_registry.rb
216
259
  - lib/ruby_llm/models.json
217
260
  - lib/ruby_llm/models.rb
218
261
  - lib/ruby_llm/models_schema.json
@@ -260,6 +303,7 @@ files:
260
303
  - lib/ruby_llm/providers/mistral/capabilities.rb
261
304
  - lib/ruby_llm/providers/mistral/chat.rb
262
305
  - lib/ruby_llm/providers/mistral/embeddings.rb
306
+ - lib/ruby_llm/providers/mistral/media.rb
263
307
  - lib/ruby_llm/providers/mistral/models.rb
264
308
  - lib/ruby_llm/providers/ollama.rb
265
309
  - lib/ruby_llm/providers/ollama/capabilities.rb
@@ -286,6 +330,7 @@ files:
286
330
  - lib/ruby_llm/providers/perplexity.rb
287
331
  - lib/ruby_llm/providers/perplexity/capabilities.rb
288
332
  - lib/ruby_llm/providers/perplexity/chat.rb
333
+ - lib/ruby_llm/providers/perplexity/media.rb
289
334
  - lib/ruby_llm/providers/perplexity/models.rb
290
335
  - lib/ruby_llm/providers/vertexai.rb
291
336
  - lib/ruby_llm/providers/vertexai/chat.rb
@@ -303,6 +348,7 @@ files:
303
348
  - lib/ruby_llm/tokens.rb
304
349
  - lib/ruby_llm/tool.rb
305
350
  - lib/ruby_llm/tool_call.rb
351
+ - lib/ruby_llm/tool_concurrency.rb
306
352
  - lib/ruby_llm/transcription.rb
307
353
  - lib/ruby_llm/utils.rb
308
354
  - lib/ruby_llm/version.rb
@@ -316,14 +362,28 @@ licenses:
316
362
  metadata:
317
363
  homepage_uri: https://rubyllm.com
318
364
  source_code_uri: https://github.com/crmne/ruby_llm
319
- changelog_uri: https://github.com/crmne/ruby_llm/commits/main
365
+ changelog_uri: https://github.com/crmne/ruby_llm/releases
320
366
  documentation_uri: https://rubyllm.com
321
367
  bug_tracker_uri: https://github.com/crmne/ruby_llm/issues
322
368
  funding_uri: https://github.com/sponsors/crmne
323
369
  rubygems_mfa_required: 'true'
324
370
  post_install_message: |
325
- Upgrading from RubyLLM < 1.10.x? Check the upgrade guide for new features and migration instructions
326
- --> https://rubyllm.com/upgrading/
371
+ RubyLLM 1.15 upgrade note:
372
+
373
+ Token accounting is now normalized across providers. `input_tokens` means
374
+ standard input tokens; prompt cache reads and writes are exposed separately
375
+ as `cache_read_tokens` and `cache_write_tokens`.
376
+
377
+ Need request-side input activity?
378
+ input_tokens + cache_read_tokens + cache_write_tokens
379
+
380
+ New cost helpers:
381
+ response.cost.total
382
+ chat.cost.total
383
+ agent.cost.total
384
+
385
+ Upgrading from RubyLLM < 1.15? Read the full upgrade guide:
386
+ https://rubyllm.com/upgrading/
327
387
  rdoc_options: []
328
388
  require_paths:
329
389
  - lib
@@ -338,7 +398,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
338
398
  - !ruby/object:Gem::Version
339
399
  version: '0'
340
400
  requirements: []
341
- rubygems_version: 4.0.3
401
+ rubygems_version: 4.0.10
342
402
  specification_version: 4
343
- summary: One beautiful Ruby API for GPT, Claude, Gemini, and more.
403
+ summary: A single, beautiful Ruby framework for all major AI providers.
344
404
  test_files: []
@@ -1,13 +0,0 @@
1
- <div id="<%= message_variable_name %>_<%%= <%= message_model_name.demodulize.underscore %>.id %>" class="<%= message_variable_name %>"
2
- style="margin-bottom: 20px; padding: 10px; border-left: 3px solid <%%= <%= message_model_name.demodulize.underscore %>.role == 'user' ? '#007bff' : '#28a745' %>">
3
- <div style="font-weight: bold; margin-bottom: 5px;">
4
- <%%= <%= message_model_name.demodulize.underscore %>.role&.capitalize %>
5
- </div>
6
- <div id="<%= message_variable_name %>_<%%= <%= message_model_name.demodulize.underscore %>.id %>_content" style="white-space: pre-wrap;"><%%= <%= message_model_name.demodulize.underscore %>.content %></div>
7
- <%% if <%= message_model_name.demodulize.underscore %>.tool_call? %>
8
- <%%= render "<%= message_model_name.underscore.pluralize %>/tool_calls", <%= message_model_name.demodulize.underscore %>: <%= message_model_name.demodulize.underscore %> %>
9
- <%% end %>
10
- <div style="font-size: 0.85em; color: #666; margin-top: 5px;">
11
- <%%= <%= message_model_name.demodulize.underscore %>.created_at&.strftime("%I:%M %p") %>
12
- </div>
13
- </div>