ruby_llm 1.15.0 → 2.0.0.rc1

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 (470) hide show
  1. checksums.yaml +4 -4
  2. data/.rdoc_options +25 -0
  3. data/README.md +87 -33
  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 -6
  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 +25 -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 +100 -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 -136
  100. data/lib/ruby_llm/active_record/attachment_helpers.rb +180 -0
  101. data/lib/ruby_llm/active_record/batch.rb +97 -0
  102. data/lib/ruby_llm/active_record/chat_methods.rb +823 -305
  103. data/lib/ruby_llm/active_record/message_methods.rb +119 -75
  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 -150
  109. data/lib/ruby_llm/aliases.json +338 -167
  110. data/lib/ruby_llm/attachment.rb +217 -61
  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 +1208 -150
  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 +274 -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 +141 -7
  122. data/lib/ruby_llm/embedding_request.rb +53 -0
  123. data/lib/ruby_llm/error.rb +161 -89
  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 +155 -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 +43 -14
  130. data/lib/ruby_llm/model/pricing_category.rb +103 -14
  131. data/lib/ruby_llm/model/pricing_tier.rb +66 -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 +70380 -33380
  137. data/lib/ruby_llm/models.rb +528 -201
  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 +540 -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 +166 -0
  152. data/lib/ruby_llm/{providers → protocols}/anthropic/tools.rb +43 -34
  153. data/lib/ruby_llm/protocols/anthropic.rb +100 -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/protocols/chat_completions/media.rb +121 -0
  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 +17 -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 +685 -0
  190. data/lib/ruby_llm/protocols/converse/media.rb +178 -0
  191. data/lib/ruby_llm/protocols/converse/streaming.rb +424 -0
  192. data/lib/ruby_llm/protocols/converse.rb +54 -0
  193. data/lib/ruby_llm/protocols/deepgram/models.rb +74 -0
  194. data/lib/ruby_llm/protocols/deepgram/speech.rb +93 -0
  195. data/lib/ruby_llm/protocols/deepgram/streaming_transcription.rb +89 -0
  196. data/lib/ruby_llm/protocols/deepgram/transcription.rb +96 -0
  197. data/lib/ruby_llm/protocols/deepgram.rb +19 -0
  198. data/lib/ruby_llm/protocols/deepseek/files.rb +31 -0
  199. data/lib/ruby_llm/protocols/elevenlabs/assets.rb +36 -0
  200. data/lib/ruby_llm/protocols/elevenlabs/flows/images.rb +74 -0
  201. data/lib/ruby_llm/protocols/elevenlabs/flows/media.rb +42 -0
  202. data/lib/ruby_llm/protocols/elevenlabs/flows/videos.rb +93 -0
  203. data/lib/ruby_llm/protocols/elevenlabs/flows.rb +14 -0
  204. data/lib/ruby_llm/protocols/elevenlabs/models.rb +64 -0
  205. data/lib/ruby_llm/protocols/elevenlabs/speech.rb +67 -0
  206. data/lib/ruby_llm/protocols/elevenlabs/streaming_transcription.rb +127 -0
  207. data/lib/ruby_llm/protocols/elevenlabs/transcription.rb +61 -0
  208. data/lib/ruby_llm/protocols/elevenlabs.rb +15 -0
  209. data/lib/ruby_llm/protocols/files.rb +119 -0
  210. data/lib/ruby_llm/protocols/gemini/batches.rb +162 -0
  211. data/lib/ruby_llm/protocols/gemini/caches.rb +59 -0
  212. data/lib/ruby_llm/protocols/gemini/chat.rb +453 -0
  213. data/lib/ruby_llm/protocols/gemini/embedding_batches.rb +86 -0
  214. data/lib/ruby_llm/protocols/gemini/embeddings.rb +70 -0
  215. data/lib/ruby_llm/protocols/gemini/file_transcription.rb +32 -0
  216. data/lib/ruby_llm/protocols/gemini/files.rb +115 -0
  217. data/lib/ruby_llm/protocols/gemini/images.rb +183 -0
  218. data/lib/ruby_llm/protocols/gemini/live_transcription.rb +140 -0
  219. data/lib/ruby_llm/{providers → protocols}/gemini/media.rb +33 -20
  220. data/lib/ruby_llm/protocols/gemini/models.rb +71 -0
  221. data/lib/ruby_llm/protocols/gemini/speech.rb +56 -0
  222. data/lib/ruby_llm/protocols/gemini/streaming.rb +96 -0
  223. data/lib/ruby_llm/protocols/gemini/tools.rb +157 -0
  224. data/lib/ruby_llm/{providers → protocols}/gemini/transcription.rb +22 -22
  225. data/lib/ruby_llm/protocols/gemini/videos.rb +103 -0
  226. data/lib/ruby_llm/protocols/gemini.rb +35 -0
  227. data/lib/ruby_llm/protocols/gpustack/responses.rb +111 -0
  228. data/lib/ruby_llm/protocols/gpustack/tokenization.rb +22 -0
  229. data/lib/ruby_llm/protocols/gpustack/videos.rb +96 -0
  230. data/lib/ruby_llm/protocols/interactions/chat.rb +145 -0
  231. data/lib/ruby_llm/protocols/interactions/content.rb +90 -0
  232. data/lib/ruby_llm/protocols/interactions/streaming.rb +91 -0
  233. data/lib/ruby_llm/protocols/interactions/tools.rb +51 -0
  234. data/lib/ruby_llm/protocols/interactions/transcription.rb +58 -0
  235. data/lib/ruby_llm/protocols/interactions.rb +29 -0
  236. data/lib/ruby_llm/protocols/invoke_model/cohere_embeddings.rb +51 -0
  237. data/lib/ruby_llm/protocols/invoke_model/embedding_batches.rb +111 -0
  238. data/lib/ruby_llm/protocols/invoke_model/nova_embeddings.rb +50 -0
  239. data/lib/ruby_llm/protocols/invoke_model/stability_images.rb +103 -0
  240. data/lib/ruby_llm/protocols/invoke_model/titan_multimodal_embeddings.rb +33 -0
  241. data/lib/ruby_llm/protocols/invoke_model/titan_text_embeddings.rb +44 -0
  242. data/lib/ruby_llm/protocols/invoke_model.rb +57 -0
  243. data/lib/ruby_llm/protocols/mistral/content.rb +49 -0
  244. data/lib/ruby_llm/protocols/mistral/conversations/chat.rb +160 -0
  245. data/lib/ruby_llm/protocols/mistral/conversations/images.rb +43 -0
  246. data/lib/ruby_llm/protocols/mistral/conversations/streaming.rb +83 -0
  247. data/lib/ruby_llm/protocols/mistral/conversations.rb +30 -0
  248. data/lib/ruby_llm/protocols/mistral/files.rb +36 -0
  249. data/lib/ruby_llm/protocols/mistral/multi_completion.rb +160 -0
  250. data/lib/ruby_llm/protocols/openai/batches.rb +126 -0
  251. data/lib/ruby_llm/protocols/openai/files.rb +42 -0
  252. data/lib/ruby_llm/protocols/openrouter/batches.rb +147 -0
  253. data/lib/ruby_llm/protocols/openrouter/files.rb +24 -0
  254. data/lib/ruby_llm/protocols/openrouter/responses.rb +53 -0
  255. data/lib/ruby_llm/protocols/openrouter/transcription.rb +51 -0
  256. data/lib/ruby_llm/protocols/perplexity/files.rb +48 -0
  257. data/lib/ruby_llm/protocols/perplexity/router.rb +59 -0
  258. data/lib/ruby_llm/protocols/responses/approvals.rb +32 -0
  259. data/lib/ruby_llm/protocols/responses/batches.rb +32 -0
  260. data/lib/ruby_llm/protocols/responses/chat.rb +476 -0
  261. data/lib/ruby_llm/protocols/responses/compaction.rb +29 -0
  262. data/lib/ruby_llm/protocols/responses/media.rb +61 -0
  263. data/lib/ruby_llm/protocols/responses/streaming.rb +117 -0
  264. data/lib/ruby_llm/protocols/responses/token_counting.rb +26 -0
  265. data/lib/ruby_llm/protocols/responses/tools.rb +39 -0
  266. data/lib/ruby_llm/protocols/responses.rb +35 -0
  267. data/lib/ruby_llm/protocols/vertexai/batch_prediction.rb +155 -0
  268. data/lib/ruby_llm/protocols/vertexai/embedding_prediction/requests.rb +85 -0
  269. data/lib/ruby_llm/protocols/vertexai/embedding_prediction/results.rb +74 -0
  270. data/lib/ruby_llm/protocols/vertexai/embedding_prediction.rb +56 -0
  271. data/lib/ruby_llm/protocols/vertexai/files.rb +101 -0
  272. data/lib/ruby_llm/protocols/vertexai/ranking.rb +69 -0
  273. data/lib/ruby_llm/protocols/vertexai/research.rb +193 -0
  274. data/lib/ruby_llm/protocols/xai/files.rb +30 -0
  275. data/lib/ruby_llm/protocols/xai/streaming_transcription.rb +120 -0
  276. data/lib/ruby_llm/protocols/xai/tokenization.rb +23 -0
  277. data/lib/ruby_llm/provider.rb +565 -124
  278. data/lib/ruby_llm/providers/anthropic/capabilities.rb +5 -7
  279. data/lib/ruby_llm/providers/anthropic.rb +4 -6
  280. data/lib/ruby_llm/providers/azure/audio.rb +18 -0
  281. data/lib/ruby_llm/providers/azure/capabilities.rb +16 -0
  282. data/lib/ruby_llm/providers/azure/chat.rb +2 -9
  283. data/lib/ruby_llm/providers/azure/chat_completions/batches.rb +29 -0
  284. data/lib/ruby_llm/providers/azure/chat_completions.rb +80 -0
  285. data/lib/ruby_llm/providers/azure/cohere.rb +33 -0
  286. data/lib/ruby_llm/providers/azure/embeddings.rb +3 -2
  287. data/lib/ruby_llm/providers/azure/images.rb +22 -0
  288. data/lib/ruby_llm/providers/azure/media.rb +6 -15
  289. data/lib/ruby_llm/providers/azure/models.rb +35 -0
  290. data/lib/ruby_llm/providers/azure/responses.rb +26 -0
  291. data/lib/ruby_llm/providers/azure/videos.rb +64 -0
  292. data/lib/ruby_llm/providers/azure.rb +77 -78
  293. data/lib/ruby_llm/providers/bedrock/auth.rb +61 -41
  294. data/lib/ruby_llm/providers/bedrock/capabilities.rb +18 -0
  295. data/lib/ruby_llm/providers/bedrock/mantle/anthropic.rb +39 -0
  296. data/lib/ruby_llm/providers/bedrock/mantle/chat_completions.rb +23 -0
  297. data/lib/ruby_llm/providers/bedrock/mantle/responses.rb +24 -0
  298. data/lib/ruby_llm/providers/bedrock/mantle/voxtral.rb +96 -0
  299. data/lib/ruby_llm/providers/bedrock/mantle.rb +57 -0
  300. data/lib/ruby_llm/providers/bedrock/models.rb +194 -42
  301. data/lib/ruby_llm/providers/bedrock.rb +217 -46
  302. data/lib/ruby_llm/providers/cohere.rb +31 -0
  303. data/lib/ruby_llm/providers/deepgram.rb +37 -0
  304. data/lib/ruby_llm/providers/deepseek/capabilities.rb +4 -8
  305. data/lib/ruby_llm/providers/deepseek/chat.rb +56 -0
  306. data/lib/ruby_llm/providers/deepseek/responses.rb +68 -0
  307. data/lib/ruby_llm/providers/deepseek.rb +9 -2
  308. data/lib/ruby_llm/providers/elevenlabs.rb +35 -0
  309. data/lib/ruby_llm/providers/gemini/capabilities.rb +8 -107
  310. data/lib/ruby_llm/providers/gemini.rb +15 -8
  311. data/lib/ruby_llm/providers/gpustack/chat.rb +2 -9
  312. data/lib/ruby_llm/providers/gpustack/embeddings.rb +28 -0
  313. data/lib/ruby_llm/providers/gpustack/media.rb +17 -16
  314. data/lib/ruby_llm/providers/gpustack/models.rb +72 -60
  315. data/lib/ruby_llm/providers/gpustack/speech.rb +15 -0
  316. data/lib/ruby_llm/providers/gpustack/transcription.rb +29 -0
  317. data/lib/ruby_llm/providers/gpustack.rb +35 -11
  318. data/lib/ruby_llm/providers/mistral/capabilities.rb +7 -155
  319. data/lib/ruby_llm/providers/mistral/chat.rb +37 -61
  320. data/lib/ruby_llm/providers/mistral/chat_completions/batches.rb +120 -0
  321. data/lib/ruby_llm/providers/mistral/chat_completions.rb +21 -0
  322. data/lib/ruby_llm/providers/mistral/conversations.rb +12 -0
  323. data/lib/ruby_llm/providers/mistral/embeddings.rb +6 -4
  324. data/lib/ruby_llm/providers/mistral/media.rb +43 -0
  325. data/lib/ruby_llm/providers/mistral/models.rb +57 -21
  326. data/lib/ruby_llm/providers/mistral/ocr.rb +47 -0
  327. data/lib/ruby_llm/providers/mistral/speech.rb +51 -0
  328. data/lib/ruby_llm/providers/mistral/transcription.rb +62 -0
  329. data/lib/ruby_llm/providers/mistral.rb +18 -6
  330. data/lib/ruby_llm/providers/ollama/chat.rb +9 -8
  331. data/lib/ruby_llm/providers/ollama/media.rb +6 -15
  332. data/lib/ruby_llm/providers/ollama/models.rb +50 -9
  333. data/lib/ruby_llm/providers/ollama.rb +9 -8
  334. data/lib/ruby_llm/providers/ollama_cloud/models.rb +14 -0
  335. data/lib/ruby_llm/providers/ollama_cloud.rb +40 -0
  336. data/lib/ruby_llm/providers/openai/capabilities.rb +54 -259
  337. data/lib/ruby_llm/providers/openai/models.rb +23 -23
  338. data/lib/ruby_llm/providers/openai/responses.rb +13 -0
  339. data/lib/ruby_llm/providers/openai.rb +92 -11
  340. data/lib/ruby_llm/providers/openrouter/chat.rb +130 -104
  341. data/lib/ruby_llm/providers/openrouter/embeddings.rb +51 -0
  342. data/lib/ruby_llm/providers/openrouter/images.rb +44 -43
  343. data/lib/ruby_llm/providers/openrouter/media.rb +34 -0
  344. data/lib/ruby_llm/providers/openrouter/models.rb +50 -11
  345. data/lib/ruby_llm/providers/openrouter/speech.rb +32 -0
  346. data/lib/ruby_llm/providers/openrouter/streaming.rb +31 -38
  347. data/lib/ruby_llm/providers/openrouter/videos.rb +81 -0
  348. data/lib/ruby_llm/providers/openrouter.rb +78 -20
  349. data/lib/ruby_llm/providers/perplexity/chat.rb +4 -0
  350. data/lib/ruby_llm/providers/perplexity/embeddings.rb +32 -0
  351. data/lib/ruby_llm/providers/perplexity/media.rb +46 -0
  352. data/lib/ruby_llm/providers/perplexity/models.rb +80 -13
  353. data/lib/ruby_llm/providers/perplexity.rb +29 -21
  354. data/lib/ruby_llm/providers/vertexai/anthropic/batches.rb +52 -0
  355. data/lib/ruby_llm/providers/vertexai/anthropic.rb +34 -0
  356. data/lib/ruby_llm/providers/vertexai/capabilities.rb +19 -0
  357. data/lib/ruby_llm/providers/vertexai/chat_completions/batches.rb +54 -0
  358. data/lib/ruby_llm/providers/vertexai/chat_completions.rb +15 -0
  359. data/lib/ruby_llm/providers/vertexai/embed_content.rb +42 -0
  360. data/lib/ruby_llm/providers/vertexai/embeddings.rb +22 -7
  361. data/lib/ruby_llm/providers/vertexai/gemini/batches.rb +42 -0
  362. data/lib/ruby_llm/providers/vertexai/gemini.rb +69 -0
  363. data/lib/ruby_llm/providers/vertexai/live_transcription.rb +24 -0
  364. data/lib/ruby_llm/providers/vertexai/mistral.rb +28 -0
  365. data/lib/ruby_llm/providers/vertexai/models.rb +145 -43
  366. data/lib/ruby_llm/providers/vertexai/transcription.rb +49 -4
  367. data/lib/ruby_llm/providers/vertexai/videos.rb +61 -0
  368. data/lib/ruby_llm/providers/vertexai.rb +164 -17
  369. data/lib/ruby_llm/providers/xai/capabilities.rb +18 -0
  370. data/lib/ruby_llm/providers/xai/chat.rb +10 -0
  371. data/lib/ruby_llm/providers/xai/chat_completions/batches.rb +108 -0
  372. data/lib/ruby_llm/providers/xai/chat_completions.rb +19 -0
  373. data/lib/ruby_llm/providers/xai/images.rb +91 -0
  374. data/lib/ruby_llm/providers/xai/models.rb +32 -48
  375. data/lib/ruby_llm/providers/xai/reported_cost.rb +18 -0
  376. data/lib/ruby_llm/providers/xai/responses.rb +52 -0
  377. data/lib/ruby_llm/providers/xai/speech.rb +45 -0
  378. data/lib/ruby_llm/providers/xai/transcription.rb +48 -0
  379. data/lib/ruby_llm/providers/xai/videos.rb +87 -0
  380. data/lib/ruby_llm/providers/xai.rb +17 -7
  381. data/lib/ruby_llm/railtie.rb +11 -16
  382. data/lib/ruby_llm/rerank.rb +105 -0
  383. data/lib/ruby_llm/research_job.rb +241 -0
  384. data/lib/ruby_llm/search_results.rb +68 -0
  385. data/lib/ruby_llm/server_tool_call.rb +73 -0
  386. data/lib/ruby_llm/speech.rb +159 -0
  387. data/lib/ruby_llm/speech_chunk.rb +33 -0
  388. data/lib/ruby_llm/support/deprecator.rb +22 -0
  389. data/lib/ruby_llm/support/inspectable.rb +49 -0
  390. data/lib/ruby_llm/support/instrumentation.rb +41 -0
  391. data/lib/ruby_llm/support/utils.rb +147 -0
  392. data/lib/ruby_llm/thinking.rb +127 -20
  393. data/lib/ruby_llm/tokenization.rb +59 -0
  394. data/lib/ruby_llm/tokens.rb +103 -33
  395. data/lib/ruby_llm/tool.rb +266 -91
  396. data/lib/ruby_llm/tool_call.rb +36 -3
  397. data/lib/ruby_llm/tools/server_tools.rb +109 -0
  398. data/lib/ruby_llm/transcription/wav_audio.rb +62 -0
  399. data/lib/ruby_llm/transcription.rb +139 -13
  400. data/lib/ruby_llm/transcription_chunk.rb +68 -0
  401. data/lib/ruby_llm/transport/connection.rb +193 -0
  402. data/lib/ruby_llm/transport/error_middleware.rb +131 -0
  403. data/lib/ruby_llm/transport/usage_middleware.rb +28 -0
  404. data/lib/ruby_llm/transport/websocket_connection.rb +220 -0
  405. data/lib/ruby_llm/uploaded_file.rb +144 -0
  406. data/lib/ruby_llm/version.rb +2 -1
  407. data/lib/ruby_llm/video.rb +136 -0
  408. data/lib/ruby_llm/video_job.rb +150 -0
  409. data/lib/ruby_llm/workflow.rb +91 -0
  410. data/lib/ruby_llm.rb +385 -4
  411. data/lib/tasks/ruby_llm.rake +21 -16
  412. data/skills/rubyllm/SKILL.md +81 -0
  413. data/skills/rubyllm/agents/openai.yaml +4 -0
  414. metadata +340 -92
  415. data/lib/generators/ruby_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt +0 -9
  416. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +0 -39
  417. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +0 -21
  418. data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +0 -3
  419. data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +0 -3
  420. data/lib/generators/ruby_llm/upgrade_to_v1_10/templates/add_v1_10_message_columns.rb.tt +0 -19
  421. data/lib/generators/ruby_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +0 -50
  422. data/lib/generators/ruby_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +0 -7
  423. data/lib/generators/ruby_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +0 -49
  424. data/lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt +0 -145
  425. data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +0 -122
  426. data/lib/generators/ruby_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +0 -15
  427. data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +0 -49
  428. data/lib/ruby_llm/active_record/acts_as_legacy.rb +0 -530
  429. data/lib/ruby_llm/active_record/model_methods.rb +0 -82
  430. data/lib/ruby_llm/active_record/tool_call_methods.rb +0 -18
  431. data/lib/ruby_llm/aliases.rb +0 -38
  432. data/lib/ruby_llm/connection.rb +0 -130
  433. data/lib/ruby_llm/content.rb +0 -77
  434. data/lib/ruby_llm/mime_type.rb +0 -71
  435. data/lib/ruby_llm/model/info.rb +0 -130
  436. data/lib/ruby_llm/models_schema.json +0 -171
  437. data/lib/ruby_llm/providers/anthropic/chat.rb +0 -257
  438. data/lib/ruby_llm/providers/anthropic/content.rb +0 -44
  439. data/lib/ruby_llm/providers/anthropic/embeddings.rb +0 -20
  440. data/lib/ruby_llm/providers/anthropic/media.rb +0 -92
  441. data/lib/ruby_llm/providers/anthropic/models.rb +0 -57
  442. data/lib/ruby_llm/providers/anthropic/streaming.rb +0 -69
  443. data/lib/ruby_llm/providers/bedrock/chat.rb +0 -403
  444. data/lib/ruby_llm/providers/bedrock/media.rb +0 -90
  445. data/lib/ruby_llm/providers/bedrock/streaming.rb +0 -322
  446. data/lib/ruby_llm/providers/gemini/chat.rb +0 -543
  447. data/lib/ruby_llm/providers/gemini/embeddings.rb +0 -37
  448. data/lib/ruby_llm/providers/gemini/images.rb +0 -47
  449. data/lib/ruby_llm/providers/gemini/models.rb +0 -38
  450. data/lib/ruby_llm/providers/gemini/streaming.rb +0 -96
  451. data/lib/ruby_llm/providers/gemini/tools.rb +0 -232
  452. data/lib/ruby_llm/providers/gpustack/capabilities.rb +0 -20
  453. data/lib/ruby_llm/providers/ollama/capabilities.rb +0 -20
  454. data/lib/ruby_llm/providers/openai/chat.rb +0 -221
  455. data/lib/ruby_llm/providers/openai/embeddings.rb +0 -33
  456. data/lib/ruby_llm/providers/openai/images.rb +0 -90
  457. data/lib/ruby_llm/providers/openai/media.rb +0 -84
  458. data/lib/ruby_llm/providers/openai/moderation.rb +0 -34
  459. data/lib/ruby_llm/providers/openai/streaming.rb +0 -53
  460. data/lib/ruby_llm/providers/openai/temperature.rb +0 -28
  461. data/lib/ruby_llm/providers/openai/transcription.rb +0 -70
  462. data/lib/ruby_llm/providers/perplexity/capabilities.rb +0 -72
  463. data/lib/ruby_llm/providers/vertexai/chat.rb +0 -14
  464. data/lib/ruby_llm/providers/vertexai/streaming.rb +0 -14
  465. data/lib/ruby_llm/stream_accumulator.rb +0 -203
  466. data/lib/ruby_llm/streaming.rb +0 -175
  467. data/lib/ruby_llm/utils.rb +0 -91
  468. data/lib/tasks/models.rake +0 -565
  469. data/lib/tasks/release.rake +0 -67
  470. data/lib/tasks/vcr.rake +0 -124
