ruby_llm 1.16.0 → 2.0.0.rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (475) hide show
  1. checksums.yaml +4 -4
  2. data/.rdoc_options +25 -0
  3. data/README.md +85 -32
  4. data/exe/ruby_llm +8 -0
  5. data/lib/generators/ruby_llm/agent/templates/agent.rb.tt +0 -1
  6. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +3 -43
  7. data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +11 -3
  8. data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +8 -0
  9. data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +4 -4
  10. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt +1 -1
  11. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +1 -1
  12. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt +1 -1
  13. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +2 -2
  14. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +1 -1
  15. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +1 -1
  16. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool.html.erb.tt +1 -1
  17. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +6 -4
  18. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +1 -1
  19. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +2 -2
  20. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt +5 -6
  21. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt +2 -2
  22. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt +5 -5
  23. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +1 -1
  24. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +1 -1
  25. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +1 -1
  26. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +2 -2
  27. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +1 -1
  28. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_system.html.erb.tt +1 -1
  29. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool.html.erb.tt +1 -1
  30. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +6 -4
  31. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_user.html.erb.tt +1 -1
  32. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +4 -6
  33. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +2 -2
  34. data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +5 -6
  35. data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +2 -2
  36. data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +3 -3
  37. data/lib/generators/ruby_llm/generator_helpers.rb +106 -62
  38. data/lib/generators/ruby_llm/install/install_generator.rb +3 -11
  39. data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +2 -0
  40. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +14 -8
  41. data/lib/generators/ruby_llm/install/templates/create_ruby_llm_records_migration.rb.tt +117 -0
  42. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +0 -8
  43. data/lib/generators/ruby_llm/provider/cli.rb +175 -0
  44. data/lib/generators/ruby_llm/provider/scaffold.rb +323 -0
  45. data/lib/generators/ruby_llm/provider/templates/core/provider.rb.erb +37 -0
  46. data/lib/generators/ruby_llm/provider/templates/core/provider_spec.rb.erb +34 -0
  47. data/lib/generators/ruby_llm/provider/templates/gem/archspec.rb.erb +14 -0
  48. data/lib/generators/ruby_llm/provider/templates/gem/bin/console.erb +15 -0
  49. data/lib/generators/ruby_llm/provider/templates/gem/bin/setup.erb +5 -0
  50. data/lib/generators/ruby_llm/provider/templates/gem/chat_schema_spec.rb.erb +30 -0
  51. data/lib/generators/ruby_llm/provider/templates/gem/chat_spec.rb.erb +35 -0
  52. data/lib/generators/ruby_llm/provider/templates/gem/chat_streaming_spec.rb.erb +22 -0
  53. data/lib/generators/ruby_llm/provider/templates/gem/chat_tools_spec.rb.erb +30 -0
  54. data/lib/generators/ruby_llm/provider/templates/gem/ci.yml.erb +32 -0
  55. data/lib/generators/ruby_llm/provider/templates/gem/embedding_spec.rb.erb +49 -0
  56. data/lib/generators/ruby_llm/provider/templates/gem/env.erb +2 -0
  57. data/lib/generators/ruby_llm/provider/templates/gem/fixtures_gitkeep.erb +1 -0
  58. data/lib/generators/ruby_llm/provider/templates/gem/flayignore.erb +1 -0
  59. data/lib/generators/ruby_llm/provider/templates/gem/gemfile.erb +23 -0
  60. data/lib/generators/ruby_llm/provider/templates/gem/gemspec.erb +28 -0
  61. data/lib/generators/ruby_llm/provider/templates/gem/gitignore.erb +7 -0
  62. data/lib/generators/ruby_llm/provider/templates/gem/gitleaks.yml.erb +22 -0
  63. data/lib/generators/ruby_llm/provider/templates/gem/image_spec.rb.erb +23 -0
  64. data/lib/generators/ruby_llm/provider/templates/gem/license.erb +21 -0
  65. data/lib/generators/ruby_llm/provider/templates/gem/models.rb.erb +19 -0
  66. data/lib/generators/ruby_llm/provider/templates/gem/models_spec.rb.erb +17 -0
  67. data/lib/generators/ruby_llm/provider/templates/gem/moderation_spec.rb.erb +22 -0
  68. data/lib/generators/ruby_llm/provider/templates/gem/overcommit.yml.erb +31 -0
  69. data/lib/generators/ruby_llm/provider/templates/gem/provider.rb.erb +52 -0
  70. data/lib/generators/ruby_llm/provider/templates/gem/provider_spec.rb.erb +38 -0
  71. data/lib/generators/ruby_llm/provider/templates/gem/rakefile.erb +38 -0
  72. data/lib/generators/ruby_llm/provider/templates/gem/readme.md.erb +50 -0
  73. data/lib/generators/ruby_llm/provider/templates/gem/release.yml.erb +36 -0
  74. data/lib/generators/ruby_llm/provider/templates/gem/rerank_spec.rb.erb +23 -0
  75. data/lib/generators/ruby_llm/provider/templates/gem/rspec.erb +2 -0
  76. data/lib/generators/ruby_llm/provider/templates/gem/rubocop.yml.erb +29 -0
  77. data/lib/generators/ruby_llm/provider/templates/gem/rubyllm_configuration.rb.erb +14 -0
  78. data/lib/generators/ruby_llm/provider/templates/gem/spec_helper.rb.erb +26 -0
  79. data/lib/generators/ruby_llm/provider/templates/gem/speech_spec.rb.erb +25 -0
  80. data/lib/generators/ruby_llm/provider/templates/gem/vcr_configuration.rb.erb +16 -0
  81. data/lib/generators/ruby_llm/provider/templates/gem/video_spec.rb.erb +27 -0
  82. data/lib/generators/ruby_llm/schema/schema_generator.rb +5 -1
  83. data/lib/generators/ruby_llm/schema/templates/schema.rb.tt +1 -1
  84. data/lib/generators/ruby_llm/tool/templates/tailwind/tool_call.html.erb.tt +13 -0
  85. data/lib/generators/ruby_llm/tool/templates/tailwind/tool_result.html.erb.tt +21 -0
  86. data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +3 -3
  87. data/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt +7 -12
  88. data/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt +5 -2
  89. data/lib/generators/ruby_llm/tool/tool_generator.rb +25 -59
  90. data/lib/generators/ruby_llm/upgrade/templates/backfill_v2_data.rb.tt +461 -0
  91. data/lib/generators/ruby_llm/upgrade/templates/cleanup_v2_upgrade.rb.tt +101 -0
  92. data/lib/generators/ruby_llm/upgrade/templates/finish_v2_upgrade.rb.tt +215 -0
  93. data/lib/generators/ruby_llm/upgrade/templates/prepare_v2_upgrade.rb.tt +660 -0
  94. data/lib/generators/ruby_llm/upgrade/templates/ruby_llm_upgrade.rb.tt +222 -0
  95. data/lib/generators/ruby_llm/upgrade/templates/upgrade_initializer.rb.tt +13 -0
  96. data/lib/generators/ruby_llm/upgrade/upgrade_generator.rb +167 -0
  97. data/lib/generators/ruby_llm/upgrade/upgrade_migration.rb +344 -0
  98. data/lib/ruby_llm/accounting/usage.rb +245 -0
  99. data/lib/ruby_llm/active_record/acts_as.rb +94 -111
  100. data/lib/ruby_llm/active_record/attachment_helpers.rb +186 -0
  101. data/lib/ruby_llm/active_record/batch.rb +97 -0
  102. data/lib/ruby_llm/active_record/chat_methods.rb +828 -305
  103. data/lib/ruby_llm/active_record/message_methods.rb +113 -136
  104. data/lib/ruby_llm/active_record/model.rb +135 -0
  105. data/lib/ruby_llm/active_record/payload_helpers.rb +1 -2
  106. data/lib/ruby_llm/active_record/tool_call.rb +33 -0
  107. data/lib/ruby_llm/active_record/usage.rb +61 -0
  108. data/lib/ruby_llm/agent.rb +1065 -151
  109. data/lib/ruby_llm/aliases.json +268 -100
  110. data/lib/ruby_llm/attachment.rb +187 -48
  111. data/lib/ruby_llm/batch.rb +432 -0
  112. data/lib/ruby_llm/cached_content.rb +112 -0
  113. data/lib/ruby_llm/chat/tool_concurrency.rb +111 -0
  114. data/lib/ruby_llm/chat.rb +1127 -198
  115. data/lib/ruby_llm/chunk.rb +10 -0
  116. data/lib/ruby_llm/citation.rb +105 -0
  117. data/lib/ruby_llm/configuration.rb +261 -24
  118. data/lib/ruby_llm/context.rb +128 -6
  119. data/lib/ruby_llm/cost.rb +217 -80
  120. data/lib/ruby_llm/downloaded_file.rb +33 -0
  121. data/lib/ruby_llm/embedding.rb +121 -17
  122. data/lib/ruby_llm/embedding_request.rb +53 -0
  123. data/lib/ruby_llm/error.rb +159 -23
  124. data/lib/ruby_llm/fallback.rb +133 -0
  125. data/lib/ruby_llm/files/mime_type.rb +97 -0
  126. data/lib/ruby_llm/image.rb +153 -32
  127. data/lib/ruby_llm/message.rb +233 -54
  128. data/lib/ruby_llm/model/modalities.rb +17 -4
  129. data/lib/ruby_llm/model/pricing.rb +24 -5
  130. data/lib/ruby_llm/model/pricing_category.rb +103 -14
  131. data/lib/ruby_llm/model/pricing_tier.rb +55 -15
  132. data/lib/ruby_llm/model.rb +244 -2
  133. data/lib/ruby_llm/models/aliases.rb +41 -0
  134. data/lib/ruby_llm/models/registry.rb +165 -0
  135. data/lib/ruby_llm/models/schema.rb +99 -0
  136. data/lib/ruby_llm/models.json +73261 -33733
  137. data/lib/ruby_llm/models.rb +477 -215
  138. data/lib/ruby_llm/moderation.rb +139 -26
  139. data/lib/ruby_llm/ocr.rb +112 -0
  140. data/lib/ruby_llm/prompt.rb +79 -0
  141. data/lib/ruby_llm/protocol/binary_streaming.rb +65 -0
  142. data/lib/ruby_llm/protocol/stream_accumulator.rb +214 -0
  143. data/lib/ruby_llm/protocol/streaming.rb +230 -0
  144. data/lib/ruby_llm/protocol.rb +662 -0
  145. data/lib/ruby_llm/protocols/anthropic/batches.rb +73 -0
  146. data/lib/ruby_llm/protocols/anthropic/chat.rb +548 -0
  147. data/lib/ruby_llm/protocols/anthropic/embeddings.rb +14 -0
  148. data/lib/ruby_llm/protocols/anthropic/files.rb +38 -0
  149. data/lib/ruby_llm/protocols/anthropic/media.rb +141 -0
  150. data/lib/ruby_llm/protocols/anthropic/models.rb +129 -0
  151. data/lib/ruby_llm/protocols/anthropic/streaming.rb +167 -0
  152. data/lib/ruby_llm/{providers → protocols}/anthropic/tools.rb +24 -41
  153. data/lib/ruby_llm/protocols/anthropic.rb +101 -0
  154. data/lib/ruby_llm/protocols/azure/files.rb +16 -0
  155. data/lib/ruby_llm/protocols/bedrock/async_videos.rb +109 -0
  156. data/lib/ruby_llm/protocols/bedrock/batches.rb +129 -0
  157. data/lib/ruby_llm/protocols/bedrock/files.rb +110 -0
  158. data/lib/ruby_llm/protocols/bedrock/guardrails.rb +122 -0
  159. data/lib/ruby_llm/protocols/bedrock/rerank.rb +95 -0
  160. data/lib/ruby_llm/protocols/chat_completions/batches.rb +32 -0
  161. data/lib/ruby_llm/protocols/chat_completions/chat.rb +493 -0
  162. data/lib/ruby_llm/protocols/chat_completions/embedding_batches.rb +35 -0
  163. data/lib/ruby_llm/protocols/chat_completions/embeddings.rb +60 -0
  164. data/lib/ruby_llm/protocols/chat_completions/images.rb +127 -0
  165. data/lib/ruby_llm/{providers/openai → protocols/chat_completions}/media.rb +30 -17
  166. data/lib/ruby_llm/protocols/chat_completions/models.rb +39 -0
  167. data/lib/ruby_llm/protocols/chat_completions/moderation.rb +52 -0
  168. data/lib/ruby_llm/protocols/chat_completions/rerank.rb +56 -0
  169. data/lib/ruby_llm/protocols/chat_completions/speech.rb +40 -0
  170. data/lib/ruby_llm/protocols/chat_completions/streaming.rb +69 -0
  171. data/lib/ruby_llm/{providers/openai → protocols/chat_completions}/tools.rb +15 -17
  172. data/lib/ruby_llm/protocols/chat_completions/transcription.rb +150 -0
  173. data/lib/ruby_llm/protocols/chat_completions.rb +21 -0
  174. data/lib/ruby_llm/protocols/cohere/batch_requests.rb +75 -0
  175. data/lib/ruby_llm/protocols/cohere/batches.rb +98 -0
  176. data/lib/ruby_llm/protocols/cohere/chat.rb +227 -0
  177. data/lib/ruby_llm/protocols/cohere/datasets.rb +102 -0
  178. data/lib/ruby_llm/protocols/cohere/embeddings.rb +68 -0
  179. data/lib/ruby_llm/protocols/cohere/media.rb +77 -0
  180. data/lib/ruby_llm/protocols/cohere/models.rb +92 -0
  181. data/lib/ruby_llm/protocols/cohere/ocr.rb +63 -0
  182. data/lib/ruby_llm/protocols/cohere/rerank.rb +52 -0
  183. data/lib/ruby_llm/protocols/cohere/streaming.rb +105 -0
  184. data/lib/ruby_llm/protocols/cohere/tokenization.rb +22 -0
  185. data/lib/ruby_llm/protocols/cohere/tools.rb +132 -0
  186. data/lib/ruby_llm/protocols/cohere/transcription.rb +41 -0
  187. data/lib/ruby_llm/protocols/cohere.rb +21 -0
  188. data/lib/ruby_llm/protocols/converse/batches.rb +55 -0
  189. data/lib/ruby_llm/protocols/converse/chat.rb +695 -0
  190. data/lib/ruby_llm/protocols/converse/media.rb +178 -0
  191. data/lib/ruby_llm/protocols/converse/streaming.rb +432 -0
  192. data/lib/ruby_llm/protocols/converse/thinking_stream.rb +56 -0
  193. data/lib/ruby_llm/protocols/converse.rb +54 -0
  194. data/lib/ruby_llm/protocols/deepgram/models.rb +74 -0
  195. data/lib/ruby_llm/protocols/deepgram/speech.rb +93 -0
  196. data/lib/ruby_llm/protocols/deepgram/streaming_transcription.rb +89 -0
  197. data/lib/ruby_llm/protocols/deepgram/transcription.rb +96 -0
  198. data/lib/ruby_llm/protocols/deepgram.rb +19 -0
  199. data/lib/ruby_llm/protocols/deepseek/files.rb +31 -0
  200. data/lib/ruby_llm/protocols/elevenlabs/assets.rb +36 -0
  201. data/lib/ruby_llm/protocols/elevenlabs/flows/images.rb +74 -0
  202. data/lib/ruby_llm/protocols/elevenlabs/flows/media.rb +42 -0
  203. data/lib/ruby_llm/protocols/elevenlabs/flows/videos.rb +93 -0
  204. data/lib/ruby_llm/protocols/elevenlabs/flows.rb +14 -0
  205. data/lib/ruby_llm/protocols/elevenlabs/models.rb +64 -0
  206. data/lib/ruby_llm/protocols/elevenlabs/speech.rb +67 -0
  207. data/lib/ruby_llm/protocols/elevenlabs/streaming_transcription.rb +127 -0
  208. data/lib/ruby_llm/protocols/elevenlabs/transcription.rb +61 -0
  209. data/lib/ruby_llm/protocols/elevenlabs.rb +15 -0
  210. data/lib/ruby_llm/protocols/files.rb +119 -0
  211. data/lib/ruby_llm/protocols/gemini/batches.rb +162 -0
  212. data/lib/ruby_llm/protocols/gemini/caches.rb +59 -0
  213. data/lib/ruby_llm/protocols/gemini/chat.rb +453 -0
  214. data/lib/ruby_llm/protocols/gemini/embedding_batches.rb +86 -0
  215. data/lib/ruby_llm/protocols/gemini/embeddings.rb +70 -0
  216. data/lib/ruby_llm/protocols/gemini/file_transcription.rb +32 -0
  217. data/lib/ruby_llm/protocols/gemini/files.rb +115 -0
  218. data/lib/ruby_llm/protocols/gemini/images.rb +183 -0
  219. data/lib/ruby_llm/protocols/gemini/live_transcription.rb +140 -0
  220. data/lib/ruby_llm/{providers → protocols}/gemini/media.rb +17 -11
  221. data/lib/ruby_llm/protocols/gemini/models.rb +71 -0
  222. data/lib/ruby_llm/protocols/gemini/speech.rb +56 -0
  223. data/lib/ruby_llm/protocols/gemini/streaming.rb +96 -0
  224. data/lib/ruby_llm/protocols/gemini/tools.rb +157 -0
  225. data/lib/ruby_llm/{providers → protocols}/gemini/transcription.rb +22 -22
  226. data/lib/ruby_llm/protocols/gemini/videos.rb +103 -0
  227. data/lib/ruby_llm/protocols/gemini.rb +35 -0
  228. data/lib/ruby_llm/protocols/gpustack/responses.rb +111 -0
  229. data/lib/ruby_llm/protocols/gpustack/tokenization.rb +22 -0
  230. data/lib/ruby_llm/protocols/gpustack/videos.rb +96 -0
  231. data/lib/ruby_llm/protocols/interactions/chat.rb +145 -0
  232. data/lib/ruby_llm/protocols/interactions/content.rb +90 -0
  233. data/lib/ruby_llm/protocols/interactions/streaming.rb +91 -0
  234. data/lib/ruby_llm/protocols/interactions/tools.rb +51 -0
  235. data/lib/ruby_llm/protocols/interactions/transcription.rb +58 -0
  236. data/lib/ruby_llm/protocols/interactions.rb +29 -0
  237. data/lib/ruby_llm/protocols/invoke_model/cohere_embeddings.rb +51 -0
  238. data/lib/ruby_llm/protocols/invoke_model/embedding_batches.rb +111 -0
  239. data/lib/ruby_llm/protocols/invoke_model/nova_embeddings.rb +50 -0
  240. data/lib/ruby_llm/protocols/invoke_model/stability_images.rb +103 -0
  241. data/lib/ruby_llm/protocols/invoke_model/titan_multimodal_embeddings.rb +33 -0
  242. data/lib/ruby_llm/protocols/invoke_model/titan_text_embeddings.rb +44 -0
  243. data/lib/ruby_llm/protocols/invoke_model.rb +57 -0
  244. data/lib/ruby_llm/protocols/mistral/content.rb +49 -0
  245. data/lib/ruby_llm/protocols/mistral/conversations/chat.rb +160 -0
  246. data/lib/ruby_llm/protocols/mistral/conversations/images.rb +43 -0
  247. data/lib/ruby_llm/protocols/mistral/conversations/streaming.rb +83 -0
  248. data/lib/ruby_llm/protocols/mistral/conversations.rb +30 -0
  249. data/lib/ruby_llm/protocols/mistral/files.rb +36 -0
  250. data/lib/ruby_llm/protocols/mistral/multi_completion.rb +160 -0
  251. data/lib/ruby_llm/protocols/openai/batches.rb +126 -0
  252. data/lib/ruby_llm/protocols/openai/files.rb +42 -0
  253. data/lib/ruby_llm/protocols/openrouter/batches.rb +147 -0
  254. data/lib/ruby_llm/protocols/openrouter/files.rb +24 -0
  255. data/lib/ruby_llm/protocols/openrouter/responses.rb +53 -0
  256. data/lib/ruby_llm/protocols/openrouter/transcription.rb +51 -0
  257. data/lib/ruby_llm/protocols/perplexity/files.rb +48 -0
  258. data/lib/ruby_llm/protocols/perplexity/router.rb +59 -0
  259. data/lib/ruby_llm/protocols/responses/approvals.rb +32 -0
  260. data/lib/ruby_llm/protocols/responses/batches.rb +32 -0
  261. data/lib/ruby_llm/protocols/responses/chat.rb +476 -0
  262. data/lib/ruby_llm/protocols/responses/compaction.rb +29 -0
  263. data/lib/ruby_llm/protocols/responses/media.rb +61 -0
  264. data/lib/ruby_llm/protocols/responses/streaming.rb +117 -0
  265. data/lib/ruby_llm/protocols/responses/token_counting.rb +26 -0
  266. data/lib/ruby_llm/protocols/responses/tools.rb +39 -0
  267. data/lib/ruby_llm/protocols/responses.rb +35 -0
  268. data/lib/ruby_llm/protocols/vertexai/batch_prediction.rb +155 -0
  269. data/lib/ruby_llm/protocols/vertexai/embedding_prediction/requests.rb +85 -0
  270. data/lib/ruby_llm/protocols/vertexai/embedding_prediction/results.rb +74 -0
  271. data/lib/ruby_llm/protocols/vertexai/embedding_prediction.rb +56 -0
  272. data/lib/ruby_llm/protocols/vertexai/files.rb +101 -0
  273. data/lib/ruby_llm/protocols/vertexai/ranking.rb +69 -0
  274. data/lib/ruby_llm/protocols/vertexai/research.rb +193 -0
  275. data/lib/ruby_llm/protocols/xai/files.rb +30 -0
  276. data/lib/ruby_llm/protocols/xai/streaming_transcription.rb +120 -0
  277. data/lib/ruby_llm/protocols/xai/tokenization.rb +23 -0
  278. data/lib/ruby_llm/provider.rb +560 -128
  279. data/lib/ruby_llm/providers/anthropic/capabilities.rb +5 -7
  280. data/lib/ruby_llm/providers/anthropic.rb +4 -6
  281. data/lib/ruby_llm/providers/azure/audio.rb +18 -0
  282. data/lib/ruby_llm/providers/azure/capabilities.rb +16 -0
  283. data/lib/ruby_llm/providers/azure/chat.rb +2 -9
  284. data/lib/ruby_llm/providers/azure/chat_completions/batches.rb +29 -0
  285. data/lib/ruby_llm/providers/azure/chat_completions.rb +80 -0
  286. data/lib/ruby_llm/providers/azure/cohere.rb +33 -0
  287. data/lib/ruby_llm/providers/azure/embeddings.rb +3 -2
  288. data/lib/ruby_llm/providers/azure/images.rb +22 -0
  289. data/lib/ruby_llm/providers/azure/media.rb +5 -14
  290. data/lib/ruby_llm/providers/azure/models.rb +35 -0
  291. data/lib/ruby_llm/providers/azure/responses.rb +26 -0
  292. data/lib/ruby_llm/providers/azure/videos.rb +64 -0
  293. data/lib/ruby_llm/providers/azure.rb +77 -78
  294. data/lib/ruby_llm/providers/bedrock/auth.rb +60 -41
  295. data/lib/ruby_llm/providers/bedrock/capabilities.rb +18 -0
  296. data/lib/ruby_llm/providers/bedrock/mantle/anthropic.rb +39 -0
  297. data/lib/ruby_llm/providers/bedrock/mantle/chat_completions.rb +23 -0
  298. data/lib/ruby_llm/providers/bedrock/mantle/responses.rb +24 -0
  299. data/lib/ruby_llm/providers/bedrock/mantle/voxtral.rb +96 -0
  300. data/lib/ruby_llm/providers/bedrock/mantle.rb +57 -0
  301. data/lib/ruby_llm/providers/bedrock/models.rb +193 -41
  302. data/lib/ruby_llm/providers/bedrock.rb +216 -45
  303. data/lib/ruby_llm/providers/cohere.rb +31 -0
  304. data/lib/ruby_llm/providers/deepgram.rb +37 -0
  305. data/lib/ruby_llm/providers/deepseek/capabilities.rb +4 -51
  306. data/lib/ruby_llm/providers/deepseek/chat.rb +49 -2
  307. data/lib/ruby_llm/providers/deepseek/responses.rb +68 -0
  308. data/lib/ruby_llm/providers/deepseek.rb +9 -2
  309. data/lib/ruby_llm/providers/elevenlabs.rb +35 -0
  310. data/lib/ruby_llm/providers/gemini/capabilities.rb +8 -107
  311. data/lib/ruby_llm/providers/gemini.rb +15 -8
  312. data/lib/ruby_llm/providers/gpustack/chat.rb +2 -16
  313. data/lib/ruby_llm/providers/gpustack/embeddings.rb +28 -0
  314. data/lib/ruby_llm/providers/gpustack/media.rb +17 -16
  315. data/lib/ruby_llm/providers/gpustack/models.rb +72 -62
  316. data/lib/ruby_llm/providers/gpustack/speech.rb +15 -0
  317. data/lib/ruby_llm/providers/gpustack/transcription.rb +29 -0
  318. data/lib/ruby_llm/providers/gpustack.rb +35 -11
  319. data/lib/ruby_llm/providers/mistral/capabilities.rb +7 -155
  320. data/lib/ruby_llm/providers/mistral/chat.rb +37 -61
  321. data/lib/ruby_llm/providers/mistral/chat_completions/batches.rb +120 -0
  322. data/lib/ruby_llm/providers/mistral/chat_completions.rb +21 -0
  323. data/lib/ruby_llm/providers/mistral/conversations.rb +12 -0
  324. data/lib/ruby_llm/providers/mistral/embeddings.rb +6 -4
  325. data/lib/ruby_llm/providers/mistral/media.rb +6 -18
  326. data/lib/ruby_llm/providers/mistral/models.rb +57 -23
  327. data/lib/ruby_llm/providers/mistral/ocr.rb +47 -0
  328. data/lib/ruby_llm/providers/mistral/speech.rb +51 -0
  329. data/lib/ruby_llm/providers/mistral/transcription.rb +62 -0
  330. data/lib/ruby_llm/providers/mistral.rb +16 -4
  331. data/lib/ruby_llm/providers/ollama/chat.rb +7 -13
  332. data/lib/ruby_llm/providers/ollama/media.rb +6 -15
  333. data/lib/ruby_llm/providers/ollama/models.rb +50 -9
  334. data/lib/ruby_llm/providers/ollama.rb +9 -8
  335. data/lib/ruby_llm/providers/ollama_cloud/models.rb +14 -0
  336. data/lib/ruby_llm/providers/ollama_cloud.rb +40 -0
  337. data/lib/ruby_llm/providers/openai/capabilities.rb +54 -259
  338. data/lib/ruby_llm/providers/openai/models.rb +23 -23
  339. data/lib/ruby_llm/providers/openai/responses.rb +13 -0
  340. data/lib/ruby_llm/providers/openai.rb +92 -11
  341. data/lib/ruby_llm/providers/openrouter/chat.rb +130 -108
  342. data/lib/ruby_llm/providers/openrouter/embeddings.rb +51 -0
  343. data/lib/ruby_llm/providers/openrouter/images.rb +44 -43
  344. data/lib/ruby_llm/providers/openrouter/media.rb +34 -0
  345. data/lib/ruby_llm/providers/openrouter/models.rb +50 -11
  346. data/lib/ruby_llm/providers/openrouter/speech.rb +32 -0
  347. data/lib/ruby_llm/providers/openrouter/streaming.rb +31 -38
  348. data/lib/ruby_llm/providers/openrouter/videos.rb +81 -0
  349. data/lib/ruby_llm/providers/openrouter.rb +78 -20
  350. data/lib/ruby_llm/providers/perplexity/chat.rb +2 -9
  351. data/lib/ruby_llm/providers/perplexity/embeddings.rb +32 -0
  352. data/lib/ruby_llm/providers/perplexity/media.rb +5 -21
  353. data/lib/ruby_llm/providers/perplexity/models.rb +80 -13
  354. data/lib/ruby_llm/providers/perplexity.rb +28 -20
  355. data/lib/ruby_llm/providers/vertexai/anthropic/batches.rb +52 -0
  356. data/lib/ruby_llm/providers/vertexai/anthropic.rb +34 -0
  357. data/lib/ruby_llm/providers/vertexai/capabilities.rb +19 -0
  358. data/lib/ruby_llm/providers/vertexai/chat_completions/batches.rb +54 -0
  359. data/lib/ruby_llm/providers/vertexai/chat_completions.rb +15 -0
  360. data/lib/ruby_llm/providers/vertexai/embed_content.rb +42 -0
  361. data/lib/ruby_llm/providers/vertexai/embeddings.rb +22 -7
  362. data/lib/ruby_llm/providers/vertexai/gemini/batches.rb +42 -0
  363. data/lib/ruby_llm/providers/vertexai/gemini.rb +69 -0
  364. data/lib/ruby_llm/providers/vertexai/live_transcription.rb +24 -0
  365. data/lib/ruby_llm/providers/vertexai/mistral.rb +28 -0
  366. data/lib/ruby_llm/providers/vertexai/models.rb +145 -43
  367. data/lib/ruby_llm/providers/vertexai/transcription.rb +49 -4
  368. data/lib/ruby_llm/providers/vertexai/videos.rb +61 -0
  369. data/lib/ruby_llm/providers/vertexai.rb +160 -17
  370. data/lib/ruby_llm/providers/xai/capabilities.rb +18 -0
  371. data/lib/ruby_llm/providers/xai/chat.rb +3 -2
  372. data/lib/ruby_llm/providers/xai/chat_completions/batches.rb +108 -0
  373. data/lib/ruby_llm/providers/xai/chat_completions.rb +19 -0
  374. data/lib/ruby_llm/providers/xai/images.rb +91 -0
  375. data/lib/ruby_llm/providers/xai/models.rb +32 -36
  376. data/lib/ruby_llm/providers/xai/reported_cost.rb +18 -0
  377. data/lib/ruby_llm/providers/xai/responses.rb +52 -0
  378. data/lib/ruby_llm/providers/xai/speech.rb +45 -0
  379. data/lib/ruby_llm/providers/xai/transcription.rb +48 -0
  380. data/lib/ruby_llm/providers/xai/videos.rb +87 -0
  381. data/lib/ruby_llm/providers/xai.rb +15 -5
  382. data/lib/ruby_llm/railtie.rb +7 -16
  383. data/lib/ruby_llm/rerank.rb +105 -0
  384. data/lib/ruby_llm/research_job.rb +241 -0
  385. data/lib/ruby_llm/search_results.rb +68 -0
  386. data/lib/ruby_llm/server_tool_call.rb +73 -0
  387. data/lib/ruby_llm/speech.rb +159 -0
  388. data/lib/ruby_llm/speech_chunk.rb +33 -0
  389. data/lib/ruby_llm/support/deprecator.rb +22 -0
  390. data/lib/ruby_llm/support/inspectable.rb +49 -0
  391. data/lib/ruby_llm/support/instrumentation.rb +41 -0
  392. data/lib/ruby_llm/support/utils.rb +147 -0
  393. data/lib/ruby_llm/thinking.rb +127 -20
  394. data/lib/ruby_llm/tokenization.rb +59 -0
  395. data/lib/ruby_llm/tokens.rb +103 -33
  396. data/lib/ruby_llm/tool.rb +266 -91
  397. data/lib/ruby_llm/tool_call.rb +36 -3
  398. data/lib/ruby_llm/tools/server_tools.rb +109 -0
  399. data/lib/ruby_llm/transcription/wav_audio.rb +62 -0
  400. data/lib/ruby_llm/transcription.rb +138 -13
  401. data/lib/ruby_llm/transcription_chunk.rb +68 -0
  402. data/lib/ruby_llm/transport/connection.rb +193 -0
  403. data/lib/ruby_llm/transport/error_middleware.rb +131 -0
  404. data/lib/ruby_llm/transport/usage_middleware.rb +28 -0
  405. data/lib/ruby_llm/transport/websocket_connection.rb +220 -0
  406. data/lib/ruby_llm/uploaded_file.rb +144 -0
  407. data/lib/ruby_llm/version.rb +2 -1
  408. data/lib/ruby_llm/video.rb +136 -0
  409. data/lib/ruby_llm/video_job.rb +150 -0
  410. data/lib/ruby_llm/workflow.rb +91 -0
  411. data/lib/ruby_llm.rb +380 -6
  412. data/lib/tasks/ruby_llm.rake +21 -16
  413. data/skills/rubyllm/SKILL.md +81 -0
  414. data/skills/rubyllm/agents/openai.yaml +4 -0
  415. metadata +339 -97
  416. data/lib/generators/ruby_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt +0 -9
  417. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +0 -39
  418. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +0 -21
  419. data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +0 -3
  420. data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +0 -3
  421. data/lib/generators/ruby_llm/upgrade_to_v1_10/templates/add_v1_10_message_columns.rb.tt +0 -19
  422. data/lib/generators/ruby_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +0 -50
  423. data/lib/generators/ruby_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +0 -7
  424. data/lib/generators/ruby_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +0 -49
  425. data/lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt +0 -145
  426. data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +0 -122
  427. data/lib/generators/ruby_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +0 -15
  428. data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +0 -49
  429. data/lib/ruby_llm/active_record/acts_as_legacy.rb +0 -597
  430. data/lib/ruby_llm/active_record/model_methods.rb +0 -82
  431. data/lib/ruby_llm/active_record/tool_call_methods.rb +0 -18
  432. data/lib/ruby_llm/aliases.rb +0 -41
  433. data/lib/ruby_llm/connection.rb +0 -159
  434. data/lib/ruby_llm/content.rb +0 -91
  435. data/lib/ruby_llm/deprecator.rb +0 -24
  436. data/lib/ruby_llm/error_middleware.rb +0 -81
  437. data/lib/ruby_llm/instrumentation.rb +0 -36
  438. data/lib/ruby_llm/mime_type.rb +0 -96
  439. data/lib/ruby_llm/model/info.rb +0 -164
  440. data/lib/ruby_llm/model_registry.rb +0 -39
  441. data/lib/ruby_llm/models_schema.json +0 -171
  442. data/lib/ruby_llm/providers/anthropic/chat.rb +0 -291
  443. data/lib/ruby_llm/providers/anthropic/content.rb +0 -44
  444. data/lib/ruby_llm/providers/anthropic/embeddings.rb +0 -20
  445. data/lib/ruby_llm/providers/anthropic/media.rb +0 -92
  446. data/lib/ruby_llm/providers/anthropic/models.rb +0 -59
  447. data/lib/ruby_llm/providers/anthropic/streaming.rb +0 -71
  448. data/lib/ruby_llm/providers/bedrock/chat.rb +0 -405
  449. data/lib/ruby_llm/providers/bedrock/media.rb +0 -108
  450. data/lib/ruby_llm/providers/bedrock/streaming.rb +0 -328
  451. data/lib/ruby_llm/providers/gemini/chat.rb +0 -542
  452. data/lib/ruby_llm/providers/gemini/embeddings.rb +0 -37
  453. data/lib/ruby_llm/providers/gemini/images.rb +0 -47
  454. data/lib/ruby_llm/providers/gemini/models.rb +0 -38
  455. data/lib/ruby_llm/providers/gemini/streaming.rb +0 -98
  456. data/lib/ruby_llm/providers/gemini/tools.rb +0 -234
  457. data/lib/ruby_llm/providers/gpustack/capabilities.rb +0 -20
  458. data/lib/ruby_llm/providers/ollama/capabilities.rb +0 -20
  459. data/lib/ruby_llm/providers/openai/chat.rb +0 -236
  460. data/lib/ruby_llm/providers/openai/embeddings.rb +0 -33
  461. data/lib/ruby_llm/providers/openai/images.rb +0 -90
  462. data/lib/ruby_llm/providers/openai/moderation.rb +0 -34
  463. data/lib/ruby_llm/providers/openai/streaming.rb +0 -55
  464. data/lib/ruby_llm/providers/openai/temperature.rb +0 -28
  465. data/lib/ruby_llm/providers/openai/transcription.rb +0 -71
  466. data/lib/ruby_llm/providers/perplexity/capabilities.rb +0 -72
  467. data/lib/ruby_llm/providers/vertexai/chat.rb +0 -14
  468. data/lib/ruby_llm/providers/vertexai/streaming.rb +0 -14
  469. data/lib/ruby_llm/stream_accumulator.rb +0 -218
  470. data/lib/ruby_llm/streaming.rb +0 -179
  471. data/lib/ruby_llm/tool_concurrency.rb +0 -105
  472. data/lib/ruby_llm/utils.rb +0 -130
  473. data/lib/tasks/models.rake +0 -593
  474. data/lib/tasks/release.rake +0 -94
  475. data/lib/tasks/vcr.rake +0 -124
