ruby_llm 1.15.0 → 2.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (470) hide show
  1. checksums.yaml +4 -4
  2. data/.rdoc_options +25 -0
  3. data/README.md +87 -33
  4. data/exe/ruby_llm +8 -0
  5. data/lib/generators/ruby_llm/agent/templates/agent.rb.tt +0 -1
  6. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +3 -43
  7. data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +11 -3
  8. data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +8 -0
  9. data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +4 -4
  10. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt +1 -1
  11. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +1 -1
  12. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt +1 -1
  13. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +2 -2
  14. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +1 -1
  15. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +1 -1
  16. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool.html.erb.tt +1 -1
  17. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +6 -4
  18. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +1 -1
  19. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +2 -2
  20. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt +5 -6
  21. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt +2 -2
  22. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt +5 -5
  23. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +1 -1
  24. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +1 -1
  25. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +1 -1
  26. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +2 -2
  27. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +1 -1
  28. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_system.html.erb.tt +1 -1
  29. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool.html.erb.tt +1 -1
  30. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +6 -4
  31. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_user.html.erb.tt +1 -1
  32. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +4 -6
  33. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +2 -2
  34. data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +5 -6
  35. data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +2 -2
  36. data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +3 -3
  37. data/lib/generators/ruby_llm/generator_helpers.rb +106 -62
  38. data/lib/generators/ruby_llm/install/install_generator.rb +3 -11
  39. data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +2 -0
  40. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +14 -6
  41. data/lib/generators/ruby_llm/install/templates/create_ruby_llm_records_migration.rb.tt +117 -0
  42. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +0 -8
  43. data/lib/generators/ruby_llm/provider/cli.rb +175 -0
  44. data/lib/generators/ruby_llm/provider/scaffold.rb +323 -0
  45. data/lib/generators/ruby_llm/provider/templates/core/provider.rb.erb +37 -0
  46. data/lib/generators/ruby_llm/provider/templates/core/provider_spec.rb.erb +34 -0
  47. data/lib/generators/ruby_llm/provider/templates/gem/archspec.rb.erb +14 -0
  48. data/lib/generators/ruby_llm/provider/templates/gem/bin/console.erb +15 -0
  49. data/lib/generators/ruby_llm/provider/templates/gem/bin/setup.erb +5 -0
  50. data/lib/generators/ruby_llm/provider/templates/gem/chat_schema_spec.rb.erb +30 -0
  51. data/lib/generators/ruby_llm/provider/templates/gem/chat_spec.rb.erb +35 -0
  52. data/lib/generators/ruby_llm/provider/templates/gem/chat_streaming_spec.rb.erb +22 -0
  53. data/lib/generators/ruby_llm/provider/templates/gem/chat_tools_spec.rb.erb +30 -0
  54. data/lib/generators/ruby_llm/provider/templates/gem/ci.yml.erb +32 -0
  55. data/lib/generators/ruby_llm/provider/templates/gem/embedding_spec.rb.erb +49 -0
  56. data/lib/generators/ruby_llm/provider/templates/gem/env.erb +2 -0
  57. data/lib/generators/ruby_llm/provider/templates/gem/fixtures_gitkeep.erb +1 -0
  58. data/lib/generators/ruby_llm/provider/templates/gem/flayignore.erb +1 -0
  59. data/lib/generators/ruby_llm/provider/templates/gem/gemfile.erb +25 -0
  60. data/lib/generators/ruby_llm/provider/templates/gem/gemspec.erb +28 -0
  61. data/lib/generators/ruby_llm/provider/templates/gem/gitignore.erb +7 -0
  62. data/lib/generators/ruby_llm/provider/templates/gem/gitleaks.yml.erb +22 -0
  63. data/lib/generators/ruby_llm/provider/templates/gem/image_spec.rb.erb +23 -0
  64. data/lib/generators/ruby_llm/provider/templates/gem/license.erb +21 -0
  65. data/lib/generators/ruby_llm/provider/templates/gem/models.rb.erb +19 -0
  66. data/lib/generators/ruby_llm/provider/templates/gem/models_spec.rb.erb +17 -0
  67. data/lib/generators/ruby_llm/provider/templates/gem/moderation_spec.rb.erb +22 -0
  68. data/lib/generators/ruby_llm/provider/templates/gem/overcommit.yml.erb +31 -0
  69. data/lib/generators/ruby_llm/provider/templates/gem/provider.rb.erb +52 -0
  70. data/lib/generators/ruby_llm/provider/templates/gem/provider_spec.rb.erb +38 -0
  71. data/lib/generators/ruby_llm/provider/templates/gem/rakefile.erb +38 -0
  72. data/lib/generators/ruby_llm/provider/templates/gem/readme.md.erb +50 -0
  73. data/lib/generators/ruby_llm/provider/templates/gem/release.yml.erb +36 -0
  74. data/lib/generators/ruby_llm/provider/templates/gem/rerank_spec.rb.erb +23 -0
  75. data/lib/generators/ruby_llm/provider/templates/gem/rspec.erb +2 -0
  76. data/lib/generators/ruby_llm/provider/templates/gem/rubocop.yml.erb +29 -0
  77. data/lib/generators/ruby_llm/provider/templates/gem/rubyllm_configuration.rb.erb +14 -0
  78. data/lib/generators/ruby_llm/provider/templates/gem/spec_helper.rb.erb +26 -0
  79. data/lib/generators/ruby_llm/provider/templates/gem/speech_spec.rb.erb +25 -0
  80. data/lib/generators/ruby_llm/provider/templates/gem/vcr_configuration.rb.erb +16 -0
  81. data/lib/generators/ruby_llm/provider/templates/gem/video_spec.rb.erb +27 -0
  82. data/lib/generators/ruby_llm/schema/schema_generator.rb +5 -1
  83. data/lib/generators/ruby_llm/schema/templates/schema.rb.tt +1 -1
  84. data/lib/generators/ruby_llm/tool/templates/tailwind/tool_call.html.erb.tt +13 -0
  85. data/lib/generators/ruby_llm/tool/templates/tailwind/tool_result.html.erb.tt +21 -0
  86. data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +3 -3
  87. data/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt +7 -12
  88. data/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt +5 -2
  89. data/lib/generators/ruby_llm/tool/tool_generator.rb +25 -59
  90. data/lib/generators/ruby_llm/upgrade/templates/backfill_v2_data.rb.tt +461 -0
  91. data/lib/generators/ruby_llm/upgrade/templates/cleanup_v2_upgrade.rb.tt +100 -0
  92. data/lib/generators/ruby_llm/upgrade/templates/finish_v2_upgrade.rb.tt +215 -0
  93. data/lib/generators/ruby_llm/upgrade/templates/prepare_v2_upgrade.rb.tt +660 -0
  94. data/lib/generators/ruby_llm/upgrade/templates/ruby_llm_upgrade.rb.tt +222 -0
  95. data/lib/generators/ruby_llm/upgrade/templates/upgrade_initializer.rb.tt +13 -0
  96. data/lib/generators/ruby_llm/upgrade/upgrade_generator.rb +167 -0
  97. data/lib/generators/ruby_llm/upgrade/upgrade_migration.rb +344 -0
  98. data/lib/ruby_llm/accounting/usage.rb +245 -0
  99. data/lib/ruby_llm/active_record/acts_as.rb +94 -136
  100. data/lib/ruby_llm/active_record/attachment_helpers.rb +180 -0
  101. data/lib/ruby_llm/active_record/batch.rb +97 -0
  102. data/lib/ruby_llm/active_record/chat_methods.rb +823 -305
  103. data/lib/ruby_llm/active_record/message_methods.rb +119 -75
  104. data/lib/ruby_llm/active_record/model.rb +135 -0
  105. data/lib/ruby_llm/active_record/payload_helpers.rb +1 -2
  106. data/lib/ruby_llm/active_record/tool_call.rb +33 -0
  107. data/lib/ruby_llm/active_record/usage.rb +61 -0
  108. data/lib/ruby_llm/agent.rb +1065 -150
  109. data/lib/ruby_llm/aliases.json +338 -167
  110. data/lib/ruby_llm/attachment.rb +217 -61
  111. data/lib/ruby_llm/batch.rb +432 -0
  112. data/lib/ruby_llm/cached_content.rb +112 -0
  113. data/lib/ruby_llm/chat/tool_concurrency.rb +111 -0
  114. data/lib/ruby_llm/chat.rb +1208 -150
  115. data/lib/ruby_llm/chunk.rb +10 -0
  116. data/lib/ruby_llm/citation.rb +105 -0
  117. data/lib/ruby_llm/configuration.rb +274 -24
  118. data/lib/ruby_llm/context.rb +128 -6
  119. data/lib/ruby_llm/cost.rb +217 -80
  120. data/lib/ruby_llm/downloaded_file.rb +33 -0
  121. data/lib/ruby_llm/embedding.rb +141 -7
  122. data/lib/ruby_llm/embedding_request.rb +53 -0
  123. data/lib/ruby_llm/error.rb +161 -89
  124. data/lib/ruby_llm/fallback.rb +133 -0
  125. data/lib/ruby_llm/files/mime_type.rb +97 -0
  126. data/lib/ruby_llm/image.rb +155 -32
  127. data/lib/ruby_llm/message.rb +233 -54
  128. data/lib/ruby_llm/model/modalities.rb +17 -4
  129. data/lib/ruby_llm/model/pricing.rb +43 -14
  130. data/lib/ruby_llm/model/pricing_category.rb +103 -14
  131. data/lib/ruby_llm/model/pricing_tier.rb +66 -15
  132. data/lib/ruby_llm/model.rb +244 -2
  133. data/lib/ruby_llm/models/aliases.rb +41 -0
  134. data/lib/ruby_llm/models/registry.rb +165 -0
  135. data/lib/ruby_llm/models/schema.rb +99 -0
  136. data/lib/ruby_llm/models.json +70380 -33380
  137. data/lib/ruby_llm/models.rb +528 -201
  138. data/lib/ruby_llm/moderation.rb +139 -26
  139. data/lib/ruby_llm/ocr.rb +112 -0
  140. data/lib/ruby_llm/prompt.rb +79 -0
  141. data/lib/ruby_llm/protocol/binary_streaming.rb +65 -0
  142. data/lib/ruby_llm/protocol/stream_accumulator.rb +214 -0
  143. data/lib/ruby_llm/protocol/streaming.rb +230 -0
  144. data/lib/ruby_llm/protocol.rb +662 -0
  145. data/lib/ruby_llm/protocols/anthropic/batches.rb +73 -0
  146. data/lib/ruby_llm/protocols/anthropic/chat.rb +540 -0
  147. data/lib/ruby_llm/protocols/anthropic/embeddings.rb +14 -0
  148. data/lib/ruby_llm/protocols/anthropic/files.rb +38 -0
  149. data/lib/ruby_llm/protocols/anthropic/media.rb +141 -0
  150. data/lib/ruby_llm/protocols/anthropic/models.rb +129 -0
  151. data/lib/ruby_llm/protocols/anthropic/streaming.rb +166 -0
  152. data/lib/ruby_llm/{providers → protocols}/anthropic/tools.rb +43 -34
  153. data/lib/ruby_llm/protocols/anthropic.rb +100 -0
  154. data/lib/ruby_llm/protocols/azure/files.rb +16 -0
  155. data/lib/ruby_llm/protocols/bedrock/async_videos.rb +109 -0
  156. data/lib/ruby_llm/protocols/bedrock/batches.rb +129 -0
  157. data/lib/ruby_llm/protocols/bedrock/files.rb +110 -0
  158. data/lib/ruby_llm/protocols/bedrock/guardrails.rb +122 -0
  159. data/lib/ruby_llm/protocols/bedrock/rerank.rb +95 -0
  160. data/lib/ruby_llm/protocols/chat_completions/batches.rb +32 -0
  161. data/lib/ruby_llm/protocols/chat_completions/chat.rb +493 -0
  162. data/lib/ruby_llm/protocols/chat_completions/embedding_batches.rb +35 -0
  163. data/lib/ruby_llm/protocols/chat_completions/embeddings.rb +60 -0
  164. data/lib/ruby_llm/protocols/chat_completions/images.rb +127 -0
  165. data/lib/ruby_llm/protocols/chat_completions/media.rb +121 -0
  166. data/lib/ruby_llm/protocols/chat_completions/models.rb +39 -0
  167. data/lib/ruby_llm/protocols/chat_completions/moderation.rb +52 -0
  168. data/lib/ruby_llm/protocols/chat_completions/rerank.rb +56 -0
  169. data/lib/ruby_llm/protocols/chat_completions/speech.rb +40 -0
  170. data/lib/ruby_llm/protocols/chat_completions/streaming.rb +69 -0
  171. data/lib/ruby_llm/{providers/openai → protocols/chat_completions}/tools.rb +17 -17
  172. data/lib/ruby_llm/protocols/chat_completions/transcription.rb +150 -0
  173. data/lib/ruby_llm/protocols/chat_completions.rb +21 -0
  174. data/lib/ruby_llm/protocols/cohere/batch_requests.rb +75 -0
  175. data/lib/ruby_llm/protocols/cohere/batches.rb +98 -0
  176. data/lib/ruby_llm/protocols/cohere/chat.rb +227 -0
  177. data/lib/ruby_llm/protocols/cohere/datasets.rb +102 -0
  178. data/lib/ruby_llm/protocols/cohere/embeddings.rb +68 -0
  179. data/lib/ruby_llm/protocols/cohere/media.rb +77 -0
  180. data/lib/ruby_llm/protocols/cohere/models.rb +92 -0
  181. data/lib/ruby_llm/protocols/cohere/ocr.rb +63 -0
  182. data/lib/ruby_llm/protocols/cohere/rerank.rb +52 -0
  183. data/lib/ruby_llm/protocols/cohere/streaming.rb +105 -0
  184. data/lib/ruby_llm/protocols/cohere/tokenization.rb +22 -0
  185. data/lib/ruby_llm/protocols/cohere/tools.rb +132 -0
  186. data/lib/ruby_llm/protocols/cohere/transcription.rb +41 -0
  187. data/lib/ruby_llm/protocols/cohere.rb +21 -0
  188. data/lib/ruby_llm/protocols/converse/batches.rb +55 -0
  189. data/lib/ruby_llm/protocols/converse/chat.rb +685 -0
  190. data/lib/ruby_llm/protocols/converse/media.rb +178 -0
  191. data/lib/ruby_llm/protocols/converse/streaming.rb +424 -0
  192. data/lib/ruby_llm/protocols/converse.rb +54 -0
  193. data/lib/ruby_llm/protocols/deepgram/models.rb +74 -0
  194. data/lib/ruby_llm/protocols/deepgram/speech.rb +93 -0
  195. data/lib/ruby_llm/protocols/deepgram/streaming_transcription.rb +89 -0
  196. data/lib/ruby_llm/protocols/deepgram/transcription.rb +96 -0
  197. data/lib/ruby_llm/protocols/deepgram.rb +19 -0
  198. data/lib/ruby_llm/protocols/deepseek/files.rb +31 -0
  199. data/lib/ruby_llm/protocols/elevenlabs/assets.rb +36 -0
  200. data/lib/ruby_llm/protocols/elevenlabs/flows/images.rb +74 -0
  201. data/lib/ruby_llm/protocols/elevenlabs/flows/media.rb +42 -0
  202. data/lib/ruby_llm/protocols/elevenlabs/flows/videos.rb +93 -0
  203. data/lib/ruby_llm/protocols/elevenlabs/flows.rb +14 -0
  204. data/lib/ruby_llm/protocols/elevenlabs/models.rb +64 -0
  205. data/lib/ruby_llm/protocols/elevenlabs/speech.rb +67 -0
  206. data/lib/ruby_llm/protocols/elevenlabs/streaming_transcription.rb +127 -0
  207. data/lib/ruby_llm/protocols/elevenlabs/transcription.rb +61 -0
  208. data/lib/ruby_llm/protocols/elevenlabs.rb +15 -0
  209. data/lib/ruby_llm/protocols/files.rb +119 -0
  210. data/lib/ruby_llm/protocols/gemini/batches.rb +162 -0
  211. data/lib/ruby_llm/protocols/gemini/caches.rb +59 -0
  212. data/lib/ruby_llm/protocols/gemini/chat.rb +453 -0
  213. data/lib/ruby_llm/protocols/gemini/embedding_batches.rb +86 -0
  214. data/lib/ruby_llm/protocols/gemini/embeddings.rb +70 -0
  215. data/lib/ruby_llm/protocols/gemini/file_transcription.rb +32 -0
  216. data/lib/ruby_llm/protocols/gemini/files.rb +115 -0
  217. data/lib/ruby_llm/protocols/gemini/images.rb +183 -0
  218. data/lib/ruby_llm/protocols/gemini/live_transcription.rb +140 -0
  219. data/lib/ruby_llm/{providers → protocols}/gemini/media.rb +33 -20
  220. data/lib/ruby_llm/protocols/gemini/models.rb +71 -0
  221. data/lib/ruby_llm/protocols/gemini/speech.rb +56 -0
  222. data/lib/ruby_llm/protocols/gemini/streaming.rb +96 -0
  223. data/lib/ruby_llm/protocols/gemini/tools.rb +157 -0
  224. data/lib/ruby_llm/{providers → protocols}/gemini/transcription.rb +22 -22
  225. data/lib/ruby_llm/protocols/gemini/videos.rb +103 -0
  226. data/lib/ruby_llm/protocols/gemini.rb +35 -0
  227. data/lib/ruby_llm/protocols/gpustack/responses.rb +111 -0
  228. data/lib/ruby_llm/protocols/gpustack/tokenization.rb +22 -0
  229. data/lib/ruby_llm/protocols/gpustack/videos.rb +96 -0
  230. data/lib/ruby_llm/protocols/interactions/chat.rb +145 -0
  231. data/lib/ruby_llm/protocols/interactions/content.rb +90 -0
  232. data/lib/ruby_llm/protocols/interactions/streaming.rb +91 -0
  233. data/lib/ruby_llm/protocols/interactions/tools.rb +51 -0
  234. data/lib/ruby_llm/protocols/interactions/transcription.rb +58 -0
  235. data/lib/ruby_llm/protocols/interactions.rb +29 -0
  236. data/lib/ruby_llm/protocols/invoke_model/cohere_embeddings.rb +51 -0
  237. data/lib/ruby_llm/protocols/invoke_model/embedding_batches.rb +111 -0
  238. data/lib/ruby_llm/protocols/invoke_model/nova_embeddings.rb +50 -0
  239. data/lib/ruby_llm/protocols/invoke_model/stability_images.rb +103 -0
  240. data/lib/ruby_llm/protocols/invoke_model/titan_multimodal_embeddings.rb +33 -0
  241. data/lib/ruby_llm/protocols/invoke_model/titan_text_embeddings.rb +44 -0
  242. data/lib/ruby_llm/protocols/invoke_model.rb +57 -0
  243. data/lib/ruby_llm/protocols/mistral/content.rb +49 -0
  244. data/lib/ruby_llm/protocols/mistral/conversations/chat.rb +160 -0
  245. data/lib/ruby_llm/protocols/mistral/conversations/images.rb +43 -0
  246. data/lib/ruby_llm/protocols/mistral/conversations/streaming.rb +83 -0
  247. data/lib/ruby_llm/protocols/mistral/conversations.rb +30 -0
  248. data/lib/ruby_llm/protocols/mistral/files.rb +36 -0
  249. data/lib/ruby_llm/protocols/mistral/multi_completion.rb +160 -0
  250. data/lib/ruby_llm/protocols/openai/batches.rb +126 -0
  251. data/lib/ruby_llm/protocols/openai/files.rb +42 -0
  252. data/lib/ruby_llm/protocols/openrouter/batches.rb +147 -0
  253. data/lib/ruby_llm/protocols/openrouter/files.rb +24 -0
  254. data/lib/ruby_llm/protocols/openrouter/responses.rb +53 -0
  255. data/lib/ruby_llm/protocols/openrouter/transcription.rb +51 -0
  256. data/lib/ruby_llm/protocols/perplexity/files.rb +48 -0
  257. data/lib/ruby_llm/protocols/perplexity/router.rb +59 -0
  258. data/lib/ruby_llm/protocols/responses/approvals.rb +32 -0
  259. data/lib/ruby_llm/protocols/responses/batches.rb +32 -0
  260. data/lib/ruby_llm/protocols/responses/chat.rb +476 -0
  261. data/lib/ruby_llm/protocols/responses/compaction.rb +29 -0
  262. data/lib/ruby_llm/protocols/responses/media.rb +61 -0
  263. data/lib/ruby_llm/protocols/responses/streaming.rb +117 -0
  264. data/lib/ruby_llm/protocols/responses/token_counting.rb +26 -0
  265. data/lib/ruby_llm/protocols/responses/tools.rb +39 -0
  266. data/lib/ruby_llm/protocols/responses.rb +35 -0
  267. data/lib/ruby_llm/protocols/vertexai/batch_prediction.rb +155 -0
  268. data/lib/ruby_llm/protocols/vertexai/embedding_prediction/requests.rb +85 -0
  269. data/lib/ruby_llm/protocols/vertexai/embedding_prediction/results.rb +74 -0
  270. data/lib/ruby_llm/protocols/vertexai/embedding_prediction.rb +56 -0
  271. data/lib/ruby_llm/protocols/vertexai/files.rb +101 -0
  272. data/lib/ruby_llm/protocols/vertexai/ranking.rb +69 -0
  273. data/lib/ruby_llm/protocols/vertexai/research.rb +193 -0
  274. data/lib/ruby_llm/protocols/xai/files.rb +30 -0
  275. data/lib/ruby_llm/protocols/xai/streaming_transcription.rb +120 -0
  276. data/lib/ruby_llm/protocols/xai/tokenization.rb +23 -0
  277. data/lib/ruby_llm/provider.rb +565 -124
  278. data/lib/ruby_llm/providers/anthropic/capabilities.rb +5 -7
  279. data/lib/ruby_llm/providers/anthropic.rb +4 -6
  280. data/lib/ruby_llm/providers/azure/audio.rb +18 -0
  281. data/lib/ruby_llm/providers/azure/capabilities.rb +16 -0
  282. data/lib/ruby_llm/providers/azure/chat.rb +2 -9
  283. data/lib/ruby_llm/providers/azure/chat_completions/batches.rb +29 -0
  284. data/lib/ruby_llm/providers/azure/chat_completions.rb +80 -0
  285. data/lib/ruby_llm/providers/azure/cohere.rb +33 -0
  286. data/lib/ruby_llm/providers/azure/embeddings.rb +3 -2
  287. data/lib/ruby_llm/providers/azure/images.rb +22 -0
  288. data/lib/ruby_llm/providers/azure/media.rb +6 -15
  289. data/lib/ruby_llm/providers/azure/models.rb +35 -0
  290. data/lib/ruby_llm/providers/azure/responses.rb +26 -0
  291. data/lib/ruby_llm/providers/azure/videos.rb +64 -0
  292. data/lib/ruby_llm/providers/azure.rb +77 -78
  293. data/lib/ruby_llm/providers/bedrock/auth.rb +61 -41
  294. data/lib/ruby_llm/providers/bedrock/capabilities.rb +18 -0
  295. data/lib/ruby_llm/providers/bedrock/mantle/anthropic.rb +39 -0
  296. data/lib/ruby_llm/providers/bedrock/mantle/chat_completions.rb +23 -0
  297. data/lib/ruby_llm/providers/bedrock/mantle/responses.rb +24 -0
  298. data/lib/ruby_llm/providers/bedrock/mantle/voxtral.rb +96 -0
  299. data/lib/ruby_llm/providers/bedrock/mantle.rb +57 -0
  300. data/lib/ruby_llm/providers/bedrock/models.rb +194 -42
  301. data/lib/ruby_llm/providers/bedrock.rb +217 -46
  302. data/lib/ruby_llm/providers/cohere.rb +31 -0
  303. data/lib/ruby_llm/providers/deepgram.rb +37 -0
  304. data/lib/ruby_llm/providers/deepseek/capabilities.rb +4 -8
  305. data/lib/ruby_llm/providers/deepseek/chat.rb +56 -0
  306. data/lib/ruby_llm/providers/deepseek/responses.rb +68 -0
  307. data/lib/ruby_llm/providers/deepseek.rb +9 -2
  308. data/lib/ruby_llm/providers/elevenlabs.rb +35 -0
  309. data/lib/ruby_llm/providers/gemini/capabilities.rb +8 -107
  310. data/lib/ruby_llm/providers/gemini.rb +15 -8
  311. data/lib/ruby_llm/providers/gpustack/chat.rb +2 -9
  312. data/lib/ruby_llm/providers/gpustack/embeddings.rb +28 -0
  313. data/lib/ruby_llm/providers/gpustack/media.rb +17 -16
  314. data/lib/ruby_llm/providers/gpustack/models.rb +72 -60
  315. data/lib/ruby_llm/providers/gpustack/speech.rb +15 -0
  316. data/lib/ruby_llm/providers/gpustack/transcription.rb +29 -0
  317. data/lib/ruby_llm/providers/gpustack.rb +35 -11
  318. data/lib/ruby_llm/providers/mistral/capabilities.rb +7 -155
  319. data/lib/ruby_llm/providers/mistral/chat.rb +37 -61
  320. data/lib/ruby_llm/providers/mistral/chat_completions/batches.rb +120 -0
  321. data/lib/ruby_llm/providers/mistral/chat_completions.rb +21 -0
  322. data/lib/ruby_llm/providers/mistral/conversations.rb +12 -0
  323. data/lib/ruby_llm/providers/mistral/embeddings.rb +6 -4
  324. data/lib/ruby_llm/providers/mistral/media.rb +43 -0
  325. data/lib/ruby_llm/providers/mistral/models.rb +57 -21
  326. data/lib/ruby_llm/providers/mistral/ocr.rb +47 -0
  327. data/lib/ruby_llm/providers/mistral/speech.rb +51 -0
  328. data/lib/ruby_llm/providers/mistral/transcription.rb +62 -0
  329. data/lib/ruby_llm/providers/mistral.rb +18 -6
  330. data/lib/ruby_llm/providers/ollama/chat.rb +9 -8
  331. data/lib/ruby_llm/providers/ollama/media.rb +6 -15
  332. data/lib/ruby_llm/providers/ollama/models.rb +50 -9
  333. data/lib/ruby_llm/providers/ollama.rb +9 -8
  334. data/lib/ruby_llm/providers/ollama_cloud/models.rb +14 -0
  335. data/lib/ruby_llm/providers/ollama_cloud.rb +40 -0
  336. data/lib/ruby_llm/providers/openai/capabilities.rb +54 -259
  337. data/lib/ruby_llm/providers/openai/models.rb +23 -23
  338. data/lib/ruby_llm/providers/openai/responses.rb +13 -0
  339. data/lib/ruby_llm/providers/openai.rb +92 -11
  340. data/lib/ruby_llm/providers/openrouter/chat.rb +130 -104
  341. data/lib/ruby_llm/providers/openrouter/embeddings.rb +51 -0
  342. data/lib/ruby_llm/providers/openrouter/images.rb +44 -43
  343. data/lib/ruby_llm/providers/openrouter/media.rb +34 -0
  344. data/lib/ruby_llm/providers/openrouter/models.rb +50 -11
  345. data/lib/ruby_llm/providers/openrouter/speech.rb +32 -0
  346. data/lib/ruby_llm/providers/openrouter/streaming.rb +31 -38
  347. data/lib/ruby_llm/providers/openrouter/videos.rb +81 -0
  348. data/lib/ruby_llm/providers/openrouter.rb +78 -20
  349. data/lib/ruby_llm/providers/perplexity/chat.rb +4 -0
  350. data/lib/ruby_llm/providers/perplexity/embeddings.rb +32 -0
  351. data/lib/ruby_llm/providers/perplexity/media.rb +46 -0
  352. data/lib/ruby_llm/providers/perplexity/models.rb +80 -13
  353. data/lib/ruby_llm/providers/perplexity.rb +29 -21
  354. data/lib/ruby_llm/providers/vertexai/anthropic/batches.rb +52 -0
  355. data/lib/ruby_llm/providers/vertexai/anthropic.rb +34 -0
  356. data/lib/ruby_llm/providers/vertexai/capabilities.rb +19 -0
  357. data/lib/ruby_llm/providers/vertexai/chat_completions/batches.rb +54 -0
  358. data/lib/ruby_llm/providers/vertexai/chat_completions.rb +15 -0
  359. data/lib/ruby_llm/providers/vertexai/embed_content.rb +42 -0
  360. data/lib/ruby_llm/providers/vertexai/embeddings.rb +22 -7
  361. data/lib/ruby_llm/providers/vertexai/gemini/batches.rb +42 -0
  362. data/lib/ruby_llm/providers/vertexai/gemini.rb +69 -0
  363. data/lib/ruby_llm/providers/vertexai/live_transcription.rb +24 -0
  364. data/lib/ruby_llm/providers/vertexai/mistral.rb +28 -0
  365. data/lib/ruby_llm/providers/vertexai/models.rb +145 -43
  366. data/lib/ruby_llm/providers/vertexai/transcription.rb +49 -4
  367. data/lib/ruby_llm/providers/vertexai/videos.rb +61 -0
  368. data/lib/ruby_llm/providers/vertexai.rb +164 -17
  369. data/lib/ruby_llm/providers/xai/capabilities.rb +18 -0
  370. data/lib/ruby_llm/providers/xai/chat.rb +10 -0
  371. data/lib/ruby_llm/providers/xai/chat_completions/batches.rb +108 -0
  372. data/lib/ruby_llm/providers/xai/chat_completions.rb +19 -0
  373. data/lib/ruby_llm/providers/xai/images.rb +91 -0
  374. data/lib/ruby_llm/providers/xai/models.rb +32 -48
  375. data/lib/ruby_llm/providers/xai/reported_cost.rb +18 -0
  376. data/lib/ruby_llm/providers/xai/responses.rb +52 -0
  377. data/lib/ruby_llm/providers/xai/speech.rb +45 -0
  378. data/lib/ruby_llm/providers/xai/transcription.rb +48 -0
  379. data/lib/ruby_llm/providers/xai/videos.rb +87 -0
  380. data/lib/ruby_llm/providers/xai.rb +17 -7
  381. data/lib/ruby_llm/railtie.rb +11 -16
  382. data/lib/ruby_llm/rerank.rb +105 -0
  383. data/lib/ruby_llm/research_job.rb +241 -0
  384. data/lib/ruby_llm/search_results.rb +68 -0
  385. data/lib/ruby_llm/server_tool_call.rb +73 -0
  386. data/lib/ruby_llm/speech.rb +159 -0
  387. data/lib/ruby_llm/speech_chunk.rb +33 -0
  388. data/lib/ruby_llm/support/deprecator.rb +22 -0
  389. data/lib/ruby_llm/support/inspectable.rb +49 -0
  390. data/lib/ruby_llm/support/instrumentation.rb +41 -0
  391. data/lib/ruby_llm/support/utils.rb +147 -0
  392. data/lib/ruby_llm/thinking.rb +127 -20
  393. data/lib/ruby_llm/tokenization.rb +59 -0
  394. data/lib/ruby_llm/tokens.rb +103 -33
  395. data/lib/ruby_llm/tool.rb +266 -91
  396. data/lib/ruby_llm/tool_call.rb +36 -3
  397. data/lib/ruby_llm/tools/server_tools.rb +109 -0
  398. data/lib/ruby_llm/transcription/wav_audio.rb +62 -0
  399. data/lib/ruby_llm/transcription.rb +139 -13
  400. data/lib/ruby_llm/transcription_chunk.rb +68 -0
  401. data/lib/ruby_llm/transport/connection.rb +193 -0
  402. data/lib/ruby_llm/transport/error_middleware.rb +131 -0
  403. data/lib/ruby_llm/transport/usage_middleware.rb +28 -0
  404. data/lib/ruby_llm/transport/websocket_connection.rb +220 -0
  405. data/lib/ruby_llm/uploaded_file.rb +144 -0
  406. data/lib/ruby_llm/version.rb +2 -1
  407. data/lib/ruby_llm/video.rb +136 -0
  408. data/lib/ruby_llm/video_job.rb +150 -0
  409. data/lib/ruby_llm/workflow.rb +91 -0
  410. data/lib/ruby_llm.rb +385 -4
  411. data/lib/tasks/ruby_llm.rake +21 -16
  412. data/skills/rubyllm/SKILL.md +81 -0
  413. data/skills/rubyllm/agents/openai.yaml +4 -0
  414. metadata +340 -92
  415. data/lib/generators/ruby_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt +0 -9
  416. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +0 -39
  417. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +0 -21
  418. data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +0 -3
  419. data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +0 -3
  420. data/lib/generators/ruby_llm/upgrade_to_v1_10/templates/add_v1_10_message_columns.rb.tt +0 -19
  421. data/lib/generators/ruby_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +0 -50
  422. data/lib/generators/ruby_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +0 -7
  423. data/lib/generators/ruby_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +0 -49
  424. data/lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt +0 -145
  425. data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +0 -122
  426. data/lib/generators/ruby_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +0 -15
  427. data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +0 -49
  428. data/lib/ruby_llm/active_record/acts_as_legacy.rb +0 -530
  429. data/lib/ruby_llm/active_record/model_methods.rb +0 -82
  430. data/lib/ruby_llm/active_record/tool_call_methods.rb +0 -18
  431. data/lib/ruby_llm/aliases.rb +0 -38
  432. data/lib/ruby_llm/connection.rb +0 -130
  433. data/lib/ruby_llm/content.rb +0 -77
  434. data/lib/ruby_llm/mime_type.rb +0 -71
  435. data/lib/ruby_llm/model/info.rb +0 -130
  436. data/lib/ruby_llm/models_schema.json +0 -171
  437. data/lib/ruby_llm/providers/anthropic/chat.rb +0 -257
  438. data/lib/ruby_llm/providers/anthropic/content.rb +0 -44
  439. data/lib/ruby_llm/providers/anthropic/embeddings.rb +0 -20
  440. data/lib/ruby_llm/providers/anthropic/media.rb +0 -92
  441. data/lib/ruby_llm/providers/anthropic/models.rb +0 -57
  442. data/lib/ruby_llm/providers/anthropic/streaming.rb +0 -69
  443. data/lib/ruby_llm/providers/bedrock/chat.rb +0 -403
  444. data/lib/ruby_llm/providers/bedrock/media.rb +0 -90
  445. data/lib/ruby_llm/providers/bedrock/streaming.rb +0 -322
  446. data/lib/ruby_llm/providers/gemini/chat.rb +0 -543
  447. data/lib/ruby_llm/providers/gemini/embeddings.rb +0 -37
  448. data/lib/ruby_llm/providers/gemini/images.rb +0 -47
  449. data/lib/ruby_llm/providers/gemini/models.rb +0 -38
  450. data/lib/ruby_llm/providers/gemini/streaming.rb +0 -96
  451. data/lib/ruby_llm/providers/gemini/tools.rb +0 -232
  452. data/lib/ruby_llm/providers/gpustack/capabilities.rb +0 -20
  453. data/lib/ruby_llm/providers/ollama/capabilities.rb +0 -20
  454. data/lib/ruby_llm/providers/openai/chat.rb +0 -221
  455. data/lib/ruby_llm/providers/openai/embeddings.rb +0 -33
  456. data/lib/ruby_llm/providers/openai/images.rb +0 -90
  457. data/lib/ruby_llm/providers/openai/media.rb +0 -84
  458. data/lib/ruby_llm/providers/openai/moderation.rb +0 -34
  459. data/lib/ruby_llm/providers/openai/streaming.rb +0 -53
  460. data/lib/ruby_llm/providers/openai/temperature.rb +0 -28
  461. data/lib/ruby_llm/providers/openai/transcription.rb +0 -70
  462. data/lib/ruby_llm/providers/perplexity/capabilities.rb +0 -72
  463. data/lib/ruby_llm/providers/vertexai/chat.rb +0 -14
  464. data/lib/ruby_llm/providers/vertexai/streaming.rb +0 -14
  465. data/lib/ruby_llm/stream_accumulator.rb +0 -203
  466. data/lib/ruby_llm/streaming.rb +0 -175
  467. data/lib/ruby_llm/utils.rb +0 -91
  468. data/lib/tasks/models.rake +0 -565
  469. data/lib/tasks/release.rake +0 -67
  470. data/lib/tasks/vcr.rake +0 -124
