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,7 +1,7 @@
1
1
  <div id="<%%= dom_id <%= chat_model_name.demodulize.underscore %> %>">
2
2
  <div>
3
3
  <strong>Model:</strong>
4
- <%%= <%= chat_model_name.demodulize.underscore %>.<%= model_table_name.singularize %>&.label || default_model_display_name %>
4
+ <%%= <%= chat_model_name.demodulize.underscore %>.model&.label || default_model_display_name %>
5
5
  </div>
6
6
 
7
7
  <div>
@@ -14,7 +14,7 @@
14
14
  <div>
15
15
  <%%= form.label :model, "Select AI model:", style: "display: block" %>
16
16
  <%%= form.select :model,
17
- options_for_select(@chat_models.map { |model| [model.label, model.id] }.unshift([default_model_display_name, nil]), @selected_model),
17
+ options_for_select(@chat_models.map { |model| [model.label, [model.provider, model.id].join(":")] }.unshift([default_model_display_name, nil]), @selected_model),
18
18
  {},
19
19
  style: "width: 100%; max-width: 600px; padding: 5px;" %>
20
20
  </div>
@@ -6,7 +6,7 @@
6
6
 
7
7
  <h1><%= chat_model_name.pluralize %></h1>
8
8
  <p>
9
- <%%= link_to "<%= model_model_name.pluralize %>", <%= model_table_name %>_path %>
9
+ <%%= link_to "<%= model_model_name.pluralize %>", <%= model_resource_name %>_path %>
10
10
  </p>
11
11
 
12
12
  <div id="<%= chat_table_name %>">
@@ -8,10 +8,10 @@
8
8
 
9
9
  <h1><%= chat_model_name %> <%%= @<%= chat_variable_name %>.id %></h1>
10
10
 
11
- <p>Using <strong><%%= @<%= chat_variable_name %>.<%= model_table_name.singularize %>&.label || default_model_display_name %></strong></p>
11
+ <p>Using <strong><%%= @<%= chat_variable_name %>.model&.label || default_model_display_name %></strong></p>
12
12
 
13
13
  <div id="<%= message_table_name %>">
14
- <%% @<%= chat_variable_name %>.<%= message_table_name %>.where.not(id: nil).each do |<%= message_variable_name %>| %>
14
+ <%% @<%= chat_variable_name %>.<%= message_table_name %>.each do |<%= message_variable_name %>| %>
15
15
  <%%= render <%= message_variable_name %> %>
16
16
  <%% end %>
17
17
  </div>
@@ -1,4 +1,4 @@
1
- <%% assistant ||= local_assigns[:message] %>
1
+ <%% assistant ||= local_assigns[:<%= message_model_name.demodulize.underscore %>] %>
2
2
  <div id="<%= message_variable_name %>_<%%= assistant.id %>" class="<%= message_variable_name %>"
3
3
  style="margin-bottom: 20px; padding: 10px; border-left: 3px solid #28a745;">
4
4
  <div style="font-weight: bold; margin-bottom: 5px;">Assistant</div>
@@ -1,4 +1,4 @@
1
- <%% system ||= local_assigns[:message] %>
1
+ <%% system ||= local_assigns[:<%= message_model_name.demodulize.underscore %>] %>
2
2
  <div id="<%= message_variable_name %>_<%%= system.id %>" class="<%= message_variable_name %>"
3
3
  style="margin-bottom: 20px; padding: 10px; border-left: 3px solid #6b7280; background: #f9fafb;">
4
4
  <div style="font-weight: bold; margin-bottom: 5px; color: #374151;">System</div>
@@ -1,2 +1,2 @@
1
- <%% tool ||= local_assigns[:message] %>
1
+ <%% tool ||= local_assigns[:<%= message_model_name.demodulize.underscore %>] %>
2
2
  <%%= render tool_result_partial(tool), tool: tool %>
