ruby_llm 1.16.0 → 2.0.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (480) hide show
  1. checksums.yaml +4 -4
  2. data/.rdoc_options +25 -0
  3. data/README.md +108 -44
  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 +109 -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 +3 -1
  40. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +15 -9
  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/legacy_content_sql.rb +34 -0
  91. data/lib/generators/ruby_llm/upgrade/online_copy_migration/data.rb +341 -0
  92. data/lib/generators/ruby_llm/upgrade/online_copy_migration/journal.rb +171 -0
  93. data/lib/generators/ruby_llm/upgrade/online_copy_migration/verification.rb +197 -0
  94. data/lib/generators/ruby_llm/upgrade/online_copy_migration.rb +174 -0
  95. data/lib/generators/ruby_llm/upgrade/templates/backfill_v2_data.rb.tt +468 -0
  96. data/lib/generators/ruby_llm/upgrade/templates/cleanup_v2_upgrade.rb.tt +101 -0
  97. data/lib/generators/ruby_llm/upgrade/templates/finish_v2_upgrade.rb.tt +247 -0
  98. data/lib/generators/ruby_llm/upgrade/templates/prepare_v2_upgrade.rb.tt +662 -0
  99. data/lib/generators/ruby_llm/upgrade/templates/ruby_llm_upgrade.rb.tt +251 -0
  100. data/lib/generators/ruby_llm/upgrade/templates/upgrade_initializer.rb.tt +13 -0
  101. data/lib/generators/ruby_llm/upgrade/upgrade_generator.rb +188 -0
  102. data/lib/generators/ruby_llm/upgrade/upgrade_migration.rb +359 -0
  103. data/lib/ruby_llm/accounting/usage.rb +254 -0
  104. data/lib/ruby_llm/active_record/acts_as.rb +94 -111
  105. data/lib/ruby_llm/active_record/attachment_helpers.rb +264 -0
  106. data/lib/ruby_llm/active_record/batch.rb +97 -0
  107. data/lib/ruby_llm/active_record/chat_methods.rb +831 -305
  108. data/lib/ruby_llm/active_record/message_methods.rb +113 -136
  109. data/lib/ruby_llm/active_record/model.rb +135 -0
  110. data/lib/ruby_llm/active_record/payload_helpers.rb +1 -2
  111. data/lib/ruby_llm/active_record/tool_call.rb +33 -0
  112. data/lib/ruby_llm/active_record/usage.rb +61 -0
  113. data/lib/ruby_llm/agent.rb +1066 -151
  114. data/lib/ruby_llm/aliases.json +291 -101
  115. data/lib/ruby_llm/attachment.rb +192 -48
  116. data/lib/ruby_llm/batch.rb +432 -0
  117. data/lib/ruby_llm/cached_content.rb +112 -0
  118. data/lib/ruby_llm/chat/tool_concurrency.rb +111 -0
  119. data/lib/ruby_llm/chat.rb +1131 -198
  120. data/lib/ruby_llm/chunk.rb +10 -0
  121. data/lib/ruby_llm/citation.rb +105 -0
  122. data/lib/ruby_llm/configuration.rb +261 -24
  123. data/lib/ruby_llm/context.rb +128 -6
  124. data/lib/ruby_llm/cost.rb +217 -80
  125. data/lib/ruby_llm/downloaded_file.rb +33 -0
  126. data/lib/ruby_llm/embedding.rb +121 -17
  127. data/lib/ruby_llm/embedding_request.rb +53 -0
  128. data/lib/ruby_llm/error.rb +159 -23
  129. data/lib/ruby_llm/fallback.rb +133 -0
  130. data/lib/ruby_llm/files/mime_type.rb +97 -0
  131. data/lib/ruby_llm/image.rb +153 -32
  132. data/lib/ruby_llm/message.rb +255 -53
  133. data/lib/ruby_llm/model/modalities.rb +17 -4
  134. data/lib/ruby_llm/model/pricing.rb +24 -5
  135. data/lib/ruby_llm/model/pricing_category.rb +103 -14
  136. data/lib/ruby_llm/model/pricing_tier.rb +55 -15
  137. data/lib/ruby_llm/model.rb +244 -2
  138. data/lib/ruby_llm/models/aliases.rb +41 -0
  139. data/lib/ruby_llm/models/registry.rb +165 -0
  140. data/lib/ruby_llm/models/schema.rb +99 -0
  141. data/lib/ruby_llm/models.json +76038 -34173
  142. data/lib/ruby_llm/models.rb +477 -215
  143. data/lib/ruby_llm/moderation.rb +139 -26
  144. data/lib/ruby_llm/ocr.rb +112 -0
  145. data/lib/ruby_llm/prompt.rb +79 -0
  146. data/lib/ruby_llm/protocol/binary_streaming.rb +65 -0
  147. data/lib/ruby_llm/protocol/stream_accumulator.rb +214 -0
  148. data/lib/ruby_llm/protocol/streaming.rb +230 -0
  149. data/lib/ruby_llm/protocol.rb +685 -0
  150. data/lib/ruby_llm/protocols/anthropic/batches.rb +73 -0
  151. data/lib/ruby_llm/protocols/anthropic/chat.rb +543 -0
  152. data/lib/ruby_llm/protocols/anthropic/embeddings.rb +14 -0
  153. data/lib/ruby_llm/protocols/anthropic/files.rb +38 -0
  154. data/lib/ruby_llm/protocols/anthropic/media.rb +141 -0
  155. data/lib/ruby_llm/protocols/anthropic/models.rb +129 -0
  156. data/lib/ruby_llm/protocols/anthropic/streaming.rb +167 -0
  157. data/lib/ruby_llm/{providers → protocols}/anthropic/tools.rb +24 -41
  158. data/lib/ruby_llm/protocols/anthropic.rb +101 -0
  159. data/lib/ruby_llm/protocols/azure/files.rb +16 -0
  160. data/lib/ruby_llm/protocols/bedrock/async_videos.rb +110 -0
  161. data/lib/ruby_llm/protocols/bedrock/batches.rb +129 -0
  162. data/lib/ruby_llm/protocols/bedrock/files.rb +110 -0
  163. data/lib/ruby_llm/protocols/bedrock/guardrails.rb +122 -0
  164. data/lib/ruby_llm/protocols/bedrock/rerank.rb +95 -0
  165. data/lib/ruby_llm/protocols/chat_completions/batches.rb +32 -0
  166. data/lib/ruby_llm/protocols/chat_completions/chat.rb +484 -0
  167. data/lib/ruby_llm/protocols/chat_completions/embedding_batches.rb +35 -0
  168. data/lib/ruby_llm/protocols/chat_completions/embeddings.rb +60 -0
  169. data/lib/ruby_llm/protocols/chat_completions/images.rb +127 -0
  170. data/lib/ruby_llm/{providers/openai → protocols/chat_completions}/media.rb +30 -17
  171. data/lib/ruby_llm/protocols/chat_completions/models.rb +39 -0
  172. data/lib/ruby_llm/protocols/chat_completions/moderation.rb +52 -0
  173. data/lib/ruby_llm/protocols/chat_completions/rerank.rb +63 -0
  174. data/lib/ruby_llm/protocols/chat_completions/speech.rb +40 -0
  175. data/lib/ruby_llm/protocols/chat_completions/streaming.rb +69 -0
  176. data/lib/ruby_llm/{providers/openai → protocols/chat_completions}/tools.rb +15 -17
  177. data/lib/ruby_llm/protocols/chat_completions/transcription.rb +150 -0
  178. data/lib/ruby_llm/protocols/chat_completions.rb +21 -0
  179. data/lib/ruby_llm/protocols/cohere/batch_requests.rb +75 -0
  180. data/lib/ruby_llm/protocols/cohere/batches.rb +98 -0
  181. data/lib/ruby_llm/protocols/cohere/chat.rb +227 -0
  182. data/lib/ruby_llm/protocols/cohere/datasets.rb +102 -0
  183. data/lib/ruby_llm/protocols/cohere/embeddings.rb +68 -0
  184. data/lib/ruby_llm/protocols/cohere/media.rb +77 -0
  185. data/lib/ruby_llm/protocols/cohere/models.rb +92 -0
  186. data/lib/ruby_llm/protocols/cohere/ocr.rb +63 -0
  187. data/lib/ruby_llm/protocols/cohere/rerank.rb +59 -0
  188. data/lib/ruby_llm/protocols/cohere/streaming.rb +105 -0
  189. data/lib/ruby_llm/protocols/cohere/tokenization.rb +22 -0
  190. data/lib/ruby_llm/protocols/cohere/tools.rb +132 -0
  191. data/lib/ruby_llm/protocols/cohere/transcription.rb +41 -0
  192. data/lib/ruby_llm/protocols/cohere.rb +21 -0
  193. data/lib/ruby_llm/protocols/converse/batches.rb +55 -0
  194. data/lib/ruby_llm/protocols/converse/chat.rb +694 -0
  195. data/lib/ruby_llm/protocols/converse/media.rb +178 -0
  196. data/lib/ruby_llm/protocols/converse/streaming.rb +432 -0
  197. data/lib/ruby_llm/protocols/converse/thinking_stream.rb +56 -0
  198. data/lib/ruby_llm/protocols/converse.rb +54 -0
  199. data/lib/ruby_llm/protocols/deepgram/models.rb +74 -0
  200. data/lib/ruby_llm/protocols/deepgram/speech.rb +93 -0
  201. data/lib/ruby_llm/protocols/deepgram/streaming_transcription.rb +89 -0
  202. data/lib/ruby_llm/protocols/deepgram/transcription.rb +96 -0
  203. data/lib/ruby_llm/protocols/deepgram.rb +19 -0
  204. data/lib/ruby_llm/protocols/deepseek/files.rb +31 -0
  205. data/lib/ruby_llm/protocols/elevenlabs/assets.rb +36 -0
  206. data/lib/ruby_llm/protocols/elevenlabs/flows/images.rb +74 -0
  207. data/lib/ruby_llm/protocols/elevenlabs/flows/media.rb +42 -0
  208. data/lib/ruby_llm/protocols/elevenlabs/flows/videos.rb +93 -0
  209. data/lib/ruby_llm/protocols/elevenlabs/flows.rb +14 -0
  210. data/lib/ruby_llm/protocols/elevenlabs/models.rb +64 -0
  211. data/lib/ruby_llm/protocols/elevenlabs/speech.rb +67 -0
  212. data/lib/ruby_llm/protocols/elevenlabs/streaming_transcription.rb +127 -0
  213. data/lib/ruby_llm/protocols/elevenlabs/transcription.rb +61 -0
  214. data/lib/ruby_llm/protocols/elevenlabs.rb +15 -0
  215. data/lib/ruby_llm/protocols/files.rb +119 -0
  216. data/lib/ruby_llm/protocols/gemini/batches.rb +162 -0
  217. data/lib/ruby_llm/protocols/gemini/caches.rb +59 -0
  218. data/lib/ruby_llm/protocols/gemini/chat.rb +453 -0
  219. data/lib/ruby_llm/protocols/gemini/embedding_batches.rb +91 -0
  220. data/lib/ruby_llm/protocols/gemini/embeddings.rb +70 -0
  221. data/lib/ruby_llm/protocols/gemini/file_transcription.rb +32 -0
  222. data/lib/ruby_llm/protocols/gemini/files.rb +115 -0
  223. data/lib/ruby_llm/protocols/gemini/images.rb +183 -0
  224. data/lib/ruby_llm/protocols/gemini/live_transcription.rb +140 -0
  225. data/lib/ruby_llm/{providers → protocols}/gemini/media.rb +17 -11
  226. data/lib/ruby_llm/protocols/gemini/models.rb +71 -0
  227. data/lib/ruby_llm/protocols/gemini/speech.rb +56 -0
  228. data/lib/ruby_llm/protocols/gemini/streaming.rb +96 -0
  229. data/lib/ruby_llm/protocols/gemini/tools.rb +157 -0
  230. data/lib/ruby_llm/{providers → protocols}/gemini/transcription.rb +22 -22
  231. data/lib/ruby_llm/protocols/gemini/videos.rb +103 -0
  232. data/lib/ruby_llm/protocols/gemini.rb +35 -0
  233. data/lib/ruby_llm/protocols/gpustack/responses.rb +111 -0
  234. data/lib/ruby_llm/protocols/gpustack/tokenization.rb +22 -0
  235. data/lib/ruby_llm/protocols/gpustack/videos.rb +96 -0
  236. data/lib/ruby_llm/protocols/interactions/chat.rb +145 -0
  237. data/lib/ruby_llm/protocols/interactions/content.rb +90 -0
  238. data/lib/ruby_llm/protocols/interactions/streaming.rb +91 -0
  239. data/lib/ruby_llm/protocols/interactions/tools.rb +53 -0
  240. data/lib/ruby_llm/protocols/interactions/transcription.rb +58 -0
  241. data/lib/ruby_llm/protocols/interactions.rb +29 -0
  242. data/lib/ruby_llm/protocols/invoke_model/cohere_embeddings.rb +51 -0
  243. data/lib/ruby_llm/protocols/invoke_model/embedding_batches.rb +111 -0
  244. data/lib/ruby_llm/protocols/invoke_model/nova_embeddings.rb +50 -0
  245. data/lib/ruby_llm/protocols/invoke_model/stability_images.rb +103 -0
  246. data/lib/ruby_llm/protocols/invoke_model/titan_multimodal_embeddings.rb +33 -0
  247. data/lib/ruby_llm/protocols/invoke_model/titan_text_embeddings.rb +44 -0
  248. data/lib/ruby_llm/protocols/invoke_model.rb +57 -0
  249. data/lib/ruby_llm/protocols/mistral/content.rb +49 -0
  250. data/lib/ruby_llm/protocols/mistral/conversations/chat.rb +160 -0
  251. data/lib/ruby_llm/protocols/mistral/conversations/images.rb +43 -0
  252. data/lib/ruby_llm/protocols/mistral/conversations/streaming.rb +83 -0
  253. data/lib/ruby_llm/protocols/mistral/conversations.rb +30 -0
  254. data/lib/ruby_llm/protocols/mistral/files.rb +36 -0
  255. data/lib/ruby_llm/protocols/mistral/multi_completion.rb +160 -0
  256. data/lib/ruby_llm/protocols/openai/batches.rb +126 -0
  257. data/lib/ruby_llm/protocols/openai/files.rb +42 -0
  258. data/lib/ruby_llm/protocols/openrouter/batches.rb +147 -0
  259. data/lib/ruby_llm/protocols/openrouter/files.rb +24 -0
  260. data/lib/ruby_llm/protocols/openrouter/responses.rb +53 -0
  261. data/lib/ruby_llm/protocols/openrouter/transcription.rb +51 -0
  262. data/lib/ruby_llm/protocols/perplexity/files.rb +48 -0
  263. data/lib/ruby_llm/protocols/perplexity/router.rb +59 -0
  264. data/lib/ruby_llm/protocols/responses/approvals.rb +32 -0
  265. data/lib/ruby_llm/protocols/responses/batches.rb +32 -0
  266. data/lib/ruby_llm/protocols/responses/chat.rb +466 -0
  267. data/lib/ruby_llm/protocols/responses/compaction.rb +29 -0
  268. data/lib/ruby_llm/protocols/responses/media.rb +61 -0
  269. data/lib/ruby_llm/protocols/responses/streaming.rb +117 -0
  270. data/lib/ruby_llm/protocols/responses/token_counting.rb +26 -0
  271. data/lib/ruby_llm/protocols/responses/tools.rb +39 -0
  272. data/lib/ruby_llm/protocols/responses.rb +35 -0
  273. data/lib/ruby_llm/protocols/vertexai/batch_prediction.rb +155 -0
  274. data/lib/ruby_llm/protocols/vertexai/embedding_prediction/requests.rb +85 -0
  275. data/lib/ruby_llm/protocols/vertexai/embedding_prediction/results.rb +74 -0
  276. data/lib/ruby_llm/protocols/vertexai/embedding_prediction.rb +56 -0
  277. data/lib/ruby_llm/protocols/vertexai/files.rb +101 -0
  278. data/lib/ruby_llm/protocols/vertexai/ranking.rb +69 -0
  279. data/lib/ruby_llm/protocols/vertexai/research.rb +193 -0
  280. data/lib/ruby_llm/protocols/xai/files.rb +30 -0
  281. data/lib/ruby_llm/protocols/xai/streaming_transcription.rb +120 -0
  282. data/lib/ruby_llm/protocols/xai/tokenization.rb +23 -0
  283. data/lib/ruby_llm/provider.rb +560 -128
  284. data/lib/ruby_llm/providers/anthropic/capabilities.rb +5 -7
  285. data/lib/ruby_llm/providers/anthropic.rb +4 -6
  286. data/lib/ruby_llm/providers/azure/audio.rb +18 -0
  287. data/lib/ruby_llm/providers/azure/capabilities.rb +16 -0
  288. data/lib/ruby_llm/providers/azure/chat.rb +2 -9
  289. data/lib/ruby_llm/providers/azure/chat_completions/batches.rb +29 -0
  290. data/lib/ruby_llm/providers/azure/chat_completions.rb +80 -0
  291. data/lib/ruby_llm/providers/azure/cohere.rb +33 -0
  292. data/lib/ruby_llm/providers/azure/embeddings.rb +3 -2
  293. data/lib/ruby_llm/providers/azure/images.rb +22 -0
  294. data/lib/ruby_llm/providers/azure/media.rb +5 -14
  295. data/lib/ruby_llm/providers/azure/models.rb +35 -0
  296. data/lib/ruby_llm/providers/azure/responses.rb +26 -0
  297. data/lib/ruby_llm/providers/azure/videos.rb +64 -0
  298. data/lib/ruby_llm/providers/azure.rb +77 -78
  299. data/lib/ruby_llm/providers/bedrock/auth.rb +60 -41
  300. data/lib/ruby_llm/providers/bedrock/capabilities.rb +18 -0
  301. data/lib/ruby_llm/providers/bedrock/mantle/anthropic.rb +39 -0
  302. data/lib/ruby_llm/providers/bedrock/mantle/chat_completions.rb +23 -0
  303. data/lib/ruby_llm/providers/bedrock/mantle/responses.rb +24 -0
  304. data/lib/ruby_llm/providers/bedrock/mantle/voxtral.rb +96 -0
  305. data/lib/ruby_llm/providers/bedrock/mantle.rb +57 -0
  306. data/lib/ruby_llm/providers/bedrock/models.rb +193 -41
  307. data/lib/ruby_llm/providers/bedrock.rb +216 -45
  308. data/lib/ruby_llm/providers/cohere.rb +31 -0
  309. data/lib/ruby_llm/providers/deepgram.rb +37 -0
  310. data/lib/ruby_llm/providers/deepseek/capabilities.rb +4 -51
  311. data/lib/ruby_llm/providers/deepseek/chat.rb +49 -2
  312. data/lib/ruby_llm/providers/deepseek/responses.rb +67 -0
  313. data/lib/ruby_llm/providers/deepseek.rb +9 -2
  314. data/lib/ruby_llm/providers/elevenlabs.rb +35 -0
  315. data/lib/ruby_llm/providers/gemini/capabilities.rb +8 -107
  316. data/lib/ruby_llm/providers/gemini.rb +15 -8
  317. data/lib/ruby_llm/providers/gpustack/chat.rb +2 -16
  318. data/lib/ruby_llm/providers/gpustack/embeddings.rb +28 -0
  319. data/lib/ruby_llm/providers/gpustack/media.rb +17 -16
  320. data/lib/ruby_llm/providers/gpustack/models.rb +72 -62
  321. data/lib/ruby_llm/providers/gpustack/speech.rb +15 -0
  322. data/lib/ruby_llm/providers/gpustack/transcription.rb +29 -0
  323. data/lib/ruby_llm/providers/gpustack.rb +35 -11
  324. data/lib/ruby_llm/providers/mistral/capabilities.rb +7 -155
  325. data/lib/ruby_llm/providers/mistral/chat.rb +37 -61
  326. data/lib/ruby_llm/providers/mistral/chat_completions/batches.rb +120 -0
  327. data/lib/ruby_llm/providers/mistral/chat_completions.rb +21 -0
  328. data/lib/ruby_llm/providers/mistral/conversations.rb +12 -0
  329. data/lib/ruby_llm/providers/mistral/embeddings.rb +6 -4
  330. data/lib/ruby_llm/providers/mistral/media.rb +6 -18
  331. data/lib/ruby_llm/providers/mistral/models.rb +57 -23
  332. data/lib/ruby_llm/providers/mistral/ocr.rb +51 -0
  333. data/lib/ruby_llm/providers/mistral/speech.rb +51 -0
  334. data/lib/ruby_llm/providers/mistral/transcription.rb +62 -0
  335. data/lib/ruby_llm/providers/mistral.rb +16 -4
  336. data/lib/ruby_llm/providers/ollama/chat.rb +7 -13
  337. data/lib/ruby_llm/providers/ollama/media.rb +6 -15
  338. data/lib/ruby_llm/providers/ollama/models.rb +50 -9
  339. data/lib/ruby_llm/providers/ollama.rb +9 -8
  340. data/lib/ruby_llm/providers/ollama_cloud/models.rb +14 -0
  341. data/lib/ruby_llm/providers/ollama_cloud.rb +40 -0
  342. data/lib/ruby_llm/providers/openai/capabilities.rb +54 -259
  343. data/lib/ruby_llm/providers/openai/models.rb +23 -23
  344. data/lib/ruby_llm/providers/openai/responses.rb +13 -0
  345. data/lib/ruby_llm/providers/openai.rb +92 -11
  346. data/lib/ruby_llm/providers/openrouter/chat.rb +130 -108
  347. data/lib/ruby_llm/providers/openrouter/embeddings.rb +51 -0
  348. data/lib/ruby_llm/providers/openrouter/images.rb +44 -43
  349. data/lib/ruby_llm/providers/openrouter/media.rb +34 -0
  350. data/lib/ruby_llm/providers/openrouter/models.rb +50 -11
  351. data/lib/ruby_llm/providers/openrouter/speech.rb +32 -0
  352. data/lib/ruby_llm/providers/openrouter/streaming.rb +31 -38
  353. data/lib/ruby_llm/providers/openrouter/videos.rb +81 -0
  354. data/lib/ruby_llm/providers/openrouter.rb +78 -20
  355. data/lib/ruby_llm/providers/perplexity/chat.rb +2 -9
  356. data/lib/ruby_llm/providers/perplexity/embeddings.rb +32 -0
  357. data/lib/ruby_llm/providers/perplexity/media.rb +5 -21
  358. data/lib/ruby_llm/providers/perplexity/models.rb +80 -13
  359. data/lib/ruby_llm/providers/perplexity.rb +28 -20
  360. data/lib/ruby_llm/providers/vertexai/anthropic/batches.rb +52 -0
  361. data/lib/ruby_llm/providers/vertexai/anthropic.rb +34 -0
  362. data/lib/ruby_llm/providers/vertexai/capabilities.rb +19 -0
  363. data/lib/ruby_llm/providers/vertexai/chat_completions/batches.rb +54 -0
  364. data/lib/ruby_llm/providers/vertexai/chat_completions.rb +15 -0
  365. data/lib/ruby_llm/providers/vertexai/embed_content.rb +42 -0
  366. data/lib/ruby_llm/providers/vertexai/embeddings.rb +22 -7
  367. data/lib/ruby_llm/providers/vertexai/gemini/batches.rb +42 -0
  368. data/lib/ruby_llm/providers/vertexai/gemini.rb +69 -0
  369. data/lib/ruby_llm/providers/vertexai/live_transcription.rb +24 -0
  370. data/lib/ruby_llm/providers/vertexai/mistral.rb +28 -0
  371. data/lib/ruby_llm/providers/vertexai/models.rb +145 -43
  372. data/lib/ruby_llm/providers/vertexai/transcription.rb +49 -4
  373. data/lib/ruby_llm/providers/vertexai/videos.rb +61 -0
  374. data/lib/ruby_llm/providers/vertexai.rb +160 -17
  375. data/lib/ruby_llm/providers/xai/capabilities.rb +18 -0
  376. data/lib/ruby_llm/providers/xai/chat.rb +3 -2
  377. data/lib/ruby_llm/providers/xai/chat_completions/batches.rb +108 -0
  378. data/lib/ruby_llm/providers/xai/chat_completions.rb +19 -0
  379. data/lib/ruby_llm/providers/xai/images.rb +91 -0
  380. data/lib/ruby_llm/providers/xai/models.rb +32 -36
  381. data/lib/ruby_llm/providers/xai/reported_cost.rb +18 -0
  382. data/lib/ruby_llm/providers/xai/responses.rb +52 -0
  383. data/lib/ruby_llm/providers/xai/speech.rb +45 -0
  384. data/lib/ruby_llm/providers/xai/transcription.rb +48 -0
  385. data/lib/ruby_llm/providers/xai/videos.rb +87 -0
  386. data/lib/ruby_llm/providers/xai.rb +15 -5
  387. data/lib/ruby_llm/railtie.rb +7 -16
  388. data/lib/ruby_llm/rerank.rb +105 -0
  389. data/lib/ruby_llm/research_job.rb +241 -0
  390. data/lib/ruby_llm/search_results.rb +68 -0
  391. data/lib/ruby_llm/server_tool_call.rb +73 -0
  392. data/lib/ruby_llm/speech.rb +159 -0
  393. data/lib/ruby_llm/speech_chunk.rb +33 -0
  394. data/lib/ruby_llm/support/deprecator.rb +22 -0
  395. data/lib/ruby_llm/support/inspectable.rb +49 -0
  396. data/lib/ruby_llm/support/instrumentation.rb +41 -0
  397. data/lib/ruby_llm/support/utils.rb +147 -0
  398. data/lib/ruby_llm/thinking.rb +127 -20
  399. data/lib/ruby_llm/tokenization.rb +59 -0
  400. data/lib/ruby_llm/tokens.rb +103 -33
  401. data/lib/ruby_llm/tool.rb +266 -91
  402. data/lib/ruby_llm/tool_call.rb +36 -3
  403. data/lib/ruby_llm/tools/provider_tools.rb +109 -0
  404. data/lib/ruby_llm/transcription/wav_audio.rb +62 -0
  405. data/lib/ruby_llm/transcription.rb +138 -13
  406. data/lib/ruby_llm/transcription_chunk.rb +68 -0
  407. data/lib/ruby_llm/transport/connection.rb +193 -0
  408. data/lib/ruby_llm/transport/error_middleware.rb +131 -0
  409. data/lib/ruby_llm/transport/usage_middleware.rb +28 -0
  410. data/lib/ruby_llm/transport/websocket_connection.rb +220 -0
  411. data/lib/ruby_llm/uploaded_file.rb +144 -0
  412. data/lib/ruby_llm/version.rb +2 -1
  413. data/lib/ruby_llm/video.rb +136 -0
  414. data/lib/ruby_llm/video_job.rb +150 -0
  415. data/lib/ruby_llm/workflow.rb +91 -0
  416. data/lib/ruby_llm.rb +380 -6
  417. data/lib/tasks/ruby_llm.rake +21 -16
  418. data/skills/rubyllm/SKILL.md +81 -0
  419. data/skills/rubyllm/agents/openai.yaml +4 -0
  420. metadata +344 -97
  421. data/lib/generators/ruby_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt +0 -9
  422. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +0 -39
  423. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +0 -21
  424. data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +0 -3
  425. data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +0 -3
  426. data/lib/generators/ruby_llm/upgrade_to_v1_10/templates/add_v1_10_message_columns.rb.tt +0 -19
  427. data/lib/generators/ruby_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +0 -50
  428. data/lib/generators/ruby_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +0 -7
  429. data/lib/generators/ruby_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +0 -49
  430. data/lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt +0 -145
  431. data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +0 -122
  432. data/lib/generators/ruby_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +0 -15
  433. data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +0 -49
  434. data/lib/ruby_llm/active_record/acts_as_legacy.rb +0 -597
  435. data/lib/ruby_llm/active_record/model_methods.rb +0 -82
  436. data/lib/ruby_llm/active_record/tool_call_methods.rb +0 -18
  437. data/lib/ruby_llm/aliases.rb +0 -41
  438. data/lib/ruby_llm/connection.rb +0 -159
  439. data/lib/ruby_llm/content.rb +0 -91
  440. data/lib/ruby_llm/deprecator.rb +0 -24
  441. data/lib/ruby_llm/error_middleware.rb +0 -81
  442. data/lib/ruby_llm/instrumentation.rb +0 -36
  443. data/lib/ruby_llm/mime_type.rb +0 -96
  444. data/lib/ruby_llm/model/info.rb +0 -164
  445. data/lib/ruby_llm/model_registry.rb +0 -39
  446. data/lib/ruby_llm/models_schema.json +0 -171
  447. data/lib/ruby_llm/providers/anthropic/chat.rb +0 -291
  448. data/lib/ruby_llm/providers/anthropic/content.rb +0 -44
  449. data/lib/ruby_llm/providers/anthropic/embeddings.rb +0 -20
  450. data/lib/ruby_llm/providers/anthropic/media.rb +0 -92
  451. data/lib/ruby_llm/providers/anthropic/models.rb +0 -59
  452. data/lib/ruby_llm/providers/anthropic/streaming.rb +0 -71
  453. data/lib/ruby_llm/providers/bedrock/chat.rb +0 -405
  454. data/lib/ruby_llm/providers/bedrock/media.rb +0 -108
  455. data/lib/ruby_llm/providers/bedrock/streaming.rb +0 -328
  456. data/lib/ruby_llm/providers/gemini/chat.rb +0 -542
  457. data/lib/ruby_llm/providers/gemini/embeddings.rb +0 -37
  458. data/lib/ruby_llm/providers/gemini/images.rb +0 -47
  459. data/lib/ruby_llm/providers/gemini/models.rb +0 -38
  460. data/lib/ruby_llm/providers/gemini/streaming.rb +0 -98
  461. data/lib/ruby_llm/providers/gemini/tools.rb +0 -234
  462. data/lib/ruby_llm/providers/gpustack/capabilities.rb +0 -20
  463. data/lib/ruby_llm/providers/ollama/capabilities.rb +0 -20
  464. data/lib/ruby_llm/providers/openai/chat.rb +0 -236
  465. data/lib/ruby_llm/providers/openai/embeddings.rb +0 -33
  466. data/lib/ruby_llm/providers/openai/images.rb +0 -90
  467. data/lib/ruby_llm/providers/openai/moderation.rb +0 -34
  468. data/lib/ruby_llm/providers/openai/streaming.rb +0 -55
  469. data/lib/ruby_llm/providers/openai/temperature.rb +0 -28
  470. data/lib/ruby_llm/providers/openai/transcription.rb +0 -71
  471. data/lib/ruby_llm/providers/perplexity/capabilities.rb +0 -72
  472. data/lib/ruby_llm/providers/vertexai/chat.rb +0 -14
  473. data/lib/ruby_llm/providers/vertexai/streaming.rb +0 -14
  474. data/lib/ruby_llm/stream_accumulator.rb +0 -218
  475. data/lib/ruby_llm/streaming.rb +0 -179
  476. data/lib/ruby_llm/tool_concurrency.rb +0 -105
  477. data/lib/ruby_llm/utils.rb +0 -130
  478. data/lib/tasks/models.rake +0 -593
  479. data/lib/tasks/release.rake +0 -94
  480. data/lib/tasks/vcr.rake +0 -124