@@ -1,6 +1,16 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyLLM
4
+ # A Chunk is one streamed fragment of an assistant response. Chunks are
5
+ # yielded to the block passed to Chat#ask or Chat#complete when streaming.
6
+ # A Chunk is a Message, so it responds to the same readers, but it holds
7
+ # only the data received in that part of the stream, such as a piece of
8
+ # content, partial tool calls, or the finish reason on the final chunk.
9
+ #
10
+ # chat.ask "Write a haiku about Ruby" do |chunk|
11
+ # print chunk.content
12
+ # end
13
+ #
4
14
  class Chunk < Message
5
15
  end
6
16
  end
@@ -0,0 +1,105 @@
1
+ # frozen_string_literal: true
2
+
3
+ module RubyLLM
4
+ # A Citation links a span of generated text to the source material that
5
+ # supports it. Providers return citations in different shapes. RubyLLM
6
+ # normalizes all of them into Citation objects on Message#citations.
7
+ # Fields a provider does not report are +nil+.
8
+ #
9
+ # chat = RubyLLM.chat(model: 'claude-sonnet-5').with_citations
10
+ # response = chat.ask "Who created Ruby?", with: "facts.txt"
11
+ #
12
+ # response.citations.each do |citation|
13
+ # citation.title # => "facts.txt"
14
+ # citation.cited_text # => the quoted passage from facts.txt
15
+ # citation.text # => the span of the answer it supports
16
+ # end
17
+ #
18
+ class Citation
19
+ include Support::Inspectable
20
+
21
+ # The URL of the cited source, when citing the web, or +nil+.
22
+ attr_reader :url
23
+
24
+ # The title or filename of the cited source, or +nil+.
25
+ attr_reader :title
26
+
27
+ # The quoted snippet from the source material, or +nil+.
28
+ attr_reader :cited_text
29
+
30
+ # The span of the response content this citation supports, or +nil+.
31
+ attr_reader :text
32
+
33
+ # The character offset where the cited span starts in the response
34
+ # content, or +nil+.
35
+ #
36
+ # response.content[citation.start_index...citation.end_index] == citation.text # => true
37
+ #
38
+ attr_reader :start_index
39
+
40
+ # The character offset where the cited span ends in the response
41
+ # content, or +nil+.
42
+ attr_reader :end_index
43
+
44
+ # The provider's identifier for the cited source, or +nil+.
45
+ attr_reader :source_id
46
+
47
+ # The 0-indexed position of the source document or search result,
48
+ # or +nil+.
49
+ attr_reader :source_index
50
+
51
+ # The first page of a PDF citation (1-indexed, inclusive), or +nil+.
52
+ attr_reader :start_page
53
+
54
+ # The last page of a PDF citation (1-indexed, inclusive), or +nil+.
55
+ attr_reader :end_page
56
+
57
+ def initialize(options = {}) # :nodoc:
58
+ @url = options[:url]
59
+ @title = options[:title]
60
+ @cited_text = options[:cited_text]
61
+ @text = options[:text]
62
+ @start_index = options[:start_index]
63
+ @end_index = options[:end_index]
64
+ @source_id = options[:source_id]
65
+ @source_index = options[:source_index]
66
+ @start_page = options[:start_page]
67
+ @end_page = options[:end_page]
68
+ end
69
+
70
+ def self.from_h(data) # :nodoc:
71
+ new(Support::Utils.deep_symbolize_keys(data))
72
+ end
73
+
74
+ def inspect_attributes # :nodoc:
75
+ { url: url, title: title, start_index: start_index, end_index: end_index }
76
+ end
77
+
78
+ # Returns the citation as a Hash with symbol keys, omitting +nil+ fields.
79
+ def to_h
80
+ {
81
+ url: url,
82
+ title: title,
83
+ cited_text: cited_text,
84
+ text: text,
85
+ start_index: start_index,
86
+ end_index: end_index,
87
+ source_id: source_id,
88
+ source_index: source_index,
89
+ start_page: start_page,
90
+ end_page: end_page
91
+ }.compact
92
+ end
93
+
94
+ # Returns +true+ if +other+ is a Citation with the same attributes,
95
+ # +false+ otherwise.
96
+ def ==(other)
97
+ other.is_a?(Citation) && to_h == other.to_h
98
+ end
99
+ alias eql? == # :nodoc:
100
+
101
+ def hash # :nodoc:
102
+ to_h.hash
103
+ end
104
+ end
105
+ end
@@ -1,23 +1,51 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'logger'
4
+
3
5
  module RubyLLM
