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,96 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class Gemini
6
- # Streaming methods for the Gemini API implementation
7
- module Streaming
8
- def stream_url
9
- "models/#{@model}:streamGenerateContent?alt=sse"
10
- end
11
-
12
- def build_chunk(data)
13
- parts = data.dig('candidates', 0, 'content', 'parts') || []
14
-
15
- Chunk.new(
16
- role: :assistant,
17
- model_id: extract_model_id(data),
18
- content: extract_text_content(parts),
19
- thinking: Thinking.build(
20
- text: extract_thought_content(parts),
21
- signature: extract_thought_signature(parts)
22
- ),
23
- input_tokens: extract_input_tokens(data),
24
- output_tokens: extract_output_tokens(data),
25
- cached_tokens: data.dig('usageMetadata', 'cachedContentTokenCount'),
26
- thinking_tokens: data.dig('usageMetadata', 'thoughtsTokenCount'),
27
- tool_calls: extract_tool_calls(data)
28
- )
29
- end
30
-
31
- private
32
-
33
- def extract_model_id(data)
34
- data['modelVersion']
35
- end
36
-
37
- def extract_text_content(parts)
38
- text_parts = parts.reject { |p| p['thought'] }
39
- text = text_parts.filter_map { |p| p['text'] }.join
40
- text.empty? ? nil : text
41
- end
42
-
43
- def extract_thought_content(parts)
44
- thought_parts = parts.select { |p| p['thought'] }
45
- thoughts = thought_parts.filter_map { |p| p['text'] }.join
46
- thoughts.empty? ? nil : thoughts
47
- end
48
-
49
- def extract_thought_signature(parts)
50
- parts.each do |part|
51
- signature = part['thoughtSignature'] ||
52
- part['thought_signature'] ||
53
- part.dig('functionCall', 'thoughtSignature') ||
54
- part.dig('functionCall', 'thought_signature')
55
- return signature if signature
56
- end
57
-
58
- nil
59
- end
60
-
61
- def extract_content(data)
62
- return nil unless data['candidates']&.any?
63
-
64
- candidate = data['candidates'][0]
65
- parts = candidate.dig('content', 'parts')
66
- return nil unless parts
67
-
68
- text_parts = parts.select { |p| p['text'] }
69
- text_parts.map { |p| p['text'] }.join if text_parts.any?
70
- end
71
-
72
- def extract_input_tokens(data)
73
- prompt_tokens = data.dig('usageMetadata', 'promptTokenCount')
74
- return unless prompt_tokens
75
-
76
- [prompt_tokens.to_i - data.dig('usageMetadata', 'cachedContentTokenCount').to_i, 0].max
77
- end
78
-
79
- def extract_output_tokens(data)
80
- candidates = data.dig('usageMetadata', 'candidatesTokenCount') || 0
81
- thoughts = data.dig('usageMetadata', 'thoughtsTokenCount') || 0
82
- total = candidates + thoughts
83
- total.positive? ? total : nil
84
- end
85
-
86
- def parse_streaming_error(data)
87
- error_data = JSON.parse(data)
88
- [error_data['error']['code'], error_data['error']['message']]
89
- rescue JSON::ParserError => e
90
- RubyLLM.logger.debug { "Failed to parse streaming error: #{e.message}" }
91
- [500, "Failed to parse error: #{data}"]
92
- end
93
- end
94
- end
95
- end
96
- end
@@ -1,232 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class Gemini
6
- # Tools methods for the Gemini API implementation
7
- module Tools
8
- def format_tools(tools)
9
- return [] if tools.empty?
10
-
11
- [{
12
- functionDeclarations: tools.values.map { |tool| function_declaration_for(tool) }
13
- }]
14
- end
15
-
16
- def format_tool_call(msg) # rubocop:disable Metrics/PerceivedComplexity
17
- parts = []
18
-
19
- if msg.content && !(msg.content.respond_to?(:empty?) && msg.content.empty?)
20
- formatted_content = Media.format_content(msg.content)
21
- parts.concat(formatted_content.is_a?(Array) ? formatted_content : [formatted_content])
22
- end
23
-
24
- fallback_signature = msg.thinking&.signature
25
- used_fallback = false
26
-
27
- msg.tool_calls.each_value do |tool_call|
28
- part = {
29
- functionCall: {
30
- name: tool_call.name,
31
- args: tool_call.arguments
32
- }
33
- }
34
-
35
- signature = tool_call.thought_signature
36
- if signature.nil? && fallback_signature && !used_fallback
37
- signature = fallback_signature
38
- used_fallback = true
39
- end
40
- part[:thoughtSignature] = signature if signature
41
- parts << part
42
- end
43
-
44
- parts
45
- end
46
-
47
- def format_tool_result(msg, function_name = nil)
48
- function_name ||= msg.tool_call_id
49
- content = msg.content
50
- content = '(no output)' if content.nil? || (content.respond_to?(:empty?) && content.empty?)
51
-
52
- [{
53
- functionResponse: {
54
- name: function_name,
55
- response: {
56
- name: function_name,
57
- content: Media.format_content(content)
58
- }
59
- }
60
- }]
61
- end
62
-
63
- def extract_tool_calls(data) # rubocop:disable Metrics/PerceivedComplexity
64
- return nil unless data
65
-
66
- candidate = data.is_a?(Hash) ? data.dig('candidates', 0) : nil
67
- return nil unless candidate
68
-
69
- parts = candidate.dig('content', 'parts')
70
- return nil unless parts.is_a?(Array)
71
-
72
- tool_calls = parts.each_with_object({}) do |part, result|
73
- function_data = part['functionCall']
74
- next unless function_data
75
-
76
- id = SecureRandom.uuid
77
- thought_signature = part['thoughtSignature'] || part['thought_signature']
78
-
79
- result[id] = ToolCall.new(
80
- id:,
81
- name: function_data['name'],
82
- arguments: function_data['args'] || {},
83
- thought_signature: thought_signature
84
- )
85
- end
86
-
87
- tool_calls.empty? ? nil : tool_calls
88
- end
89
-
90
- private
91
-
92
- def function_declaration_for(tool)
93
- parameters_schema = tool.params_schema ||
94
- RubyLLM::Tool::SchemaDefinition.from_parameters(tool.parameters)&.json_schema
95
-
96
- declaration = {
97
- name: tool.name,
98
- description: tool.description
99
- }
100
-
101
- declaration[:parameters] = convert_tool_schema_to_gemini(parameters_schema) if parameters_schema
102
-
103
- return declaration if tool.provider_params.empty?
104
-
105
- RubyLLM::Utils.deep_merge(declaration, tool.provider_params)
106
- end
107
-
108
- def convert_tool_schema_to_gemini(schema)
109
- return nil unless schema
110
-
111
- schema = RubyLLM::Utils.deep_stringify_keys(schema)
112
-
113
- raise ArgumentError, 'Gemini tool parameters must be objects' unless schema['type'] == 'object'
114
-
115
- {
116
- type: 'OBJECT',
117
- properties: schema.fetch('properties', {}).transform_values { |property| convert_property(property) },
118
- required: (schema['required'] || []).map(&:to_s)
119
- }
120
- end
121
-
122
- def convert_property(property_schema) # rubocop:disable Metrics/PerceivedComplexity
123
- normalized_schema = normalize_any_of_schema(property_schema)
124
- working_schema = normalized_schema || property_schema
125
-
126
- type = param_type_for_gemini(working_schema['type'])
127
-
128
- property = {
129
- type: type
130
- }
131
-
132
- copy_common_attributes(property, property_schema)
133
- copy_common_attributes(property, working_schema)
134
-
135
- case type
136
- when 'ARRAY'
137
- items_schema = working_schema['items'] || property_schema['items'] || { 'type' => 'string' }
138
- property[:items] = convert_property(items_schema)
139
- copy_tool_attributes(property, working_schema, %w[minItems maxItems])
140
- copy_tool_attributes(property, property_schema, %w[minItems maxItems])
141
- when 'OBJECT'
142
- nested_properties = working_schema.fetch('properties', {}).transform_values do |child|
143
- convert_property(child)
144
- end
145
- property[:properties] = nested_properties
146
- required = working_schema['required'] || property_schema['required']
147
- property[:required] = required.map(&:to_s) if required
148
- end
149
-
150
- property
151
- end
152
-
153
- def copy_common_attributes(target, source)
154
- copy_tool_attributes(target, source, %w[description enum format nullable maximum minimum multipleOf])
155
- end
156
-
157
- def copy_tool_attributes(target, source, attributes)
158
- attributes.each do |attribute|
159
- value = schema_value(source, attribute)
160
- next if value.nil?
161
-
162
- target[attribute.to_sym] = value
163
- end
164
- end
165
-
166
- def normalize_any_of_schema(schema) # rubocop:disable Metrics/PerceivedComplexity
167
- any_of = schema['anyOf'] || schema[:anyOf]
168
- return nil unless any_of.is_a?(Array) && any_of.any?
169
-
170
- null_entries, non_null_entries = any_of.partition { |entry| schema_type(entry).to_s == 'null' }
171
-
172
- if non_null_entries.size == 1 && null_entries.any?
173
- normalized = RubyLLM::Utils.deep_dup(non_null_entries.first)
174
- normalized['nullable'] = true
175
- normalized
176
- elsif non_null_entries.any?
177
- RubyLLM::Utils.deep_dup(non_null_entries.first)
178
- else
179
- { 'type' => 'string', 'nullable' => true }
180
- end
181
- end
182
-
183
- def schema_type(schema)
184
- schema['type'] || schema[:type]
185
- end
186
-
187
- def schema_value(source, attribute) # rubocop:disable Metrics/PerceivedComplexity
188
- case attribute
189
- when 'multipleOf'
190
- source['multipleOf'] || source[:multipleOf] || source['multiple_of'] || source[:multiple_of]
191
- when 'minItems'
192
- source['minItems'] || source[:minItems] || source['min_items'] || source[:min_items]
193
- when 'maxItems'
194
- source['maxItems'] || source[:maxItems] || source['max_items'] || source[:max_items]
195
- else
196
- source[attribute] || source[attribute.to_sym]
197
- end
198
- end
199
-
200
- def param_type_for_gemini(type)
201
- case type.to_s.downcase
202
- when 'integer' then 'INTEGER'
203
- when 'number', 'float', 'double' then 'NUMBER'
204
- when 'boolean' then 'BOOLEAN'
205
- when 'array' then 'ARRAY'
206
- when 'object' then 'OBJECT'
207
- else 'STRING'
208
- end
209
- end
210
-
211
- def build_tool_config(tool_choice)
212
- {
213
- functionCallingConfig: {
214
- mode: forced_tool_choice?(tool_choice) ? 'any' : tool_choice
215
- }.tap do |config|
216
- # Use allowedFunctionNames to simulate specific tool choice
217
- config[:allowedFunctionNames] = [tool_choice] if specific_tool_choice?(tool_choice)
218
- end
219
- }
220
- end
221
-
222
- def forced_tool_choice?(tool_choice)
223
- tool_choice == :required || specific_tool_choice?(tool_choice)
224
- end
225
-
226
- def specific_tool_choice?(tool_choice)
227
- !%i[auto none required].include?(tool_choice)
228
- end
229
- end
230
- end
231
- end
232
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class GPUStack
6
- # Determines capabilities for GPUStack models
7
- module Capabilities
8
- module_function
9
-
10
- def supports_tool_choice?(_model_id)
11
- false
12
- end
13
-
14
- def supports_tool_parallel_control?(_model_id)
15
- false
16
- end
17
- end
18
- end
19
- end
20
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class Ollama
6
- # Determines capabilities for Ollama models
7
- module Capabilities
8
- module_function
9
-
10
- def supports_tool_choice?(_model_id)
11
- false
12
- end
13
-
14
- def supports_tool_parallel_control?(_model_id)
15
- false
16
- end
17
- end
18
- end
19
- end
20
- end
@@ -1,221 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class OpenAI
6
- # Chat methods of the OpenAI API integration
7
- module Chat
8
- def completion_url
9
- 'chat/completions'
10
- end
11
-
12
- module_function
13
-
14
- # rubocop:disable Metrics/ParameterLists,Metrics/PerceivedComplexity
15
- def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil,
16
- thinking: nil, tool_prefs: nil)
17
- tool_prefs ||= {}
18
- payload = {
19
- model: model.id,
20
- messages: format_messages(messages),
21
- stream: stream
22
- }
23
-
24
- payload[:temperature] = temperature unless temperature.nil?
25
- if tools.any?
26
- payload[:tools] = tools.map { |_, tool| tool_for(tool) }
27
- payload[:tool_choice] = build_tool_choice(tool_prefs[:choice]) unless tool_prefs[:choice].nil?
28
- payload[:parallel_tool_calls] = tool_prefs[:calls] == :many unless tool_prefs[:calls].nil?
29
- end
30
-
31
- if schema
32
- schema_name = schema[:name]
33
- schema_def = schema[:schema]
34
- strict = schema[:strict]
35
-
36
- payload[:response_format] = {
37
- type: 'json_schema',
38
- json_schema: {
39
- name: schema_name,
40
- schema: schema_def,
41
- strict: strict
42
- }
43
- }
44
- end
45
-
46
- effort = resolve_effort(thinking)
47
- payload[:reasoning_effort] = effort if effort
48
-
49
- payload[:stream_options] = { include_usage: true } if stream
50
- payload
51
- end
52
- # rubocop:enable Metrics/ParameterLists,Metrics/PerceivedComplexity
53
-
54
- def parse_completion_response(response)
55
- data = response.body
56
- return if data.empty?
57
-
58
- raise Error.new(response, data.dig('error', 'message')) if data.dig('error', 'message')
59
-
60
- message_data = data.dig('choices', 0, 'message')
61
- return unless message_data
62
-
63
- usage = data['usage'] || {}
64
- thinking_tokens = thinking_tokens(usage)
65
- content, thinking_from_blocks = extract_content_and_thinking(message_data['content'])
66
- thinking_text = thinking_from_blocks || extract_thinking_text(message_data)
67
- thinking_signature = extract_thinking_signature(message_data)
68
-
69
- Message.new(
70
- role: :assistant,
71
- content: content,
72
- thinking: Thinking.build(text: thinking_text, signature: thinking_signature),
73
- tool_calls: parse_tool_calls(message_data['tool_calls']),
74
- input_tokens: input_tokens(usage),
75
- output_tokens: output_tokens(usage),
76
- cached_tokens: cache_read_tokens(usage),
77
- cache_creation_tokens: cache_write_tokens(usage),
78
- thinking_tokens: thinking_tokens,
79
- model_id: data['model'],
80
- raw: response
81
- )
82
- end
83
-
84
- def input_tokens(usage)
85
- return usage['prompt_cache_miss_tokens'] if usage['prompt_cache_miss_tokens']
86
-
87
- prompt_tokens = usage['prompt_tokens']
88
- return unless prompt_tokens
89
-
90
- [prompt_tokens.to_i - cache_read_tokens(usage).to_i - cache_write_tokens(usage).to_i, 0].max
91
- end
92
-
93
- def output_tokens(usage)
94
- completion_tokens = usage['completion_tokens']
95
- return unless completion_tokens
96
-
97
- completion_tokens = completion_tokens.to_i
98
- generated_tokens = generated_tokens_from_total(usage)
99
- return completion_tokens unless generated_tokens && generated_tokens > completion_tokens
100
-
101
- generated_tokens
102
- end
103
-
104
- def generated_tokens_from_total(usage)
105
- prompt_tokens = usage['prompt_tokens']
106
- total_tokens = usage['total_tokens']
107
- return unless prompt_tokens && total_tokens
108
-
109
- [total_tokens.to_i - prompt_tokens.to_i, 0].max
110
- end
111
-
112
- def cache_read_tokens(usage)
113
- usage.dig('prompt_tokens_details', 'cached_tokens') || usage['prompt_cache_hit_tokens']
114
- end
115
-
116
- def cache_write_tokens(usage)
117
- usage.dig('prompt_tokens_details', 'cache_write_tokens') || 0
118
- end
119
-
120
- def thinking_tokens(usage)
121
- usage.dig('completion_tokens_details', 'reasoning_tokens') || usage['reasoning_tokens']
122
- end
123
-
124
- def format_messages(messages)
125
- messages.map do |msg|
126
- {
127
- role: format_role(msg.role),
128
- content: Media.format_content(msg.content),
129
- tool_calls: format_tool_calls(msg.tool_calls),
130
- tool_call_id: msg.tool_call_id
131
- }.compact.merge(format_thinking(msg))
132
- end
133
- end
134
-
135
- def format_role(role)
136
- case role
137
- when :system
138
- @config.openai_use_system_role ? 'system' : 'developer'
139
- else
140
- role.to_s
141
- end
142
- end
143
-
144
- def resolve_effort(thinking)
145
- return nil unless thinking
146
-
147
- thinking.respond_to?(:effort) ? thinking.effort : thinking
148
- end
149
-
150
- def format_thinking(msg)
151
- return {} unless msg.role == :assistant
152
-
153
- thinking = msg.thinking
154
- return {} unless thinking
155
-
156
- payload = {}
157
- if thinking.text
158
- payload[:reasoning] = thinking.text
159
- payload[:reasoning_content] = thinking.text
160
- end
161
- payload[:reasoning_signature] = thinking.signature if thinking.signature
162
- payload
163
- end
164
-
165
- def extract_thinking_text(message_data)
166
- candidate = message_data['reasoning_content'] || message_data['reasoning'] || message_data['thinking']
167
- candidate.is_a?(String) ? candidate : nil
168
- end
169
-
170
- def extract_thinking_signature(message_data)
171
- candidate = message_data['reasoning_signature'] || message_data['signature']
172
- candidate.is_a?(String) ? candidate : nil
173
- end
174
-
175
- def extract_content_and_thinking(content)
176
- return extract_think_tag_content(content) if content.is_a?(String)
177
- return [content, nil] unless content.is_a?(Array)
178
-
179
- text = extract_text_from_blocks(content)
180
- thinking = extract_thinking_from_blocks(content)
181
-
182
- [text.empty? ? nil : text, thinking.empty? ? nil : thinking]
183
- end
184
-
185
- def extract_text_from_blocks(blocks)
186
- blocks.filter_map do |block|
187
- block['text'] if block['type'] == 'text' && block['text'].is_a?(String)
188
- end.join
189
- end
190
-
191
- def extract_thinking_from_blocks(blocks)
192
- blocks.filter_map do |block|
193
- next unless block['type'] == 'thinking'
194
-
195
- extract_thinking_text_from_block(block)
196
- end.join
197
- end
198
-
199
- def extract_thinking_text_from_block(block)
200
- thinking_block = block['thinking']
201
- return thinking_block if thinking_block.is_a?(String)
202
-
203
- if thinking_block.is_a?(Array)
204
- return thinking_block.filter_map { |item| item['text'] if item['type'] == 'text' }.join
205
- end
206
-
207
- block['text'] if block['text'].is_a?(String)
208
- end
209
-
210
- def extract_think_tag_content(text)
211
- return [text, nil] unless text.include?('<think>')
212
-
213
- thinking = text.scan(%r{<think>(.*?)</think>}m).join
214
- content = text.gsub(%r{<think>.*?</think>}m, '').strip
215
-
216
- [content.empty? ? nil : content, thinking.empty? ? nil : thinking]
217
- end
218
- end
219
- end
220
- end
221
- end
@@ -1,33 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class OpenAI
6
- # Embeddings methods of the OpenAI API integration
7
- module Embeddings
8
- module_function
9
-
10
- def embedding_url(...)
11
- 'embeddings'
12
- end
13
-
14
- def render_embedding_payload(text, model:, dimensions:)
15
- {
16
- model: model,
17
- input: text,
18
- dimensions: dimensions
19
- }.compact
20
- end
21
-
22
- def parse_embedding_response(response, model:, text:)
23
- data = response.body
24
- input_tokens = data.dig('usage', 'prompt_tokens') || 0
25
- vectors = data['data'].map { |d| d['embedding'] }
26
- vectors = vectors.first if vectors.length == 1 && !text.is_a?(Array)
27
-
28
- Embedding.new(vectors:, model:, input_tokens:)
29
- end
30
- end
31
- end
32
- end
33
- end