@@ -1,4 +1,6 @@
1
- <%% tool_calls ||= local_assigns[:message] %>
2
- <%% tool_calls.<%= tool_call_variable_name.pluralize %>.each do |tool_call| %>
3
- <%%= render tool_call_partial(tool_call), tool_calls: tool_calls, tool_call: tool_call %>
4
- <%% end %>
1
+ <%% message = local_assigns[:<%= message_model_name.demodulize.underscore %>] || tool_calls %>
2
+ <div id="<%= message_variable_name %>_<%%= message.id %>">
3
+ <%% message.<%= tool_call_variable_name.pluralize %>.each_value do |tool_call| %>
4
+ <%%= render tool_call_partial(tool_call), message: message, tool_call: tool_call %>
5
+ <%% end %>
6
+ </div>
@@ -1,4 +1,4 @@
1
- <%% user ||= local_assigns[:message] %>
1
+ <%% user ||= local_assigns[:<%= message_model_name.demodulize.underscore %>] %>
2
2
  <div id="<%= message_variable_name %>_<%%= user.id %>" class="<%= message_variable_name %>"
3
3
  style="margin-bottom: 20px; padding: 10px; border-left: 3px solid #007bff;">
4
4
  <div style="font-weight: bold; margin-bottom: 5px;">User</div>
@@ -1,7 +1,5 @@
1
- <%% if defined?(@<%= chat_variable_name %>) && @<%= chat_variable_name %>.present? %>
2
- <%%= turbo_stream.replace "new_<%= message_variable_name %>" do %>
3
- <%%= render "<%= message_model_name.underscore.pluralize %>/form",
4
- <%= chat_variable_name %>: @<%= chat_variable_name %>,
5
- <%= message_variable_name %>: @<%= chat_variable_name %>.<%= message_table_name %>.build %>
6
- <%% end %>
1
+ <%%= turbo_stream.replace "new_<%= message_variable_name %>" do %>
2
+ <%%= render "<%= message_model_name.underscore.pluralize %>/form",
3
+ <%= chat_variable_name %>: @<%= chat_variable_name %>,
4
+ <%= message_variable_name %>: @<%= chat_variable_name %>.<%= message_table_name %>.build %>
7
5
  <%% end %>
@@ -1,8 +1,8 @@
1
- <div id="<%= message_variable_name %>_<%%= tool_calls.id %>" class="<%= message_variable_name %>"
1
+ <div id="<%= message_variable_name %>_tool_call_<%%= tool_call.id %>" class="<%= message_variable_name %>"
2
2
  style="margin-bottom: 20px; padding: 10px; border-left: 3px solid #6b7280; background: #f9fafb;">
3
3
  <div style="font-weight: bold; margin-bottom: 5px;">Tool Call</div>
4
4
  <pre style="white-space: pre-wrap; margin: 0;"><%%= tool_call.name %>(<%%= tool_call.arguments.map { |k, v| "#{k}: #{v.inspect}" }.join(", ") %>)</pre>
5
5
  <div style="font-size: 0.85em; color: #666; margin-top: 5px;">
6
- <%%= tool_calls.created_at&.strftime("%I:%M %p") %>
6
+ <%%= message.created_at&.strftime("%I:%M %p") %>
7
7
  </div>
8
8
  </div>
@@ -1,15 +1,14 @@
1
- <%% model_id = <%= model_model_name.demodulize.underscore %>.respond_to?(:model_id) ? <%= model_model_name.demodulize.underscore %>.model_id : <%= model_model_name.demodulize.underscore %>.id %>
2
- <%% row_id = [<%= model_model_name.demodulize.underscore %>.provider, model_id].join("_").parameterize(separator: "_") %>
1
+ <%% row_id = [<%= model_model_name.demodulize.underscore %>.provider, <%= model_model_name.demodulize.underscore %>.id].join("_").parameterize(separator: "_") %>
3
2
  <tr id="model_<%%= row_id %>">
4
3
  <td><%%= <%= model_model_name.demodulize.underscore %>.provider_class&.name || <%= model_model_name.demodulize.underscore %>.provider %></td>