4
- # Global configuration for RubyLLM
6
+ # A Configuration holds every RubyLLM setting: provider credentials,
7
+ # default models, timeouts, retries, logging, and the model registry.
8
+ # The global instance is yielded by RubyLLM.configure and available as
9
+ # RubyLLM.config.
10
+ #
11
+ # RubyLLM.configure do |config|
12
+ # config.openai_api_key = ENV['OPENAI_API_KEY']
13
+ # config.anthropic_api_key = ENV['ANTHROPIC_API_KEY']
14
+ # end
15
+ #
16
+ # RubyLLM.context yields an isolated copy for per-request or per-tenant
17
+ # overrides.
18
+ #
19
+ # Provider credentials such as +openai_api_key+ are declared by each
20
+ # provider. See https://rubyllm.com/next/configuration-providers/ for
21
+ # provider credentials and endpoint settings.
22
+ #
23
+ # Assigning an empty or whitespace-only string to any option stores
24
+ # +nil+, so unset environment variables behave as if the option was
25
+ # never set.
5
26
  class Configuration
6
27
  class << self
7
- # Declare a single configuration option.
8
- def option(key, default = nil)
28
+ def option(key, default = nil) # :nodoc:
9
29
  key = key.to_sym
10
30
  return if options.include?(key)
11
31
 