@@ -1,16 +1,16 @@
1
- <%% content_for :title, "Showing <%= model_table_name.singularize.humanize.downcase %>" %>
1
+ <%% content_for :title, "Showing <%= model_resource_name.singularize.humanize.downcase %>" %>
2
2
 
3
3
  <div class="md:w-2/3 w-full">
4
4
  <%% if notice.present? %>
5
5
  <p class="py-2 px-3 bg-green-50 mb-5 text-green-500 font-medium rounded-md inline-block" id="notice"><%%= notice %></p>
6
6
  <%% end %>
7
7
 
8
- <h1 class="font-bold text-4xl">Showing <%= model_table_name.singularize.humanize.downcase %></h1>
8
+ <h1 class="font-bold text-4xl">Showing <%= model_resource_name.singularize.humanize.downcase %></h1>
9
9
 
10
10
  <div class="my-5 space-y-3">
11
11
  <p><strong class="block font-medium mb-1">Provider:</strong> <%%= @<%= model_variable_name %>.provider_class&.name || @<%= model_variable_name %>.provider %></p>
12
12
  <p><strong class="block font-medium mb-1">Model:</strong> <%%= @<%= model_variable_name %>.name %></p>
13
- <p><strong class="block font-medium mb-1">ID:</strong> <%%= @<%= model_variable_name %>.model_id %></p>
13
+ <p><strong class="block font-medium mb-1">ID:</strong> <%%= @<%= model_variable_name %>.id %></p>
14
14
  <p><strong class="block font-medium mb-1">Context Window:</strong> <%%= number_with_delimiter(@<%= model_variable_name %>.context_window) if @<%= model_variable_name %>.context_window %></p>