@@ -1,597 +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.deprecator.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
- attachment_sources.each do |attachment, attachable|
509
- add_attachment_to_content(content_obj, attachment, attachable)
510
- end
511
- end
512
- end
513
-
514
- def attachment_sources
515
- change = pending_attachment_change
516
- return attachments.map { |attachment| [attachment, nil] } unless pending_attachment_change?(change)
517
-
518
- change.attachments.zip(change.attachables)
519
- end
520
-
521
- def pending_attachment_change
522
- attachment_changes['attachments'] if respond_to?(:attachment_changes)
523
- end
524
-
525
- def pending_attachment_change?(change)
526
- change.respond_to?(:attachments) && change.respond_to?(:attachables)
527
- end
528
-
529
- def add_attachment_to_content(content_obj, attachment, attachable)
530
- if pending_upload_attachable?(attachable)
531
- add_pending_upload_attachment(content_obj, attachable)
532
- else
533
- tempfile = download_attachment(attachment)
534
- content_obj.add_attachment(tempfile, filename: attachment.filename.to_s)
535
- end
536
- end
537
-
538
- def pending_upload_attachable?(attachable)
539
- return false if attachable.nil? || attachable.is_a?(String)
540
- return false if instance_of_class?(attachable, 'ActiveStorage::Blob')
541
-
542
- uploaded_file?(attachable) || active_storage_upload_hash?(attachable) ||
543
- attachable.is_a?(File) || pathname?(attachable)
544
- end
545
-
546
- def uploaded_file?(attachable)
547
- instance_of_class?(attachable, 'ActionDispatch::Http::UploadedFile') ||
548
- instance_of_class?(attachable, 'Rack::Test::UploadedFile')
549
- end
550
-
551
- def active_storage_upload_hash?(attachable)
552
- attachable.is_a?(Hash) && attachment_hash_io(attachable).present?
553
- end
554
-
555
- def pathname?(attachable)
556
- defined?(Pathname) && attachable.is_a?(Pathname)
557
- end
558
-
559
- def add_pending_upload_attachment(content_obj, attachable)
560
- if attachable.is_a?(Hash)
561
- content_obj.add_attachment(attachment_hash_io(attachable), filename: attachment_hash_filename(attachable))
562
- else
563
- content_obj.add_attachment(attachable)
564
- end
565
- end
566
-
567
- def attachment_hash_io(attachable)
568
- attachable[:io] || attachable['io']
569
- end
570
-
571
- def attachment_hash_filename(attachable)
572
- filename = attachable[:filename] || attachable['filename']
573
- filename&.to_s
574
- end
575
-
576
- def instance_of_class?(object, class_name)
577
- Object.const_get(class_name).then { |klass| object.is_a?(klass) }
578
- rescue NameError
579
- false
580
- end
581
-
582
- def download_attachment(attachment)
583
- ext = File.extname(attachment.filename.to_s)
584
- basename = File.basename(attachment.filename.to_s, ext)
585
- tempfile = Tempfile.new([basename, ext])
586
- tempfile.binmode
587
-
588
- attachment.download { |chunk| tempfile.write(chunk) }
589
-
590
- tempfile.flush
591
- tempfile.rewind
592
- @_tempfiles << tempfile
593
- tempfile
594
- end
595
- end
596
- end
597
- 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