12
- send(:attr_accessor, key)
32
+ attr_reader key
33
+
34
+ define_method("#{key}=") do |value|
35
+ value = nil if value.is_a?(String) && value.strip.empty?
36
+ instance_variable_set(:"@#{key}", value)
37
+ end
38
+
13
39
  option_keys << key
14
40
  defaults[key] = default
15
41
  end
16
42
 
17
- def register_provider_options(options)
43
+ def register_provider_options(options) # :nodoc:
18
44
  Array(options).each { |key| option(key, nil) }
19
45
  end
20
46
 
47
+ # Returns the names of all declared options as an array of symbols,
48
+ # including options registered by providers.
21
49
  def options
22
50
  option_keys.dup
23
51
  end
@@ -32,50 +60,272 @@ module RubyLLM
32
60
  # System-level options are declared here.
33
61
  # Provider-specific options are declared in each provider class via
34
62
  # `self.configuration_options` and registered through Provider.register.
35
- option :default_model, 'gpt-5.4'
63
+
64
+ ##
65
+ # :attr_accessor: default_model
66
+ #
67
+ # The model id used by RubyLLM.chat when no model is given.
68
+ # Default: <tt>'gpt-5.6'</tt>.
69
+ option :default_model, 'gpt-5.6'
70
+
71
+ ##
72
+ # :attr_accessor: default_embedding_model
73
+ #
74
+ # The model id used by RubyLLM.embed when no model is given.
75
+ # Default: <tt>'text-embedding-3-small'</tt>.
36
76
  option :default_embedding_model, 'text-embedding-3-small'
