ruby_llm 1.15.0 → 2.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (470) hide show
  1. checksums.yaml +4 -4
  2. data/.rdoc_options +25 -0
  3. data/README.md +87 -33
  4. data/exe/ruby_llm +8 -0
  5. data/lib/generators/ruby_llm/agent/templates/agent.rb.tt +0 -1
  6. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +3 -43
  7. data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +11 -3
  8. data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +8 -0
  9. data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +4 -4
  10. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt +1 -1
  11. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +1 -1
  12. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt +1 -1
  13. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +2 -2
  14. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +1 -1
  15. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +1 -1
  16. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool.html.erb.tt +1 -1
  17. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +6 -4
  18. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +1 -1
  19. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +2 -2
  20. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt +5 -6
  21. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt +2 -2
  22. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt +5 -5
  23. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +1 -1
  24. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +1 -1
  25. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +1 -1
  26. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +2 -2
  27. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +1 -1
  28. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_system.html.erb.tt +1 -1
  29. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool.html.erb.tt +1 -1
  30. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +6 -4
  31. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_user.html.erb.tt +1 -1
  32. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +4 -6
  33. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +2 -2
  34. data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +5 -6
  35. data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +2 -2
  36. data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +3 -3
  37. data/lib/generators/ruby_llm/generator_helpers.rb +106 -62
  38. data/lib/generators/ruby_llm/install/install_generator.rb +3 -11
  39. data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +2 -0
  40. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +14 -6
  41. data/lib/generators/ruby_llm/install/templates/create_ruby_llm_records_migration.rb.tt +117 -0
  42. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +0 -8
  43. data/lib/generators/ruby_llm/provider/cli.rb +175 -0
  44. data/lib/generators/ruby_llm/provider/scaffold.rb +323 -0
  45. data/lib/generators/ruby_llm/provider/templates/core/provider.rb.erb +37 -0
  46. data/lib/generators/ruby_llm/provider/templates/core/provider_spec.rb.erb +34 -0
  47. data/lib/generators/ruby_llm/provider/templates/gem/archspec.rb.erb +14 -0
  48. data/lib/generators/ruby_llm/provider/templates/gem/bin/console.erb +15 -0
  49. data/lib/generators/ruby_llm/provider/templates/gem/bin/setup.erb +5 -0
  50. data/lib/generators/ruby_llm/provider/templates/gem/chat_schema_spec.rb.erb +30 -0
  51. data/lib/generators/ruby_llm/provider/templates/gem/chat_spec.rb.erb +35 -0
  52. data/lib/generators/ruby_llm/provider/templates/gem/chat_streaming_spec.rb.erb +22 -0
  53. data/lib/generators/ruby_llm/provider/templates/gem/chat_tools_spec.rb.erb +30 -0
  54. data/lib/generators/ruby_llm/provider/templates/gem/ci.yml.erb +32 -0
  55. data/lib/generators/ruby_llm/provider/templates/gem/embedding_spec.rb.erb +49 -0
  56. data/lib/generators/ruby_llm/provider/templates/gem/env.erb +2 -0
  57. data/lib/generators/ruby_llm/provider/templates/gem/fixtures_gitkeep.erb +1 -0
  58. data/lib/generators/ruby_llm/provider/templates/gem/flayignore.erb +1 -0
  59. data/lib/generators/ruby_llm/provider/templates/gem/gemfile.erb +25 -0
  60. data/lib/generators/ruby_llm/provider/templates/gem/gemspec.erb +28 -0
  61. data/lib/generators/ruby_llm/provider/templates/gem/gitignore.erb +7 -0
  62. data/lib/generators/ruby_llm/provider/templates/gem/gitleaks.yml.erb +22 -0
  63. data/lib/generators/ruby_llm/provider/templates/gem/image_spec.rb.erb +23 -0
  64. data/lib/generators/ruby_llm/provider/templates/gem/license.erb +21 -0
  65. data/lib/generators/ruby_llm/provider/templates/gem/models.rb.erb +19 -0
  66. data/lib/generators/ruby_llm/provider/templates/gem/models_spec.rb.erb +17 -0
  67. data/lib/generators/ruby_llm/provider/templates/gem/moderation_spec.rb.erb +22 -0
  68. data/lib/generators/ruby_llm/provider/templates/gem/overcommit.yml.erb +31 -0
  69. data/lib/generators/ruby_llm/provider/templates/gem/provider.rb.erb +52 -0
  70. data/lib/generators/ruby_llm/provider/templates/gem/provider_spec.rb.erb +38 -0
  71. data/lib/generators/ruby_llm/provider/templates/gem/rakefile.erb +38 -0
  72. data/lib/generators/ruby_llm/provider/templates/gem/readme.md.erb +50 -0
  73. data/lib/generators/ruby_llm/provider/templates/gem/release.yml.erb +36 -0
  74. data/lib/generators/ruby_llm/provider/templates/gem/rerank_spec.rb.erb +23 -0
  75. data/lib/generators/ruby_llm/provider/templates/gem/rspec.erb +2 -0
  76. data/lib/generators/ruby_llm/provider/templates/gem/rubocop.yml.erb +29 -0
  77. data/lib/generators/ruby_llm/provider/templates/gem/rubyllm_configuration.rb.erb +14 -0
  78. data/lib/generators/ruby_llm/provider/templates/gem/spec_helper.rb.erb +26 -0
  79. data/lib/generators/ruby_llm/provider/templates/gem/speech_spec.rb.erb +25 -0
  80. data/lib/generators/ruby_llm/provider/templates/gem/vcr_configuration.rb.erb +16 -0
  81. data/lib/generators/ruby_llm/provider/templates/gem/video_spec.rb.erb +27 -0
  82. data/lib/generators/ruby_llm/schema/schema_generator.rb +5 -1
  83. data/lib/generators/ruby_llm/schema/templates/schema.rb.tt +1 -1
  84. data/lib/generators/ruby_llm/tool/templates/tailwind/tool_call.html.erb.tt +13 -0
  85. data/lib/generators/ruby_llm/tool/templates/tailwind/tool_result.html.erb.tt +21 -0
  86. data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +3 -3
  87. data/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt +7 -12
  88. data/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt +5 -2
  89. data/lib/generators/ruby_llm/tool/tool_generator.rb +25 -59
  90. data/lib/generators/ruby_llm/upgrade/templates/backfill_v2_data.rb.tt +461 -0
  91. data/lib/generators/ruby_llm/upgrade/templates/cleanup_v2_upgrade.rb.tt +100 -0
  92. data/lib/generators/ruby_llm/upgrade/templates/finish_v2_upgrade.rb.tt +215 -0
  93. data/lib/generators/ruby_llm/upgrade/templates/prepare_v2_upgrade.rb.tt +660 -0
  94. data/lib/generators/ruby_llm/upgrade/templates/ruby_llm_upgrade.rb.tt +222 -0
  95. data/lib/generators/ruby_llm/upgrade/templates/upgrade_initializer.rb.tt +13 -0
  96. data/lib/generators/ruby_llm/upgrade/upgrade_generator.rb +167 -0
  97. data/lib/generators/ruby_llm/upgrade/upgrade_migration.rb +344 -0
  98. data/lib/ruby_llm/accounting/usage.rb +245 -0
  99. data/lib/ruby_llm/active_record/acts_as.rb +94 -136
  100. data/lib/ruby_llm/active_record/attachment_helpers.rb +180 -0
  101. data/lib/ruby_llm/active_record/batch.rb +97 -0
  102. data/lib/ruby_llm/active_record/chat_methods.rb +823 -305
  103. data/lib/ruby_llm/active_record/message_methods.rb +119 -75
  104. data/lib/ruby_llm/active_record/model.rb +135 -0
  105. data/lib/ruby_llm/active_record/payload_helpers.rb +1 -2
  106. data/lib/ruby_llm/active_record/tool_call.rb +33 -0
  107. data/lib/ruby_llm/active_record/usage.rb +61 -0
  108. data/lib/ruby_llm/agent.rb +1065 -150
  109. data/lib/ruby_llm/aliases.json +338 -167
  110. data/lib/ruby_llm/attachment.rb +217 -61
  111. data/lib/ruby_llm/batch.rb +432 -0
  112. data/lib/ruby_llm/cached_content.rb +112 -0
  113. data/lib/ruby_llm/chat/tool_concurrency.rb +111 -0
  114. data/lib/ruby_llm/chat.rb +1208 -150
  115. data/lib/ruby_llm/chunk.rb +10 -0
  116. data/lib/ruby_llm/citation.rb +105 -0
  117. data/lib/ruby_llm/configuration.rb +274 -24
  118. data/lib/ruby_llm/context.rb +128 -6
  119. data/lib/ruby_llm/cost.rb +217 -80
  120. data/lib/ruby_llm/downloaded_file.rb +33 -0
  121. data/lib/ruby_llm/embedding.rb +141 -7
  122. data/lib/ruby_llm/embedding_request.rb +53 -0
  123. data/lib/ruby_llm/error.rb +161 -89
  124. data/lib/ruby_llm/fallback.rb +133 -0
  125. data/lib/ruby_llm/files/mime_type.rb +97 -0
  126. data/lib/ruby_llm/image.rb +155 -32
  127. data/lib/ruby_llm/message.rb +233 -54
  128. data/lib/ruby_llm/model/modalities.rb +17 -4
  129. data/lib/ruby_llm/model/pricing.rb +43 -14
  130. data/lib/ruby_llm/model/pricing_category.rb +103 -14
  131. data/lib/ruby_llm/model/pricing_tier.rb +66 -15
  132. data/lib/ruby_llm/model.rb +244 -2
  133. data/lib/ruby_llm/models/aliases.rb +41 -0
  134. data/lib/ruby_llm/models/registry.rb +165 -0
  135. data/lib/ruby_llm/models/schema.rb +99 -0
  136. data/lib/ruby_llm/models.json +70380 -33380
  137. data/lib/ruby_llm/models.rb +528 -201
  138. data/lib/ruby_llm/moderation.rb +139 -26
  139. data/lib/ruby_llm/ocr.rb +112 -0
  140. data/lib/ruby_llm/prompt.rb +79 -0
  141. data/lib/ruby_llm/protocol/binary_streaming.rb +65 -0
  142. data/lib/ruby_llm/protocol/stream_accumulator.rb +214 -0
  143. data/lib/ruby_llm/protocol/streaming.rb +230 -0
  144. data/lib/ruby_llm/protocol.rb +662 -0
  145. data/lib/ruby_llm/protocols/anthropic/batches.rb +73 -0
  146. data/lib/ruby_llm/protocols/anthropic/chat.rb +540 -0
  147. data/lib/ruby_llm/protocols/anthropic/embeddings.rb +14 -0
  148. data/lib/ruby_llm/protocols/anthropic/files.rb +38 -0
  149. data/lib/ruby_llm/protocols/anthropic/media.rb +141 -0
  150. data/lib/ruby_llm/protocols/anthropic/models.rb +129 -0
  151. data/lib/ruby_llm/protocols/anthropic/streaming.rb +166 -0
  152. data/lib/ruby_llm/{providers → protocols}/anthropic/tools.rb +43 -34
  153. data/lib/ruby_llm/protocols/anthropic.rb +100 -0
  154. data/lib/ruby_llm/protocols/azure/files.rb +16 -0
  155. data/lib/ruby_llm/protocols/bedrock/async_videos.rb +109 -0
  156. data/lib/ruby_llm/protocols/bedrock/batches.rb +129 -0
  157. data/lib/ruby_llm/protocols/bedrock/files.rb +110 -0
  158. data/lib/ruby_llm/protocols/bedrock/guardrails.rb +122 -0
  159. data/lib/ruby_llm/protocols/bedrock/rerank.rb +95 -0
  160. data/lib/ruby_llm/protocols/chat_completions/batches.rb +32 -0
  161. data/lib/ruby_llm/protocols/chat_completions/chat.rb +493 -0
  162. data/lib/ruby_llm/protocols/chat_completions/embedding_batches.rb +35 -0
  163. data/lib/ruby_llm/protocols/chat_completions/embeddings.rb +60 -0
  164. data/lib/ruby_llm/protocols/chat_completions/images.rb +127 -0
  165. data/lib/ruby_llm/protocols/chat_completions/media.rb +121 -0
  166. data/lib/ruby_llm/protocols/chat_completions/models.rb +39 -0
  167. data/lib/ruby_llm/protocols/chat_completions/moderation.rb +52 -0
  168. data/lib/ruby_llm/protocols/chat_completions/rerank.rb +56 -0
  169. data/lib/ruby_llm/protocols/chat_completions/speech.rb +40 -0
  170. data/lib/ruby_llm/protocols/chat_completions/streaming.rb +69 -0
  171. data/lib/ruby_llm/{providers/openai → protocols/chat_completions}/tools.rb +17 -17
  172. data/lib/ruby_llm/protocols/chat_completions/transcription.rb +150 -0
  173. data/lib/ruby_llm/protocols/chat_completions.rb +21 -0
  174. data/lib/ruby_llm/protocols/cohere/batch_requests.rb +75 -0
  175. data/lib/ruby_llm/protocols/cohere/batches.rb +98 -0
  176. data/lib/ruby_llm/protocols/cohere/chat.rb +227 -0
  177. data/lib/ruby_llm/protocols/cohere/datasets.rb +102 -0
  178. data/lib/ruby_llm/protocols/cohere/embeddings.rb +68 -0
  179. data/lib/ruby_llm/protocols/cohere/media.rb +77 -0
  180. data/lib/ruby_llm/protocols/cohere/models.rb +92 -0
  181. data/lib/ruby_llm/protocols/cohere/ocr.rb +63 -0
  182. data/lib/ruby_llm/protocols/cohere/rerank.rb +52 -0
  183. data/lib/ruby_llm/protocols/cohere/streaming.rb +105 -0
  184. data/lib/ruby_llm/protocols/cohere/tokenization.rb +22 -0
  185. data/lib/ruby_llm/protocols/cohere/tools.rb +132 -0
  186. data/lib/ruby_llm/protocols/cohere/transcription.rb +41 -0
  187. data/lib/ruby_llm/protocols/cohere.rb +21 -0
  188. data/lib/ruby_llm/protocols/converse/batches.rb +55 -0
  189. data/lib/ruby_llm/protocols/converse/chat.rb +685 -0
  190. data/lib/ruby_llm/protocols/converse/media.rb +178 -0
  191. data/lib/ruby_llm/protocols/converse/streaming.rb +424 -0
  192. data/lib/ruby_llm/protocols/converse.rb +54 -0
  193. data/lib/ruby_llm/protocols/deepgram/models.rb +74 -0
  194. data/lib/ruby_llm/protocols/deepgram/speech.rb +93 -0
  195. data/lib/ruby_llm/protocols/deepgram/streaming_transcription.rb +89 -0
  196. data/lib/ruby_llm/protocols/deepgram/transcription.rb +96 -0
  197. data/lib/ruby_llm/protocols/deepgram.rb +19 -0
  198. data/lib/ruby_llm/protocols/deepseek/files.rb +31 -0
  199. data/lib/ruby_llm/protocols/elevenlabs/assets.rb +36 -0
  200. data/lib/ruby_llm/protocols/elevenlabs/flows/images.rb +74 -0
  201. data/lib/ruby_llm/protocols/elevenlabs/flows/media.rb +42 -0
  202. data/lib/ruby_llm/protocols/elevenlabs/flows/videos.rb +93 -0
  203. data/lib/ruby_llm/protocols/elevenlabs/flows.rb +14 -0
  204. data/lib/ruby_llm/protocols/elevenlabs/models.rb +64 -0
  205. data/lib/ruby_llm/protocols/elevenlabs/speech.rb +67 -0
  206. data/lib/ruby_llm/protocols/elevenlabs/streaming_transcription.rb +127 -0
  207. data/lib/ruby_llm/protocols/elevenlabs/transcription.rb +61 -0
  208. data/lib/ruby_llm/protocols/elevenlabs.rb +15 -0
  209. data/lib/ruby_llm/protocols/files.rb +119 -0
  210. data/lib/ruby_llm/protocols/gemini/batches.rb +162 -0
  211. data/lib/ruby_llm/protocols/gemini/caches.rb +59 -0
  212. data/lib/ruby_llm/protocols/gemini/chat.rb +453 -0
  213. data/lib/ruby_llm/protocols/gemini/embedding_batches.rb +86 -0
  214. data/lib/ruby_llm/protocols/gemini/embeddings.rb +70 -0
  215. data/lib/ruby_llm/protocols/gemini/file_transcription.rb +32 -0
  216. data/lib/ruby_llm/protocols/gemini/files.rb +115 -0
  217. data/lib/ruby_llm/protocols/gemini/images.rb +183 -0
  218. data/lib/ruby_llm/protocols/gemini/live_transcription.rb +140 -0
  219. data/lib/ruby_llm/{providers → protocols}/gemini/media.rb +33 -20
  220. data/lib/ruby_llm/protocols/gemini/models.rb +71 -0
  221. data/lib/ruby_llm/protocols/gemini/speech.rb +56 -0
  222. data/lib/ruby_llm/protocols/gemini/streaming.rb +96 -0
  223. data/lib/ruby_llm/protocols/gemini/tools.rb +157 -0
  224. data/lib/ruby_llm/{providers → protocols}/gemini/transcription.rb +22 -22
  225. data/lib/ruby_llm/protocols/gemini/videos.rb +103 -0
  226. data/lib/ruby_llm/protocols/gemini.rb +35 -0
  227. data/lib/ruby_llm/protocols/gpustack/responses.rb +111 -0
  228. data/lib/ruby_llm/protocols/gpustack/tokenization.rb +22 -0
  229. data/lib/ruby_llm/protocols/gpustack/videos.rb +96 -0
  230. data/lib/ruby_llm/protocols/interactions/chat.rb +145 -0
  231. data/lib/ruby_llm/protocols/interactions/content.rb +90 -0
  232. data/lib/ruby_llm/protocols/interactions/streaming.rb +91 -0
  233. data/lib/ruby_llm/protocols/interactions/tools.rb +51 -0
  234. data/lib/ruby_llm/protocols/interactions/transcription.rb +58 -0
  235. data/lib/ruby_llm/protocols/interactions.rb +29 -0
  236. data/lib/ruby_llm/protocols/invoke_model/cohere_embeddings.rb +51 -0
  237. data/lib/ruby_llm/protocols/invoke_model/embedding_batches.rb +111 -0
  238. data/lib/ruby_llm/protocols/invoke_model/nova_embeddings.rb +50 -0
  239. data/lib/ruby_llm/protocols/invoke_model/stability_images.rb +103 -0
  240. data/lib/ruby_llm/protocols/invoke_model/titan_multimodal_embeddings.rb +33 -0
  241. data/lib/ruby_llm/protocols/invoke_model/titan_text_embeddings.rb +44 -0
  242. data/lib/ruby_llm/protocols/invoke_model.rb +57 -0
  243. data/lib/ruby_llm/protocols/mistral/content.rb +49 -0
  244. data/lib/ruby_llm/protocols/mistral/conversations/chat.rb +160 -0
  245. data/lib/ruby_llm/protocols/mistral/conversations/images.rb +43 -0
  246. data/lib/ruby_llm/protocols/mistral/conversations/streaming.rb +83 -0
  247. data/lib/ruby_llm/protocols/mistral/conversations.rb +30 -0
  248. data/lib/ruby_llm/protocols/mistral/files.rb +36 -0
  249. data/lib/ruby_llm/protocols/mistral/multi_completion.rb +160 -0
  250. data/lib/ruby_llm/protocols/openai/batches.rb +126 -0
  251. data/lib/ruby_llm/protocols/openai/files.rb +42 -0
  252. data/lib/ruby_llm/protocols/openrouter/batches.rb +147 -0
  253. data/lib/ruby_llm/protocols/openrouter/files.rb +24 -0
  254. data/lib/ruby_llm/protocols/openrouter/responses.rb +53 -0
  255. data/lib/ruby_llm/protocols/openrouter/transcription.rb +51 -0
  256. data/lib/ruby_llm/protocols/perplexity/files.rb +48 -0
  257. data/lib/ruby_llm/protocols/perplexity/router.rb +59 -0
  258. data/lib/ruby_llm/protocols/responses/approvals.rb +32 -0
  259. data/lib/ruby_llm/protocols/responses/batches.rb +32 -0
  260. data/lib/ruby_llm/protocols/responses/chat.rb +476 -0
  261. data/lib/ruby_llm/protocols/responses/compaction.rb +29 -0
  262. data/lib/ruby_llm/protocols/responses/media.rb +61 -0
  263. data/lib/ruby_llm/protocols/responses/streaming.rb +117 -0
  264. data/lib/ruby_llm/protocols/responses/token_counting.rb +26 -0
  265. data/lib/ruby_llm/protocols/responses/tools.rb +39 -0
  266. data/lib/ruby_llm/protocols/responses.rb +35 -0
  267. data/lib/ruby_llm/protocols/vertexai/batch_prediction.rb +155 -0
  268. data/lib/ruby_llm/protocols/vertexai/embedding_prediction/requests.rb +85 -0
  269. data/lib/ruby_llm/protocols/vertexai/embedding_prediction/results.rb +74 -0
  270. data/lib/ruby_llm/protocols/vertexai/embedding_prediction.rb +56 -0
  271. data/lib/ruby_llm/protocols/vertexai/files.rb +101 -0
  272. data/lib/ruby_llm/protocols/vertexai/ranking.rb +69 -0
  273. data/lib/ruby_llm/protocols/vertexai/research.rb +193 -0
  274. data/lib/ruby_llm/protocols/xai/files.rb +30 -0
  275. data/lib/ruby_llm/protocols/xai/streaming_transcription.rb +120 -0
  276. data/lib/ruby_llm/protocols/xai/tokenization.rb +23 -0
  277. data/lib/ruby_llm/provider.rb +565 -124
  278. data/lib/ruby_llm/providers/anthropic/capabilities.rb +5 -7
  279. data/lib/ruby_llm/providers/anthropic.rb +4 -6
  280. data/lib/ruby_llm/providers/azure/audio.rb +18 -0
  281. data/lib/ruby_llm/providers/azure/capabilities.rb +16 -0
  282. data/lib/ruby_llm/providers/azure/chat.rb +2 -9
  283. data/lib/ruby_llm/providers/azure/chat_completions/batches.rb +29 -0
  284. data/lib/ruby_llm/providers/azure/chat_completions.rb +80 -0
  285. data/lib/ruby_llm/providers/azure/cohere.rb +33 -0
  286. data/lib/ruby_llm/providers/azure/embeddings.rb +3 -2
  287. data/lib/ruby_llm/providers/azure/images.rb +22 -0
  288. data/lib/ruby_llm/providers/azure/media.rb +6 -15
  289. data/lib/ruby_llm/providers/azure/models.rb +35 -0
  290. data/lib/ruby_llm/providers/azure/responses.rb +26 -0
  291. data/lib/ruby_llm/providers/azure/videos.rb +64 -0
  292. data/lib/ruby_llm/providers/azure.rb +77 -78
  293. data/lib/ruby_llm/providers/bedrock/auth.rb +61 -41
  294. data/lib/ruby_llm/providers/bedrock/capabilities.rb +18 -0
  295. data/lib/ruby_llm/providers/bedrock/mantle/anthropic.rb +39 -0
  296. data/lib/ruby_llm/providers/bedrock/mantle/chat_completions.rb +23 -0
  297. data/lib/ruby_llm/providers/bedrock/mantle/responses.rb +24 -0
  298. data/lib/ruby_llm/providers/bedrock/mantle/voxtral.rb +96 -0
  299. data/lib/ruby_llm/providers/bedrock/mantle.rb +57 -0
  300. data/lib/ruby_llm/providers/bedrock/models.rb +194 -42
  301. data/lib/ruby_llm/providers/bedrock.rb +217 -46
  302. data/lib/ruby_llm/providers/cohere.rb +31 -0
  303. data/lib/ruby_llm/providers/deepgram.rb +37 -0
  304. data/lib/ruby_llm/providers/deepseek/capabilities.rb +4 -8
  305. data/lib/ruby_llm/providers/deepseek/chat.rb +56 -0
  306. data/lib/ruby_llm/providers/deepseek/responses.rb +68 -0
  307. data/lib/ruby_llm/providers/deepseek.rb +9 -2
  308. data/lib/ruby_llm/providers/elevenlabs.rb +35 -0
  309. data/lib/ruby_llm/providers/gemini/capabilities.rb +8 -107
  310. data/lib/ruby_llm/providers/gemini.rb +15 -8
  311. data/lib/ruby_llm/providers/gpustack/chat.rb +2 -9
  312. data/lib/ruby_llm/providers/gpustack/embeddings.rb +28 -0
  313. data/lib/ruby_llm/providers/gpustack/media.rb +17 -16
  314. data/lib/ruby_llm/providers/gpustack/models.rb +72 -60
  315. data/lib/ruby_llm/providers/gpustack/speech.rb +15 -0
  316. data/lib/ruby_llm/providers/gpustack/transcription.rb +29 -0
  317. data/lib/ruby_llm/providers/gpustack.rb +35 -11
  318. data/lib/ruby_llm/providers/mistral/capabilities.rb +7 -155
  319. data/lib/ruby_llm/providers/mistral/chat.rb +37 -61
  320. data/lib/ruby_llm/providers/mistral/chat_completions/batches.rb +120 -0
  321. data/lib/ruby_llm/providers/mistral/chat_completions.rb +21 -0
  322. data/lib/ruby_llm/providers/mistral/conversations.rb +12 -0
  323. data/lib/ruby_llm/providers/mistral/embeddings.rb +6 -4
  324. data/lib/ruby_llm/providers/mistral/media.rb +43 -0
  325. data/lib/ruby_llm/providers/mistral/models.rb +57 -21
  326. data/lib/ruby_llm/providers/mistral/ocr.rb +47 -0
  327. data/lib/ruby_llm/providers/mistral/speech.rb +51 -0
  328. data/lib/ruby_llm/providers/mistral/transcription.rb +62 -0
  329. data/lib/ruby_llm/providers/mistral.rb +18 -6
  330. data/lib/ruby_llm/providers/ollama/chat.rb +9 -8
  331. data/lib/ruby_llm/providers/ollama/media.rb +6 -15
  332. data/lib/ruby_llm/providers/ollama/models.rb +50 -9
  333. data/lib/ruby_llm/providers/ollama.rb +9 -8
  334. data/lib/ruby_llm/providers/ollama_cloud/models.rb +14 -0
  335. data/lib/ruby_llm/providers/ollama_cloud.rb +40 -0
  336. data/lib/ruby_llm/providers/openai/capabilities.rb +54 -259
  337. data/lib/ruby_llm/providers/openai/models.rb +23 -23
  338. data/lib/ruby_llm/providers/openai/responses.rb +13 -0
  339. data/lib/ruby_llm/providers/openai.rb +92 -11
  340. data/lib/ruby_llm/providers/openrouter/chat.rb +130 -104
  341. data/lib/ruby_llm/providers/openrouter/embeddings.rb +51 -0
  342. data/lib/ruby_llm/providers/openrouter/images.rb +44 -43
  343. data/lib/ruby_llm/providers/openrouter/media.rb +34 -0
  344. data/lib/ruby_llm/providers/openrouter/models.rb +50 -11
  345. data/lib/ruby_llm/providers/openrouter/speech.rb +32 -0
  346. data/lib/ruby_llm/providers/openrouter/streaming.rb +31 -38
  347. data/lib/ruby_llm/providers/openrouter/videos.rb +81 -0
  348. data/lib/ruby_llm/providers/openrouter.rb +78 -20
  349. data/lib/ruby_llm/providers/perplexity/chat.rb +4 -0
  350. data/lib/ruby_llm/providers/perplexity/embeddings.rb +32 -0
  351. data/lib/ruby_llm/providers/perplexity/media.rb +46 -0
  352. data/lib/ruby_llm/providers/perplexity/models.rb +80 -13
  353. data/lib/ruby_llm/providers/perplexity.rb +29 -21
  354. data/lib/ruby_llm/providers/vertexai/anthropic/batches.rb +52 -0
  355. data/lib/ruby_llm/providers/vertexai/anthropic.rb +34 -0
  356. data/lib/ruby_llm/providers/vertexai/capabilities.rb +19 -0
  357. data/lib/ruby_llm/providers/vertexai/chat_completions/batches.rb +54 -0
  358. data/lib/ruby_llm/providers/vertexai/chat_completions.rb +15 -0
  359. data/lib/ruby_llm/providers/vertexai/embed_content.rb +42 -0
  360. data/lib/ruby_llm/providers/vertexai/embeddings.rb +22 -7
  361. data/lib/ruby_llm/providers/vertexai/gemini/batches.rb +42 -0
  362. data/lib/ruby_llm/providers/vertexai/gemini.rb +69 -0
  363. data/lib/ruby_llm/providers/vertexai/live_transcription.rb +24 -0
  364. data/lib/ruby_llm/providers/vertexai/mistral.rb +28 -0
  365. data/lib/ruby_llm/providers/vertexai/models.rb +145 -43
  366. data/lib/ruby_llm/providers/vertexai/transcription.rb +49 -4
  367. data/lib/ruby_llm/providers/vertexai/videos.rb +61 -0
  368. data/lib/ruby_llm/providers/vertexai.rb +164 -17
  369. data/lib/ruby_llm/providers/xai/capabilities.rb +18 -0
  370. data/lib/ruby_llm/providers/xai/chat.rb +10 -0
  371. data/lib/ruby_llm/providers/xai/chat_completions/batches.rb +108 -0
  372. data/lib/ruby_llm/providers/xai/chat_completions.rb +19 -0
  373. data/lib/ruby_llm/providers/xai/images.rb +91 -0
  374. data/lib/ruby_llm/providers/xai/models.rb +32 -48
  375. data/lib/ruby_llm/providers/xai/reported_cost.rb +18 -0
  376. data/lib/ruby_llm/providers/xai/responses.rb +52 -0
  377. data/lib/ruby_llm/providers/xai/speech.rb +45 -0
  378. data/lib/ruby_llm/providers/xai/transcription.rb +48 -0
  379. data/lib/ruby_llm/providers/xai/videos.rb +87 -0
  380. data/lib/ruby_llm/providers/xai.rb +17 -7
  381. data/lib/ruby_llm/railtie.rb +11 -16
  382. data/lib/ruby_llm/rerank.rb +105 -0
  383. data/lib/ruby_llm/research_job.rb +241 -0
  384. data/lib/ruby_llm/search_results.rb +68 -0
  385. data/lib/ruby_llm/server_tool_call.rb +73 -0
  386. data/lib/ruby_llm/speech.rb +159 -0
  387. data/lib/ruby_llm/speech_chunk.rb +33 -0
  388. data/lib/ruby_llm/support/deprecator.rb +22 -0
  389. data/lib/ruby_llm/support/inspectable.rb +49 -0
  390. data/lib/ruby_llm/support/instrumentation.rb +41 -0
  391. data/lib/ruby_llm/support/utils.rb +147 -0
  392. data/lib/ruby_llm/thinking.rb +127 -20
  393. data/lib/ruby_llm/tokenization.rb +59 -0
  394. data/lib/ruby_llm/tokens.rb +103 -33
  395. data/lib/ruby_llm/tool.rb +266 -91
  396. data/lib/ruby_llm/tool_call.rb +36 -3
  397. data/lib/ruby_llm/tools/server_tools.rb +109 -0
  398. data/lib/ruby_llm/transcription/wav_audio.rb +62 -0
  399. data/lib/ruby_llm/transcription.rb +139 -13
  400. data/lib/ruby_llm/transcription_chunk.rb +68 -0
  401. data/lib/ruby_llm/transport/connection.rb +193 -0
  402. data/lib/ruby_llm/transport/error_middleware.rb +131 -0
  403. data/lib/ruby_llm/transport/usage_middleware.rb +28 -0
  404. data/lib/ruby_llm/transport/websocket_connection.rb +220 -0
  405. data/lib/ruby_llm/uploaded_file.rb +144 -0
  406. data/lib/ruby_llm/version.rb +2 -1
  407. data/lib/ruby_llm/video.rb +136 -0
  408. data/lib/ruby_llm/video_job.rb +150 -0
  409. data/lib/ruby_llm/workflow.rb +91 -0
  410. data/lib/ruby_llm.rb +385 -4
  411. data/lib/tasks/ruby_llm.rake +21 -16
  412. data/skills/rubyllm/SKILL.md +81 -0
  413. data/skills/rubyllm/agents/openai.yaml +4 -0
  414. metadata +340 -92
  415. data/lib/generators/ruby_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt +0 -9
  416. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +0 -39
  417. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +0 -21
  418. data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +0 -3
  419. data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +0 -3
  420. data/lib/generators/ruby_llm/upgrade_to_v1_10/templates/add_v1_10_message_columns.rb.tt +0 -19
  421. data/lib/generators/ruby_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +0 -50
  422. data/lib/generators/ruby_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +0 -7
  423. data/lib/generators/ruby_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +0 -49
  424. data/lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt +0 -145
  425. data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +0 -122
  426. data/lib/generators/ruby_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +0 -15
  427. data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +0 -49
  428. data/lib/ruby_llm/active_record/acts_as_legacy.rb +0 -530
  429. data/lib/ruby_llm/active_record/model_methods.rb +0 -82
  430. data/lib/ruby_llm/active_record/tool_call_methods.rb +0 -18
  431. data/lib/ruby_llm/aliases.rb +0 -38
  432. data/lib/ruby_llm/connection.rb +0 -130
  433. data/lib/ruby_llm/content.rb +0 -77
  434. data/lib/ruby_llm/mime_type.rb +0 -71
  435. data/lib/ruby_llm/model/info.rb +0 -130
  436. data/lib/ruby_llm/models_schema.json +0 -171
  437. data/lib/ruby_llm/providers/anthropic/chat.rb +0 -257
  438. data/lib/ruby_llm/providers/anthropic/content.rb +0 -44
  439. data/lib/ruby_llm/providers/anthropic/embeddings.rb +0 -20
  440. data/lib/ruby_llm/providers/anthropic/media.rb +0 -92
  441. data/lib/ruby_llm/providers/anthropic/models.rb +0 -57
  442. data/lib/ruby_llm/providers/anthropic/streaming.rb +0 -69
  443. data/lib/ruby_llm/providers/bedrock/chat.rb +0 -403
  444. data/lib/ruby_llm/providers/bedrock/media.rb +0 -90
  445. data/lib/ruby_llm/providers/bedrock/streaming.rb +0 -322
  446. data/lib/ruby_llm/providers/gemini/chat.rb +0 -543
  447. data/lib/ruby_llm/providers/gemini/embeddings.rb +0 -37
  448. data/lib/ruby_llm/providers/gemini/images.rb +0 -47
  449. data/lib/ruby_llm/providers/gemini/models.rb +0 -38
  450. data/lib/ruby_llm/providers/gemini/streaming.rb +0 -96
  451. data/lib/ruby_llm/providers/gemini/tools.rb +0 -232
  452. data/lib/ruby_llm/providers/gpustack/capabilities.rb +0 -20
  453. data/lib/ruby_llm/providers/ollama/capabilities.rb +0 -20
  454. data/lib/ruby_llm/providers/openai/chat.rb +0 -221
  455. data/lib/ruby_llm/providers/openai/embeddings.rb +0 -33
  456. data/lib/ruby_llm/providers/openai/images.rb +0 -90
  457. data/lib/ruby_llm/providers/openai/media.rb +0 -84
  458. data/lib/ruby_llm/providers/openai/moderation.rb +0 -34
  459. data/lib/ruby_llm/providers/openai/streaming.rb +0 -53
  460. data/lib/ruby_llm/providers/openai/temperature.rb +0 -28
  461. data/lib/ruby_llm/providers/openai/transcription.rb +0 -70
  462. data/lib/ruby_llm/providers/perplexity/capabilities.rb +0 -72
  463. data/lib/ruby_llm/providers/vertexai/chat.rb +0 -14
  464. data/lib/ruby_llm/providers/vertexai/streaming.rb +0 -14
  465. data/lib/ruby_llm/stream_accumulator.rb +0 -203
  466. data/lib/ruby_llm/streaming.rb +0 -175
  467. data/lib/ruby_llm/utils.rb +0 -91
  468. data/lib/tasks/models.rake +0 -565
  469. data/lib/tasks/release.rake +0 -67
  470. data/lib/tasks/vcr.rake +0 -124