5
- <td><%%= <%= model_model_name.demodulize.underscore %>.respond_to?(:display_name) ? <%= model_model_name.demodulize.underscore %>.display_name : <%= model_model_name.demodulize.underscore %>.name %></td>
4
+ <td><%%= <%= model_model_name.demodulize.underscore %>.name %></td>
6
5
  <td><%%= number_with_delimiter(<%= model_model_name.demodulize.underscore %>.context_window) if <%= model_model_name.demodulize.underscore %>.context_window %></td>
7
6
  <td>
8
- <%% input = <%= model_model_name.demodulize.underscore %>.input_price_per_million %>
9
- <%% output = <%= model_model_name.demodulize.underscore %>.output_price_per_million %>
7
+ <%% input = <%= model_model_name.demodulize.underscore %>.price(:input) %>
8
+ <%% output = <%= model_model_name.demodulize.underscore %>.price(:output) %>
10
9
  <%% if input && output %>
11
10
  $<%%= "%.2f" % input %> / $<%%= "%.2f" % output %>
12
11
  <%% end %>
13
12
  </td>
14
- <td><%%= link_to "Start <%= chat_table_name.singularize.humanize.downcase %>", new_<%= chat_variable_name %>_path(model: model_id) %></td>
13
+ <td><%%= link_to "Start <%= chat_table_name.singularize.humanize.downcase %>", new_<%= chat_variable_name %>_path(model: [<%= model_model_name.demodulize.underscore %>.provider, <%= model_model_name.demodulize.underscore %>.id].join(":")) %></td>
15
14
  </tr>
@@ -10,10 +10,10 @@
10
10
  </p>
11
11
 
12
12
  <p>
13
- <%%= button_to "Refresh <%= model_model_name.pluralize %>", refresh_<%= model_table_name %>_path, method: :post %>
13
+ <%%= button_to "Refresh <%= model_model_name.pluralize %>", refresh_<%= model_resource_name %>_path, method: :post %>
14
14
  </p>
15
15
 
16
- <div id="<%= model_table_name %>">
16
+ <div id="<%= model_resource_name %>">
17
17
  <%% if @<%= model_variable_name.pluralize %>.any? %>
18
18
  <table>
19
19
  <thead>
@@ -2,7 +2,7 @@
2
2
 
3
3
  <h1><%%= @<%= model_variable_name %>.name %></h1>
4
4
 
5
- <p><strong>ID:</strong> <%%= @<%= model_variable_name %>.model_id %></p>
5
+ <p><strong>ID:</strong> <%%= @<%= model_variable_name %>.id %></p>
6
6
  <p><strong>Provider:</strong> <%%= @<%= model_variable_name %>.provider_class&.name || @<%= model_variable_name %>.provider %></p>
7
7
  <p><strong>Context Window:</strong> <%%= number_with_delimiter(@<%= model_variable_name %>.context_window) %> tokens</p>
8
8
  <p><strong>Max Output:</strong> <%%= number_with_delimiter(@<%= model_variable_name %>.max_output_tokens) %> tokens</p>
@@ -12,6 +12,6 @@
12
12
  <%% end %>
13
13
 
14
14
  <p>
15
- <%%= link_to "Start chat with this model", new_<%= chat_variable_name %>_path(model: @<%= model_variable_name %>.model_id) %> |
16
- <%%= link_to "All models", <%= model_table_name %>_path %>
15
+ <%%= link_to "Start chat with this model", new_<%= chat_variable_name %>_path(model: [@<%= model_variable_name %>.provider, @<%= model_variable_name %>.id].join(":")) %> |
16
+ <%%= link_to "All models", <%= model_resource_name %>_path %>
17
17
  </p>
@@ -4,25 +4,36 @@ module RubyLLM
4
4
  module Generators
5
5
  # Shared helpers for RubyLLM generators
6
6
  module GeneratorHelpers
