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
@@ -0,0 +1,91 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ # Adds workflow and step context to RubyLLM instrumentation while leaving
5
+ # orchestration in ordinary Ruby code.
6
+ #
7
+ # Create workflows through RubyLLM.workflow rather than constructing this
8
+ # class directly:
9
+ #
10
+ # RubyLLM.workflow("Write article", id: "article-42") do |workflow|
11
+ # notes = workflow.step("Research") { ResearchAgent.new.ask(topic).content }
12
+ # workflow.step("Draft") { WriterAgent.new.ask(notes).content }
13
+ # end
14
+ #
15
+ class Workflow
16
+ # Returns the workflow identifier shared by its instrumentation events.
17
+ attr_reader :id
18
+
19
+ # Returns the workflow name.
20
+ attr_reader :name
21
+
22
+ def initialize(name, id: nil, metadata: nil, config: RubyLLM.config) # :nodoc:
23
+ @name = normalize(name, 'name')
24
+ @id = normalize(id || SecureRandom.uuid, 'id')
25
+ @metadata = metadata
26
+ @config = config
27
+ end
28
+
29
+ def run # :nodoc:
30
+ raise ArgumentError, 'a workflow block is required' unless block_given?
31
+
32
+ link_parent
33
+ Support::Instrumentation.with_workflow(workflow_context) do
34
+ RubyLLM.instrument('workflow.ruby_llm', config: @config) { yield self }
35
+ end
36
+ end
37
+
38
+ # Runs a named section of Ruby code and adds its identity to every RubyLLM
39
+ # event emitted by the block. An ID is generated when one is not supplied.
40
+ # Steps can contain regular Ruby control flow and may be nested.
41
+ # Returns the block's result.
42
+ def step(name, id: nil, &block)
43
+ raise ArgumentError, 'a workflow step block is required' unless block
44
+
45
+ step_context = workflow_context.merge(
46
+ workflow_step_id: normalize(id || SecureRandom.uuid, 'step id'),
47
+ workflow_step_name: normalize(name, 'step name')
48
+ )
49
+ parent_id = current_step_id
50
+ step_context[:workflow_step_parent_id] = parent_id if parent_id
51
+
52
+ Support::Instrumentation.with_workflow(step_context.freeze) do
53
+ RubyLLM.instrument('workflow_step.ruby_llm', config: @config) { block.call }
54
+ end
55
+ end
56
+
57
+ private
58
+
59
+ def workflow_context
60
+ @workflow_context ||= begin
61
+ context = { workflow_id: id, workflow_name: name }
62
+ context[:workflow_metadata] = @metadata unless @metadata.nil?
63
+ context.freeze
64
+ end
65
+ end
66
+
67
+ def link_parent
68
+ current = Support::Instrumentation.current_workflow
69
+ context = workflow_context.dup
70
+ context.delete(:workflow_parent_id)
71
+ context.delete(:workflow_parent_step_id)
72
+ if current && current[:workflow_id] != id
73
+ context[:workflow_parent_id] = current[:workflow_id]
74
+ context[:workflow_parent_step_id] = current[:workflow_step_id] if current[:workflow_step_id]
75
+ end
76
+ @workflow_context = context.freeze
77
+ end
78
+
79
+ def current_step_id
80
+ current = Support::Instrumentation.current_workflow
81
+ current[:workflow_step_id] if current && current[:workflow_id] == id
82
+ end
83
+
84
+ def normalize(value, attribute)
85
+ value = value.to_s
86
+ raise ArgumentError, "workflow #{attribute} cannot be empty" if value.empty?
87
+
88
+ value
89
+ end
90
+ end
91
+ end
data/lib/ruby_llm.rb CHANGED
@@ -8,6 +8,7 @@ require 'faraday/retry'
8
8
  require 'json'
9
9
  require 'logger'
10
10
  require 'marcel'
11
+ require 'schematist'
11
12
  require 'securerandom'
12
13
  require 'date'
13
14
  require 'time'