77
+
78
+ ##
79
+ # :attr_accessor: default_moderation_model
80
+ #
81
+ # The model id used by RubyLLM.moderate when no model is given.
82
+ # Default: <tt>'omni-moderation-latest'</tt>.
37
83
  option :default_moderation_model, 'omni-moderation-latest'
38
- option :default_image_model, 'gpt-image-1.5'
39
- option :default_transcription_model, 'whisper-1'
40
84
 
41
- option :model_registry_file, -> { File.expand_path('models.json', __dir__) }
42
- option :model_registry_class, 'Model'
85
+ ##
86
+ # :attr_accessor: default_image_model
87
+ #
88
+ # The model id used by RubyLLM.paint when no model is given.
89
+ # Default: <tt>'gpt-image-2'</tt>.
90
+ option :default_image_model, 'gpt-image-2'
91
+
92
+ ##
93
+ # :attr_accessor: default_speech_model
94
+ #
95
+ # The model id used by RubyLLM.speak when no model is given.
96
+ # Default: <tt>'gpt-4o-mini-tts-2025-12-15'</tt>.
97
+ option :default_speech_model, 'gpt-4o-mini-tts-2025-12-15'
98
+
99
+ ##
100
+ # :attr_accessor: default_transcription_model
101
+ #
102
+ # The model id used by RubyLLM.transcribe when no model is given.
103
+ # Default: <tt>'gpt-transcribe'</tt>.
104
+ option :default_transcription_model, 'gpt-transcribe'
105
+
106
+ ##
107
+ # :attr_accessor: default_ocr_model
108
+ #
109
+ # The model id used by RubyLLM.ocr when no model is given.
110
+ # Default: <tt>'mistral-ocr-latest'</tt>.
111
+ option :default_ocr_model, 'mistral-ocr-latest'
112
+
113
+ ##
114
+ # :attr_accessor: default_video_model
115
+ #
116
+ # The model id used by RubyLLM.animate when no model is given.
117
+ # Default: <tt>'grok-imagine-video-1.5'</tt>.
118
+ option :default_video_model, 'grok-imagine-video-1.5'
43
119
 