7
- def parse_model_mappings
8
- @model_names = {
9
- chat: 'Chat',
10
- message: 'Message',
11
- tool_call: 'ToolCall',
12
- model: 'Model'
13
- }
7
+ APPLICATION_MODEL_TYPES = %w[chat message].freeze
8
+ DEFAULT_MODEL_NAMES = {
9
+ chat: 'Chat',
10
+ message: 'Message'
11
+ }.freeze
12
+
13
+ def parse_model_mappings(allowed_types: APPLICATION_MODEL_TYPES, defaults: DEFAULT_MODEL_NAMES)
14
+ @model_names = defaults.dup
15
+ @explicit_model_mappings = []
14
16
 
15
17
  model_mappings.each do |mapping|
16
- if mapping.include?(':')
17
- key, value = mapping.split(':', 2)
18
- @model_names[key.to_sym] = value.classify
19
- end
18
+ key, value = mapping.split(':', 2)
19
+ validate_model_mapping!(mapping, key, value, allowed_types)
20
+ raise Thor::Error, "Duplicate model mapping: #{key}" if @explicit_model_mappings.include?(key.to_sym)
21
+
22
+ @explicit_model_mappings << key.to_sym
23
+ @model_names[key.to_sym] = value.classify
20
24
  end
21
25
 
22
26
  @model_names
23
27
  end
24
28
 
25
- %i[chat message tool_call model].each do |type|
29
+ def validate_model_mapping!(mapping, key, value, allowed_types)
30
+ return if allowed_types.include?(key) && value.present?
31
+
32
+ expected = allowed_types.map { |type| "#{type}:ModelName" }.join(', ')
33
+ raise Thor::Error, "Invalid model mapping #{mapping.inspect}. Expected one of: #{expected}"
34
+ end
35
+
36
+ %i[chat message].each do |type|
26
37
  define_method("#{type}_model_name") do
27
38
  @model_names ||= parse_model_mappings
28
39
  @model_names[type]
@@ -35,18 +46,41 @@ module RubyLLM
35
46
  define_method("#{type}_variable_name") do
36
47
  variable_name_for(send("#{type}_model_name"))
37
48
  end
49
+ end
38
50
 
39
- define_method("#{type}_controller_class_name") do
40
- controller_class_name_for(send("#{type}_model_name"))
41
- end
51
+ # Models remain a UI resource even though RubyLLM owns their persistence.
52
+ # Keep that resource beside a namespaced chat without implying that the
53
+ # application has a corresponding ActiveRecord model.
54
+ def model_model_name
55
+ return 'Model' unless chat_model_name.include?('::')
42
56
 
43
- define_method("#{type}_job_class_name") do
44
- "#{variable_name_for(send("#{type}_model_name")).camelize}ResponseJob"
45
- end
57
+ "#{chat_model_name.deconstantize}::Model"
58
+ end
46
59
 
47
- define_method("#{type}_partial") do
48
- partial_path_for(send("#{type}_model_name"))
49
- end
60
+ # Routes, view paths and DOM ids for the models UI. Deliberately not a
61
+ # table name: models live in ruby_llm_models whatever the app calls this
62
+ # resource, and they are reached through the :model association.
63
+ def model_resource_name = table_name_for(model_model_name)
64
+ def model_variable_name = variable_name_for(model_model_name)
65
+
66
+ def usage_operations_sql = sql_string_list(::RubyLLM::Accounting::Usage::Entry::OPERATIONS)
67
+ def usage_statuses_sql = sql_string_list(::RubyLLM::Accounting::Usage::Entry::STATUSES)
68
+ def tool_call_variable_name = 'tool_call'
69
+
70
+ def chat_controller_class_name
71
+ controller_class_name_for(chat_model_name)
72
+ end
73
+
74
+ def message_controller_class_name
75
+ controller_class_name_for(message_model_name)
76
+ end
77
+
78
+ def model_controller_class_name
79
+ controller_class_name_for(model_model_name)
80
+ end
81
+
82
+ def chat_job_class_name
83
+ "#{chat_variable_name.camelize}ResponseJob"
50
84
  end
51
85
 
52
86
  def acts_as_chat_declaration
@@ -54,9 +88,6 @@ module RubyLLM
54
88
 
55
89
  add_association_params(params, :messages, message_table_name, message_model_name,
56
90
  owner_table: chat_table_name, owner_model_name: chat_model_name, plural: true)
