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,90 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class OpenAI
6
- # Image generation methods for the OpenAI API integration
7
- module Images
8
- module_function
9
-
10
- def images_url(with: nil, mask: nil)
11
- editing?(with, mask) ? 'images/edits' : 'images/generations'
12
- end
13
-
14
- def render_image_payload(prompt, model:, size:, with: nil, mask: nil, params: {}) # rubocop:disable Metrics/ParameterLists
15
- return render_edit_payload(prompt, model:, with:, mask:, params:) if editing?(with, mask)
16
-
17
- {
18
- model: model,
19
- prompt: prompt,
20
- n: 1,
21
- size: size
22
- }.merge(params)
23
- end
24
-
25
- def parse_image_response(response, model:)
26
- data = response.body
27
- image_data = Array(data['data']).first
28
-
29
- raise Error.new(nil, 'Unexpected response format from OpenAI image API') unless image_data
30
-
31
- Image.new(
32
- url: image_data['url'],
33
- mime_type: 'image/png', # DALL-E typically returns PNGs
34
- revised_prompt: image_data['revised_prompt'],
35
- model_id: model,
36
- data: image_data['b64_json'],
37
- usage: data['usage'] || {}
38
- )
39
- end
40
-
41
- def validate_paint_inputs!(with:, mask:)
42
- return unless editing?(with, mask)
43
-
44
- raise ArgumentError, 'with: is required when mask: is provided' if mask && !attachments?(with)
45
- end
46
-
47
- def render_edit_payload(prompt, model:, with:, mask:, params:)
48
- payload = params.merge(
49
- model: model,
50
- prompt: prompt,
51
- image: build_upload_parts(with, label: 'images'),
52
- n: 1
53
- )
54
- payload[:mask] = build_upload_part(mask, label: 'mask') if mask
55
- payload
56
- end
57
-
58
- def build_upload_parts(sources, label:)
59
- Array(sources).filter_map do |source|
60
- next if blank_attachment?(source)
61
-
62
- build_upload_part(source, label:)
63
- end
64
- end
65
-
66
- def build_upload_part(source, label:)
67
- attachment = Attachment.new(source)
68
- unless attachment.image?
69
- raise UnsupportedAttachmentError,
70
- "OpenAI image editing only supports image attachments for #{label}"
71
- end
72
-
73
- Faraday::UploadIO.new(StringIO.new(attachment.content), attachment.mime_type, attachment.filename)
74
- end
75
-
76
- def editing?(with, mask)
77
- attachments?(with) || !mask.nil?
78
- end
79
-
80
- def attachments?(value)
81
- Array(value).any? { |item| !blank_attachment?(item) }
82
- end
83
-
84
- def blank_attachment?(value)
85
- value.nil? || (value.is_a?(String) && value.strip.empty?)
86
- end
87
- end
88
- end
89
- end
90
- end
@@ -1,84 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class OpenAI
6
- # Handles formatting of media content (images, audio) for OpenAI APIs
7
- module Media
8
- module_function
9
-
10
- def format_content(content) # rubocop:disable Metrics/PerceivedComplexity
11
- if content.is_a?(RubyLLM::Content::Raw)
12
- value = content.value
13
- return value.is_a?(Hash) ? value.to_json : value
14
- end
15
- return content.to_json if content.is_a?(Hash) || content.is_a?(Array)
16
- return content unless content.is_a?(Content)
17
-
18
- parts = []
19
- parts << format_text(content.text) if content.text
20
-
21
- content.attachments.each do |attachment|
22
- case attachment.type
23
- when :image
24
- parts << format_image(attachment)
25
- when :pdf
26
- parts << format_pdf(attachment)
27
- when :audio
28
- parts << format_audio(attachment)
29
- when :text
30
- parts << format_text_file(attachment)
31
- else
32
- raise UnsupportedAttachmentError, attachment.type
33
- end
34
- end
35
-
36
- parts
37
- end
38
-
39
- def format_image(image)
40
- {
41
- type: 'image_url',
42
- image_url: {
43
- url: image.url? ? image.source.to_s : image.for_llm
44
- }
45
- }
46
- end
47
-
48
- def format_pdf(pdf)
49
- {
50
- type: 'file',
51
- file: {
52
- filename: pdf.filename,
53
- file_data: pdf.for_llm
54
- }
55
- }
56
- end
57
-
58
- def format_text_file(text_file)
59
- {
60
- type: 'text',
61
- text: text_file.for_llm
62
- }
63
- end
64
-
65
- def format_audio(audio)
66
- {
67
- type: 'input_audio',
68
- input_audio: {
69
- data: audio.encoded,
70
- format: audio.format
71
- }
72
- }
73
- end
74
-
75
- def format_text(text)
76
- {
77
- type: 'text',
78
- text: text
79
- }
80
- end
81
- end
82
- end
83
- end
84
- end
@@ -1,34 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class OpenAI
6
- # Moderation methods of the OpenAI API integration
7
- module Moderation
8
- module_function
9
-
10
- def moderation_url
11
- 'moderations'
12
- end
13
-
14
- def render_moderation_payload(input, model:)
15
- {
16
- model: model,
17
- input: input
18
- }
19
- end
20
-
21
- def parse_moderation_response(response, model:)
22
- data = response.body
23
- raise Error.new(response, data.dig('error', 'message')) if data.dig('error', 'message')
24
-
25
- RubyLLM::Moderation.new(
26
- id: data['id'],
27
- model: model,
28
- results: data['results'] || []
29
- )
30
- end
31
- end
32
- end
33
- end
34
- end
@@ -1,53 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class OpenAI
6
- # Streaming methods of the OpenAI API integration
7
- module Streaming
8
- module_function
9
-
10
- def stream_url
11
- completion_url
12
- end
13
-
14
- def build_chunk(data)
15
- usage = data['usage'] || {}
16
- delta = data.dig('choices', 0, 'delta') || {}
17
- content_source = delta['content'] || data.dig('choices', 0, 'message', 'content')
18
- content, thinking_from_blocks = OpenAI::Chat.extract_content_and_thinking(content_source)
19
-
20
- Chunk.new(
21
- role: :assistant,
22
- model_id: data['model'],
23
- content: content,
24
- thinking: Thinking.build(
25
- text: thinking_from_blocks || delta['reasoning_content'] || delta['reasoning'],
26
- signature: delta['reasoning_signature']
27
- ),
28
- tool_calls: parse_tool_calls(delta['tool_calls'], parse_arguments: false),
29
- input_tokens: OpenAI::Chat.input_tokens(usage),
30
- output_tokens: OpenAI::Chat.output_tokens(usage),
31
- cached_tokens: OpenAI::Chat.cache_read_tokens(usage),
32
- cache_creation_tokens: OpenAI::Chat.cache_write_tokens(usage),
33
- thinking_tokens: OpenAI::Chat.thinking_tokens(usage)
34
- )
35
- end
36
-
37
- def parse_streaming_error(data)
38
- error_data = JSON.parse(data)
39
- return unless error_data['error']
40
-
41
- case error_data.dig('error', 'type')
42
- when 'server_error'
43
- [500, error_data['error']['message']]
44
- when 'rate_limit_exceeded', 'insufficient_quota'
45
- [429, error_data['error']['message']]
46
- else
47
- [400, error_data['error']['message']]
48
- end
49
- end
50
- end
51
- end
52
- end
53
- end
@@ -1,28 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class OpenAI
6
- # Normalizes temperature for OpenAI models with provider-specific requirements.
7
- module Temperature
8
- module_function
9
-
10
- def normalize(temperature, model_id)
11
- if model_id.match?(/^(o\d|gpt-5)/) && !temperature.nil? && !temperature_close_to_one?(temperature)
12
- RubyLLM.logger.debug { "Model #{model_id} requires temperature=1.0, setting that instead." }
13
- 1.0
14
- elsif model_id.include?('-search')
15
- RubyLLM.logger.debug { "Model #{model_id} does not accept temperature parameter, removing" }
16
- nil
17
- else
18
- temperature
19
- end
20
- end
21
-
22
- def temperature_close_to_one?(temperature)
23
- (temperature.to_f - 1.0).abs <= Float::EPSILON
24
- end
25
- end
26
- end
27
- end
28
- end
@@ -1,70 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class OpenAI
6
- # Audio transcription methods for the OpenAI API integration
7
- module Transcription
8
- module_function
9
-
10
- def transcription_url
11
- 'audio/transcriptions'
12
- end
13
-
14
- def render_transcription_payload(file_part, model:, language:, **options)
15
- {
16
- model: model,
17
- file: file_part,
18
- language: language,
19
- chunking_strategy: (options[:chunking_strategy] || 'auto' if supports_chunking_strategy?(model, options)),
20
- response_format: response_format_for(model, options),
21
- prompt: options[:prompt],
22
- temperature: options[:temperature],
23
- timestamp_granularities: options[:timestamp_granularities],
24
- known_speaker_names: options[:speaker_names],
25
- known_speaker_references: encode_speaker_references(options[:speaker_references])
26
- }.compact
27
- end
28
-
29
- def encode_speaker_references(references)
30
- return nil unless references
31
-
32
- references.map do |ref|
33
- Attachment.new(ref).for_llm
34
- end
35
- end
36
-
37
- def response_format_for(model, options)
38
- return options[:response_format] if options.key?(:response_format)
39
-
40
- 'diarized_json' if model.include?('diarize')
41
- end
42
-
43
- def supports_chunking_strategy?(model, options)
44
- return false if model.start_with?('whisper')
45
- return true if options.key?(:chunking_strategy)
46
-
47
- model.include?('diarize')
48
- end
49
-
50
- def parse_transcription_response(response, model:)
51
- data = response.body
52
-
53
- return RubyLLM::Transcription.new(text: data, model: model) if data.is_a?(String)
54
-
55
- usage = data['usage'] || {}
56
-
57
- RubyLLM::Transcription.new(
58
- text: data['text'],
59
- model: model,
60
- language: data['language'],
61
- duration: data['duration'],
62
- segments: data['segments'],
63
- input_tokens: usage['input_tokens'] || usage['prompt_tokens'],
64
- output_tokens: usage['output_tokens'] || usage['completion_tokens']
65
- )
66
- end
67
- end
68
- end
69
- end
70
- end
@@ -1,72 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class Perplexity
6
- # Provider-level capability checks and narrow registry fallbacks.
7
- module Capabilities
8
- module_function
9
-
10
- PRICES = {
11
- sonar: { input: 1.0, output: 1.0 },
12
- sonar_pro: { input: 3.0, output: 15.0 },
13
- sonar_reasoning: { input: 1.0, output: 5.0 },
14
- sonar_reasoning_pro: { input: 2.0, output: 8.0 },
15
- sonar_deep_research: {
16
- input: 2.0,
17
- output: 8.0,
18
- reasoning_output: 3.0
19
- }
20
- }.freeze
21
-
22
- def supports_tool_choice?(_model_id)
23
- false
24
- end
25
-
26
- def supports_tool_parallel_control?(_model_id)
27
- false
28
- end
29
-
30
- def context_window_for(model_id)
31
- model_id.match?(/sonar-pro/) ? 200_000 : 128_000
32
- end
33
-
34
- def max_tokens_for(model_id)
35
- model_id.match?(/sonar-(?:pro|reasoning-pro)/) ? 8_192 : 4_096
36
- end
37
-
38
- def critical_capabilities_for(model_id)
39
- capabilities = []
40
- capabilities << 'vision' if model_id.match?(/sonar(?:-pro|-reasoning(?:-pro)?)?$/)
41
- capabilities << 'reasoning' if model_id.match?(/reasoning|deep-research/)
42
- capabilities
43
- end
44
-
45
- def pricing_for(model_id)
46
- prices = PRICES.fetch(model_family(model_id), { input: 1.0, output: 1.0 })
47
-
48
- standard = {
49
- input_per_million: prices[:input],
50
- output_per_million: prices[:output]
51
- }
52
- standard[:reasoning_output_per_million] = prices[:reasoning_output] if prices[:reasoning_output]
53
-
54
- { text_tokens: { standard: standard } }
55
- end
56
-
57
- def model_family(model_id)
58
- case model_id
59
- when 'sonar' then :sonar
60
- when 'sonar-pro' then :sonar_pro
61
- when 'sonar-reasoning' then :sonar_reasoning
62
- when 'sonar-reasoning-pro' then :sonar_reasoning_pro
63
- when 'sonar-deep-research' then :sonar_deep_research
64
- else :unknown
65
- end
66
- end
67
-
68
- module_function :context_window_for, :max_tokens_for, :critical_capabilities_for, :pricing_for, :model_family
69
- end
70
- end
71
- end
72
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class VertexAI
6
- # Chat methods for the Vertex AI implementation
7
- module Chat
8
- def completion_url
9
- "projects/#{@config.vertexai_project_id}/locations/#{@config.vertexai_location}/publishers/google/models/#{@model}:generateContent" # rubocop:disable Layout/LineLength
10
- end
11
- end
12
- end
13
- end
14
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class VertexAI
6
- # Streaming methods for the Vertex AI implementation
7
- module Streaming
8
- def stream_url
9
- "projects/#{@config.vertexai_project_id}/locations/#{@config.vertexai_location}/publishers/google/models/#{@model}:streamGenerateContent?alt=sse" # rubocop:disable Layout/LineLength
10
- end
11
- end
12
- end
13
- end
14
- end
@@ -1,203 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- # Assembles streaming responses from LLMs into complete messages.
5
- class StreamAccumulator
6
- attr_reader :content, :model_id, :tool_calls
7
-
8
- def initialize
9
- @content = +''
10
- @thinking_text = +''
11
- @thinking_signature = nil
12
- @tool_calls = {}
13
- @input_tokens = nil
14
- @output_tokens = nil
15
- @cached_tokens = nil
16
- @cache_creation_tokens = nil
17
- @thinking_tokens = nil
18
- @inside_think_tag = false
19
- @pending_think_tag = +''
20
- @latest_tool_call_id = nil
21
- end
22
-
23
- def add(chunk)
24
- RubyLLM.logger.debug { chunk.inspect } if RubyLLM.config.log_stream_debug
25
- @model_id ||= chunk.model_id
26
-
27
- handle_chunk_content(chunk)
28
- append_thinking_from_chunk(chunk)
29
- count_tokens chunk
30
- RubyLLM.logger.debug { inspect } if RubyLLM.config.log_stream_debug
31
- end
32
-
33
- def to_message(response)
34
- Message.new(
35
- role: :assistant,
36
- content: content.empty? ? nil : content,
37
- thinking: Thinking.build(
38
- text: @thinking_text.empty? ? nil : @thinking_text,
39
- signature: @thinking_signature
40
- ),
41
- tokens: Tokens.build(
42
- input: @input_tokens,
43
- output: @output_tokens,
44
- cached: @cached_tokens,
45
- cache_creation: @cache_creation_tokens,
46
- thinking: @thinking_tokens
47
- ),
48
- model_id: model_id,
49
- tool_calls: tool_calls_from_stream,
50
- raw: response
51
- )
52
- end
53
-
54
- private
55
-
56
- def tool_calls_from_stream
57
- tool_calls.transform_values do |tc|
58
- arguments = if tc.arguments.is_a?(String) && !tc.arguments.empty?
59
- JSON.parse(tc.arguments)
60
- elsif tc.arguments.is_a?(String)
61
- {}
62
- else
63
- tc.arguments
64
- end
65
-
66
- ToolCall.new(
67
- id: tc.id,
68
- name: tc.name,
69
- arguments: arguments,
70
- thought_signature: tc.thought_signature
71
- )
72
- end
73
- end
74
-
75
- def accumulate_tool_calls(new_tool_calls) # rubocop:disable Metrics/PerceivedComplexity
76
- RubyLLM.logger.debug { "Accumulating tool calls: #{new_tool_calls}" } if RubyLLM.config.log_stream_debug
77
- new_tool_calls.each_value do |tool_call|
78
- if tool_call.id
79
- tool_call_id = tool_call.id.empty? ? SecureRandom.uuid : tool_call.id
80
- tool_call_arguments = tool_call.arguments
81
- if tool_call_arguments.nil? || (tool_call_arguments.respond_to?(:empty?) && tool_call_arguments.empty?)
82
- tool_call_arguments = +''
83
- end
84
- @tool_calls[tool_call.id] = ToolCall.new(
85
- id: tool_call_id,
86
- name: tool_call.name,
87
- arguments: tool_call_arguments,
88
- thought_signature: tool_call.thought_signature
89
- )
90
- @latest_tool_call_id = tool_call.id
91
- else
92
- existing = @tool_calls[@latest_tool_call_id]
93
- if existing
94
- fragment = tool_call.arguments
95
- fragment = '' if fragment.nil?
96
- existing.arguments << fragment
97
- if tool_call.thought_signature && existing.thought_signature.nil?
98
- existing.thought_signature = tool_call.thought_signature
99
- end
100
- end
101
- end
102
- end
103
- end
104
-
105
- def find_tool_call(tool_call_id)
106
- if tool_call_id.nil?
107
- @tool_calls[@latest_tool_call]
108
- else
109
- @latest_tool_call_id = tool_call_id
110
- @tool_calls[tool_call_id]
111
- end
112
- end
113
-
114
- def count_tokens(chunk)
115
- @input_tokens = chunk.input_tokens if chunk.input_tokens
116
- @output_tokens = chunk.output_tokens if chunk.output_tokens
117
- @cached_tokens = chunk.cached_tokens if chunk.cached_tokens
118
- @cache_creation_tokens = chunk.cache_creation_tokens if chunk.cache_creation_tokens
119
- @thinking_tokens = chunk.thinking_tokens if chunk.thinking_tokens
120
- end
121
-
122
- def handle_chunk_content(chunk)
123
- return accumulate_tool_calls(chunk.tool_calls) if chunk.tool_call?
124
-
125
- content_text = chunk.content || ''
126
- if content_text.is_a?(String)
127
- append_text_with_thinking(content_text)
128
- else
129
- @content << content_text.to_s
130
- end
131
- end
132
-
133
- def append_text_with_thinking(text)
134
- content_chunk, thinking_chunk = extract_think_tags(text)
135
- @content << content_chunk
136
- @thinking_text << thinking_chunk if thinking_chunk
137
- end
138
-
139
- def append_thinking_from_chunk(chunk)
140
- thinking = chunk.thinking
141
- return unless thinking
142
-
143
- @thinking_text << thinking.text.to_s if thinking.text
144
- @thinking_signature ||= thinking.signature # rubocop:disable Naming/MemoizedInstanceVariableName
145
- end
146
-
147
- def extract_think_tags(text)
148
- start_tag = '<think>'
149
- end_tag = '</think>'
150
- remaining = @pending_think_tag + text
151
- @pending_think_tag = +''
152
-
153
- output = +''
154
- thinking = +''
155
-
156
- until remaining.empty?
157
- remaining = if @inside_think_tag
158
- consume_think_content(remaining, end_tag, thinking)
159
- else
160
- consume_non_think_content(remaining, start_tag, output)
161
- end
162
- end
163
-
164
- [output, thinking.empty? ? nil : thinking]
165
- end
166
-
167
- def consume_think_content(remaining, end_tag, thinking)
168
- end_index = remaining.index(end_tag)
169
- if end_index
170
- thinking << remaining.slice(0, end_index)
171
- @inside_think_tag = false
172
- remaining.slice((end_index + end_tag.length)..) || +''
173
- else
174
- suffix_len = longest_suffix_prefix(remaining, end_tag)
175
- thinking << remaining.slice(0, remaining.length - suffix_len)
176
- @pending_think_tag = remaining.slice(-suffix_len, suffix_len)
177
- +''
178
- end
179
- end
180
-
181
- def consume_non_think_content(remaining, start_tag, output)
182
- start_index = remaining.index(start_tag)
183
- if start_index
184
- output << remaining.slice(0, start_index)
185
- @inside_think_tag = true
186
- remaining.slice((start_index + start_tag.length)..) || +''
187
- else
188
- suffix_len = longest_suffix_prefix(remaining, start_tag)
189
- output << remaining.slice(0, remaining.length - suffix_len)
190
- @pending_think_tag = remaining.slice(-suffix_len, suffix_len)
191
- +''
192
- end
193
- end
194
-
195
- def longest_suffix_prefix(text, tag)
196
- max = [text.length, tag.length - 1].min
197
- max.downto(1) do |len|
198
- return len if text.end_with?(tag[0, len])
199
- end
200
- 0
201
- end
202
- end
203
- end