ruby_llm 1.16.0 → 2.0.0.rc2

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 (475) hide show
  1. checksums.yaml +4 -4
  2. data/.rdoc_options +25 -0
  3. data/README.md +85 -32
  4. data/exe/ruby_llm +8 -0
  5. data/lib/generators/ruby_llm/agent/templates/agent.rb.tt +0 -1
  6. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +3 -43
  7. data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +11 -3
  8. data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +8 -0
  9. data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +4 -4
  10. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt +1 -1
  11. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +1 -1
  12. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt +1 -1
  13. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +2 -2
  14. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +1 -1
  15. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +1 -1
  16. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool.html.erb.tt +1 -1
  17. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +6 -4
  18. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +1 -1
  19. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +2 -2
  20. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt +5 -6
  21. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt +2 -2
  22. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt +5 -5
  23. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +1 -1
  24. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +1 -1
  25. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +1 -1
  26. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +2 -2
  27. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +1 -1
  28. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_system.html.erb.tt +1 -1
  29. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool.html.erb.tt +1 -1
  30. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +6 -4
  31. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_user.html.erb.tt +1 -1
  32. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +4 -6
  33. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +2 -2
  34. data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +5 -6
  35. data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +2 -2
  36. data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +3 -3
  37. data/lib/generators/ruby_llm/generator_helpers.rb +106 -62
  38. data/lib/generators/ruby_llm/install/install_generator.rb +3 -11
  39. data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +2 -0
  40. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +14 -8
  41. data/lib/generators/ruby_llm/install/templates/create_ruby_llm_records_migration.rb.tt +117 -0
  42. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +0 -8
  43. data/lib/generators/ruby_llm/provider/cli.rb +175 -0
  44. data/lib/generators/ruby_llm/provider/scaffold.rb +323 -0
  45. data/lib/generators/ruby_llm/provider/templates/core/provider.rb.erb +37 -0
  46. data/lib/generators/ruby_llm/provider/templates/core/provider_spec.rb.erb +34 -0
  47. data/lib/generators/ruby_llm/provider/templates/gem/archspec.rb.erb +14 -0
  48. data/lib/generators/ruby_llm/provider/templates/gem/bin/console.erb +15 -0
  49. data/lib/generators/ruby_llm/provider/templates/gem/bin/setup.erb +5 -0
  50. data/lib/generators/ruby_llm/provider/templates/gem/chat_schema_spec.rb.erb +30 -0
  51. data/lib/generators/ruby_llm/provider/templates/gem/chat_spec.rb.erb +35 -0
  52. data/lib/generators/ruby_llm/provider/templates/gem/chat_streaming_spec.rb.erb +22 -0
  53. data/lib/generators/ruby_llm/provider/templates/gem/chat_tools_spec.rb.erb +30 -0
  54. data/lib/generators/ruby_llm/provider/templates/gem/ci.yml.erb +32 -0
  55. data/lib/generators/ruby_llm/provider/templates/gem/embedding_spec.rb.erb +49 -0
  56. data/lib/generators/ruby_llm/provider/templates/gem/env.erb +2 -0
  57. data/lib/generators/ruby_llm/provider/templates/gem/fixtures_gitkeep.erb +1 -0
  58. data/lib/generators/ruby_llm/provider/templates/gem/flayignore.erb +1 -0
  59. data/lib/generators/ruby_llm/provider/templates/gem/gemfile.erb +23 -0
  60. data/lib/generators/ruby_llm/provider/templates/gem/gemspec.erb +28 -0
  61. data/lib/generators/ruby_llm/provider/templates/gem/gitignore.erb +7 -0
  62. data/lib/generators/ruby_llm/provider/templates/gem/gitleaks.yml.erb +22 -0
  63. data/lib/generators/ruby_llm/provider/templates/gem/image_spec.rb.erb +23 -0
  64. data/lib/generators/ruby_llm/provider/templates/gem/license.erb +21 -0
  65. data/lib/generators/ruby_llm/provider/templates/gem/models.rb.erb +19 -0
  66. data/lib/generators/ruby_llm/provider/templates/gem/models_spec.rb.erb +17 -0
  67. data/lib/generators/ruby_llm/provider/templates/gem/moderation_spec.rb.erb +22 -0
  68. data/lib/generators/ruby_llm/provider/templates/gem/overcommit.yml.erb +31 -0
  69. data/lib/generators/ruby_llm/provider/templates/gem/provider.rb.erb +52 -0
  70. data/lib/generators/ruby_llm/provider/templates/gem/provider_spec.rb.erb +38 -0
  71. data/lib/generators/ruby_llm/provider/templates/gem/rakefile.erb +38 -0
  72. data/lib/generators/ruby_llm/provider/templates/gem/readme.md.erb +50 -0
  73. data/lib/generators/ruby_llm/provider/templates/gem/release.yml.erb +36 -0
  74. data/lib/generators/ruby_llm/provider/templates/gem/rerank_spec.rb.erb +23 -0
  75. data/lib/generators/ruby_llm/provider/templates/gem/rspec.erb +2 -0
  76. data/lib/generators/ruby_llm/provider/templates/gem/rubocop.yml.erb +29 -0
  77. data/lib/generators/ruby_llm/provider/templates/gem/rubyllm_configuration.rb.erb +14 -0
  78. data/lib/generators/ruby_llm/provider/templates/gem/spec_helper.rb.erb +26 -0
  79. data/lib/generators/ruby_llm/provider/templates/gem/speech_spec.rb.erb +25 -0
  80. data/lib/generators/ruby_llm/provider/templates/gem/vcr_configuration.rb.erb +16 -0
  81. data/lib/generators/ruby_llm/provider/templates/gem/video_spec.rb.erb +27 -0
  82. data/lib/generators/ruby_llm/schema/schema_generator.rb +5 -1
  83. data/lib/generators/ruby_llm/schema/templates/schema.rb.tt +1 -1
  84. data/lib/generators/ruby_llm/tool/templates/tailwind/tool_call.html.erb.tt +13 -0
  85. data/lib/generators/ruby_llm/tool/templates/tailwind/tool_result.html.erb.tt +21 -0
  86. data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +3 -3
  87. data/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt +7 -12
  88. data/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt +5 -2
  89. data/lib/generators/ruby_llm/tool/tool_generator.rb +25 -59
  90. data/lib/generators/ruby_llm/upgrade/templates/backfill_v2_data.rb.tt +461 -0
  91. data/lib/generators/ruby_llm/upgrade/templates/cleanup_v2_upgrade.rb.tt +101 -0
  92. data/lib/generators/ruby_llm/upgrade/templates/finish_v2_upgrade.rb.tt +215 -0
  93. data/lib/generators/ruby_llm/upgrade/templates/prepare_v2_upgrade.rb.tt +660 -0
  94. data/lib/generators/ruby_llm/upgrade/templates/ruby_llm_upgrade.rb.tt +222 -0
  95. data/lib/generators/ruby_llm/upgrade/templates/upgrade_initializer.rb.tt +13 -0
  96. data/lib/generators/ruby_llm/upgrade/upgrade_generator.rb +167 -0
  97. data/lib/generators/ruby_llm/upgrade/upgrade_migration.rb +344 -0
  98. data/lib/ruby_llm/accounting/usage.rb +245 -0
  99. data/lib/ruby_llm/active_record/acts_as.rb +94 -111
  100. data/lib/ruby_llm/active_record/attachment_helpers.rb +186 -0
  101. data/lib/ruby_llm/active_record/batch.rb +97 -0
  102. data/lib/ruby_llm/active_record/chat_methods.rb +828 -305
  103. data/lib/ruby_llm/active_record/message_methods.rb +113 -136
  104. data/lib/ruby_llm/active_record/model.rb +135 -0
  105. data/lib/ruby_llm/active_record/payload_helpers.rb +1 -2
  106. data/lib/ruby_llm/active_record/tool_call.rb +33 -0
  107. data/lib/ruby_llm/active_record/usage.rb +61 -0
  108. data/lib/ruby_llm/agent.rb +1065 -151
  109. data/lib/ruby_llm/aliases.json +268 -100
  110. data/lib/ruby_llm/attachment.rb +187 -48
  111. data/lib/ruby_llm/batch.rb +432 -0
  112. data/lib/ruby_llm/cached_content.rb +112 -0
  113. data/lib/ruby_llm/chat/tool_concurrency.rb +111 -0
  114. data/lib/ruby_llm/chat.rb +1127 -198
  115. data/lib/ruby_llm/chunk.rb +10 -0
  116. data/lib/ruby_llm/citation.rb +105 -0
  117. data/lib/ruby_llm/configuration.rb +261 -24
  118. data/lib/ruby_llm/context.rb +128 -6
  119. data/lib/ruby_llm/cost.rb +217 -80
  120. data/lib/ruby_llm/downloaded_file.rb +33 -0
  121. data/lib/ruby_llm/embedding.rb +121 -17
  122. data/lib/ruby_llm/embedding_request.rb +53 -0
  123. data/lib/ruby_llm/error.rb +159 -23
  124. data/lib/ruby_llm/fallback.rb +133 -0
  125. data/lib/ruby_llm/files/mime_type.rb +97 -0
  126. data/lib/ruby_llm/image.rb +153 -32
  127. data/lib/ruby_llm/message.rb +233 -54
  128. data/lib/ruby_llm/model/modalities.rb +17 -4
  129. data/lib/ruby_llm/model/pricing.rb +24 -5
  130. data/lib/ruby_llm/model/pricing_category.rb +103 -14
  131. data/lib/ruby_llm/model/pricing_tier.rb +55 -15
  132. data/lib/ruby_llm/model.rb +244 -2
  133. data/lib/ruby_llm/models/aliases.rb +41 -0
  134. data/lib/ruby_llm/models/registry.rb +165 -0
  135. data/lib/ruby_llm/models/schema.rb +99 -0
  136. data/lib/ruby_llm/models.json +73261 -33733
  137. data/lib/ruby_llm/models.rb +477 -215
  138. data/lib/ruby_llm/moderation.rb +139 -26
  139. data/lib/ruby_llm/ocr.rb +112 -0
  140. data/lib/ruby_llm/prompt.rb +79 -0
  141. data/lib/ruby_llm/protocol/binary_streaming.rb +65 -0
  142. data/lib/ruby_llm/protocol/stream_accumulator.rb +214 -0
  143. data/lib/ruby_llm/protocol/streaming.rb +230 -0
  144. data/lib/ruby_llm/protocol.rb +662 -0
  145. data/lib/ruby_llm/protocols/anthropic/batches.rb +73 -0
  146. data/lib/ruby_llm/protocols/anthropic/chat.rb +548 -0
  147. data/lib/ruby_llm/protocols/anthropic/embeddings.rb +14 -0
  148. data/lib/ruby_llm/protocols/anthropic/files.rb +38 -0
  149. data/lib/ruby_llm/protocols/anthropic/media.rb +141 -0
  150. data/lib/ruby_llm/protocols/anthropic/models.rb +129 -0
  151. data/lib/ruby_llm/protocols/anthropic/streaming.rb +167 -0
  152. data/lib/ruby_llm/{providers → protocols}/anthropic/tools.rb +24 -41
  153. data/lib/ruby_llm/protocols/anthropic.rb +101 -0
  154. data/lib/ruby_llm/protocols/azure/files.rb +16 -0
  155. data/lib/ruby_llm/protocols/bedrock/async_videos.rb +109 -0
  156. data/lib/ruby_llm/protocols/bedrock/batches.rb +129 -0
  157. data/lib/ruby_llm/protocols/bedrock/files.rb +110 -0
  158. data/lib/ruby_llm/protocols/bedrock/guardrails.rb +122 -0
  159. data/lib/ruby_llm/protocols/bedrock/rerank.rb +95 -0
  160. data/lib/ruby_llm/protocols/chat_completions/batches.rb +32 -0
  161. data/lib/ruby_llm/protocols/chat_completions/chat.rb +493 -0
  162. data/lib/ruby_llm/protocols/chat_completions/embedding_batches.rb +35 -0
  163. data/lib/ruby_llm/protocols/chat_completions/embeddings.rb +60 -0
  164. data/lib/ruby_llm/protocols/chat_completions/images.rb +127 -0
  165. data/lib/ruby_llm/{providers/openai → protocols/chat_completions}/media.rb +30 -17
  166. data/lib/ruby_llm/protocols/chat_completions/models.rb +39 -0
  167. data/lib/ruby_llm/protocols/chat_completions/moderation.rb +52 -0
  168. data/lib/ruby_llm/protocols/chat_completions/rerank.rb +56 -0
  169. data/lib/ruby_llm/protocols/chat_completions/speech.rb +40 -0
  170. data/lib/ruby_llm/protocols/chat_completions/streaming.rb +69 -0
  171. data/lib/ruby_llm/{providers/openai → protocols/chat_completions}/tools.rb +15 -17
  172. data/lib/ruby_llm/protocols/chat_completions/transcription.rb +150 -0
  173. data/lib/ruby_llm/protocols/chat_completions.rb +21 -0
  174. data/lib/ruby_llm/protocols/cohere/batch_requests.rb +75 -0
  175. data/lib/ruby_llm/protocols/cohere/batches.rb +98 -0
  176. data/lib/ruby_llm/protocols/cohere/chat.rb +227 -0
  177. data/lib/ruby_llm/protocols/cohere/datasets.rb +102 -0
  178. data/lib/ruby_llm/protocols/cohere/embeddings.rb +68 -0
  179. data/lib/ruby_llm/protocols/cohere/media.rb +77 -0
  180. data/lib/ruby_llm/protocols/cohere/models.rb +92 -0
  181. data/lib/ruby_llm/protocols/cohere/ocr.rb +63 -0
  182. data/lib/ruby_llm/protocols/cohere/rerank.rb +52 -0
  183. data/lib/ruby_llm/protocols/cohere/streaming.rb +105 -0
  184. data/lib/ruby_llm/protocols/cohere/tokenization.rb +22 -0
  185. data/lib/ruby_llm/protocols/cohere/tools.rb +132 -0
  186. data/lib/ruby_llm/protocols/cohere/transcription.rb +41 -0
  187. data/lib/ruby_llm/protocols/cohere.rb +21 -0
  188. data/lib/ruby_llm/protocols/converse/batches.rb +55 -0
  189. data/lib/ruby_llm/protocols/converse/chat.rb +695 -0
  190. data/lib/ruby_llm/protocols/converse/media.rb +178 -0
  191. data/lib/ruby_llm/protocols/converse/streaming.rb +432 -0
  192. data/lib/ruby_llm/protocols/converse/thinking_stream.rb +56 -0
  193. data/lib/ruby_llm/protocols/converse.rb +54 -0
  194. data/lib/ruby_llm/protocols/deepgram/models.rb +74 -0
  195. data/lib/ruby_llm/protocols/deepgram/speech.rb +93 -0
  196. data/lib/ruby_llm/protocols/deepgram/streaming_transcription.rb +89 -0
  197. data/lib/ruby_llm/protocols/deepgram/transcription.rb +96 -0
  198. data/lib/ruby_llm/protocols/deepgram.rb +19 -0
  199. data/lib/ruby_llm/protocols/deepseek/files.rb +31 -0
  200. data/lib/ruby_llm/protocols/elevenlabs/assets.rb +36 -0
  201. data/lib/ruby_llm/protocols/elevenlabs/flows/images.rb +74 -0
  202. data/lib/ruby_llm/protocols/elevenlabs/flows/media.rb +42 -0
  203. data/lib/ruby_llm/protocols/elevenlabs/flows/videos.rb +93 -0
  204. data/lib/ruby_llm/protocols/elevenlabs/flows.rb +14 -0
  205. data/lib/ruby_llm/protocols/elevenlabs/models.rb +64 -0
  206. data/lib/ruby_llm/protocols/elevenlabs/speech.rb +67 -0
  207. data/lib/ruby_llm/protocols/elevenlabs/streaming_transcription.rb +127 -0
  208. data/lib/ruby_llm/protocols/elevenlabs/transcription.rb +61 -0
  209. data/lib/ruby_llm/protocols/elevenlabs.rb +15 -0
  210. data/lib/ruby_llm/protocols/files.rb +119 -0
  211. data/lib/ruby_llm/protocols/gemini/batches.rb +162 -0
  212. data/lib/ruby_llm/protocols/gemini/caches.rb +59 -0
  213. data/lib/ruby_llm/protocols/gemini/chat.rb +453 -0
  214. data/lib/ruby_llm/protocols/gemini/embedding_batches.rb +86 -0
  215. data/lib/ruby_llm/protocols/gemini/embeddings.rb +70 -0
  216. data/lib/ruby_llm/protocols/gemini/file_transcription.rb +32 -0
  217. data/lib/ruby_llm/protocols/gemini/files.rb +115 -0
  218. data/lib/ruby_llm/protocols/gemini/images.rb +183 -0
  219. data/lib/ruby_llm/protocols/gemini/live_transcription.rb +140 -0
  220. data/lib/ruby_llm/{providers → protocols}/gemini/media.rb +17 -11
  221. data/lib/ruby_llm/protocols/gemini/models.rb +71 -0
  222. data/lib/ruby_llm/protocols/gemini/speech.rb +56 -0
  223. data/lib/ruby_llm/protocols/gemini/streaming.rb +96 -0
  224. data/lib/ruby_llm/protocols/gemini/tools.rb +157 -0
  225. data/lib/ruby_llm/{providers → protocols}/gemini/transcription.rb +22 -22
  226. data/lib/ruby_llm/protocols/gemini/videos.rb +103 -0
  227. data/lib/ruby_llm/protocols/gemini.rb +35 -0
  228. data/lib/ruby_llm/protocols/gpustack/responses.rb +111 -0
  229. data/lib/ruby_llm/protocols/gpustack/tokenization.rb +22 -0
  230. data/lib/ruby_llm/protocols/gpustack/videos.rb +96 -0
  231. data/lib/ruby_llm/protocols/interactions/chat.rb +145 -0
  232. data/lib/ruby_llm/protocols/interactions/content.rb +90 -0
  233. data/lib/ruby_llm/protocols/interactions/streaming.rb +91 -0
  234. data/lib/ruby_llm/protocols/interactions/tools.rb +51 -0
  235. data/lib/ruby_llm/protocols/interactions/transcription.rb +58 -0
  236. data/lib/ruby_llm/protocols/interactions.rb +29 -0
  237. data/lib/ruby_llm/protocols/invoke_model/cohere_embeddings.rb +51 -0
  238. data/lib/ruby_llm/protocols/invoke_model/embedding_batches.rb +111 -0
  239. data/lib/ruby_llm/protocols/invoke_model/nova_embeddings.rb +50 -0
  240. data/lib/ruby_llm/protocols/invoke_model/stability_images.rb +103 -0
  241. data/lib/ruby_llm/protocols/invoke_model/titan_multimodal_embeddings.rb +33 -0
  242. data/lib/ruby_llm/protocols/invoke_model/titan_text_embeddings.rb +44 -0
  243. data/lib/ruby_llm/protocols/invoke_model.rb +57 -0
  244. data/lib/ruby_llm/protocols/mistral/content.rb +49 -0
  245. data/lib/ruby_llm/protocols/mistral/conversations/chat.rb +160 -0
  246. data/lib/ruby_llm/protocols/mistral/conversations/images.rb +43 -0
  247. data/lib/ruby_llm/protocols/mistral/conversations/streaming.rb +83 -0
  248. data/lib/ruby_llm/protocols/mistral/conversations.rb +30 -0
  249. data/lib/ruby_llm/protocols/mistral/files.rb +36 -0
  250. data/lib/ruby_llm/protocols/mistral/multi_completion.rb +160 -0
  251. data/lib/ruby_llm/protocols/openai/batches.rb +126 -0
  252. data/lib/ruby_llm/protocols/openai/files.rb +42 -0
  253. data/lib/ruby_llm/protocols/openrouter/batches.rb +147 -0
  254. data/lib/ruby_llm/protocols/openrouter/files.rb +24 -0
  255. data/lib/ruby_llm/protocols/openrouter/responses.rb +53 -0
  256. data/lib/ruby_llm/protocols/openrouter/transcription.rb +51 -0
  257. data/lib/ruby_llm/protocols/perplexity/files.rb +48 -0
  258. data/lib/ruby_llm/protocols/perplexity/router.rb +59 -0
  259. data/lib/ruby_llm/protocols/responses/approvals.rb +32 -0
  260. data/lib/ruby_llm/protocols/responses/batches.rb +32 -0
  261. data/lib/ruby_llm/protocols/responses/chat.rb +476 -0
  262. data/lib/ruby_llm/protocols/responses/compaction.rb +29 -0
  263. data/lib/ruby_llm/protocols/responses/media.rb +61 -0
  264. data/lib/ruby_llm/protocols/responses/streaming.rb +117 -0
  265. data/lib/ruby_llm/protocols/responses/token_counting.rb +26 -0
  266. data/lib/ruby_llm/protocols/responses/tools.rb +39 -0
  267. data/lib/ruby_llm/protocols/responses.rb +35 -0
  268. data/lib/ruby_llm/protocols/vertexai/batch_prediction.rb +155 -0
  269. data/lib/ruby_llm/protocols/vertexai/embedding_prediction/requests.rb +85 -0
  270. data/lib/ruby_llm/protocols/vertexai/embedding_prediction/results.rb +74 -0
  271. data/lib/ruby_llm/protocols/vertexai/embedding_prediction.rb +56 -0
  272. data/lib/ruby_llm/protocols/vertexai/files.rb +101 -0
  273. data/lib/ruby_llm/protocols/vertexai/ranking.rb +69 -0
  274. data/lib/ruby_llm/protocols/vertexai/research.rb +193 -0
  275. data/lib/ruby_llm/protocols/xai/files.rb +30 -0
  276. data/lib/ruby_llm/protocols/xai/streaming_transcription.rb +120 -0
  277. data/lib/ruby_llm/protocols/xai/tokenization.rb +23 -0
  278. data/lib/ruby_llm/provider.rb +560 -128
  279. data/lib/ruby_llm/providers/anthropic/capabilities.rb +5 -7
  280. data/lib/ruby_llm/providers/anthropic.rb +4 -6
  281. data/lib/ruby_llm/providers/azure/audio.rb +18 -0
  282. data/lib/ruby_llm/providers/azure/capabilities.rb +16 -0
  283. data/lib/ruby_llm/providers/azure/chat.rb +2 -9
  284. data/lib/ruby_llm/providers/azure/chat_completions/batches.rb +29 -0
  285. data/lib/ruby_llm/providers/azure/chat_completions.rb +80 -0
  286. data/lib/ruby_llm/providers/azure/cohere.rb +33 -0
  287. data/lib/ruby_llm/providers/azure/embeddings.rb +3 -2
  288. data/lib/ruby_llm/providers/azure/images.rb +22 -0
  289. data/lib/ruby_llm/providers/azure/media.rb +5 -14
  290. data/lib/ruby_llm/providers/azure/models.rb +35 -0
  291. data/lib/ruby_llm/providers/azure/responses.rb +26 -0
  292. data/lib/ruby_llm/providers/azure/videos.rb +64 -0
  293. data/lib/ruby_llm/providers/azure.rb +77 -78
  294. data/lib/ruby_llm/providers/bedrock/auth.rb +60 -41
  295. data/lib/ruby_llm/providers/bedrock/capabilities.rb +18 -0
  296. data/lib/ruby_llm/providers/bedrock/mantle/anthropic.rb +39 -0
  297. data/lib/ruby_llm/providers/bedrock/mantle/chat_completions.rb +23 -0
  298. data/lib/ruby_llm/providers/bedrock/mantle/responses.rb +24 -0
  299. data/lib/ruby_llm/providers/bedrock/mantle/voxtral.rb +96 -0
  300. data/lib/ruby_llm/providers/bedrock/mantle.rb +57 -0
  301. data/lib/ruby_llm/providers/bedrock/models.rb +193 -41
  302. data/lib/ruby_llm/providers/bedrock.rb +216 -45
  303. data/lib/ruby_llm/providers/cohere.rb +31 -0
  304. data/lib/ruby_llm/providers/deepgram.rb +37 -0
  305. data/lib/ruby_llm/providers/deepseek/capabilities.rb +4 -51
  306. data/lib/ruby_llm/providers/deepseek/chat.rb +49 -2
  307. data/lib/ruby_llm/providers/deepseek/responses.rb +68 -0
  308. data/lib/ruby_llm/providers/deepseek.rb +9 -2
  309. data/lib/ruby_llm/providers/elevenlabs.rb +35 -0
  310. data/lib/ruby_llm/providers/gemini/capabilities.rb +8 -107
  311. data/lib/ruby_llm/providers/gemini.rb +15 -8
  312. data/lib/ruby_llm/providers/gpustack/chat.rb +2 -16
  313. data/lib/ruby_llm/providers/gpustack/embeddings.rb +28 -0
  314. data/lib/ruby_llm/providers/gpustack/media.rb +17 -16
  315. data/lib/ruby_llm/providers/gpustack/models.rb +72 -62
  316. data/lib/ruby_llm/providers/gpustack/speech.rb +15 -0
  317. data/lib/ruby_llm/providers/gpustack/transcription.rb +29 -0
  318. data/lib/ruby_llm/providers/gpustack.rb +35 -11
  319. data/lib/ruby_llm/providers/mistral/capabilities.rb +7 -155
  320. data/lib/ruby_llm/providers/mistral/chat.rb +37 -61
  321. data/lib/ruby_llm/providers/mistral/chat_completions/batches.rb +120 -0
  322. data/lib/ruby_llm/providers/mistral/chat_completions.rb +21 -0
  323. data/lib/ruby_llm/providers/mistral/conversations.rb +12 -0
  324. data/lib/ruby_llm/providers/mistral/embeddings.rb +6 -4
  325. data/lib/ruby_llm/providers/mistral/media.rb +6 -18
  326. data/lib/ruby_llm/providers/mistral/models.rb +57 -23
  327. data/lib/ruby_llm/providers/mistral/ocr.rb +47 -0
  328. data/lib/ruby_llm/providers/mistral/speech.rb +51 -0
  329. data/lib/ruby_llm/providers/mistral/transcription.rb +62 -0
  330. data/lib/ruby_llm/providers/mistral.rb +16 -4
  331. data/lib/ruby_llm/providers/ollama/chat.rb +7 -13
  332. data/lib/ruby_llm/providers/ollama/media.rb +6 -15
  333. data/lib/ruby_llm/providers/ollama/models.rb +50 -9
  334. data/lib/ruby_llm/providers/ollama.rb +9 -8
  335. data/lib/ruby_llm/providers/ollama_cloud/models.rb +14 -0
  336. data/lib/ruby_llm/providers/ollama_cloud.rb +40 -0
  337. data/lib/ruby_llm/providers/openai/capabilities.rb +54 -259
  338. data/lib/ruby_llm/providers/openai/models.rb +23 -23
  339. data/lib/ruby_llm/providers/openai/responses.rb +13 -0
  340. data/lib/ruby_llm/providers/openai.rb +92 -11
  341. data/lib/ruby_llm/providers/openrouter/chat.rb +130 -108
  342. data/lib/ruby_llm/providers/openrouter/embeddings.rb +51 -0
  343. data/lib/ruby_llm/providers/openrouter/images.rb +44 -43
  344. data/lib/ruby_llm/providers/openrouter/media.rb +34 -0
  345. data/lib/ruby_llm/providers/openrouter/models.rb +50 -11
  346. data/lib/ruby_llm/providers/openrouter/speech.rb +32 -0
  347. data/lib/ruby_llm/providers/openrouter/streaming.rb +31 -38
  348. data/lib/ruby_llm/providers/openrouter/videos.rb +81 -0
  349. data/lib/ruby_llm/providers/openrouter.rb +78 -20
  350. data/lib/ruby_llm/providers/perplexity/chat.rb +2 -9
  351. data/lib/ruby_llm/providers/perplexity/embeddings.rb +32 -0
  352. data/lib/ruby_llm/providers/perplexity/media.rb +5 -21
  353. data/lib/ruby_llm/providers/perplexity/models.rb +80 -13
  354. data/lib/ruby_llm/providers/perplexity.rb +28 -20
  355. data/lib/ruby_llm/providers/vertexai/anthropic/batches.rb +52 -0
  356. data/lib/ruby_llm/providers/vertexai/anthropic.rb +34 -0
  357. data/lib/ruby_llm/providers/vertexai/capabilities.rb +19 -0
  358. data/lib/ruby_llm/providers/vertexai/chat_completions/batches.rb +54 -0
  359. data/lib/ruby_llm/providers/vertexai/chat_completions.rb +15 -0
  360. data/lib/ruby_llm/providers/vertexai/embed_content.rb +42 -0
  361. data/lib/ruby_llm/providers/vertexai/embeddings.rb +22 -7
  362. data/lib/ruby_llm/providers/vertexai/gemini/batches.rb +42 -0
  363. data/lib/ruby_llm/providers/vertexai/gemini.rb +69 -0
  364. data/lib/ruby_llm/providers/vertexai/live_transcription.rb +24 -0
  365. data/lib/ruby_llm/providers/vertexai/mistral.rb +28 -0
  366. data/lib/ruby_llm/providers/vertexai/models.rb +145 -43
  367. data/lib/ruby_llm/providers/vertexai/transcription.rb +49 -4
  368. data/lib/ruby_llm/providers/vertexai/videos.rb +61 -0
  369. data/lib/ruby_llm/providers/vertexai.rb +160 -17
  370. data/lib/ruby_llm/providers/xai/capabilities.rb +18 -0
  371. data/lib/ruby_llm/providers/xai/chat.rb +3 -2
  372. data/lib/ruby_llm/providers/xai/chat_completions/batches.rb +108 -0
  373. data/lib/ruby_llm/providers/xai/chat_completions.rb +19 -0
  374. data/lib/ruby_llm/providers/xai/images.rb +91 -0
  375. data/lib/ruby_llm/providers/xai/models.rb +32 -36
  376. data/lib/ruby_llm/providers/xai/reported_cost.rb +18 -0
  377. data/lib/ruby_llm/providers/xai/responses.rb +52 -0
  378. data/lib/ruby_llm/providers/xai/speech.rb +45 -0
  379. data/lib/ruby_llm/providers/xai/transcription.rb +48 -0
  380. data/lib/ruby_llm/providers/xai/videos.rb +87 -0
  381. data/lib/ruby_llm/providers/xai.rb +15 -5
  382. data/lib/ruby_llm/railtie.rb +7 -16
  383. data/lib/ruby_llm/rerank.rb +105 -0
  384. data/lib/ruby_llm/research_job.rb +241 -0
  385. data/lib/ruby_llm/search_results.rb +68 -0
  386. data/lib/ruby_llm/server_tool_call.rb +73 -0
  387. data/lib/ruby_llm/speech.rb +159 -0
  388. data/lib/ruby_llm/speech_chunk.rb +33 -0
  389. data/lib/ruby_llm/support/deprecator.rb +22 -0
  390. data/lib/ruby_llm/support/inspectable.rb +49 -0
  391. data/lib/ruby_llm/support/instrumentation.rb +41 -0
  392. data/lib/ruby_llm/support/utils.rb +147 -0
  393. data/lib/ruby_llm/thinking.rb +127 -20
  394. data/lib/ruby_llm/tokenization.rb +59 -0
  395. data/lib/ruby_llm/tokens.rb +103 -33
  396. data/lib/ruby_llm/tool.rb +266 -91
  397. data/lib/ruby_llm/tool_call.rb +36 -3
  398. data/lib/ruby_llm/tools/server_tools.rb +109 -0
  399. data/lib/ruby_llm/transcription/wav_audio.rb +62 -0
  400. data/lib/ruby_llm/transcription.rb +138 -13
  401. data/lib/ruby_llm/transcription_chunk.rb +68 -0
  402. data/lib/ruby_llm/transport/connection.rb +193 -0
  403. data/lib/ruby_llm/transport/error_middleware.rb +131 -0
  404. data/lib/ruby_llm/transport/usage_middleware.rb +28 -0
  405. data/lib/ruby_llm/transport/websocket_connection.rb +220 -0
  406. data/lib/ruby_llm/uploaded_file.rb +144 -0
  407. data/lib/ruby_llm/version.rb +2 -1
  408. data/lib/ruby_llm/video.rb +136 -0
  409. data/lib/ruby_llm/video_job.rb +150 -0
  410. data/lib/ruby_llm/workflow.rb +91 -0
  411. data/lib/ruby_llm.rb +380 -6
  412. data/lib/tasks/ruby_llm.rake +21 -16
  413. data/skills/rubyllm/SKILL.md +81 -0
  414. data/skills/rubyllm/agents/openai.yaml +4 -0
  415. metadata +339 -97
  416. data/lib/generators/ruby_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt +0 -9
  417. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +0 -39
  418. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +0 -21
  419. data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +0 -3
  420. data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +0 -3
  421. data/lib/generators/ruby_llm/upgrade_to_v1_10/templates/add_v1_10_message_columns.rb.tt +0 -19
  422. data/lib/generators/ruby_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +0 -50
  423. data/lib/generators/ruby_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +0 -7
  424. data/lib/generators/ruby_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +0 -49
  425. data/lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt +0 -145
  426. data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +0 -122
  427. data/lib/generators/ruby_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +0 -15
  428. data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +0 -49
  429. data/lib/ruby_llm/active_record/acts_as_legacy.rb +0 -597
  430. data/lib/ruby_llm/active_record/model_methods.rb +0 -82
  431. data/lib/ruby_llm/active_record/tool_call_methods.rb +0 -18
  432. data/lib/ruby_llm/aliases.rb +0 -41
  433. data/lib/ruby_llm/connection.rb +0 -159
  434. data/lib/ruby_llm/content.rb +0 -91
  435. data/lib/ruby_llm/deprecator.rb +0 -24
  436. data/lib/ruby_llm/error_middleware.rb +0 -81
  437. data/lib/ruby_llm/instrumentation.rb +0 -36
  438. data/lib/ruby_llm/mime_type.rb +0 -96
  439. data/lib/ruby_llm/model/info.rb +0 -164
  440. data/lib/ruby_llm/model_registry.rb +0 -39
  441. data/lib/ruby_llm/models_schema.json +0 -171
  442. data/lib/ruby_llm/providers/anthropic/chat.rb +0 -291
  443. data/lib/ruby_llm/providers/anthropic/content.rb +0 -44
  444. data/lib/ruby_llm/providers/anthropic/embeddings.rb +0 -20
  445. data/lib/ruby_llm/providers/anthropic/media.rb +0 -92
  446. data/lib/ruby_llm/providers/anthropic/models.rb +0 -59
  447. data/lib/ruby_llm/providers/anthropic/streaming.rb +0 -71
  448. data/lib/ruby_llm/providers/bedrock/chat.rb +0 -405
  449. data/lib/ruby_llm/providers/bedrock/media.rb +0 -108
  450. data/lib/ruby_llm/providers/bedrock/streaming.rb +0 -328
  451. data/lib/ruby_llm/providers/gemini/chat.rb +0 -542
  452. data/lib/ruby_llm/providers/gemini/embeddings.rb +0 -37
  453. data/lib/ruby_llm/providers/gemini/images.rb +0 -47
  454. data/lib/ruby_llm/providers/gemini/models.rb +0 -38
  455. data/lib/ruby_llm/providers/gemini/streaming.rb +0 -98
  456. data/lib/ruby_llm/providers/gemini/tools.rb +0 -234
  457. data/lib/ruby_llm/providers/gpustack/capabilities.rb +0 -20
  458. data/lib/ruby_llm/providers/ollama/capabilities.rb +0 -20
  459. data/lib/ruby_llm/providers/openai/chat.rb +0 -236
  460. data/lib/ruby_llm/providers/openai/embeddings.rb +0 -33
  461. data/lib/ruby_llm/providers/openai/images.rb +0 -90
  462. data/lib/ruby_llm/providers/openai/moderation.rb +0 -34
  463. data/lib/ruby_llm/providers/openai/streaming.rb +0 -55
  464. data/lib/ruby_llm/providers/openai/temperature.rb +0 -28
  465. data/lib/ruby_llm/providers/openai/transcription.rb +0 -71
  466. data/lib/ruby_llm/providers/perplexity/capabilities.rb +0 -72
  467. data/lib/ruby_llm/providers/vertexai/chat.rb +0 -14
  468. data/lib/ruby_llm/providers/vertexai/streaming.rb +0 -14
  469. data/lib/ruby_llm/stream_accumulator.rb +0 -218
  470. data/lib/ruby_llm/streaming.rb +0 -179
  471. data/lib/ruby_llm/tool_concurrency.rb +0 -105
  472. data/lib/ruby_llm/utils.rb +0 -130
  473. data/lib/tasks/models.rake +0 -593
  474. data/lib/tasks/release.rake +0 -94
  475. data/lib/tasks/vcr.rake +0 -124