57
- add_association_params(params, :model, model_table_name, model_model_name,
58
- owner_table: chat_table_name, owner_model_name: chat_model_name)
59
-
60
91
  "acts_as_chat#{" #{params.join(', ')}" if params.any?}"
61
92
  end
62
93
 
@@ -65,37 +96,13 @@ module RubyLLM
65
96
 
66
97
  add_association_params(params, :chat, chat_table_name, chat_model_name,
67
98
  owner_table: message_table_name, owner_model_name: message_model_name)
68
- add_association_params(params, :tool_calls, tool_call_table_name, tool_call_model_name,
69
- owner_table: message_table_name, owner_model_name: message_model_name, plural: true)
70
- add_association_params(params, :model, model_table_name, model_model_name,
71
- owner_table: message_table_name, owner_model_name: message_model_name)
72
-
73
99
  "acts_as_message#{" #{params.join(', ')}" if params.any?}"
74
100
  end
75
101
 
76
- def acts_as_model_declaration
77
- params = []
78
-
79
- add_association_params(params, :chats, chat_table_name, chat_model_name,
80
- owner_table: model_table_name, owner_model_name: model_model_name, plural: true)
81
-
82
- "acts_as_model#{" #{params.join(', ')}" if params.any?}"
83
- end
84
-
85
- def acts_as_tool_call_declaration
86
- params = []
87
-
88
- add_association_params(params, :message, message_table_name, message_model_name,
89
- owner_table: tool_call_table_name, owner_model_name: tool_call_model_name)
90
- add_result_foreign_key_param(params)
91
-
92
- "acts_as_tool_call#{" #{params.join(', ')}" if params.any?}"
93
- end
94
-
95
102
  def create_namespace_modules
96
103
  namespaces = []
97
104
 
98
- [chat_model_name, message_model_name, tool_call_model_name, model_model_name].each do |model_name|
105
+ [chat_model_name, message_model_name].each do |model_name|
99
106
  if model_name.include?('::')
100
107
  namespace = model_name.split('::').first
101
108
  namespaces << namespace unless namespaces.include?(namespace)
@@ -122,6 +129,18 @@ module RubyLLM
122
129
  "[#{Rails::VERSION::MAJOR}.#{Rails::VERSION::MINOR}]"
123
130
  end
124
131
 
132
+ # Rack 3.1 renamed the 422 symbol and Rails mirrors the rename from 7.2 on.
133
+ def unprocessable_content_status
134
+ constants = ::ActionDispatch::Constants
135
+ return :unprocessable_entity unless constants.const_defined?(:UNPROCESSABLE_CONTENT)
136
+
137
+ constants::UNPROCESSABLE_CONTENT
138
+ end
139
+
140
+ def reference_type
141
+ Rails.application.config.generators.options.dig(:active_record, :primary_key_type) || :bigint
142
+ end
143
+
125
144
  def create_migration_class_name(table_name)
126
145
  "create_#{table_name}".camelize
127
146
  end
@@ -144,9 +163,44 @@ module RubyLLM
144
163
  false
145
164
  end
146
165
 
166
+ def ui_variant
167
+ @ui_variant ||= case options[:ui]
168
+ when 'tailwind'
169
+ :tailwind
170
+ when 'auto'
171
+ tailwind_available? ? :tailwind : :scaffold
172
+ else
173
+ :scaffold
174
+ end
175
+ end
176
+
177
+ def ui_template(template_path)
178
+ return template_path unless ui_variant == :tailwind
179
+
180
+ # Keep Tailwind templates as a separate set so we can mirror Rails/Tailwind
181
+ # scaffold conventions without complicating scaffold templates.
182
+ tailwind_template = "tailwind/#{template_path}"
183
+ File.exist?(File.join(self.class.source_root, "#{tailwind_template}.tt")) ? tailwind_template : template_path
184
+ end
185
+
186
+ def tailwind_available?
187
+ Rails.root.join('app/assets/tailwind/application.css').exist? ||
188
+ Rails.root.join('config/tailwind.config.js').exist? ||
189
+ gem_in_bundle?('tailwindcss-rails') ||
190
+ gem_in_bundle?('cssbundling-rails')
191
+ end
192
+
193
+ def gem_in_bundle?(gem_name)
194
+ gemfile_path = Rails.root.join('Gemfile')
195
+ lockfile_path = Rails.root.join('Gemfile.lock')
196
+
197
+ [gemfile_path, lockfile_path].any? do |path|
198
+ path.exist? && path.read.include?(gem_name)
199
+ end
200
+ end
201
+
147
202
  private