15
15
  <p><strong class="block font-medium mb-1">Max Output Tokens:</strong> <%%= number_with_delimiter(@<%= model_variable_name %>.max_output_tokens) if @<%= model_variable_name %>.max_output_tokens %></p>
16
16
  </div>
@@ -22,6 +22,6 @@
22
22
  </div>
23
23
  <%% end %>
24
24
 
25
- <%%= link_to "Start <%= chat_table_name.singularize.humanize.downcase %> with this model", new_<%= chat_variable_name %>_path(model: @<%= model_variable_name %>.model_id), class: "w-full sm:w-auto text-center rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
26
- <%%= link_to "All <%= model_table_name.humanize.downcase %>", <%= model_table_name %>_path, class: "w-full sm:w-auto text-center mt-2 sm:mt-0 sm:ml-2 rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
25
+ <%%= link_to "Start <%= chat_table_name.singularize.humanize.downcase %> with this model", new_<%= chat_variable_name %>_path(model: [@<%= model_variable_name %>.provider, @<%= model_variable_name %>.id].join(":")), class: "w-full sm:w-auto text-center rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
26
+ <%%= link_to "All <%= model_resource_name.humanize.downcase %>", <%= model_resource_name %>_path, class: "w-full sm:w-auto text-center mt-2 sm:mt-0 sm:ml-2 rounded-md px-3.5 py-2.5 bg-gray-100 hover:bg-gray-50 inline-block font-medium" %>
27
27
  </div>