44
- option :use_new_acts_as, false
120
+ ##
121
+ # :attr_accessor: video_generation_timeout
122
+ #
123
+ # Seconds RubyLLM.animate waits for a video job to finish before
124
+ # raising an error. Default: 600.
125
+ option :video_generation_timeout, 600
45
126
 
127
+ ##
128
+ # :attr_accessor: video_generation_poll_interval
129
+ #
130
+ # Seconds between status polls while RubyLLM.animate waits for a
131
+ # video job. Default: 5.
132
+ option :video_generation_poll_interval, 5
133
+
134
+ ##
135
+ # :attr_accessor: model_registry_file
136
+ #
137
+ # Path of the writable JSON cache holding the model registry. Defaults
138
+ # to the operating system's user cache directory. The copy bundled with
139
+ # the gem is used until this file exists.
140
+ option :model_registry_file, -> { Models::Registry.cache_path }
141
+
142
+ ##
143
+ # :attr_accessor: model_registry_store
144
+ #
145
+ # Store object the model registry reads from and persists to. Rails
146
+ # apps using the acts_as helpers set this to the database store
147
+ # automatically. A store must respond to +read+, returning an array of
148
+ # Model entries, and may respond to +write(registry)+ to let
149
+ # Models#refresh persist. Default: +nil+ (use +model_registry_file+).
150
+ option :model_registry_store, nil
151
+
152
+ # Optional persistence adapter installed by the Rails integration.
153
+ option :batch_store, nil # :nodoc:
154
+
155
+ # Keeps custom 1.x initializers bootable long enough to run the 2.0
156
+ # upgrade generator. The registry is always RubyLLM-owned in 2.0.
157
+ def model_registry_class=(_value) # :nodoc:
158
+ RubyLLM.deprecator.warn(
159
+ 'config.model_registry_class is ignored in RubyLLM 2.0; remove it from your initializer'
160
+ )
161
+ end
162
+
163
+ def use_new_acts_as=(_value) # :nodoc:
164
+ RubyLLM.deprecator.warn(
165
+ 'config.use_new_acts_as is ignored in RubyLLM 2.0; remove it from your initializer'
166
+ )
167
+ end
168
+
169
+ ##
170
+ # :attr_accessor: request_timeout
171
+ #
172
+ # Seconds to wait for a response before timing out. Default: 300.
46
173
  option :request_timeout, 300