@@ -1,565 +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/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
- sorted_aliases = aliases.sort.to_h
471
- File.write(RubyLLM::Aliases.aliases_file, JSON.pretty_generate(sorted_aliases))
472
-
473
- puts "Generated #{sorted_aliases.size} aliases"
474
- end
475
-
476
- def group_anthropic_models_by_base_name(anthropic_models)
477
- grouped = Hash.new { |h, k| h[k] = [] }
478
-
479
- anthropic_models.each do |model|
480
- base_name = extract_base_name(model)
481
- grouped[base_name] << model
482
- end
483
-
484
- latest_models = {}
485
- grouped.each do |base_name, model_list|
486
- if model_list.size == 1
487
- latest_models[base_name] = model_list.first
488
- else
489
- latest_model = model_list.max_by { |model| extract_date_from_model(model) }
490
- latest_models[base_name] = latest_model
491
- end
492
- end
493
-
494
- latest_models
495
- end
496
-
497
- def extract_base_name(model)
498
- if model =~ /^(.+)-(\d{8})$/
499
- Regexp.last_match(1)
500
- else
501
- model
502
- end
503
- end
504
-
505
- def extract_date_from_model(model)
506
- if model =~ /-(\d{8})$/
507
- Regexp.last_match(1)
508
- else
509
- '00000000'
510
- end
511
- end
512
-
513
- def find_best_bedrock_model(anthropic_model, bedrock_models) # rubocop:disable Metrics/PerceivedComplexity
514
- base_pattern = case anthropic_model
515
- when 'claude-2.0', 'claude-2'
516
- 'claude-v2'
517
- when 'claude-2.1'
518
- 'claude-v2:1'
519
- when 'claude-instant-v1', 'claude-instant'
520
- 'claude-instant'
521
- else
522
- extract_base_name(anthropic_model)
523
- end
524
-
525
- matching_models = bedrock_models.select do |bedrock_model|
526
- model_without_prefix = bedrock_model.sub(/^(?:us\.)?anthropic\./, '')
527
- model_without_prefix.start_with?(base_pattern)
528
- end
529
-
530
- return nil if matching_models.empty?
531
-
532
- begin
533
- model_info = RubyLLM.models.find(anthropic_model)
534
- target_context = model_info.context_window
535
- rescue StandardError
536
- target_context = nil
537
- end
538
-
539
- if target_context
540
- target_k = target_context / 1000
541
-
542
- with_context = matching_models.select do |m|
543
- m.include?(":#{target_k}k") || m.include?(":0:#{target_k}k")
544
- end
545
-
546
- return with_context.first if with_context.any?
547
- end
548
-
549
- matching_models.min_by do |model|
550
- context_priority = if model =~ /:(?:\d+:)?(\d+)k/
551
- -Regexp.last_match(1).to_i
552
- else
553
- 0
554
- end
555
-
556
- version_priority = if model =~ /-v(\d+):/
557
- -Regexp.last_match(1).to_i
558
- else
559
- 0
560
- end
561
-
562
- has_context_priority = model.include?('k') ? -1 : 0
563
- [has_context_priority, context_priority, version_priority]
564
- end
565
- end
@@ -1,67 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- namespace :release do # rubocop:disable Metrics/BlockLength
4
- desc 'Prepare for release'
5
- task :prepare do
6
- Rake::Task['release:refresh_stale_cassettes'].invoke
7
- sh 'overcommit --run'
8
- Rake::Task['models'].invoke
9
- end
10
-
11
- desc 'Remove stale cassettes and re-record them'
12
- task :refresh_stale_cassettes do
13
- max_age_days = 1
14
- cassette_dir = 'spec/fixtures/vcr_cassettes'
15
-
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
20
-
21
- puts "Removing stale cassette: #{File.basename(cassette)} (#{age_days.round(1)} days old)"
22
- File.delete(cassette)
23
- stale_count += 1
24
- end
25
-
26
- if stale_count.positive?
27
- puts "\nšŸ—‘ļø Removed #{stale_count} stale cassettes"
28
- puts 'šŸ”„ Re-recording cassettes...'
29
- run_test_queue_rspec || exit(1)
30
- puts 'āœ… Cassettes refreshed!'
31
- else
32
- puts 'āœ… No stale cassettes found'
33
- end
34
- end
35
-
36
- desc 'Verify cassettes are fresh enough for release'
37
- task :verify_cassettes do
38
- max_age_days = 1
39
- 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
52
-
53
- if stale_cassettes.any?
54
- puts "\nāŒ Found stale cassettes (older than #{max_age_days} days):"
55
- stale_files = []
56
- stale_cassettes.each do |c|
57
- puts " - #{c[:file]} (#{c[:age]} days old)"
58
- stale_files << File.join(cassette_dir, '**', c[:file])
59
- end
60
-
61
- puts "\nRun locally: bundle exec rake release:refresh_stale_cassettes"
62
- exit 1
63
- else
64
- puts "āœ… All cassettes are fresh (< #{max_age_days} days old)"
65
- end
66
- end
67
- end