@@ -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,21 @@ 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
+ application = Rails.application if Rails.respond_to?(:application)
142
+ return :bigint unless application
143
+
144
+ application.config.generators.options.dig(:active_record, :primary_key_type) || :bigint
145
+ end
146
+
125
147
  def create_migration_class_name(table_name)
126
148
  "create_#{table_name}".camelize
127
149
  end
@@ -144,9 +166,44 @@ module RubyLLM
144
166
  false
145
167
  end
146
168
 
169
+ def ui_variant
170
+ @ui_variant ||= case options[:ui]
171
+ when 'tailwind'
172
+ :tailwind
173
+ when 'auto'
174
+ tailwind_available? ? :tailwind : :scaffold
175
+ else
176
+ :scaffold
177
+ end
178
+ end
179
+
180
+ def ui_template(template_path)
181
+ return template_path unless ui_variant == :tailwind
182
+
183
+ # Keep Tailwind templates as a separate set so we can mirror Rails/Tailwind
184
+ # scaffold conventions without complicating scaffold templates.
185
+ tailwind_template = "tailwind/#{template_path}"
186
+ File.exist?(File.join(self.class.source_root, "#{tailwind_template}.tt")) ? tailwind_template : template_path
187
+ end
188
+
189
+ def tailwind_available?
190
+ Rails.root.join('app/assets/tailwind/application.css').exist? ||
191
+ Rails.root.join('config/tailwind.config.js').exist? ||
192
+ gem_in_bundle?('tailwindcss-rails') ||
193
+ gem_in_bundle?('cssbundling-rails')
194
+ end
195
+
196
+ def gem_in_bundle?(gem_name)
197
+ gemfile_path = Rails.root.join('Gemfile')
198
+ lockfile_path = Rails.root.join('Gemfile.lock')
199
+
200
+ [gemfile_path, lockfile_path].any? do |path|
201
+ path.exist? && path.read.include?(gem_name)
202
+ end
203
+ end
204
+
147
205
  private