174
+
175
+ ##
176
+ # :attr_accessor: max_retries
177
+ #
178
+ # Number of times to retry failed requests. Default: 3.
47
179
  option :max_retries, 3
180
+
181
+ ##
182
+ # :attr_accessor: retry_interval
183
+ #
184
+ # Initial delay in seconds before the first retry. Default: 0.1.
48
185
  option :retry_interval, 0.1
186
+
187
+ ##
188
+ # :attr_accessor: retry_backoff_factor
189
+ #
190
+ # Multiplier applied to the retry delay after each attempt.
191
+ # Default: 2.
49
192
  option :retry_backoff_factor, 2
193
+
194
+ ##
195
+ # :attr_accessor: retry_interval_randomness
196
+ #
197
+ # Random jitter factor applied to retry delays. Default: 0.5.
50
198
  option :retry_interval_randomness, 0.5
199
+
200
+ ##
201
+ # :attr_accessor: retry_max_interval
202
+ #
203
+ # Longest delay in seconds to wait between retries, including delays
204
+ # requested by the provider through rate-limit headers. When a
205
+ # provider asks for a longer wait, the request fails immediately
206
+ # instead of sleeping. Default: 30.
207
+ option :retry_max_interval, 30
208
+
209
+ ##
210
+ # :attr_accessor: http_proxy
211
+ #
212
+ # Proxy URL for all requests, such as
213
+ # <tt>'http://proxy.example.com:8080'</tt>. HTTP, authenticated, and
214
+ # SOCKS5 proxies are supported. Default: +nil+.
51
215
  option :http_proxy, nil
52
216
 
217
+ ##
218
+ # :attr_accessor: tool_concurrency
219
+ #
220
+ # How chats run multiple tool calls from one response: +false+ for
221
+ # sequential execution, +true+ or +:threads+ for threads, +:fibers+
222
+ # for fibers via the async gem. Default: +false+.
223
+ option :tool_concurrency, false
224
+
225
+ ##
226
+ # :attr_accessor: auto_upload_large_files
227
+ #
228
+ # Whether oversized local attachments are uploaded to provider file
229
+ # storage automatically. Default: +true+.
230
+ option :auto_upload_large_files, true
231
+
232
+ ##
233
+ # :attr_accessor: logger
234
+ #
235
+ # Logger receiving RubyLLM output. When set, it overrides #log_file
236
+ # and #log_level. Default: +nil+.
53
237
  option :logger, nil
54
- option :log_file, -> { $stdout }
55
- option :log_level, -> { ENV['RUBYLLM_DEBUG'] ? Logger::DEBUG : Logger::INFO }
56
- option :log_stream_debug, -> { ENV['RUBYLLM_STREAM_DEBUG'] == 'true' }
238
+
239
+ ##
240
+ # :attr_accessor: instrumenter
241
+ #
242
+ # Object receiving instrumentation events. It must respond to
243
+ # <tt>instrument(name, payload)</tt> and accept an optional block,
244
+ # like ActiveSupport::Notifications, which Rails apps use
245
+ # automatically. Default: +nil+.
246
+ option :instrumenter, nil
247
+
248
+ ##
249
+ # :attr_accessor: deprecation_behavior
250
+ #
251
+ # How deprecation warnings are handled: +:warn+, +:silence+, or
252
+ # +:raise+. Default: +:warn+.
253
+ option :deprecation_behavior, :warn
254
+
255
+ ##
256
+ # :attr_accessor: faraday_adapter
257
+ #
258
+ # Faraday adapter used for HTTP requests. Default: +:net_http+.
259
+ option :faraday_adapter, :net_http
260
+
261
+ ##
262
+ # :attr_accessor: log_file
263
+ #
264
+ # Destination for the built-in logger, a path or an IO.
265
+ # Defaults to the +RUBYLLM_LOG_FILE+ environment variable when set,
266
+ # <tt>$stdout</tt> otherwise.
267
+ option :log_file, -> { ENV.fetch('RUBYLLM_LOG_FILE', nil) || $stdout }
268
+
269
+ ##
270
+ # :attr_accessor: log_level
271
+ #
272
+ # Severity of the built-in logger. Defaults to <tt>Logger::DEBUG</tt>
273
+ # when the +RUBYLLM_DEBUG+ environment variable says yes
274
+ # (<tt>true</tt>, <tt>1</tt>, <tt>yes</tt>, or <tt>on</tt>),
275
+ # <tt>Logger::INFO</tt> otherwise.
276
+ option :log_level, -> { env_says_yes?('RUBYLLM_DEBUG') ? Logger::DEBUG : Logger::INFO }
277
+
278
+ ##
279
+ # :attr_accessor: log_stream_debug
280
+ #
281
+ # Whether raw streaming chunks are logged. Defaults to +true+ when the
282
+ # +RUBYLLM_STREAM_DEBUG+ environment variable says yes, +false+
283
+ # otherwise.
284
+ option :log_stream_debug, -> { env_says_yes?('RUBYLLM_STREAM_DEBUG') }
285
+
286
+ ##
287
+ # :attr_accessor: log_regexp_timeout
288
+ #
289
+ # Timeout in seconds for the regular expressions that scrub logged
290
+ # payloads. Defaults to the global <tt>Regexp.timeout</tt>, or 1.0
291
+ # when none is set. Requires Ruby 3.2 or later; on older Rubies
292
+ # setting a value logs a warning.
57
293
  option :log_regexp_timeout, -> { Regexp.respond_to?(:timeout) ? (Regexp.timeout || 1.0) : nil }
58
294
 
59
- def initialize
295
+ def initialize # :nodoc:
60
296
  self.class.send(:defaults).each do |key, default|
61
297
  value = default.respond_to?(:call) ? instance_exec(&default) : default
62
298
  public_send("#{key}=", value)
63
299
  end
64
300
  end
65
301
 
66
- def instance_variables
67
- super.reject { |ivar| ivar.to_s.match?(/_id|_key|_secret|_token$/) }
302
+ def instance_variables # :nodoc:
303
+ super.reject { |ivar| ivar.to_s.match?(/(_id|_key|_secret|_token|_credential_provider)$/) }
68
304
  end
69
305
 