148
203
 
149
- # rubocop:disable Metrics/ParameterLists
150
204
  def add_association_params(params, default_assoc, table_name, model_name,
151
205
  owner_table:, owner_model_name:, plural: false)
152
206
  assoc = plural ? table_name.to_sym : table_name.singularize.to_sym
@@ -158,7 +212,6 @@ module RubyLLM
158
212
  params << "#{default_assoc.to_s.singularize}_class: '#{model_name}'" if model_name != assoc.to_s.classify
159
213
  params << "#{default_assoc}_foreign_key: :#{foreign_key}" if foreign_key != default_foreign_key
160
214
  end
161
- # rubocop:enable Metrics/ParameterLists
162
215
 
163
216
  def collection_association?(default_assoc, plural)
164
217
  plural || default_assoc.to_s.pluralize == default_assoc.to_s
@@ -179,11 +232,8 @@ module RubyLLM
179
232
  "#{owner_model_name.demodulize.underscore}_id"
180
233
  end
181
234
 
182
- def add_result_foreign_key_param(params)
183
- foreign_key = "#{tool_call_table_name.singularize}_id"
184
- default_foreign_key = "#{tool_call_model_name.demodulize.underscore}_id"
185
-
186
- params << "result_foreign_key: :#{foreign_key}" if foreign_key != default_foreign_key
235
+ def sql_string_list(values)
236
+ values.map { |value| "'#{value}'" }.join(', ')
187
237
  end
188
238
 
189
239
  # Convert namespaced model names to proper table names
@@ -211,12 +261,6 @@ module RubyLLM
211
261
  "#{model_name.pluralize}Controller"
212
262
  end
213
263
  end
214
-
215
- # Convert model name to partial path
216
- # e.g., "LLM::Message" -> "llm/message" (not "llm_message")
217
- def partial_path_for(model_name)
218
- "#{model_name.underscore.pluralize}/#{model_name.demodulize.underscore}"
219
- end
220
264
  end
221
265
  end
222
266
  end
@@ -28,17 +28,12 @@ module RubyLLM
28
28
  end
29
29
 
30
30
  def create_migration_files
31
+ migration_template 'create_ruby_llm_records_migration.rb.tt',
32
+ 'db/migrate/create_ruby_llm_records.rb'
31
33
  migration_template 'create_chats_migration.rb.tt',
32
34
  "db/migrate/create_#{chat_table_name}.rb"
33
35
  migration_template 'create_messages_migration.rb.tt',
34
36
  "db/migrate/create_#{message_table_name}.rb"
35
- migration_template 'create_tool_calls_migration.rb.tt',
36
- "db/migrate/create_#{tool_call_table_name}.rb"
37
- migration_template 'create_models_migration.rb.tt',
38
- "db/migrate/create_#{model_table_name}.rb"
39
- migration_template 'add_references_to_chats_tool_calls_and_messages_migration.rb.tt',
40
- 'db/migrate/add_references_to_' \
41
- "#{chat_table_name}_#{tool_call_table_name}_and_#{message_table_name}.rb"
42
37
  end
43
38
 
44
39
  def create_model_files
@@ -46,9 +41,6 @@ module RubyLLM
46
41
 
47
42
  template 'chat_model.rb.tt', "app/models/#{chat_model_name.underscore}.rb"
48
43
  template 'message_model.rb.tt', "app/models/#{message_model_name.underscore}.rb"