148
206
 
149
- # rubocop:disable Metrics/ParameterLists
150
207
  def add_association_params(params, default_assoc, table_name, model_name,
151
208
  owner_table:, owner_model_name:, plural: false)
152
209
  assoc = plural ? table_name.to_sym : table_name.singularize.to_sym
@@ -158,7 +215,6 @@ module RubyLLM
158
215
  params << "#{default_assoc.to_s.singularize}_class: '#{model_name}'" if model_name != assoc.to_s.classify
159
216
  params << "#{default_assoc}_foreign_key: :#{foreign_key}" if foreign_key != default_foreign_key
160
217
  end
161
- # rubocop:enable Metrics/ParameterLists
162
218
 
163
219
  def collection_association?(default_assoc, plural)
164
220
  plural || default_assoc.to_s.pluralize == default_assoc.to_s
@@ -179,11 +235,8 @@ module RubyLLM
179
235
  "#{owner_model_name.demodulize.underscore}_id"
180
236
  end
181
237
 
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
238
+ def sql_string_list(values)
239
+ values.map { |value| "'#{value}'" }.join(', ')
187
240
  end
188
241
 
189
242
  # Convert namespaced model names to proper table names
@@ -211,12 +264,6 @@ module RubyLLM
211
264
  "#{model_name.pluralize}Controller"
212
265
  end
213
266
  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
267
  end
221
268
  end
222
269
  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
- create_table :<%= chat_table_name %> do |t|
3
+ create_table :<%= chat_table_name %>, id: :<%= reference_type %> 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
- create_table :<%= message_table_name %> do |t|
3
+ create_table :<%= message_table_name %>, id: :<%= reference_type %> 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 <%= create_migration_class_name('ruby_llm_records') %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ create_table :ruby_llm_models, id: :<%= reference_type %> 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, id: :<%= reference_type %> 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, id: :<%= reference_type %> 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, id: :<%= reference_type %> 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