70
- def log_regexp_timeout=(value)
71
- if value.nil?
72
- @log_regexp_timeout = nil
73
- elsif Regexp.respond_to?(:timeout)
74
- @log_regexp_timeout = value
75
- else
306
+ # Object#inspect reads the instance variable table directly, so
307
+ # credentials only stay out of console and log output when #inspect is
308
+ # built from the redacted #instance_variables.
309
+ def inspect # :nodoc:
310
+ attributes = instance_variables.map { |ivar| "#{ivar}=#{instance_variable_get(ivar).inspect}" }
311
+ "#<#{self.class.name} #{attributes.join(', ')}>"
312
+ end
313
+
314
+ remove_method :log_regexp_timeout=
315
+ def log_regexp_timeout=(value) # :nodoc:
316
+ if value && !Regexp.respond_to?(:timeout)
76
317
  RubyLLM.logger.warn("log_regexp_timeout is not supported on Ruby #{RUBY_VERSION}")
77
- @log_regexp_timeout = value
78
318
  end
319
+ @log_regexp_timeout = value
320
+ end
321
+
322
+ private
323
+
324
+ # Whether the +name+ environment variable is set to something that means
325
+ # yes, so that <tt>RUBYLLM_DEBUG=false</tt> reads as off rather than as
326
+ # merely set.
327
+ def env_says_yes?(name)
328
+ %w[true 1 yes on].include?(ENV[name].to_s.strip.downcase)
79
329
  end
80
330
  end
81
331
  end
@@ -1,29 +1,151 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module RubyLLM
4
- # Holds per-call configs
4
+ # A Context is an isolated configuration scope. It offers the same entry
5
+ # points as the top-level RubyLLM module but reads from its own
6
+ # Configuration copy instead of the global one, which suits multi-tenant
7
+ # applications and per-request overrides.
8
+ #
9
+ # Contexts are created with RubyLLM.context:
10
+ #
11
+ # ctx = RubyLLM.context do |config|
12
+ # config.openai_api_key = ENV.fetch('TENANT_OPENAI_API_KEY')
13
+ # config.request_timeout = 180
14
+ # end
15
+ #
16
+ # ctx.chat.ask "Explain Ruby blocks."
17
+ # ctx.paint("A paper boat in the rain").save("boat.png")
18
+ # ctx.transcribe("meeting.wav").text
19
+ #
20
+ # The global configuration is left untouched.
21
+ #
22
+ # Batch submission uses each chat's own configuration (a chat carries
23
+ # the config it was built with), so there is no context entry point for
24
+ # it. To look up an existing batch, where no chats carry the config,
25
+ # pass a context to Batch.find:
26
+ #
27
+ # RubyLLM::Batch.find(id, provider: :anthropic, context: ctx)
28
+ #
5
29
  class Context
6
- attr_reader :config
30
+ attr_reader :config # :nodoc:
7
31
 
8
- def initialize(config)
32
+ def initialize(config) # :nodoc:
9
33
  @config = config
10
- @connections = {}
11
34
  end
12
35
 
36
+ # Creates a new Chat that uses this context's configuration.
37
+ # Accepts the same arguments as RubyLLM.chat.
13
38
  def chat(*args, **kwargs, &)
14
39
  Chat.new(*args, **kwargs, context: self, &)
15
40
  end
16
41
 
42
+ # Counts tokens using this context's configuration.
43
+ # Accepts the same arguments as RubyLLM.count_tokens.
44
+ def count_tokens(text, model: nil, provider: nil)
45
+ chat(model:, provider:).count_tokens(text)
46
+ end
47
+
48
+ # Tokenizes plain text using this context's configuration.
49
+ # Accepts the same arguments as RubyLLM.tokenize.
50
+ def tokenize(*args, **kwargs)
51
+ Tokenization.tokenize(*args, **kwargs, context: self)
52
+ end
53
+
54
+ # Runs a named workflow using this context's instrumenter. Accepts the same
55
+ # arguments as RubyLLM.workflow.
56
+ def workflow(name, id: nil, metadata: nil, &)
57
+ Workflow.new(name, id:, metadata:, config: config).run(&)
58
+ end
59
+
60
+ # Generates embeddings using this context's configuration.
61
+ # Accepts the same arguments as RubyLLM.embed.
17
62
  def embed(*args, **kwargs, &)
18
63
  Embedding.embed(*args, **kwargs, context: self, &)
19
64
  end
20
65
 
66
+ # Stages an embedding request using this context's configuration.
67
+ # Accepts the same arguments as RubyLLM.embed_later.
68
+ def embed_later(text, model: nil, provider: nil, dimensions: nil)
69
+ EmbeddingRequest.new(text, model:, provider:, dimensions:, context: self)
70
+ end
71
+
72
+ # Generates an image using this context's configuration.
73
+ # Accepts the same arguments as RubyLLM.paint.
21
74
  def paint(*args, **kwargs, &)
22
75
  Image.paint(*args, **kwargs, context: self, &)
23
76
  end
24
77
 
25
- def connection_for(provider_instance)
26
- provider_instance.connection
78
+ # Generates a video using this context's configuration, blocking
79
+ # until it is ready. Accepts the same arguments as RubyLLM.animate.
80
+ def animate(*args, **kwargs, &)
81
+ Video.animate(*args, **kwargs, context: self, &)
82
+ end
83
+
84
+ # Submits a video generation job using this context's configuration.
85
+ # Accepts the same arguments as RubyLLM.animate_later.
86
+ def animate_later(*args, **kwargs, &)
87
+ VideoJob.animate_later(*args, **kwargs, context: self, &)
88
+ end
89
+
90
+ # Runs content moderation using this context's configuration.
91
+ # Accepts the same arguments as RubyLLM.moderate.
92
+ def moderate(*args, **kwargs, &)
93
+ Moderation.moderate(*args, **kwargs, context: self, &)
94
+ end
95
+
96
+ # Runs hosted research using this context's configuration.
97
+ # Accepts the same arguments as RubyLLM.research.
98
+ def research(*args, **kwargs)
99
+ ResearchJob.research(*args, **kwargs, context: self)
100
+ end
101
+
102
+ # Submits hosted research using this context's configuration.
103
+ # Accepts the same arguments as RubyLLM.research_later.
104
+ def research_later(*args, **kwargs)
105
+ ResearchJob.research_later(*args, **kwargs, context: self)
106
+ end
107
+
108
+ # Generates speech audio using this context's configuration. Given a
109
+ # block, yields SpeechChunk objects and returns the complete Speech.
110
+ # Accepts the same arguments as RubyLLM.speak.
111
+ def speak(*args, **kwargs, &)
112
+ Speech.speak(*args, **kwargs, context: self, &)
113
+ end
114
+
115
+ # Transcribes audio using this context's configuration.
116
+ # Accepts the same arguments as RubyLLM.transcribe.
117
+ def transcribe(*args, **kwargs, &)
118
+ Transcription.transcribe(*args, **kwargs, context: self, &)
119
+ end
120
+
121
+ # Extracts document text using this context's configuration.
122
+ # Accepts the same arguments as RubyLLM.ocr.
123
+ def ocr(*args, **kwargs, &)
124
+ OCR.ocr(*args, **kwargs, context: self, &)
125
+ end
126
+
127
+ # Ranks documents using this context's configuration.
128
+ # Accepts the same arguments as RubyLLM.rerank.
129
+ def rerank(*args, **kwargs, &)
130
+ Rerank.rerank(*args, **kwargs, context: self, &)
131
+ end
132
+
133
+ # Uploads a file to a provider using this context's configuration.
134
+ # Accepts the same arguments as RubyLLM.upload.
135
+ def upload(*args, **kwargs, &)
136
+ UploadedFile.upload(*args, **kwargs, context: self, &)
137
+ end
138
+
139
+ # Downloads a provider-hosted file using this context's configuration.
140
+ # Accepts the same arguments as RubyLLM.download.
141
+ def download(*args, **kwargs, &)
142
+ UploadedFile.download(*args, **kwargs, context: self, &)
143
+ end
144
+
145
+ # Creates a provider-side prompt cache using this context's
146
+ # configuration. Accepts the same arguments as RubyLLM.cache.
147
+ def cache(*args, **kwargs, &)
148
+ CachedContent.create(*args, **kwargs, context: self, &)
27
149
  end
28
150
  end
29
151
  end