49
- template 'tool_call_model.rb.tt', "app/models/#{tool_call_model_name.underscore}.rb"
50
-
51
- template 'model_model.rb.tt', "app/models/#{model_model_name.underscore}.rb"
52
44
  end
53
45
 
54
46
  def create_initializer
@@ -79,7 +71,7 @@ module RubyLLM
79
71
  say ' 2. Run: bin/rails ruby_llm:load_models'
80
72
  say ' 3. Set your API keys in config/initializers/ruby_llm.rb'
81
73
 
82
- say " 4. Start chatting: #{chat_model_name}.create!(model: 'gpt-5-nano').ask('Hello!')"
74
+ say " 4. Start chatting: #{chat_model_name}.create!(model: 'gpt-5.6-luna').ask('Hello!')"
83
75
  say " 5. Optional UI: #{chat_ui_generator_command}"
84
76
 
85
77
  if options[:skip_active_storage]
@@ -1,6 +1,8 @@
1
1
  class <%= create_migration_class_name(chat_table_name) %> < ActiveRecord::Migration<%= migration_version %>
2
2
  def change
3
3
  create_table :<%= chat_table_name %> do |t|
4
+ t.references :ruby_llm_model, null: false, foreign_key: { to_table: :ruby_llm_models }, type: :<%= reference_type %>
5
+ t.boolean :cancelled, null: false, default: false
4
6
  t.timestamps
5
7
  end
6
8
  end
@@ -1,19 +1,25 @@
1
1
  class <%= create_migration_class_name(message_table_name) %> < ActiveRecord::Migration<%= migration_version %>
2
2
  def change
3
3
  create_table :<%= message_table_name %> do |t|
4
+ t.references :<%= chat_table_name.singularize %>, null: false, foreign_key: { to_table: :<%= chat_table_name %> }, type: :<%= reference_type %>
4
5
  t.string :role, null: false
5
6
  t.text :content
6
- t.json :content_raw
7
+ t.boolean :cache_until_here, null: false, default: false
7
8
  t.text :thinking_text
8
9
  t.text :thinking_signature
9
- t.integer :thinking_tokens
10
- t.integer :input_tokens
11
- t.integer :output_tokens
12
- t.integer :cached_tokens
13
- t.integer :cache_creation_tokens
10
+ <% if postgresql? %>
11
+ t.jsonb :citations
12
+ t.jsonb :server_tool_calls
13
+ t.jsonb :raw_content
14
+ t.jsonb :raw_reasoning
15
+ <% else %>
16
+ t.json :citations
17
+ t.json :server_tool_calls
18
+ t.json :raw_content
19
+ t.json :raw_reasoning
20
+ <% end %>
21
+ t.string :finish_reason
14
22
  t.timestamps
15
23
  end
16
-
17
- add_index :<%= message_table_name %>, :role
18
24
  end
19
25
  end