@@ -1,593 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'dotenv/load'
4
- require 'ruby_llm'
5
- require 'json'
6
- require 'json-schema'
7
- require 'fileutils'
8
-
9
- desc 'Update models, docs, and aliases'
10
- task models: ['models:update', 'models:docs', 'models:aliases']
11
-
12
- namespace :models do
13
- desc 'Update available models from providers (API keys needed)'
14
- task :update do
15
- puts 'Configuring RubyLLM...'
16
- configure_from_env
17
- refresh_models
18
- display_model_stats
19
- end
20
-
21
- desc 'Generate available models documentation'
22
- task :docs do
23
- FileUtils.mkdir_p('docs/_reference')
24
- output = generate_models_markdown
25
- File.write('docs/_reference/available-models.md', output)
26
- puts 'Generated docs/_reference/available-models.md'
27
- end
28
-
29
- desc 'Generate model aliases from registry'
30
- task :aliases do
31
- generate_aliases
32
- end
33
- end
34
-
35
- # Keep aliases:generate for backwards compatibility
36
- namespace :aliases do
37
- task generate: ['models:aliases']
38
- end
39
-
40
- def configure_from_env
41
- RubyLLM.configure do |config|
42
- config.anthropic_api_key = ENV.fetch('ANTHROPIC_API_KEY', nil)
43
- config.azure_api_base = ENV.fetch('AZURE_API_BASE', nil)
44
- config.azure_api_key = ENV.fetch('AZURE_API_KEY', nil)
45
- config.deepseek_api_key = ENV.fetch('DEEPSEEK_API_KEY', nil)
46
- config.gemini_api_key = ENV.fetch('GEMINI_API_KEY', nil)
47
- config.mistral_api_key = ENV.fetch('MISTRAL_API_KEY', nil)
48
- config.openai_api_key = ENV.fetch('OPENAI_API_KEY', nil)
49
- config.openrouter_api_key = ENV.fetch('OPENROUTER_API_KEY', nil)
50
- config.perplexity_api_key = ENV.fetch('PERPLEXITY_API_KEY', nil)
51
- config.vertexai_location = ENV.fetch('GOOGLE_CLOUD_LOCATION', nil)
52
- config.vertexai_project_id = ENV.fetch('GOOGLE_CLOUD_PROJECT', nil)
53
- config.xai_api_key = ENV.fetch('XAI_API_KEY', nil)
54
- configure_bedrock(config)
55
- config.request_timeout = 30
56
- end
57
- end
58
-
59
- def configure_bedrock(config)
60
- config.bedrock_api_key = ENV.fetch('AWS_ACCESS_KEY_ID', nil)
61
- config.bedrock_secret_key = ENV.fetch('AWS_SECRET_ACCESS_KEY', nil)
62
- config.bedrock_region = ENV.fetch('AWS_REGION', nil)
63
- config.bedrock_session_token = ENV.fetch('AWS_SESSION_TOKEN', nil)
64
- end
65
-
66
- def refresh_models
67
- existing_models = RubyLLM::Models.read_from_json
68
- initial_count = existing_models.size
69
- puts "Refreshing models (#{initial_count} cached)..."
70
-
71
- models = RubyLLM.models.refresh!
72
-
73
- if models.all.empty? && initial_count.zero?
74
- puts 'Error: Failed to fetch models.'
75
- exit(1)
76
- else
77
- existing_data = sorted_models_data(existing_models)
78
- new_data = sorted_models_data(models.all)
79
-
80
- if new_data == existing_data && initial_count.positive?
81
- puts 'Warning: Model list unchanged.'
82
- else
83
- puts 'Validating models...'
84
- validate_models!(models)
85
-
86
- puts "Saving models.json (#{models.all.size} models)"
87
- models.save_to_json
88
- end
89
- end
90
-
91
- @models = models
92
- end
93
-
94
- def sorted_models_data(models)
95
- models.map(&:to_h)
96
- .sort_by { |model| [model[:provider].to_s, model[:id].to_s] }
97
- end
98
-
99
- def validate_models!(models)
100
- schema_path = RubyLLM::Models.schema_file
101
- models_data = models.all.map(&:to_h)
102
-
103
- validation_errors = JSON::Validator.fully_validate(schema_path, models_data)
104
-
105
- unless validation_errors.empty?
106
- # Save failed models for inspection
107
- failed_path = File.expand_path('../ruby_llm/models.failed.json', __dir__)
108
- File.write(failed_path, JSON.pretty_generate(models_data))
109
-
110
- puts 'ERROR: Models validation failed:'
111
- puts "\nValidation errors:"
112
- validation_errors.first(10).each { |error| puts " - #{error}" }
113
- puts " ... and #{validation_errors.size - 10} more errors" if validation_errors.size > 10
114
- puts "-> Failed models saved to: #{failed_path}"
115
- exit(1)
116
- end
117
-
118
- puts 'āœ“ Models validation passed'
119
- end
120
-
121
- def display_model_stats
122
- puts "\nModel count:"
123
- provider_counts = @models.all.group_by(&:provider).transform_values(&:count)
124
-
125
- RubyLLM::Provider.providers.each do |sym, provider_class|
126
- name = provider_class.name
127
- count = provider_counts[sym.to_s] || 0
128
- status = status(sym)
129
- puts " #{name}: #{count} models #{status}"
130
- end
131
-
132
- puts 'Refresh complete.'
133
- end
134
-
135
- def status(provider_sym)
136
- provider_class = RubyLLM::Provider.providers[provider_sym]
137
- if provider_class.local?
138
- ' (LOCAL - SKIP)'
139
- elsif provider_class.configured?(RubyLLM.config)
140
- ' (OK)'
141
- else
142
- ' (NOT CONFIGURED)'
143
- end
144
- end
145
-
146
- def generate_models_markdown
147
- models = RubyLLM.models.all
148
- total_models = models.count
149
- provider_count = models.map(&:provider).uniq.count
150
- generated_on = Time.now.utc.strftime('%Y-%m-%d')
151
-
152
- <<~MARKDOWN
153
- ---
154
- layout: default
155
- title: Available Models
156
- nav_order: 1
157
- description: Browse #{total_models} AI models across #{provider_count} providers (not including local providers). Updated #{generated_on}.
158
- redirect_from:
159
- - /guides/available-models
160
- ---
161
-
162
- # {{ page.title }}
163
- {: .no_toc }
164
-
165
- {{ page.description }}
166
- {: .fs-6 .fw-300 }
167
-
168
- ## Table of contents
169
- {: .no_toc .text-delta }
170
-
171
- 1. TOC
172
- {:toc}
173
-
174
- ---
175
-
176
- _Model information enriched by [models.dev](https://models.dev) and our custom code._
177
-
178
- Can't find a newly released model? Refresh your registry:
179
-
180
- ```ruby
181
- # Plain Ruby
182
- RubyLLM.models.refresh!
183
-
184
- # Rails
185
- Model.refresh!
186
- ```
187
-
188
- See [Model Registry: Refreshing the Registry]({% link _advanced/models.md %}#refreshing-the-registry).
189
-
190
- ## Models by Provider
191
-
192
- #{generate_provider_sections}
193
-
194
- ## Models by Capability
195
-
196
- #{generate_capability_sections}
197
-
198
- ## Models by Modality
199
-
200
- #{generate_modality_sections}
201
- MARKDOWN
202
- end
203
-
204
- def generate_provider_sections
205
- RubyLLM::Provider.providers.filter_map do |provider, provider_class|
206
- models = RubyLLM.models.by_provider(provider)
207
- next if models.none?
208
-
209
- <<~PROVIDER
210
- ### #{provider_class.name} (#{models.count})
211
-
212
- #{models_table(models)}
213
- PROVIDER
214
- end.join("\n\n")
215
- end
216
-
217
- def generate_capability_sections
218
- capabilities = {
219
- 'Function Calling' => RubyLLM.models.select(&:function_calling?),
220
- 'Structured Output' => RubyLLM.models.select(&:structured_output?),
221
- 'Streaming' => RubyLLM.models.select { |m| m.capabilities.include?('streaming') },
222
- 'Batch Processing' => RubyLLM.models.select { |m| m.capabilities.include?('batch') }
223
- }
224
-
225
- capabilities.filter_map do |capability, models|
226
- next if models.none?
227
-
228
- <<~CAPABILITY
229
- ### #{capability} (#{models.count})
230
-
231
- #{models_table(models)}
232
- CAPABILITY
233
- end.join("\n\n")
234
- end
235
-
236
- def generate_modality_sections # rubocop:disable Metrics/PerceivedComplexity
237
- sections = []
238
-
239
- vision_models = RubyLLM.models.select { |m| (m.modalities.input || []).include?('image') }
240
- if vision_models.any?
241
- sections << <<~SECTION
242
- ### Vision Models (#{vision_models.count})
243
-
244
- Models that can process images:
245
-
246
- #{models_table(vision_models)}
247
- SECTION
248
- end
249
-
250
- audio_models = RubyLLM.models.select { |m| (m.modalities.input || []).include?('audio') }
251
- if audio_models.any?
252
- sections << <<~SECTION
253
- ### Audio Input Models (#{audio_models.count})
254
-
255
- Models that can process audio:
256
-
257
- #{models_table(audio_models)}
258
- SECTION
259
- end
260
-
261
- pdf_models = RubyLLM.models.select { |m| (m.modalities.input || []).include?('pdf') }
262
- if pdf_models.any?
263
- sections << <<~SECTION
264
- ### PDF Models (#{pdf_models.count})
265
-
266
- Models that can process PDF documents:
267
-
268
- #{models_table(pdf_models)}
269
- SECTION
270
- end
271
-
272
- embedding_models = RubyLLM.models.select { |m| (m.modalities.output || []).include?('embeddings') }
273
- if embedding_models.any?
274
- sections << <<~SECTION
275
- ### Embedding Models (#{embedding_models.count})
276
-
277
- Models that generate embeddings:
278
-
279
- #{models_table(embedding_models)}
280
- SECTION
281
- end
282
-
283
- sections.join("\n\n")
284
- end
285
-
286
- def models_table(models)
287
- return '*No models found*' if models.none?
288
-
289
- headers = ['Model', 'Provider', 'I/O', 'Capabilities', 'Context', 'Max Output', 'Standard Pricing (per 1M tokens)']
290
- alignment = [':--', ':--', ':--', ':--', '--:', '--:', ':--']
291
-
292
- rows = models.sort_by { |m| [m.provider, m.name] }.map do |model|
293
- pricing = standard_pricing_display(model)
294
-
295
- [
296
- model.id,
297
- model.provider,
298
- modalities_display(model),
299
- list_display(model.capabilities),
300
- model.context_window || '-',
301
- model.max_output_tokens || '-',
302
- pricing
303
- ]
304
- end
305
-
306
- table = []
307
- table << "| #{headers.join(' | ')} |"
308
- table << "| #{alignment.join(' | ')} |"
309
-
310
- rows.each do |row|
311
- table << "| #{row.join(' | ')} |"
312
- end
313
-
314
- table.join("\n")
315
- end
316
-
317
- def modalities_display(model)
318
- input_modalities = list_display(model.modalities.input)
319
- output_modalities = list_display(model.modalities.output)
320
- "In: #{input_modalities}; Out: #{output_modalities}"
321
- end
322
-
323
- def list_display(values)
324
- items = Array(values).compact.map(&:to_s).reject(&:empty?)
325
- return '-' if items.empty?
326
-
327
- items.join(', ')
328
- end
329
-
330
- def standard_pricing_display(model)
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
338
-
339
- return parts.join(', ') if parts.any?
340
-
341
- '-'
342
- end
343
-
344
- def pricing_part(pricing_data, key, label)
345
- key = Array(key).find { |candidate| pricing_data[candidate] }
346
- return unless key
347
-
348
- "#{label}: $#{format('%.2f', pricing_data[key])}"
349
- end
350
-
351
- def generate_aliases # rubocop:disable Metrics/AbcSize, Metrics/CyclomaticComplexity, Metrics/PerceivedComplexity
352
- models = Hash.new { |h, k| h[k] = [] }
353
-
354
- RubyLLM.models.all.each do |model|
355
- models[model.provider] << model.id
356
- end
357
-
358
- aliases = {}
359
-
360
- # OpenAI models
361
- models['openai'].each do |model|
362
- openrouter_model = "openai/#{model}"
363
- azure_model = models['azure'].include?(model) ? model : nil
364
- next unless models['openrouter'].include?(openrouter_model)
365
-
366
- alias_key = model.gsub('-latest', '')
367
- aliases[alias_key] = {
368
- 'openai' => model,
369
- 'openrouter' => openrouter_model
370
- }
371
- aliases[alias_key]['azure'] = azure_model if azure_model
372
- end
373
-
374
- anthropic_latest = group_anthropic_models_by_base_name(models['anthropic'])
375
-
376
- anthropic_latest.each do |base_name, latest_model|
377
- openrouter_variants = [
378
- "anthropic/#{base_name}",
379
- "anthropic/#{base_name.gsub(/-(\d)/, '.\1')}",
380
- "anthropic/#{base_name.gsub(/claude-(\d+)-(\d+)/, 'claude-\1.\2')}",
381
- "anthropic/#{base_name.gsub(/(\d+)-(\d+)/, '\1.\2')}"
382
- ]
383
-
384
- openrouter_model = openrouter_variants.find { |v| models['openrouter'].include?(v) }
385
- bedrock_model = find_best_bedrock_model(latest_model, models['bedrock'])
386
-
387
- next unless openrouter_model || bedrock_model || models['anthropic'].include?(latest_model)
388
-
389
- aliases[base_name] = { 'anthropic' => latest_model }
390
- aliases[base_name]['openrouter'] = openrouter_model if openrouter_model
391
- aliases[base_name]['bedrock'] = bedrock_model if bedrock_model
392
- aliases[base_name]['azure'] = latest_model if models['azure'].include?(latest_model)
393
- end
394
-
395
- models['bedrock'].each do |bedrock_model|
396
- next unless bedrock_model.start_with?('anthropic.')
397
- next unless bedrock_model =~ /anthropic\.(claude-[a-z0-9.-]+)-\d{8}/
398
-
399
- base_name = Regexp.last_match(1)
400
- anthropic_name = base_name.tr('.', '-')
401
-
402
- next if aliases[anthropic_name]
403
-
404
- openrouter_variants = [
405
- "anthropic/#{anthropic_name}",
406
- "anthropic/#{base_name}"
407
- ]
408
-
409
- openrouter_model = openrouter_variants.find { |v| models['openrouter'].include?(v) }
410
-
411
- aliases[anthropic_name] = { 'bedrock' => bedrock_model }
412
- aliases[anthropic_name]['anthropic'] = anthropic_name if models['anthropic'].include?(anthropic_name)
413
- aliases[anthropic_name]['openrouter'] = openrouter_model if openrouter_model
414
- end
415
-
416
- # Gemini models (also map to vertexai)
417
- models['gemini'].each do |model|
418
- openrouter_variants = [
419
- "google/#{model}",
420
- "google/#{model.gsub('gemini-', 'gemini-').tr('.', '-')}",
421
- "google/#{model.gsub('gemini-', 'gemini-')}"
422
- ]
423
-
424
- openrouter_model = openrouter_variants.find { |v| models['openrouter'].include?(v) }
425
- vertexai_model = models['vertexai'].include?(model) ? model : nil
426
-
427
- next unless openrouter_model || vertexai_model
428
-
429
- alias_key = model.gsub('-latest', '')
430
- aliases[alias_key] = { 'gemini' => model }
431
- aliases[alias_key]['openrouter'] = openrouter_model if openrouter_model
432
- aliases[alias_key]['vertexai'] = vertexai_model if vertexai_model
433
- end
434
-
435
- # VertexAI models that aren't in Gemini (e.g. older models like text-bison)
436
- models['vertexai'].each do |model|
437
- # Skip if already handled above
438
- next if models['gemini'].include?(model)
439
-
440
- # Check if OpenRouter has this Google model
441
- openrouter_variants = [
442
- "google/#{model}",
443
- "google/#{model.tr('.', '-')}"
444
- ]
445
-
446
- openrouter_model = openrouter_variants.find { |v| models['openrouter'].include?(v) }
447
- gemini_model = models['gemini'].include?(model) ? model : nil
448
-
449
- next unless openrouter_model || gemini_model
450
-
451
- alias_key = model.gsub('-latest', '')
452
- next if aliases[alias_key] # Skip if already created
453
-
454
- aliases[alias_key] = { 'vertexai' => model }
455
- aliases[alias_key]['openrouter'] = openrouter_model if openrouter_model
456
- aliases[alias_key]['gemini'] = gemini_model if gemini_model
457
- end
458
-
459
- models['deepseek'].each do |model|
460
- openrouter_model = "deepseek/#{model}"
461
- next unless models['openrouter'].include?(openrouter_model)
462
-
463
- alias_key = model.gsub('-latest', '')
464
- aliases[alias_key] = {
465
- 'deepseek' => model,
466
- 'openrouter' => openrouter_model
467
- }
468
- end
469
-
470
- add_xai_aliases(aliases, models['xai'])
471
-
472
- sorted_aliases = aliases.sort.to_h
473
- File.write(RubyLLM::Aliases.aliases_file, JSON.pretty_generate(sorted_aliases))
474
-
475
- puts "Generated #{sorted_aliases.size} aliases"
476
- end
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
-
504
- def group_anthropic_models_by_base_name(anthropic_models)
505
- grouped = Hash.new { |h, k| h[k] = [] }
506
-
507
- anthropic_models.each do |model|
508
- base_name = extract_base_name(model)
509
- grouped[base_name] << model
510
- end
511
-
512
- latest_models = {}
513
- grouped.each do |base_name, model_list|
514
- if model_list.size == 1
515
- latest_models[base_name] = model_list.first
516
- else
517
- latest_model = model_list.max_by { |model| extract_date_from_model(model) }
518
- latest_models[base_name] = latest_model
519
- end
520
- end
521
-
522
- latest_models
523
- end
524
-
525
- def extract_base_name(model)
526
- if model =~ /^(.+)-(\d{8})$/
527
- Regexp.last_match(1)
528
- else
529
- model
530
- end
531
- end
532
-
533
- def extract_date_from_model(model)
534
- if model =~ /-(\d{8})$/
535
- Regexp.last_match(1)
536
- else
537
- '00000000'
538
- end
539
- end
540
-
541
- def find_best_bedrock_model(anthropic_model, bedrock_models) # rubocop:disable Metrics/PerceivedComplexity
542
- base_pattern = case anthropic_model
543
- when 'claude-2.0', 'claude-2'
544
- 'claude-v2'
545
- when 'claude-2.1'
546
- 'claude-v2:1'
547
- when 'claude-instant-v1', 'claude-instant'
548
- 'claude-instant'
549
- else
550
- anthropic_model
551
- end
552
-
553
- matching_models = bedrock_models.select do |bedrock_model|
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|$)/)
556
- end
557
-
558
- return nil if matching_models.empty?
559
-
560
- begin
561
- model_info = RubyLLM.models.find(anthropic_model)
562
- target_context = model_info.context_window
563
- rescue StandardError
564
- target_context = nil
565
- end
566
-
567
- if target_context
568
- target_k = target_context / 1000
569
-
570
- with_context = matching_models.select do |m|
571
- m.include?(":#{target_k}k") || m.include?(":0:#{target_k}k")
572
- end
573
-
574
- return with_context.first if with_context.any?
575
- end
576
-
577
- matching_models.min_by do |model|
578
- context_priority = if model =~ /:(?:\d+:)?(\d+)k/
579
- -Regexp.last_match(1).to_i
580
- else
581
- 0
582
- end
583
-
584
- version_priority = if model =~ /-v(\d+):/
585
- -Regexp.last_match(1).to_i
586
- else
587
- 0
588
- end
589
-
590
- has_context_priority = model.include?('k') ? -1 : 0
591
- [has_context_priority, context_priority, version_priority]
592
- end
593
- end
@@ -1,94 +0,0 @@
1
- # frozen_string_literal: true
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
-
46
- namespace :release do # rubocop:disable Metrics/BlockLength
47
- desc 'Prepare for release'
48
- task :prepare do
49
- Rake::Task['release:refresh_stale_cassettes'].invoke
50
- sh 'overcommit --run'
51
- Rake::Task['models'].invoke
52
- end
53
-
54
- desc 'Remove stale cassettes and re-record them'
55
- task :refresh_stale_cassettes do
56
- max_age_days = 1
57
- cassette_dir = 'spec/fixtures/vcr_cassettes'
58
-
59
- stale_cassettes = ReleaseTasks.find_stale_cassettes(cassette_dir, max_age_days)
60
-
61
- stale_cassettes.each do |cassette|
62
- puts "Removing stale cassette: #{cassette[:file]} (#{cassette[:age]} days old)"
63
- File.delete(cassette[:path])
64
- end
65
-
66
- if stale_cassettes.any?
67
- puts "\nšŸ—‘ļø Removed #{stale_cassettes.size} stale cassettes"
68
- puts 'šŸ”„ Re-recording cassettes...'
69
- run_test_queue_rspec || exit(1)
70
- puts 'āœ… Cassettes refreshed!'
71
- else
72
- puts 'āœ… No stale cassettes found'
73
- end
74
- end
75
-
76
- desc 'Verify cassettes are fresh enough for release'
77
- task :verify_cassettes do
78
- max_age_days = 1
79
- cassette_dir = 'spec/fixtures/vcr_cassettes'
80
- stale_cassettes = ReleaseTasks.find_stale_cassettes(cassette_dir, max_age_days)
81
-
82
- if stale_cassettes.any?
83
- puts "\nāŒ Found stale cassettes (older than #{max_age_days} days):"
84
- stale_cassettes.each do |c|
85
- puts " - #{c[:file]} (#{c[:age]} days old)"
86
- end
87
-
88
- puts "\nRun locally: bundle exec rake release:refresh_stale_cassettes"
89
- exit 1
90
- else
91
- puts "āœ… All cassettes are fresh (< #{max_age_days} days old)"
92
- end
93
- end
94
- end