@@ -1,530 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_support/concern'
4
- require 'active_support/inflector'
5
-
6
- module RubyLLM
7
- module ActiveRecord
8
- # Adds chat and message persistence capabilities to ActiveRecord models.
9
- module ActsAsLegacy
10
- extend ActiveSupport::Concern
11
-
12
- class_methods do # rubocop:disable Metrics/BlockLength
13
- def acts_as_chat(message_class: 'Message', tool_call_class: 'ToolCall')
14
- include ChatLegacyMethods
15
-
16
- @message_class = message_class.to_s
17
- @tool_call_class = tool_call_class.to_s
18
-
19
- has_many :messages,
20
- -> { order(created_at: :asc) },
21
- class_name: @message_class,
22
- inverse_of: :chat,
23
- dependent: :destroy
24
- end
25
-
26
- def acts_as_message(chat_class: 'Chat',
27
- chat_foreign_key: nil,
28
- tool_call_class: 'ToolCall',
29
- tool_call_foreign_key: nil,
30
- touch_chat: false)
31
- include MessageLegacyMethods
32
-
33
- @chat_class = chat_class.to_s
34
- @chat_foreign_key = chat_foreign_key || ActiveSupport::Inflector.foreign_key(@chat_class)
35
-
36
- @tool_call_class = tool_call_class.to_s
37
- @tool_call_foreign_key = tool_call_foreign_key || ActiveSupport::Inflector.foreign_key(@tool_call_class)
38
-
39
- belongs_to :chat,
40
- class_name: @chat_class,
41
- foreign_key: @chat_foreign_key,
42
- inverse_of: :messages,
43
- touch: touch_chat
44
-
45
- has_many :tool_calls,
46
- class_name: @tool_call_class,
47
- dependent: :destroy
48
-
49
- belongs_to :parent_tool_call,
50
- class_name: @tool_call_class,
51
- foreign_key: @tool_call_foreign_key,
52
- optional: true,
53
- inverse_of: :result
54
-
55
- has_many :tool_results,
56
- through: :tool_calls,
57
- source: :result,
58
- class_name: @message_class
59
-
60
- delegate :tool_call?, :tool_result?, to: :to_llm
61
- end
62
-
63
- def acts_as_tool_call(message_class: 'Message', message_foreign_key: nil, result_foreign_key: nil)
64
- include RubyLLM::ActiveRecord::ToolCallMethods
65
-
66
- @message_class = message_class.to_s
67
- @message_foreign_key = message_foreign_key || ActiveSupport::Inflector.foreign_key(@message_class)
68
- @result_foreign_key = result_foreign_key || ActiveSupport::Inflector.foreign_key(name)
69
-
70
- belongs_to :message,
71
- class_name: @message_class,
72
- foreign_key: @message_foreign_key,
73
- inverse_of: :tool_calls
74
-
75
- has_one :result,
76
- class_name: @message_class,
77
- foreign_key: @result_foreign_key,
78
- inverse_of: :parent_tool_call,
79
- dependent: :nullify
80
- end
81
- end
82
- end
83
-
84
- # Methods mixed into chat models.
85
- module ChatLegacyMethods
86
- extend ActiveSupport::Concern
87
-
88
- class_methods do
89
- attr_reader :tool_call_class
90
- end
91
-
92
- def to_llm(context: nil)
93
- # model_id is a string that RubyLLM can resolve
94
- @chat ||= if context
95
- context.chat(model: model_id)
96
- else
97
- RubyLLM.chat(model: model_id)
98
- end
99
- @chat.reset_messages!
100
-
101
- ordered_messages = order_messages_for_llm(messages.to_a)
102
- ordered_messages.each do |msg|
103
- @chat.add_message(msg.to_llm)
104
- end
105
- reapply_runtime_instructions(@chat)
106
-
107
- setup_persistence_callbacks
108
- end
109
-
110
- def with_instructions(instructions, append: false, replace: nil)
111
- append = append_instructions?(append:, replace:)
112
- persist_system_instruction(instructions, append:)
113
-
114
- to_llm.with_instructions(instructions, append:, replace:)
115
- self
116
- end
117
-
118
- def with_runtime_instructions(instructions, append: false, replace: nil)
119
- append = append_instructions?(append:, replace:)
120
- store_runtime_instruction(instructions, append:)
121
-
122
- to_llm.with_instructions(instructions, append:, replace:)
123
- self
124
- end
125
-
126
- def with_tool(...)
127
- to_llm.with_tool(...)
128
- self
129
- end
130
-
131
- def with_tools(...)
132
- to_llm.with_tools(...)
133
- self
134
- end
135
-
136
- def with_model(...)
137
- update(model_id: to_llm.with_model(...).model.id)
138
- self
139
- end
140
-
141
- def with_temperature(...)
142
- to_llm.with_temperature(...)
143
- self
144
- end
145
-
146
- def with_context(context)
147
- to_llm(context: context)
148
- self
149
- end
150
-
151
- def with_params(...)
152
- to_llm.with_params(...)
153
- self
154
- end
155
-
156
- def with_headers(...)
157
- to_llm.with_headers(...)
158
- self
159
- end
160
-
161
- def with_schema(...)
162
- to_llm.with_schema(...)
163
- self
164
- end
165
-
166
- def on_new_message(&)
167
- to_llm.on_new_message(&)
168
- self
169
- end
170
-
171
- def on_end_message(&)
172
- to_llm.on_end_message(&)
173
- self
174
- end
175
-
176
- def before_message(...)
177
- to_llm.before_message(...)
178
- self
179
- end
180
-
181
- def after_message(...)
182
- to_llm.after_message(...)
183
- self
184
- end
185
-
186
- def before_tool_call(...)
187
- to_llm.before_tool_call(...)
188
- self
189
- end
190
-
191
- def after_tool_result(...)
192
- to_llm.after_tool_result(...)
193
- self
194
- end
195
-
196
- def on_tool_call(...)
197
- to_llm.on_tool_call(...)
198
- self
199
- end
200
-
201
- def on_tool_result(...)
202
- to_llm.on_tool_result(...)
203
- self
204
- end
205
-
206
- def add_message(message_or_attributes)
207
- llm_message = message_or_attributes.is_a?(RubyLLM::Message) ? message_or_attributes : RubyLLM::Message.new(message_or_attributes)
208
- content, attachments_to_persist = prepare_content_for_storage(llm_message.content)
209
-
210
- attrs = { role: llm_message.role, content: }
211
- tool_call_foreign_key = messages.klass.tool_call_foreign_key
212
- if llm_message.tool_call_id && tool_call_foreign_key
213
- tool_call_id = find_tool_call_id(llm_message.tool_call_id)
214
- attrs[tool_call_foreign_key] = tool_call_id if tool_call_id
215
- end
216
-
217
- message_record = messages.create!(attrs)
218
- persist_content(message_record, attachments_to_persist) if attachments_to_persist.present?
219
- persist_tool_calls(llm_message.tool_calls, message_record:) if llm_message.tool_calls.present?
220
-
221
- message_record
222
- end
223
-
224
- def create_user_message(content, with: nil)
225
- RubyLLM.logger.warn(
226
- '`create_user_message` is deprecated and will be removed in RubyLLM 2.0. ' \
227
- 'Use `add_message(role: :user, content: ...)` instead.'
228
- )
229
- add_message(role: :user, content: build_content(content, with))
230
- end
231
-
232
- def ask(message = nil, with: nil, &)
233
- add_message(role: :user, content: build_content(message, with))
234
- complete(&)
235
- end
236
-
237
- alias say ask
238
-
239
- def complete(...)
240
- to_llm.complete(...)
241
- rescue RubyLLM::Error => e
242
- cleanup_failed_messages if @message&.persisted? && @message.content.blank?
243
- cleanup_orphaned_tool_results
244
- raise e
245
- end
246
-
247
- private
248
-
249
- def cleanup_failed_messages
250
- RubyLLM.logger.warn "RubyLLM: API call failed, destroying message: #{@message.id}"
251
- @message.destroy
252
- end
253
-
254
- def cleanup_orphaned_tool_results # rubocop:disable Metrics/PerceivedComplexity
255
- messages.reload
256
- last = messages.order(:id).last
257
-
258
- return unless last&.tool_call? || last&.tool_result?
259
-
260
- if last.tool_call?
261
- last.destroy
262
- elsif last.tool_result?
263
- tool_call_message = last.parent_tool_call.message
264
- expected_results = tool_call_message.tool_calls.pluck(:id)
265
- actual_results = tool_call_message.tool_results.pluck(:tool_call_id)
266
-
267
- if expected_results.sort != actual_results.sort
268
- tool_call_message.tool_results.each(&:destroy)
269
- tool_call_message.destroy
270
- end
271
- end
272
- end
273
-
274
- def replace_persisted_system_instructions(instructions)
275
- system_messages = messages.where(role: :system).order(:id).to_a
276
-
277
- if system_messages.empty?
278
- messages.create!(role: :system, content: instructions)
279
- return
280
- end
281
-
282
- primary_message = system_messages.shift
283
- primary_message.update!(content: instructions) if primary_message.content != instructions
284
- system_messages.each(&:destroy!)
285
- end
286
-
287
- def append_instructions?(append:, replace:)
288
- return append if replace.nil?
289
-
290
- append || (replace == false)
291
- end
292
-
293
- def persist_system_instruction(instructions, append:)
294
- transaction do
295
- if append
296
- messages.create!(role: :system, content: instructions)
297
- else
298
- replace_persisted_system_instructions(instructions)
299
- end
300
- end
301
- end
302
-
303
- def order_messages_for_llm(messages)
304
- system_messages, non_system_messages = messages.partition { |msg| msg.role.to_s == 'system' }
305
- system_messages + non_system_messages
306
- end
307
-
308
- def runtime_instructions
309
- @runtime_instructions ||= []
310
- end
311
-
312
- def store_runtime_instruction(instructions, append:)
313
- if append
314
- runtime_instructions << instructions
315
- else
316
- @runtime_instructions = [instructions]
317
- end
318
- end
319
-
320
- def reapply_runtime_instructions(chat)
321
- return if runtime_instructions.empty?
322
-
323
- first, *rest = runtime_instructions
324
- chat.with_instructions(first)
325
- rest.each { |instruction| chat.with_instructions(instruction, append: true) }
326
- end
327
-
328
- def setup_persistence_callbacks
329
- return @chat if @chat.instance_variable_get(:@_persistence_callbacks_setup)
330
-
331
- @chat.before_message { persist_new_message }
332
- @chat.after_message { |msg| persist_message_completion(msg) }
333
-
334
- @chat.instance_variable_set(:@_persistence_callbacks_setup, true)
335
- @chat
336
- end
337
-
338
- def persist_new_message
339
- @message = messages.create!(role: :assistant, content: '')
340
- end
341
-
342
- def persist_message_completion(message)
343
- return unless message
344
-
345
- tool_call_id = find_tool_call_id(message.tool_call_id) if message.tool_call_id
346
-
347
- transaction do
348
- content, attachments_to_persist = prepare_content_for_storage(message.content)
349
-
350
- @message.update!(
351
- role: message.role,
352
- content: content,
353
- model_id: message.model_id,
354
- input_tokens: message.input_tokens,
355
- output_tokens: message.output_tokens
356
- )
357
- @message.write_attribute(@message.class.tool_call_foreign_key, tool_call_id) if tool_call_id
358
- @message.save!
359
-
360
- persist_content(@message, attachments_to_persist) if attachments_to_persist
361
- persist_tool_calls(message.tool_calls) if message.tool_calls.present?
362
- end
363
- end
364
-
365
- def persist_tool_calls(tool_calls, message_record: @message)
366
- tool_call_assoc = message_record.respond_to?(:tool_calls) ? message_record.tool_calls : nil
367
- return unless tool_call_assoc
368
-
369
- supports_thought_signature = tool_call_assoc.klass.column_names.include?('thought_signature')
370
-
371
- tool_calls.each_value do |tool_call|
372
- attributes = tool_call.to_h
373
- attributes.delete(:thought_signature) unless supports_thought_signature
374
- attributes[:tool_call_id] = attributes.delete(:id)
375
- tool_call_assoc.create!(**attributes)
376
- end
377
- end
378
-
379
- def find_tool_call_id(tool_call_id)
380
- self.class.tool_call_class.constantize.find_by(tool_call_id: tool_call_id)&.id
381
- end
382
-
383
- def persist_content(message_record, attachments)
384
- return unless message_record.respond_to?(:attachments)
385
-
386
- attachables = prepare_for_active_storage(attachments)
387
- message_record.attachments.attach(attachables) if attachables.any?
388
- end
389
-
390
- def prepare_for_active_storage(attachments)
391
- Utils.to_safe_array(attachments).filter_map do |attachment|
392
- case attachment
393
- when ActionDispatch::Http::UploadedFile, ActiveStorage::Blob
394
- attachment
395
- when ActiveStorage::Attachment, ActiveStorage::Attached::One, ActiveStorage::Attached::Many
396
- active_storage_blobs(attachment)
397
- when Hash
398
- attachment.values.map { |v| prepare_for_active_storage(v) }
399
- else
400
- convert_to_active_storage_format(attachment)
401
- end
402
- end.flatten.compact
403
- end
404
-
405
- def convert_to_active_storage_format(source)
406
- return if source.blank?
407
-
408
- attachment = source.is_a?(RubyLLM::Attachment) ? source : RubyLLM::Attachment.new(source)
409
-
410
- if attachment.active_storage?
411
- active_storage_blobs(attachment.source)
412
- else
413
- {
414
- io: StringIO.new(attachment.content),
415
- filename: attachment.filename,
416
- content_type: attachment.mime_type
417
- }
418
- end
419
- rescue StandardError => e
420
- RubyLLM.logger.warn "Failed to process attachment #{source}: #{e.message}"
421
- nil
422
- end
423
-
424
- def active_storage_blobs(attachment)
425
- case attachment
426
- when ActiveStorage::Blob then attachment
427
- when ActiveStorage::Attachment, ActiveStorage::Attached::One then attachment.blob
428
- when ActiveStorage::Attached::Many then attachment.blobs
429
- end
430
- end
431
-
432
- def build_content(message, attachments)
433
- return message if content_like?(message)
434
-
435
- RubyLLM::Content.new(message, attachments)
436
- end
437
-
438
- def content_like?(object)
439
- object.is_a?(RubyLLM::Content) || object.is_a?(RubyLLM::Content::Raw)
440
- end
441
-
442
- def prepare_content_for_storage(content)
443
- attachments = nil
444
-
445
- if content.is_a?(RubyLLM::Content)
446
- attachments = content.attachments if content.attachments.any?
447
- [content.text, attachments]
448
- elsif content.is_a?(Hash) || content.is_a?(Array)
449
- [content.to_json, attachments]
450
- else
451
- [content, attachments]
452
- end
453
- end
454
- end
455
-
456
- # Methods mixed into message models.
457
- module MessageLegacyMethods
458
- extend ActiveSupport::Concern
459
-
460
- class_methods do
461
- attr_reader :chat_class, :tool_call_class, :chat_foreign_key, :tool_call_foreign_key
462
- end
463
-
464
- def to_llm
465
- RubyLLM::Message.new(
466
- role: role.to_sym,
467
- content: extract_content,
468
- tool_calls: extract_tool_calls,
469
- tool_call_id: extract_tool_call_id,
470
- input_tokens: input_tokens,
471
- output_tokens: output_tokens,
472
- model_id: model_id
473
- )
474
- end
475
-
476
- private
477
-
478
- def extract_tool_calls
479
- tool_calls.to_h do |tool_call|
480
- [
481
- tool_call.tool_call_id,
482
- RubyLLM::ToolCall.new(
483
- id: tool_call.tool_call_id,
484
- name: tool_call.name,
485
- arguments: tool_call.arguments,
486
- thought_signature: tool_call.try(:thought_signature)
487
- )
488
- ]
489
- end
490
- end
491
-
492
- def extract_tool_call_id
493
- parent_tool_call&.tool_call_id
494
- end
495
-
496
- def extract_content
497
- text_content = if content.respond_to?(:to_plain_text)
498
- content.to_plain_text
499
- else
500
- content
501
- end
502
-
503
- return text_content unless respond_to?(:attachments) && attachments.attached?
504
-
505
- RubyLLM::Content.new(text_content).tap do |content_obj|
506
- @_tempfiles = []
507
-
508
- attachments.each do |attachment|
509
- tempfile = download_attachment(attachment)
510
- content_obj.add_attachment(tempfile, filename: attachment.filename.to_s)
511
- end
512
- end
513
- end
514
-
515
- def download_attachment(attachment)
516
- ext = File.extname(attachment.filename.to_s)
517
- basename = File.basename(attachment.filename.to_s, ext)
518
- tempfile = Tempfile.new([basename, ext])
519
- tempfile.binmode
520
-
521
- attachment.download { |chunk| tempfile.write(chunk) }
522
-
523
- tempfile.flush
524
- tempfile.rewind
525
- @_tempfiles << tempfile
526
- tempfile
527
- end
528
- end
529
- end
530
- end
@@ -1,82 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_support/concern'
4
- require 'active_support/core_ext/module/delegation'
5
-
6
- module RubyLLM
7
- module ActiveRecord
8
- # Methods mixed into model registry models.
9
- module ModelMethods
10
- extend ActiveSupport::Concern
11
-
12
- class_methods do # rubocop:disable Metrics/BlockLength
13
- def refresh!
14
- RubyLLM.models.refresh!
15
-
16
- save_to_database
17
- end
18
-
19
- def save_to_database
20
- transaction do
21
- RubyLLM.models.all.each do |model_info|
22
- model = find_or_initialize_by(
23
- model_id: model_info.id,
24
- provider: model_info.provider
25
- )
26
- model.update!(from_llm_attributes(model_info))
27
- end
28
- end
29
- end
30
-
31
- def from_llm(model_info)
32
- new(from_llm_attributes(model_info))
33
- end
34
-
35
- private
36
-
37
- def from_llm_attributes(model_info)
38
- {
39
- model_id: model_info.id,
40
- name: model_info.name,
41
- provider: model_info.provider,
42
- family: model_info.family,
43
- model_created_at: model_info.created_at,
44
- context_window: model_info.context_window,
45
- max_output_tokens: model_info.max_output_tokens,
46
- knowledge_cutoff: model_info.knowledge_cutoff,
47
- modalities: model_info.modalities.to_h,
48
- capabilities: model_info.capabilities,
49
- pricing: model_info.pricing.to_h,
50
- metadata: model_info.metadata
51
- }
52
- end
53
- end
54
-
55
- def to_llm
56
- RubyLLM::Model::Info.new(
57
- id: model_id,
58
- name: name,
59
- provider: provider,
60
- family: family,
61
- created_at: model_created_at,
62
- context_window: context_window,
63
- max_output_tokens: max_output_tokens,
64
- knowledge_cutoff: knowledge_cutoff,
65
- modalities: modalities&.deep_symbolize_keys || {},
66
- capabilities: capabilities,
67
- pricing: pricing&.deep_symbolize_keys || {},
68
- metadata: metadata&.deep_symbolize_keys || {}
69
- )
70
- end
71
-
72
- delegate :supports?, :supports_vision?, :supports_functions?, :type,
73
- :input_price_per_million, :output_price_per_million,
74
- :cache_read_input_price_per_million, :cache_write_input_price_per_million,
75
- :cached_input_price_per_million, :cache_creation_input_price_per_million,
76
- :function_calling?, :structured_output?, :batch?,
77
- :reasoning?, :citations?, :streaming?, :provider_class, :label,
78
- :cost_for,
79
- to: :to_llm
80
- end
81
- end
82
- end
@@ -1,18 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'active_support/concern'
4
- require 'ruby_llm/active_record/payload_helpers'
5
-
6
- module RubyLLM
7
- module ActiveRecord
8
- # Methods mixed into tool call models.
9
- module ToolCallMethods
10
- extend ActiveSupport::Concern
11
- include PayloadHelpers
12
-
13
- def tool_error_message
14
- payload_error_message(arguments)
15
- end
16
- end
17
- end
18
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- # Manages model aliases for provider-specific versions
5
- class Aliases
6
- class << self
7
- def resolve(model_id, provider = nil)
8
- return model_id unless aliases[model_id]
9
-
10
- if provider
11
- aliases[model_id][provider.to_s] || model_id
12
- else
13
- aliases[model_id].values.first || model_id
14
- end
15
- end
16
-
17
- def aliases
18
- @aliases ||= load_aliases
19
- end
20
-
21
- def aliases_file
22
- File.expand_path('aliases.json', __dir__)
23
- end
24
-
25
- def load_aliases
26
- if File.exist?(aliases_file)
27
- JSON.parse(File.read(aliases_file))
28
- else
29
- {}
30
- end
31
- end
32
-
33
- def reload!
34
- @aliases = load_aliases
35
- end
36
- end
37
- end
38
- end