@@ -20,10 +21,13 @@ loader.inflector.inflect(
20
21
  'UI' => 'UI',
21
22
  'api' => 'API',
22
23
  'bedrock' => 'Bedrock',
24
+ 'cli' => 'CLI',
23
25
  'deepseek' => 'DeepSeek',
26
+ 'elevenlabs' => 'ElevenLabs',
24
27
  'gpustack' => 'GPUStack',
25
28
  'llm' => 'LLM',
26
29
  'mistral' => 'Mistral',
30
+ 'ocr' => 'OCR',
27
31
  'openai' => 'OpenAI',
28
32
  'openrouter' => 'OpenRouter',
29
33
  'pdf' => 'PDF',
@@ -38,60 +42,426 @@ loader.ignore("#{__dir__}/ruby_llm/active_record")
38
42
  loader.ignore("#{__dir__}/ruby_llm/railtie.rb")
39
43
  loader.setup
40
44
 
41
- # A delightful Ruby interface to modern AI language models.
45
+ # RubyLLM is an AI framework for Ruby and Rails. Build conversations and
46
+ # agents, generate media, process documents, and work with model providers
47
+ # through one Ruby API. The guides at https://rubyllm.com/next/ introduce
48
+ # each feature; this reference documents its classes, arguments, and results.
49
+ #
50
+ # RubyLLM.configure do |config|
51
+ # config.openai_api_key = ENV['OPENAI_API_KEY']
52
+ # end
53
+ #
54
+ # chat = RubyLLM.chat
55
+ # chat.ask "What is the capital of France?"
56
+ #
57
+ # == Conversations, tools, and agents
58
+ #
59
+ # RubyLLM.chat returns a Chat that holds the conversation. Chat#ask accepts
60
+ # text and attachments, runs tools as needed, and returns a Message. Give
61
+ # it a block to receive Chunk objects as the response streams:
62
+ #
63
+ # chat.ask("Summarize this report", with: "report.pdf") do |chunk|
64
+ # print chunk.content
65
+ # end
66
+ #
67
+ # Configure the request with chainable methods:
68
+ #
69
+ # - Chat#with_schema requests structured output; Message#parsed reads it.
70
+ # - Chat#with_thinking sets thinking effort, budget, or display preferences.
71
+ # - Chat#with_citations requests source citations, read through Message#citations.
72
+ # - Chat#with_fallbacks tries other models when a request fails.
73
+ # - Chat#with_caching enables prompt caching; Chat#cache_until_here marks
74
+ # a reusable prefix. Chat#with_compaction manages long conversations.
75
+ #
76
+ # Subclass Tool and implement +execute+ to give the model an application
77
+ # action. Tool.requires_approval pauses execution for a human decision;
78
+ # Chat#approve and Chat#deny record it. Chat#with_server_tools enables
79
+ # provider-executed tools such as web search, code execution, and remote MCP.
80
+ # Their calls appear as ServerToolCall values, with Citation values for sources.
81
+ #
82
+ # Agent defines a reusable configuration with model, instructions, tools,
83
+ # schema, and runtime inputs. Chat#ask_later, Chat#generate, Chat#run_tools,
84
+ # and Chat#step expose the conversation loop for jobs and application logic.
85
+ #
86
+ # == Images, video, and speech
87
+ #
88
+ # Individual operations do not require a chat. Image, Video, and Speech
89
+ # results share +save(path)+ and +to_blob+:
90
+ #
91
+ # RubyLLM.paint("A red panda coding Ruby, watercolor").save("panda.png")
92
+ # RubyLLM.animate("A paper boat sailing down a gutter").save("boat.mp4")
93
+ # RubyLLM.speak("Welcome to RubyLLM.").save("welcome.mp3")
94
+ #
95
+ # Image.paint accepts source images and masks for editing. Video.animate
96
+ # accepts reference media, video edits, and extensions on supported models.
97
+ # RubyLLM.animate waits for the clip; RubyLLM.animate_later returns a
98
+ # VideoJob that you can poll. Speech.speak also streams SpeechChunk objects
99
+ # while retaining the complete audio result.
100
+ #
101
+ # == Documents, audio, and retrieval
102
+ #
103
+ # transcript = RubyLLM.transcribe("meeting.wav")
104
+ # document = RubyLLM.ocr("report.pdf", pages: [0, 1])
105
+ # embedding = RubyLLM.embed("Ruby is a programmer's best friend")
106
+ #
107
+ # Transcription provides text, timestamps, and speaker information when
108
+ # the model reports them; streaming yields TranscriptionChunk objects.
109
+ # OCR returns document pages and combined markdown. Embedding returns
110
+ # vectors for text or supported media, and RubyLLM.rerank returns a Rerank
111
+ # whose results order documents by relevance. SearchResults lets a Tool
112
+ # return source documents that the model can cite.
113
+ #
114
+ # RubyLLM.upload returns an UploadedFile for reuse across requests.
115
+ # RubyLLM.download returns a DownloadedFile with the same saving interface:
116
+ #
117
+ # RubyLLM.download(file.id, provider: file.provider).save("report.pdf")
118
+ #
119
+ # == Tokenization, moderation, and research
120
+ #
121
+ # RubyLLM.count_tokens and Chat#count_tokens count a model request without
122
+ # generating a response. RubyLLM.tokenize returns plain-text token IDs and
123
+ # a count as a Tokenization, excluding chat formatting and attachments.
124
+ #
125
+ # result = RubyLLM.tokenize("Hello Ruby", model: "grok-4.3", provider: :xai)
126
+ # result.ids
127
+ # result.count
128
+ #
129
+ # RubyLLM.moderate screens text and images, returning Moderation results
130
+ # with categories, scores, and +flagged?+. RubyLLM.research runs a hosted
131
+ # research task and returns its report as a Message; RubyLLM.research_later
132
+ # returns a ResearchJob for polling and cancellation. Hosted agent identities
133
+ # are selected separately from model IDs.
134
+ #
135
+ # == Batches, usage, and configuration
136
+ #
137
+ # RubyLLM.batch submits staged chats or EmbeddingRequest objects for
138
+ # provider-side processing. Batch exposes progress, results, token usage,
139
+ # and cost. RubyLLM.cache creates a managed CachedContent resource for
140
+ # reuse with Chat#with_caching.
141
+ #
142
+ # Tokens and Cost report usage and pricing. Chat totals include retries
143
+ # and attempts that produced no message. Provider-reported costs take
144
+ # precedence over estimates; unknown usage and prices remain +nil+.
145
+ # RubyLLM.workflow groups instrumentation from ordinary Ruby code into
146
+ # named Workflow steps.
147
+ #
148
+ # RubyLLM.configure sets global Configuration; RubyLLM.context creates
149
+ # isolated settings for a request or tenant. Models finds, filters, and
150
+ # describes the model catalog. Provider supplies endpoints, authentication,
151
+ # and protocol selection; Protocol implements request and response formats.
152
+ # Error subclasses normalize provider failures.
153
+ #
154
+ # == Rails integration
155
+ #
156
+ # ActiveRecord::ActsAs adds +acts_as_chat+ and +acts_as_message+ to your
157
+ # application's models. ActiveRecord::ChatMethods and
158
+ # ActiveRecord::MessageMethods provide the conversation API with persistence,
159
+ # Active Storage attachments, and support for Hotwire streaming and jobs.
160
+ # Approvals and cancellation survive requests and processes.
161
+ #
162
+ # Your application owns chats and messages; RubyLLM owns usage, tool calls,
163
+ # models, and batches. Agent can create and reload your chat records through
164
+ # Agent.chat_model. Individual operations also work directly in Rails
165
+ # services and jobs.
42
166
  module RubyLLM
43
167
  class << self
44
- def deprecator
45
- @deprecator ||= Deprecator.new
168
+ def deprecator # :nodoc:
169
+ @deprecator ||= Support::Deprecator.new
46
170
  end
47
171
 
48
- def instrument(...)
49
- Instrumentation.instrument(...)
172
+ def instrument(...) # :nodoc:
173
+ Support::Instrumentation.instrument(...)
50
174
  end
51
175
 
176
+ # Returns a Context, an isolated set of configuration overrides.
177
+ # Duplicates the global configuration and yields the copy if a block is
178
+ # given. The context offers the same entry points as the top-level
179
+ # RubyLLM module (Context#chat, Context#embed, and so on) using its
180
+ # own configuration.
181
+ #
182
+ # context = RubyLLM.context do |config|
183
+ # config.openai_api_key = 'sk-customer-specific-key'
184
+ # end
185
+ # context.chat.ask "Hello"
186
+ #
52
187
  def context
53
188
  context_config = config.dup
54
189
  yield context_config if block_given?
55
190
  Context.new(context_config)
56
191
  end
57
192
 
193
+ # Runs ordinary Ruby code as a named, instrumented workflow. Every RubyLLM
194
+ # event emitted inside the block includes the workflow ID and name. Wrap
195
+ # meaningful regions with Workflow#step to add step correlation.
196
+ #
197
+ # RubyLLM.workflow("Write article", id: "article-42") do |workflow|
198
+ # notes = workflow.step("Research") { researcher.ask(topic).content }
199
+ # workflow.step("Draft") { writer.ask(notes).content }
200
+ # end
201
+ #
202
+ # If +id:+ is omitted, RubyLLM generates one. Pass +metadata:+ to attach
203
+ # application data to every nested event as +workflow_metadata+. Workflows
204
+ # may nest; an inner workflow keeps its own identity and records its
205
+ # parent as +workflow_parent_id+. The block's return value is returned
206
+ # unchanged.
207
+ def workflow(name, id: nil, metadata: nil, &)
208
+ Workflow.new(name, id:, metadata:, config: config).run(&)
209
+ end
210
+
211
+ # Creates a Chat conversation. Arguments are forwarded to Chat.new:
212
+ # +model:+, +provider:+, +protocol:+, +assume_model_exists:+, and
213
+ # +context:+. With no arguments, uses the configured default model.
214
+ #
215
+ # chat = RubyLLM.chat
216
+ # chat.ask "What is the capital of France?"
217
+ #
218
+ # chat = RubyLLM.chat(model: 'claude-sonnet-5')
219
+ #
58
220
  def chat(...)
59
221
  Chat.new(...)
60
222
  end
61
223
 
224
+ # Counts the tokens +text+ would consume as a single user message,
225
+ # without requesting a completion. Builds a minimal Chat and delegates
226
+ # to Chat#count_tokens. Returns an Integer.
227
+ #
228
+ # RubyLLM.count_tokens("What is the capital of France?", model: 'claude-haiku-4-5')
229
+ #
230
+ def count_tokens(text, model: nil, provider: nil)
231
+ chat(model: model, provider: provider).count_tokens(text)
232
+ end
233
+
234
+ # Tokenizes plain text and returns a Tokenization with its token IDs
235
+ # and count. Excludes chat formatting and billable generation usage.
236
+ # See Tokenization.tokenize for options.
237
+ def tokenize(...)
238
+ Tokenization.tokenize(...)
239
+ end
240
+
241
+ # Submits requests staged with Chat#ask_later or ::embed_later as a
242
+ # provider-side batch and returns a Batch. A batch takes chats or
243
+ # embedding requests, not both. Look up an existing batch with
244
+ # Batch.find.
245
+ #
246
+ # chats = documents.map do |doc|
247
+ # RubyLLM.chat(model: 'claude-haiku-4-5').ask_later(doc.text)
248
+ # end
249
+ # batch = RubyLLM.batch(chats)
250
+ #
251
+ def batch(chats)
252
+ Batch.submit(chats)
253
+ end
254
+
255
+ # Generates a vector embedding for a text, or one embedding per element
256
+ # when given an array of strings. Returns an Embedding. Arguments are
257
+ # forwarded to Embedding.embed.
258
+ #
259
+ # embedding = RubyLLM.embed("Ruby is a programmer's best friend")
260
+ # embedding.vectors # => [0.018, -0.027, ...]
261
+ #
62
262
  def embed(...)
63
263
  Embedding.embed(...)
64
264
  end
65
265
 
266
+ # Stages a text for embedding without contacting the provider, and
267
+ # returns an EmbeddingRequest. Submit an array of staged requests as a
268
+ # provider-side batch with ::batch; once the batch completes, each
269
+ # request's EmbeddingRequest#result holds its Embedding.
270
+ #
271
+ # requests = texts.map { |text| RubyLLM.embed_later(text) }
272
+ # batch = RubyLLM.batch(requests)
273
+ #
274
+ def embed_later(text, model: nil, provider: nil, dimensions: nil)
275
+ EmbeddingRequest.new(text, model:, provider:, dimensions:)
276
+ end
277
+
278
+ # Checks text or image attachments against the provider's moderation model and returns a
279
+ # Moderation result. Arguments are forwarded to Moderation.moderate.
280
+ # An explicitly selected provider can use a configured moderation
281
+ # resource without a model.
282
+ #
283
+ # result = RubyLLM.moderate("Some user input text")
284
+ # result.flagged? # => false
285
+ #
66
286
  def moderate(...)
67
287
  Moderation.moderate(...)
68
288
  end
69
289
 
290
+ # Runs a hosted research task and returns its report as a Message.
291
+ # Requires explicit +provider:+ and +agent:+. See ResearchJob.research.
292
+ def research(...)
293
+ ResearchJob.research(...)
294
+ end
295
+
296
+ # Submits a hosted research task and returns a ResearchJob immediately.
297
+ # Requires explicit +provider:+ and +agent:+. See ResearchJob.research_later.
298
+ def research_later(...)
299
+ ResearchJob.research_later(...)
300
+ end
301
+
302
+ # Generates or edits an image and returns an Image, or an array when
303
+ # the provider returns several images. Pass +with:+ for source images,
304
+ # +mask:+ for a mask, and +count:+ for multiple results. See Image.paint.
305
+ #
306
+ # image = RubyLLM.paint("a sunset over mountains in watercolor style")
307
+ # image.save("sunset.png")
308
+ #
70
309
  def paint(...)
71
310
  Image.paint(...)
72
311
  end
73
312
 
313
+ # Generates a video from a text prompt, blocks until the provider
314
+ # finishes rendering it, and returns a Video. Arguments are forwarded
315
+ # to Video.animate.
316
+ #
317
+ # video = RubyLLM.animate("a paper boat sailing down a rainy gutter")
318
+ # video.save("boat.mp4")
319
+ #
320
+ def animate(...)
321
+ Video.animate(...)
322
+ end
323
+
324
+ # Submits a video generation job and returns a VideoJob immediately,
325
+ # without waiting for the result. Arguments are forwarded to
326
+ # VideoJob.animate_later.
327
+ #
328
+ # job = RubyLLM.animate_later("a paper boat sailing down a gutter")
329
+ # job.wait
330
+ # job.video.save("boat.mp4")
331
+ #
332
+ def animate_later(...)
333
+ VideoJob.animate_later(...)
334
+ end
335
+
336
+ # Synthesizes speech from text and returns a Speech. Given a block,
337
+ # yields SpeechChunk objects as audio arrives. Arguments are
338
+ # forwarded to Speech.speak.
339
+ #
340
+ # speech = RubyLLM.speak "Hello, welcome to RubyLLM!"
341
+ # speech.save("welcome.mp3")
342
+ #
343
+ def speak(...)
344
+ Speech.speak(...)
345
+ end
346
+
347
+ # Transcribes an audio file and returns a Transcription. Arguments are
348
+ # forwarded to Transcription.transcribe. Given a block, the transcript
349
+ # streams as TranscriptionChunk objects.
350
+ #
351
+ # transcription = RubyLLM.transcribe("meeting.wav")
352
+ # transcription.text
353
+ #
354
+ # RubyLLM.transcribe("meeting.wav", model: "gpt-4o-transcribe") do |chunk|
355
+ # print chunk.delta
356
+ # end
357
+ #
74
358
  def transcribe(...)
75
359
  Transcription.transcribe(...)
76
360
  end
77
361
 
362
+ # Extracts the text of a document or image and returns an OCR result.
363
+ # Arguments are forwarded to OCR.ocr.
364
+ #
365
+ # ocr = RubyLLM.ocr("contract.pdf")
366
+ # ocr.markdown
367
+ #
368
+ def ocr(...)
369
+ OCR.ocr(...)
370
+ end
371
+
372
+ # Ranks documents by relevance to a query on providers with a rerank
373
+ # endpoint. Arguments are forwarded to Rerank.rerank.
374
+ #
375
+ # rerank = RubyLLM.rerank("what is ruby", docs,
376
+ # model: "voyageai/rerank-2.5-lite", provider: :openrouter)
377
+ # rerank.results.first.document
378
+ #
379
+ def rerank(...)
380
+ Rerank.rerank(...)
381
+ end
382
+
383
+ # Uploads a file to a provider and returns an UploadedFile that can be
384
+ # reused across chats. Arguments are forwarded to UploadedFile.upload.
385
+ #
386
+ # file = RubyLLM.upload("document.pdf", provider: :anthropic)
387
+ # chat.ask "Summarize this document", with: file
388
+ #
389
+ def upload(...)
390
+ UploadedFile.upload(...)
391
+ end
392
+
393
+ # Downloads a provider file and returns a DownloadedFile. Save it with
394
+ # DownloadedFile#save or read its bytes with DownloadedFile#to_blob.
395
+ # Arguments are forwarded to UploadedFile.download.
396
+ #
397
+ # RubyLLM.download(file.id, provider: :openai).save("report.pdf")
398
+ #
399
+ def download(...)
400
+ UploadedFile.download(...)
401
+ end
402
+
403
+ # Creates a provider-side prompt cache and returns a CachedContent
404
+ # that chats can attach with Chat#with_caching. Arguments are
405
+ # forwarded to CachedContent.create.
406
+ #
407
+ # cache = RubyLLM.cache(big_document, model: 'gemini-3.7-flash', ttl: 3600)
408
+ # chat = RubyLLM.chat(model: 'gemini-3.7-flash').with_caching(id: cache)
409
+ #
410
+ def cache(...)
411
+ CachedContent.create(...)
412
+ end
413
+
414
+ # Renders the ERB prompt template +name+ and returns the result as a
415
+ # String. The name resolves to a <tt>.txt.erb</tt> file under
416
+ # app/prompts. Keyword arguments become locals in the template.
417
+ #
418
+ # instructions = RubyLLM.render_prompt(
419
+ # "support/instructions",
420
+ # product_name: "BillingHub"
421
+ # )
422
+ # chat.with_instructions(instructions)
423
+ #
424
+ # Raises PromptNotFoundError if the template file does not exist.
425
+ def render_prompt(name, **locals)
426
+ Prompt.render(name, **locals)
427
+ end
428
+
429
+ # Returns the Models registry, used to browse, find, and refresh model
430
+ # metadata.
431
+ #
432
+ # RubyLLM.models.find("claude-haiku-4-5")
433
+ # RubyLLM.models.refresh
434
+ #
78
435
  def models
79
436
  Models.instance
80
437
  end
81
438
 
439
+ # Returns the registered provider classes.
440
+ #
441
+ # RubyLLM.providers.map(&:slug)
442
+ # # => ["anthropic", "azure", "bedrock", ...]
443
+ #
82
444
  def providers
83
445
  Provider.providers.values
84
446
  end
85
447
 
448
+ # Yields the global configuration for block-style setup. Call this once
449
+ # at startup to set API keys and defaults.
450
+ #
451
+ # RubyLLM.configure do |config|
452
+ # config.openai_api_key = ENV['OPENAI_API_KEY']
453
+ # end
454
+ #
86
455
  def configure
87
456
  yield config
88
457
  end
89
458
 
459
+ # Returns the global Configuration instance.
90
460
  def config
91
461
  @config ||= Configuration.new
92
462
  end
93
463
 
94
- def logger
464
+ def logger # :nodoc:
95
465
  @logger ||= config.logger || Logger.new(
96
466
  config.log_file,
97
467
  progname: 'RubyLLM',
@@ -104,11 +474,15 @@ end
104
474
  RubyLLM::Provider.register :anthropic, RubyLLM::Providers::Anthropic
105
475
  RubyLLM::Provider.register :azure, RubyLLM::Providers::Azure
106
476
  RubyLLM::Provider.register :bedrock, RubyLLM::Providers::Bedrock
477
+ RubyLLM::Provider.register :cohere, RubyLLM::Providers::Cohere
478
+ RubyLLM::Provider.register :deepgram, RubyLLM::Providers::Deepgram
107
479
  RubyLLM::Provider.register :deepseek, RubyLLM::Providers::DeepSeek
480
+ RubyLLM::Provider.register :elevenlabs, RubyLLM::Providers::ElevenLabs
108
481
  RubyLLM::Provider.register :gemini, RubyLLM::Providers::Gemini
109
482
  RubyLLM::Provider.register :gpustack, RubyLLM::Providers::GPUStack
110
483
  RubyLLM::Provider.register :mistral, RubyLLM::Providers::Mistral
111
484
  RubyLLM::Provider.register :ollama, RubyLLM::Providers::Ollama
485
+ RubyLLM::Provider.register :ollama_cloud, RubyLLM::Providers::OllamaCloud
112
486
  RubyLLM::Provider.register :openai, RubyLLM::Providers::OpenAI
113
487
  RubyLLM::Provider.register :openrouter, RubyLLM::Providers::OpenRouter
114
488
  RubyLLM::Provider.register :perplexity, RubyLLM::Providers::Perplexity
@@ -1,23 +1,28 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- def run_test_queue_rspec
4
- workers = ENV.fetch('RSPEC_WORKERS', nil)
5
- env = {}
6
- env['TEST_QUEUE_WORKERS'] = workers if workers && !workers.empty? && ENV.fetch('TEST_QUEUE_WORKERS', '').empty?
3
+ namespace :ruby_llm do
4
+ namespace :upgrade do
5
+ %i[rollback resume finalize].each do |action|
6
+ desc "#{action.to_s.capitalize} a copy-mode RubyLLM upgrade with affected processes stopped"
7
+ task action => :environment do
8
+ require 'generators/ruby_llm/upgrade/upgrade_migration'
7
9
 
8
- system(env, 'bundle', 'exec', 'bin/rspec-queue')
9
- end
10
+ RubyLLM::Generators::UpgradeMigration.new.public_send(action)
11
+ puts "RubyLLM copy upgrade: #{action} completed"
12
+ end
13
+ end
14
+ end
10
15
 
11
- namespace :ruby_llm do
12
- desc 'Load models from models.json into the database'
16
+ desc 'Load the selected model registry into the database'
13
17
  task load_models: :environment do
14
- if RubyLLM.config.model_registry_class
15
- RubyLLM.models.load_from_json!
16
- model_class = RubyLLM.config.model_registry_class.constantize
17
- model_class.save_to_database
18
- puts "✅ Loaded #{model_class.count} models into database"
19
- else
20
- puts 'Model registry not configured. Run bin/rails generate ruby_llm:install'
21
- end
18
+ # Rails 8.1 loads ActiveRecord::Base lazily, so outside a runner or console
19
+ # the :active_record load hook that defines RubyLLM::ActiveRecord::Model
20
+ # never fires. Requiring it here runs the hook; it is a no-op if it already did.
21
+ require 'active_record/base'
22
+
23
+ RubyLLM.models.load_from_json
24
+ model_class = RubyLLM::ActiveRecord::Model
25
+ model_class.save_to_database
26
+ puts "✅ Loaded #{model_class.count} models into database"
22
27
  end
23
28
  end