@@ -0,0 +1,117 @@
1
+ class CreateRubyLlmRecords < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :ruby_llm_models do |t|
4
+ t.string :model_id, null: false
5
+ t.string :name, null: false
6
+ t.string :provider, null: false
7
+ t.string :family
8
+ t.datetime :model_created_at
9
+ t.integer :context_window
10
+ t.integer :max_output_tokens
11
+ t.date :knowledge_cutoff
12
+ t.datetime :unlisted_at
13
+ <% if postgresql? %>
14
+ t.jsonb :modalities, default: {}
15
+ t.jsonb :capabilities, default: []
16
+ t.jsonb :pricing, default: {}
17
+ t.jsonb :metadata, default: {}
18
+ <% elsif mysql? %>
19
+ t.json :modalities
20
+ t.json :capabilities
21
+ t.json :pricing
22
+ t.json :metadata
23
+ <% else %>
24
+ t.json :modalities, default: {}
25
+ t.json :capabilities, default: []
26
+ t.json :pricing, default: {}
27
+ t.json :metadata, default: {}
28
+ <% end %>
29
+ t.timestamps
30
+
31
+ t.index [ :provider, :model_id ], unique: true
32
+ t.index :provider
33
+ t.index :family
34
+ <% if postgresql? -%>
35
+ t.index :capabilities, using: :gin
36
+ t.index :modalities, using: :gin
37
+ <% end -%>
38
+ end
39
+
40
+ create_table :ruby_llm_tool_calls do |t|
41
+ t.references :message, polymorphic: true, null: false, type: :<%= reference_type %>, index: false
42
+ t.references :result, polymorphic: true, type: :<%= reference_type %>, index: false
43
+ t.string :tool_call_id, null: false
44
+ t.string :name, null: false
45
+ t.text :thought_signature
46
+ t.string :approval
47
+ t.boolean :remote, default: false, null: false
48
+ <% if postgresql? %>
49
+ t.jsonb :arguments, default: {}
50
+ <% elsif mysql? %>
51
+ t.json :arguments
52
+ <% else %>
53
+ t.json :arguments, default: {}
54
+ <% end %>
55
+ t.timestamps
56
+
57
+ t.index [ :message_type, :message_id ]
58
+ t.index [ :result_type, :result_id ]
59
+ t.index :tool_call_id, unique: true
60
+ t.index :name
61
+ end
62
+
63
+ create_table :ruby_llm_usages do |t|
64
+ t.references :chat, polymorphic: true, null: false, type: :<%= reference_type %>, index: false
65
+ t.references :message, polymorphic: true, type: :<%= reference_type %>, index: false
66
+ t.string :operation, null: false
67
+ t.string :provider, null: false
68
+ t.string :model, null: false
69
+ t.string :status, null: false
70
+ t.integer :input_tokens
71
+ t.integer :output_tokens
72
+ t.integer :cache_read_tokens
73
+ t.integer :cache_write_tokens
74
+ t.integer :thinking_tokens
75
+ t.decimal :input_cost, precision: 16, scale: 10
76
+ t.decimal :output_cost, precision: 16, scale: 10
77
+ t.decimal :cache_read_cost, precision: 16, scale: 10
78
+ t.decimal :cache_write_cost, precision: 16, scale: 10
79
+ t.decimal :thinking_cost, precision: 16, scale: 10
80
+ t.decimal :total_cost, precision: 16, scale: 10
81
+ t.timestamps
82
+
83
+ t.index [ :chat_type, :chat_id ]
84
+ t.index [ :message_type, :message_id ]
85
+ t.index :status
86
+ t.check_constraint "operation IN (<%= usage_operations_sql %>)"
87
+ t.check_constraint "status IN (<%= usage_statuses_sql %>)"
88
+ end
89
+
90
+ create_table :ruby_llm_batches do |t|
91
+ t.string :provider_batch_id, null: false
92
+ t.string :provider, null: false
93
+ t.string :status, null: false
94
+ t.string :raw_status
95
+ t.boolean :completed, null: false, default: false
96
+ t.string :chat_type
97
+ t.string :batch_protocol
98
+ <% if postgresql? %>
99
+ t.jsonb :chat_ids, default: []
100
+ t.jsonb :request_counts
101
+ t.jsonb :reported_cost
102
+ <% elsif mysql? %>
103
+ t.json :chat_ids
104
+ t.json :request_counts
105
+ t.json :reported_cost
106
+ <% else %>
107
+ t.json :chat_ids, default: []
108
+ t.json :request_counts
109
+ t.json :reported_cost
110
+ <% end %>
111
+ t.timestamps
112
+
113
+ t.index [ :provider, :provider_batch_id ], unique: true
114
+ t.index :status
115
+ end
116
+ end
117
+ end
@@ -1,12 +1,4 @@
1
1
  RubyLLM.configure do |config|
2
2
  config.openai_api_key = ENV.fetch("OPENAI_API_KEY", Rails.application.credentials.dig(:openai_api_key))
3
3
  # config.default_model = "gpt-5-nano"
4
-
5
- # Use the association-based acts_as API (recommended)
6
- config.use_new_acts_as = true
7
- <% if model_model_name != 'Model' -%>
8
-
9
- # Custom model registry class name
10
- config.model_registry_class = "<%= model_model_name %>"
11
- <% end -%>
12
4
  end