ruby_llm 1.16.0 → 2.0.0.rc2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (475) hide show
  1. checksums.yaml +4 -4
  2. data/.rdoc_options +25 -0
  3. data/README.md +85 -32
  4. data/exe/ruby_llm +8 -0
  5. data/lib/generators/ruby_llm/agent/templates/agent.rb.tt +0 -1
  6. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +3 -43
  7. data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +11 -3
  8. data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +8 -0
  9. data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +4 -4
  10. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt +1 -1
  11. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +1 -1
  12. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt +1 -1
  13. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +2 -2
  14. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +1 -1
  15. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +1 -1
  16. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool.html.erb.tt +1 -1
  17. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +6 -4
  18. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +1 -1
  19. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +2 -2
  20. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt +5 -6
  21. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt +2 -2
  22. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt +5 -5
  23. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +1 -1
  24. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +1 -1
  25. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +1 -1
  26. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +2 -2
  27. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +1 -1
  28. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_system.html.erb.tt +1 -1
  29. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool.html.erb.tt +1 -1
  30. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +6 -4
  31. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_user.html.erb.tt +1 -1
  32. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +4 -6
  33. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +2 -2
  34. data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +5 -6
  35. data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +2 -2
  36. data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +3 -3
  37. data/lib/generators/ruby_llm/generator_helpers.rb +106 -62
  38. data/lib/generators/ruby_llm/install/install_generator.rb +3 -11
  39. data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +2 -0
  40. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +14 -8
  41. data/lib/generators/ruby_llm/install/templates/create_ruby_llm_records_migration.rb.tt +117 -0
  42. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +0 -8
  43. data/lib/generators/ruby_llm/provider/cli.rb +175 -0
  44. data/lib/generators/ruby_llm/provider/scaffold.rb +323 -0
  45. data/lib/generators/ruby_llm/provider/templates/core/provider.rb.erb +37 -0
  46. data/lib/generators/ruby_llm/provider/templates/core/provider_spec.rb.erb +34 -0
  47. data/lib/generators/ruby_llm/provider/templates/gem/archspec.rb.erb +14 -0
  48. data/lib/generators/ruby_llm/provider/templates/gem/bin/console.erb +15 -0
  49. data/lib/generators/ruby_llm/provider/templates/gem/bin/setup.erb +5 -0
  50. data/lib/generators/ruby_llm/provider/templates/gem/chat_schema_spec.rb.erb +30 -0
  51. data/lib/generators/ruby_llm/provider/templates/gem/chat_spec.rb.erb +35 -0
  52. data/lib/generators/ruby_llm/provider/templates/gem/chat_streaming_spec.rb.erb +22 -0
  53. data/lib/generators/ruby_llm/provider/templates/gem/chat_tools_spec.rb.erb +30 -0
  54. data/lib/generators/ruby_llm/provider/templates/gem/ci.yml.erb +32 -0
  55. data/lib/generators/ruby_llm/provider/templates/gem/embedding_spec.rb.erb +49 -0
  56. data/lib/generators/ruby_llm/provider/templates/gem/env.erb +2 -0
  57. data/lib/generators/ruby_llm/provider/templates/gem/fixtures_gitkeep.erb +1 -0
  58. data/lib/generators/ruby_llm/provider/templates/gem/flayignore.erb +1 -0
  59. data/lib/generators/ruby_llm/provider/templates/gem/gemfile.erb +23 -0
  60. data/lib/generators/ruby_llm/provider/templates/gem/gemspec.erb +28 -0
  61. data/lib/generators/ruby_llm/provider/templates/gem/gitignore.erb +7 -0
  62. data/lib/generators/ruby_llm/provider/templates/gem/gitleaks.yml.erb +22 -0
  63. data/lib/generators/ruby_llm/provider/templates/gem/image_spec.rb.erb +23 -0
  64. data/lib/generators/ruby_llm/provider/templates/gem/license.erb +21 -0
  65. data/lib/generators/ruby_llm/provider/templates/gem/models.rb.erb +19 -0
  66. data/lib/generators/ruby_llm/provider/templates/gem/models_spec.rb.erb +17 -0
  67. data/lib/generators/ruby_llm/provider/templates/gem/moderation_spec.rb.erb +22 -0
  68. data/lib/generators/ruby_llm/provider/templates/gem/overcommit.yml.erb +31 -0
  69. data/lib/generators/ruby_llm/provider/templates/gem/provider.rb.erb +52 -0
  70. data/lib/generators/ruby_llm/provider/templates/gem/provider_spec.rb.erb +38 -0
  71. data/lib/generators/ruby_llm/provider/templates/gem/rakefile.erb +38 -0
  72. data/lib/generators/ruby_llm/provider/templates/gem/readme.md.erb +50 -0
  73. data/lib/generators/ruby_llm/provider/templates/gem/release.yml.erb +36 -0
  74. data/lib/generators/ruby_llm/provider/templates/gem/rerank_spec.rb.erb +23 -0
  75. data/lib/generators/ruby_llm/provider/templates/gem/rspec.erb +2 -0
  76. data/lib/generators/ruby_llm/provider/templates/gem/rubocop.yml.erb +29 -0
  77. data/lib/generators/ruby_llm/provider/templates/gem/rubyllm_configuration.rb.erb +14 -0
  78. data/lib/generators/ruby_llm/provider/templates/gem/spec_helper.rb.erb +26 -0
  79. data/lib/generators/ruby_llm/provider/templates/gem/speech_spec.rb.erb +25 -0
  80. data/lib/generators/ruby_llm/provider/templates/gem/vcr_configuration.rb.erb +16 -0
  81. data/lib/generators/ruby_llm/provider/templates/gem/video_spec.rb.erb +27 -0
  82. data/lib/generators/ruby_llm/schema/schema_generator.rb +5 -1
  83. data/lib/generators/ruby_llm/schema/templates/schema.rb.tt +1 -1
  84. data/lib/generators/ruby_llm/tool/templates/tailwind/tool_call.html.erb.tt +13 -0
  85. data/lib/generators/ruby_llm/tool/templates/tailwind/tool_result.html.erb.tt +21 -0
  86. data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +3 -3
  87. data/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt +7 -12
  88. data/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt +5 -2
  89. data/lib/generators/ruby_llm/tool/tool_generator.rb +25 -59
  90. data/lib/generators/ruby_llm/upgrade/templates/backfill_v2_data.rb.tt +461 -0
  91. data/lib/generators/ruby_llm/upgrade/templates/cleanup_v2_upgrade.rb.tt +101 -0
  92. data/lib/generators/ruby_llm/upgrade/templates/finish_v2_upgrade.rb.tt +215 -0
  93. data/lib/generators/ruby_llm/upgrade/templates/prepare_v2_upgrade.rb.tt +660 -0
  94. data/lib/generators/ruby_llm/upgrade/templates/ruby_llm_upgrade.rb.tt +222 -0
  95. data/lib/generators/ruby_llm/upgrade/templates/upgrade_initializer.rb.tt +13 -0
  96. data/lib/generators/ruby_llm/upgrade/upgrade_generator.rb +167 -0
  97. data/lib/generators/ruby_llm/upgrade/upgrade_migration.rb +344 -0
  98. data/lib/ruby_llm/accounting/usage.rb +245 -0
  99. data/lib/ruby_llm/active_record/acts_as.rb +94 -111
  100. data/lib/ruby_llm/active_record/attachment_helpers.rb +186 -0
  101. data/lib/ruby_llm/active_record/batch.rb +97 -0
  102. data/lib/ruby_llm/active_record/chat_methods.rb +828 -305
  103. data/lib/ruby_llm/active_record/message_methods.rb +113 -136
  104. data/lib/ruby_llm/active_record/model.rb +135 -0
  105. data/lib/ruby_llm/active_record/payload_helpers.rb +1 -2
  106. data/lib/ruby_llm/active_record/tool_call.rb +33 -0
  107. data/lib/ruby_llm/active_record/usage.rb +61 -0
  108. data/lib/ruby_llm/agent.rb +1065 -151
  109. data/lib/ruby_llm/aliases.json +268 -100
  110. data/lib/ruby_llm/attachment.rb +187 -48
  111. data/lib/ruby_llm/batch.rb +432 -0
  112. data/lib/ruby_llm/cached_content.rb +112 -0
  113. data/lib/ruby_llm/chat/tool_concurrency.rb +111 -0
  114. data/lib/ruby_llm/chat.rb +1127 -198
  115. data/lib/ruby_llm/chunk.rb +10 -0
  116. data/lib/ruby_llm/citation.rb +105 -0
  117. data/lib/ruby_llm/configuration.rb +261 -24
  118. data/lib/ruby_llm/context.rb +128 -6
  119. data/lib/ruby_llm/cost.rb +217 -80
  120. data/lib/ruby_llm/downloaded_file.rb +33 -0
  121. data/lib/ruby_llm/embedding.rb +121 -17
  122. data/lib/ruby_llm/embedding_request.rb +53 -0
  123. data/lib/ruby_llm/error.rb +159 -23
  124. data/lib/ruby_llm/fallback.rb +133 -0
  125. data/lib/ruby_llm/files/mime_type.rb +97 -0
  126. data/lib/ruby_llm/image.rb +153 -32
  127. data/lib/ruby_llm/message.rb +233 -54
  128. data/lib/ruby_llm/model/modalities.rb +17 -4
  129. data/lib/ruby_llm/model/pricing.rb +24 -5
  130. data/lib/ruby_llm/model/pricing_category.rb +103 -14
  131. data/lib/ruby_llm/model/pricing_tier.rb +55 -15
  132. data/lib/ruby_llm/model.rb +244 -2
  133. data/lib/ruby_llm/models/aliases.rb +41 -0
  134. data/lib/ruby_llm/models/registry.rb +165 -0
  135. data/lib/ruby_llm/models/schema.rb +99 -0
  136. data/lib/ruby_llm/models.json +73261 -33733
  137. data/lib/ruby_llm/models.rb +477 -215
  138. data/lib/ruby_llm/moderation.rb +139 -26
  139. data/lib/ruby_llm/ocr.rb +112 -0
  140. data/lib/ruby_llm/prompt.rb +79 -0
  141. data/lib/ruby_llm/protocol/binary_streaming.rb +65 -0
  142. data/lib/ruby_llm/protocol/stream_accumulator.rb +214 -0
  143. data/lib/ruby_llm/protocol/streaming.rb +230 -0
  144. data/lib/ruby_llm/protocol.rb +662 -0
  145. data/lib/ruby_llm/protocols/anthropic/batches.rb +73 -0
  146. data/lib/ruby_llm/protocols/anthropic/chat.rb +548 -0
  147. data/lib/ruby_llm/protocols/anthropic/embeddings.rb +14 -0
  148. data/lib/ruby_llm/protocols/anthropic/files.rb +38 -0
  149. data/lib/ruby_llm/protocols/anthropic/media.rb +141 -0
  150. data/lib/ruby_llm/protocols/anthropic/models.rb +129 -0
  151. data/lib/ruby_llm/protocols/anthropic/streaming.rb +167 -0
  152. data/lib/ruby_llm/{providers → protocols}/anthropic/tools.rb +24 -41
  153. data/lib/ruby_llm/protocols/anthropic.rb +101 -0
  154. data/lib/ruby_llm/protocols/azure/files.rb +16 -0
  155. data/lib/ruby_llm/protocols/bedrock/async_videos.rb +109 -0
  156. data/lib/ruby_llm/protocols/bedrock/batches.rb +129 -0
  157. data/lib/ruby_llm/protocols/bedrock/files.rb +110 -0
  158. data/lib/ruby_llm/protocols/bedrock/guardrails.rb +122 -0
  159. data/lib/ruby_llm/protocols/bedrock/rerank.rb +95 -0
  160. data/lib/ruby_llm/protocols/chat_completions/batches.rb +32 -0
  161. data/lib/ruby_llm/protocols/chat_completions/chat.rb +493 -0
  162. data/lib/ruby_llm/protocols/chat_completions/embedding_batches.rb +35 -0
  163. data/lib/ruby_llm/protocols/chat_completions/embeddings.rb +60 -0
  164. data/lib/ruby_llm/protocols/chat_completions/images.rb +127 -0
  165. data/lib/ruby_llm/{providers/openai → protocols/chat_completions}/media.rb +30 -17
  166. data/lib/ruby_llm/protocols/chat_completions/models.rb +39 -0
  167. data/lib/ruby_llm/protocols/chat_completions/moderation.rb +52 -0
  168. data/lib/ruby_llm/protocols/chat_completions/rerank.rb +56 -0
  169. data/lib/ruby_llm/protocols/chat_completions/speech.rb +40 -0
  170. data/lib/ruby_llm/protocols/chat_completions/streaming.rb +69 -0
  171. data/lib/ruby_llm/{providers/openai → protocols/chat_completions}/tools.rb +15 -17
  172. data/lib/ruby_llm/protocols/chat_completions/transcription.rb +150 -0
  173. data/lib/ruby_llm/protocols/chat_completions.rb +21 -0
  174. data/lib/ruby_llm/protocols/cohere/batch_requests.rb +75 -0
  175. data/lib/ruby_llm/protocols/cohere/batches.rb +98 -0
  176. data/lib/ruby_llm/protocols/cohere/chat.rb +227 -0
  177. data/lib/ruby_llm/protocols/cohere/datasets.rb +102 -0
  178. data/lib/ruby_llm/protocols/cohere/embeddings.rb +68 -0
  179. data/lib/ruby_llm/protocols/cohere/media.rb +77 -0
  180. data/lib/ruby_llm/protocols/cohere/models.rb +92 -0
  181. data/lib/ruby_llm/protocols/cohere/ocr.rb +63 -0
  182. data/lib/ruby_llm/protocols/cohere/rerank.rb +52 -0
  183. data/lib/ruby_llm/protocols/cohere/streaming.rb +105 -0
  184. data/lib/ruby_llm/protocols/cohere/tokenization.rb +22 -0
  185. data/lib/ruby_llm/protocols/cohere/tools.rb +132 -0
  186. data/lib/ruby_llm/protocols/cohere/transcription.rb +41 -0
  187. data/lib/ruby_llm/protocols/cohere.rb +21 -0
  188. data/lib/ruby_llm/protocols/converse/batches.rb +55 -0
  189. data/lib/ruby_llm/protocols/converse/chat.rb +695 -0
  190. data/lib/ruby_llm/protocols/converse/media.rb +178 -0
  191. data/lib/ruby_llm/protocols/converse/streaming.rb +432 -0
  192. data/lib/ruby_llm/protocols/converse/thinking_stream.rb +56 -0
  193. data/lib/ruby_llm/protocols/converse.rb +54 -0
  194. data/lib/ruby_llm/protocols/deepgram/models.rb +74 -0
  195. data/lib/ruby_llm/protocols/deepgram/speech.rb +93 -0
  196. data/lib/ruby_llm/protocols/deepgram/streaming_transcription.rb +89 -0
  197. data/lib/ruby_llm/protocols/deepgram/transcription.rb +96 -0
  198. data/lib/ruby_llm/protocols/deepgram.rb +19 -0
  199. data/lib/ruby_llm/protocols/deepseek/files.rb +31 -0
  200. data/lib/ruby_llm/protocols/elevenlabs/assets.rb +36 -0
  201. data/lib/ruby_llm/protocols/elevenlabs/flows/images.rb +74 -0
  202. data/lib/ruby_llm/protocols/elevenlabs/flows/media.rb +42 -0
  203. data/lib/ruby_llm/protocols/elevenlabs/flows/videos.rb +93 -0
  204. data/lib/ruby_llm/protocols/elevenlabs/flows.rb +14 -0
  205. data/lib/ruby_llm/protocols/elevenlabs/models.rb +64 -0
  206. data/lib/ruby_llm/protocols/elevenlabs/speech.rb +67 -0
  207. data/lib/ruby_llm/protocols/elevenlabs/streaming_transcription.rb +127 -0
  208. data/lib/ruby_llm/protocols/elevenlabs/transcription.rb +61 -0
  209. data/lib/ruby_llm/protocols/elevenlabs.rb +15 -0
  210. data/lib/ruby_llm/protocols/files.rb +119 -0
  211. data/lib/ruby_llm/protocols/gemini/batches.rb +162 -0
  212. data/lib/ruby_llm/protocols/gemini/caches.rb +59 -0
  213. data/lib/ruby_llm/protocols/gemini/chat.rb +453 -0
  214. data/lib/ruby_llm/protocols/gemini/embedding_batches.rb +86 -0
  215. data/lib/ruby_llm/protocols/gemini/embeddings.rb +70 -0
  216. data/lib/ruby_llm/protocols/gemini/file_transcription.rb +32 -0
  217. data/lib/ruby_llm/protocols/gemini/files.rb +115 -0
  218. data/lib/ruby_llm/protocols/gemini/images.rb +183 -0
  219. data/lib/ruby_llm/protocols/gemini/live_transcription.rb +140 -0
  220. data/lib/ruby_llm/{providers → protocols}/gemini/media.rb +17 -11
  221. data/lib/ruby_llm/protocols/gemini/models.rb +71 -0
  222. data/lib/ruby_llm/protocols/gemini/speech.rb +56 -0
  223. data/lib/ruby_llm/protocols/gemini/streaming.rb +96 -0
  224. data/lib/ruby_llm/protocols/gemini/tools.rb +157 -0
  225. data/lib/ruby_llm/{providers → protocols}/gemini/transcription.rb +22 -22
  226. data/lib/ruby_llm/protocols/gemini/videos.rb +103 -0
  227. data/lib/ruby_llm/protocols/gemini.rb +35 -0
  228. data/lib/ruby_llm/protocols/gpustack/responses.rb +111 -0
  229. data/lib/ruby_llm/protocols/gpustack/tokenization.rb +22 -0
  230. data/lib/ruby_llm/protocols/gpustack/videos.rb +96 -0
  231. data/lib/ruby_llm/protocols/interactions/chat.rb +145 -0
  232. data/lib/ruby_llm/protocols/interactions/content.rb +90 -0
  233. data/lib/ruby_llm/protocols/interactions/streaming.rb +91 -0
  234. data/lib/ruby_llm/protocols/interactions/tools.rb +51 -0
  235. data/lib/ruby_llm/protocols/interactions/transcription.rb +58 -0
  236. data/lib/ruby_llm/protocols/interactions.rb +29 -0
  237. data/lib/ruby_llm/protocols/invoke_model/cohere_embeddings.rb +51 -0
  238. data/lib/ruby_llm/protocols/invoke_model/embedding_batches.rb +111 -0
  239. data/lib/ruby_llm/protocols/invoke_model/nova_embeddings.rb +50 -0
  240. data/lib/ruby_llm/protocols/invoke_model/stability_images.rb +103 -0
  241. data/lib/ruby_llm/protocols/invoke_model/titan_multimodal_embeddings.rb +33 -0
  242. data/lib/ruby_llm/protocols/invoke_model/titan_text_embeddings.rb +44 -0
  243. data/lib/ruby_llm/protocols/invoke_model.rb +57 -0
  244. data/lib/ruby_llm/protocols/mistral/content.rb +49 -0
  245. data/lib/ruby_llm/protocols/mistral/conversations/chat.rb +160 -0
  246. data/lib/ruby_llm/protocols/mistral/conversations/images.rb +43 -0
  247. data/lib/ruby_llm/protocols/mistral/conversations/streaming.rb +83 -0
  248. data/lib/ruby_llm/protocols/mistral/conversations.rb +30 -0
  249. data/lib/ruby_llm/protocols/mistral/files.rb +36 -0
  250. data/lib/ruby_llm/protocols/mistral/multi_completion.rb +160 -0
  251. data/lib/ruby_llm/protocols/openai/batches.rb +126 -0
  252. data/lib/ruby_llm/protocols/openai/files.rb +42 -0
  253. data/lib/ruby_llm/protocols/openrouter/batches.rb +147 -0
  254. data/lib/ruby_llm/protocols/openrouter/files.rb +24 -0
  255. data/lib/ruby_llm/protocols/openrouter/responses.rb +53 -0
  256. data/lib/ruby_llm/protocols/openrouter/transcription.rb +51 -0
  257. data/lib/ruby_llm/protocols/perplexity/files.rb +48 -0
  258. data/lib/ruby_llm/protocols/perplexity/router.rb +59 -0
  259. data/lib/ruby_llm/protocols/responses/approvals.rb +32 -0
  260. data/lib/ruby_llm/protocols/responses/batches.rb +32 -0
  261. data/lib/ruby_llm/protocols/responses/chat.rb +476 -0
  262. data/lib/ruby_llm/protocols/responses/compaction.rb +29 -0
  263. data/lib/ruby_llm/protocols/responses/media.rb +61 -0
  264. data/lib/ruby_llm/protocols/responses/streaming.rb +117 -0
  265. data/lib/ruby_llm/protocols/responses/token_counting.rb +26 -0
  266. data/lib/ruby_llm/protocols/responses/tools.rb +39 -0
  267. data/lib/ruby_llm/protocols/responses.rb +35 -0
  268. data/lib/ruby_llm/protocols/vertexai/batch_prediction.rb +155 -0
  269. data/lib/ruby_llm/protocols/vertexai/embedding_prediction/requests.rb +85 -0
  270. data/lib/ruby_llm/protocols/vertexai/embedding_prediction/results.rb +74 -0
  271. data/lib/ruby_llm/protocols/vertexai/embedding_prediction.rb +56 -0
  272. data/lib/ruby_llm/protocols/vertexai/files.rb +101 -0
  273. data/lib/ruby_llm/protocols/vertexai/ranking.rb +69 -0
  274. data/lib/ruby_llm/protocols/vertexai/research.rb +193 -0
  275. data/lib/ruby_llm/protocols/xai/files.rb +30 -0
  276. data/lib/ruby_llm/protocols/xai/streaming_transcription.rb +120 -0
  277. data/lib/ruby_llm/protocols/xai/tokenization.rb +23 -0
  278. data/lib/ruby_llm/provider.rb +560 -128
  279. data/lib/ruby_llm/providers/anthropic/capabilities.rb +5 -7
  280. data/lib/ruby_llm/providers/anthropic.rb +4 -6
  281. data/lib/ruby_llm/providers/azure/audio.rb +18 -0
  282. data/lib/ruby_llm/providers/azure/capabilities.rb +16 -0
  283. data/lib/ruby_llm/providers/azure/chat.rb +2 -9
  284. data/lib/ruby_llm/providers/azure/chat_completions/batches.rb +29 -0
  285. data/lib/ruby_llm/providers/azure/chat_completions.rb +80 -0
  286. data/lib/ruby_llm/providers/azure/cohere.rb +33 -0
  287. data/lib/ruby_llm/providers/azure/embeddings.rb +3 -2
  288. data/lib/ruby_llm/providers/azure/images.rb +22 -0
  289. data/lib/ruby_llm/providers/azure/media.rb +5 -14
  290. data/lib/ruby_llm/providers/azure/models.rb +35 -0
  291. data/lib/ruby_llm/providers/azure/responses.rb +26 -0
  292. data/lib/ruby_llm/providers/azure/videos.rb +64 -0
  293. data/lib/ruby_llm/providers/azure.rb +77 -78
  294. data/lib/ruby_llm/providers/bedrock/auth.rb +60 -41
  295. data/lib/ruby_llm/providers/bedrock/capabilities.rb +18 -0
  296. data/lib/ruby_llm/providers/bedrock/mantle/anthropic.rb +39 -0
  297. data/lib/ruby_llm/providers/bedrock/mantle/chat_completions.rb +23 -0
  298. data/lib/ruby_llm/providers/bedrock/mantle/responses.rb +24 -0
  299. data/lib/ruby_llm/providers/bedrock/mantle/voxtral.rb +96 -0
  300. data/lib/ruby_llm/providers/bedrock/mantle.rb +57 -0
  301. data/lib/ruby_llm/providers/bedrock/models.rb +193 -41
  302. data/lib/ruby_llm/providers/bedrock.rb +216 -45
  303. data/lib/ruby_llm/providers/cohere.rb +31 -0
  304. data/lib/ruby_llm/providers/deepgram.rb +37 -0
  305. data/lib/ruby_llm/providers/deepseek/capabilities.rb +4 -51
  306. data/lib/ruby_llm/providers/deepseek/chat.rb +49 -2
  307. data/lib/ruby_llm/providers/deepseek/responses.rb +68 -0
  308. data/lib/ruby_llm/providers/deepseek.rb +9 -2
  309. data/lib/ruby_llm/providers/elevenlabs.rb +35 -0
  310. data/lib/ruby_llm/providers/gemini/capabilities.rb +8 -107
  311. data/lib/ruby_llm/providers/gemini.rb +15 -8
  312. data/lib/ruby_llm/providers/gpustack/chat.rb +2 -16
  313. data/lib/ruby_llm/providers/gpustack/embeddings.rb +28 -0
  314. data/lib/ruby_llm/providers/gpustack/media.rb +17 -16
  315. data/lib/ruby_llm/providers/gpustack/models.rb +72 -62
  316. data/lib/ruby_llm/providers/gpustack/speech.rb +15 -0
  317. data/lib/ruby_llm/providers/gpustack/transcription.rb +29 -0
  318. data/lib/ruby_llm/providers/gpustack.rb +35 -11
  319. data/lib/ruby_llm/providers/mistral/capabilities.rb +7 -155
  320. data/lib/ruby_llm/providers/mistral/chat.rb +37 -61
  321. data/lib/ruby_llm/providers/mistral/chat_completions/batches.rb +120 -0
  322. data/lib/ruby_llm/providers/mistral/chat_completions.rb +21 -0
  323. data/lib/ruby_llm/providers/mistral/conversations.rb +12 -0
  324. data/lib/ruby_llm/providers/mistral/embeddings.rb +6 -4
  325. data/lib/ruby_llm/providers/mistral/media.rb +6 -18
  326. data/lib/ruby_llm/providers/mistral/models.rb +57 -23
  327. data/lib/ruby_llm/providers/mistral/ocr.rb +47 -0
  328. data/lib/ruby_llm/providers/mistral/speech.rb +51 -0
  329. data/lib/ruby_llm/providers/mistral/transcription.rb +62 -0
  330. data/lib/ruby_llm/providers/mistral.rb +16 -4
  331. data/lib/ruby_llm/providers/ollama/chat.rb +7 -13
  332. data/lib/ruby_llm/providers/ollama/media.rb +6 -15
  333. data/lib/ruby_llm/providers/ollama/models.rb +50 -9
  334. data/lib/ruby_llm/providers/ollama.rb +9 -8
  335. data/lib/ruby_llm/providers/ollama_cloud/models.rb +14 -0
  336. data/lib/ruby_llm/providers/ollama_cloud.rb +40 -0
  337. data/lib/ruby_llm/providers/openai/capabilities.rb +54 -259
  338. data/lib/ruby_llm/providers/openai/models.rb +23 -23
  339. data/lib/ruby_llm/providers/openai/responses.rb +13 -0
  340. data/lib/ruby_llm/providers/openai.rb +92 -11
  341. data/lib/ruby_llm/providers/openrouter/chat.rb +130 -108
  342. data/lib/ruby_llm/providers/openrouter/embeddings.rb +51 -0
  343. data/lib/ruby_llm/providers/openrouter/images.rb +44 -43
  344. data/lib/ruby_llm/providers/openrouter/media.rb +34 -0
  345. data/lib/ruby_llm/providers/openrouter/models.rb +50 -11
  346. data/lib/ruby_llm/providers/openrouter/speech.rb +32 -0
  347. data/lib/ruby_llm/providers/openrouter/streaming.rb +31 -38
  348. data/lib/ruby_llm/providers/openrouter/videos.rb +81 -0
  349. data/lib/ruby_llm/providers/openrouter.rb +78 -20
  350. data/lib/ruby_llm/providers/perplexity/chat.rb +2 -9
  351. data/lib/ruby_llm/providers/perplexity/embeddings.rb +32 -0
  352. data/lib/ruby_llm/providers/perplexity/media.rb +5 -21
  353. data/lib/ruby_llm/providers/perplexity/models.rb +80 -13
  354. data/lib/ruby_llm/providers/perplexity.rb +28 -20
  355. data/lib/ruby_llm/providers/vertexai/anthropic/batches.rb +52 -0
  356. data/lib/ruby_llm/providers/vertexai/anthropic.rb +34 -0
  357. data/lib/ruby_llm/providers/vertexai/capabilities.rb +19 -0
  358. data/lib/ruby_llm/providers/vertexai/chat_completions/batches.rb +54 -0
  359. data/lib/ruby_llm/providers/vertexai/chat_completions.rb +15 -0
  360. data/lib/ruby_llm/providers/vertexai/embed_content.rb +42 -0
  361. data/lib/ruby_llm/providers/vertexai/embeddings.rb +22 -7
  362. data/lib/ruby_llm/providers/vertexai/gemini/batches.rb +42 -0
  363. data/lib/ruby_llm/providers/vertexai/gemini.rb +69 -0
  364. data/lib/ruby_llm/providers/vertexai/live_transcription.rb +24 -0
  365. data/lib/ruby_llm/providers/vertexai/mistral.rb +28 -0
  366. data/lib/ruby_llm/providers/vertexai/models.rb +145 -43
  367. data/lib/ruby_llm/providers/vertexai/transcription.rb +49 -4
  368. data/lib/ruby_llm/providers/vertexai/videos.rb +61 -0
  369. data/lib/ruby_llm/providers/vertexai.rb +160 -17
  370. data/lib/ruby_llm/providers/xai/capabilities.rb +18 -0
  371. data/lib/ruby_llm/providers/xai/chat.rb +3 -2
  372. data/lib/ruby_llm/providers/xai/chat_completions/batches.rb +108 -0
  373. data/lib/ruby_llm/providers/xai/chat_completions.rb +19 -0
  374. data/lib/ruby_llm/providers/xai/images.rb +91 -0
  375. data/lib/ruby_llm/providers/xai/models.rb +32 -36
  376. data/lib/ruby_llm/providers/xai/reported_cost.rb +18 -0
  377. data/lib/ruby_llm/providers/xai/responses.rb +52 -0
  378. data/lib/ruby_llm/providers/xai/speech.rb +45 -0
  379. data/lib/ruby_llm/providers/xai/transcription.rb +48 -0
  380. data/lib/ruby_llm/providers/xai/videos.rb +87 -0
  381. data/lib/ruby_llm/providers/xai.rb +15 -5
  382. data/lib/ruby_llm/railtie.rb +7 -16
  383. data/lib/ruby_llm/rerank.rb +105 -0
  384. data/lib/ruby_llm/research_job.rb +241 -0
  385. data/lib/ruby_llm/search_results.rb +68 -0
  386. data/lib/ruby_llm/server_tool_call.rb +73 -0
  387. data/lib/ruby_llm/speech.rb +159 -0
  388. data/lib/ruby_llm/speech_chunk.rb +33 -0
  389. data/lib/ruby_llm/support/deprecator.rb +22 -0
  390. data/lib/ruby_llm/support/inspectable.rb +49 -0
  391. data/lib/ruby_llm/support/instrumentation.rb +41 -0
  392. data/lib/ruby_llm/support/utils.rb +147 -0
  393. data/lib/ruby_llm/thinking.rb +127 -20
  394. data/lib/ruby_llm/tokenization.rb +59 -0
  395. data/lib/ruby_llm/tokens.rb +103 -33
  396. data/lib/ruby_llm/tool.rb +266 -91
  397. data/lib/ruby_llm/tool_call.rb +36 -3
  398. data/lib/ruby_llm/tools/server_tools.rb +109 -0
  399. data/lib/ruby_llm/transcription/wav_audio.rb +62 -0
  400. data/lib/ruby_llm/transcription.rb +138 -13
  401. data/lib/ruby_llm/transcription_chunk.rb +68 -0
  402. data/lib/ruby_llm/transport/connection.rb +193 -0
  403. data/lib/ruby_llm/transport/error_middleware.rb +131 -0
  404. data/lib/ruby_llm/transport/usage_middleware.rb +28 -0
  405. data/lib/ruby_llm/transport/websocket_connection.rb +220 -0
  406. data/lib/ruby_llm/uploaded_file.rb +144 -0
  407. data/lib/ruby_llm/version.rb +2 -1
  408. data/lib/ruby_llm/video.rb +136 -0
  409. data/lib/ruby_llm/video_job.rb +150 -0
  410. data/lib/ruby_llm/workflow.rb +91 -0
  411. data/lib/ruby_llm.rb +380 -6
  412. data/lib/tasks/ruby_llm.rake +21 -16
  413. data/skills/rubyllm/SKILL.md +81 -0
  414. data/skills/rubyllm/agents/openai.yaml +4 -0
  415. metadata +339 -97
  416. data/lib/generators/ruby_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt +0 -9
  417. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +0 -39
  418. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +0 -21
  419. data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +0 -3
  420. data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +0 -3
  421. data/lib/generators/ruby_llm/upgrade_to_v1_10/templates/add_v1_10_message_columns.rb.tt +0 -19
  422. data/lib/generators/ruby_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +0 -50
  423. data/lib/generators/ruby_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +0 -7
  424. data/lib/generators/ruby_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +0 -49
  425. data/lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt +0 -145
  426. data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +0 -122
  427. data/lib/generators/ruby_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +0 -15
  428. data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +0 -49
  429. data/lib/ruby_llm/active_record/acts_as_legacy.rb +0 -597
  430. data/lib/ruby_llm/active_record/model_methods.rb +0 -82
  431. data/lib/ruby_llm/active_record/tool_call_methods.rb +0 -18
  432. data/lib/ruby_llm/aliases.rb +0 -41
  433. data/lib/ruby_llm/connection.rb +0 -159
  434. data/lib/ruby_llm/content.rb +0 -91
  435. data/lib/ruby_llm/deprecator.rb +0 -24
  436. data/lib/ruby_llm/error_middleware.rb +0 -81
  437. data/lib/ruby_llm/instrumentation.rb +0 -36
  438. data/lib/ruby_llm/mime_type.rb +0 -96
  439. data/lib/ruby_llm/model/info.rb +0 -164
  440. data/lib/ruby_llm/model_registry.rb +0 -39
  441. data/lib/ruby_llm/models_schema.json +0 -171
  442. data/lib/ruby_llm/providers/anthropic/chat.rb +0 -291
  443. data/lib/ruby_llm/providers/anthropic/content.rb +0 -44
  444. data/lib/ruby_llm/providers/anthropic/embeddings.rb +0 -20
  445. data/lib/ruby_llm/providers/anthropic/media.rb +0 -92
  446. data/lib/ruby_llm/providers/anthropic/models.rb +0 -59
  447. data/lib/ruby_llm/providers/anthropic/streaming.rb +0 -71
  448. data/lib/ruby_llm/providers/bedrock/chat.rb +0 -405
  449. data/lib/ruby_llm/providers/bedrock/media.rb +0 -108
  450. data/lib/ruby_llm/providers/bedrock/streaming.rb +0 -328
  451. data/lib/ruby_llm/providers/gemini/chat.rb +0 -542
  452. data/lib/ruby_llm/providers/gemini/embeddings.rb +0 -37
  453. data/lib/ruby_llm/providers/gemini/images.rb +0 -47
  454. data/lib/ruby_llm/providers/gemini/models.rb +0 -38
  455. data/lib/ruby_llm/providers/gemini/streaming.rb +0 -98
  456. data/lib/ruby_llm/providers/gemini/tools.rb +0 -234
  457. data/lib/ruby_llm/providers/gpustack/capabilities.rb +0 -20
  458. data/lib/ruby_llm/providers/ollama/capabilities.rb +0 -20
  459. data/lib/ruby_llm/providers/openai/chat.rb +0 -236
  460. data/lib/ruby_llm/providers/openai/embeddings.rb +0 -33
  461. data/lib/ruby_llm/providers/openai/images.rb +0 -90
  462. data/lib/ruby_llm/providers/openai/moderation.rb +0 -34
  463. data/lib/ruby_llm/providers/openai/streaming.rb +0 -55
  464. data/lib/ruby_llm/providers/openai/temperature.rb +0 -28
  465. data/lib/ruby_llm/providers/openai/transcription.rb +0 -71
  466. data/lib/ruby_llm/providers/perplexity/capabilities.rb +0 -72
  467. data/lib/ruby_llm/providers/vertexai/chat.rb +0 -14
  468. data/lib/ruby_llm/providers/vertexai/streaming.rb +0 -14
  469. data/lib/ruby_llm/stream_accumulator.rb +0 -218
  470. data/lib/ruby_llm/streaming.rb +0 -179
  471. data/lib/ruby_llm/tool_concurrency.rb +0 -105
  472. data/lib/ruby_llm/utils.rb +0 -130
  473. data/lib/tasks/models.rake +0 -593
  474. data/lib/tasks/release.rake +0 -94
  475. data/lib/tasks/vcr.rake +0 -124
@@ -1,98 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
-
5
- module RubyLLM
6
- module Providers
7
- class Gemini
8
- # Streaming methods for the Gemini API implementation
9
- module Streaming
10
- def stream_url
11
- "models/#{@model}:streamGenerateContent?alt=sse"
12
- end
13
-
14
- def build_chunk(data)
15
- parts = data.dig('candidates', 0, 'content', 'parts') || []
16
-
17
- Chunk.new(
18
- role: :assistant,
19
- model_id: extract_model_id(data),
20
- content: extract_text_content(parts),
21
- thinking: Thinking.build(
22
- text: extract_thought_content(parts),
23
- signature: extract_thought_signature(parts)
24
- ),
25
- input_tokens: extract_input_tokens(data),
26
- output_tokens: extract_output_tokens(data),
27
- cached_tokens: data.dig('usageMetadata', 'cachedContentTokenCount'),
28
- thinking_tokens: data.dig('usageMetadata', 'thoughtsTokenCount'),
29
- tool_calls: extract_tool_calls(data)
30
- )
31
- end
32
-
33
- private
34
-
35
- def extract_model_id(data)
36
- data['modelVersion']
37
- end
38
-
39
- def extract_text_content(parts)
40
- text_parts = parts.reject { |p| p['thought'] }
41
- text = text_parts.filter_map { |p| p['text'] }.join
42
- text.empty? ? nil : text
43
- end
44
-
45
- def extract_thought_content(parts)
46
- thought_parts = parts.select { |p| p['thought'] }
47
- thoughts = thought_parts.filter_map { |p| p['text'] }.join
48
- thoughts.empty? ? nil : thoughts
49
- end
50
-
51
- def extract_thought_signature(parts)
52
- parts.each do |part|
53
- signature = part['thoughtSignature'] ||
54
- part['thought_signature'] ||
55
- part.dig('functionCall', 'thoughtSignature') ||
56
- part.dig('functionCall', 'thought_signature')
57
- return signature if signature
58
- end
59
-
60
- nil
61
- end
62
-
63
- def extract_content(data)
64
- return nil unless data['candidates']&.any?
65
-
66
- candidate = data['candidates'][0]
67
- parts = candidate.dig('content', 'parts')
68
- return nil unless parts
69
-
70
- text_parts = parts.select { |p| p['text'] }
71
- text_parts.map { |p| p['text'] }.join if text_parts.any?
72
- end
73
-
74
- def extract_input_tokens(data)
75
- prompt_tokens = data.dig('usageMetadata', 'promptTokenCount')
76
- return unless prompt_tokens
77
-
78
- [prompt_tokens.to_i - data.dig('usageMetadata', 'cachedContentTokenCount').to_i, 0].max
79
- end
80
-
81
- def extract_output_tokens(data)
82
- candidates = data.dig('usageMetadata', 'candidatesTokenCount') || 0
83
- thoughts = data.dig('usageMetadata', 'thoughtsTokenCount') || 0
84
- total = candidates + thoughts
85
- total.positive? ? total : nil
86
- end
87
-
88
- def parse_streaming_error(data)
89
- error_data = JSON.parse(data)
90
- [error_data['error']['code'], error_data['error']['message']]
91
- rescue JSON::ParserError => e
92
- RubyLLM.logger.debug { "Failed to parse streaming error: #{e.message}" }
93
- [500, "Failed to parse error: #{data}"]
94
- end
95
- end
96
- end
97
- end
98
- end
@@ -1,234 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'securerandom'
4
-
5
- module RubyLLM
6
- module Providers
7
- class Gemini
8
- # Tools methods for the Gemini API implementation
9
- module Tools
10
- def format_tools(tools)
11
- return [] if tools.empty?
12
-
13
- [{
14
- functionDeclarations: tools.values.map { |tool| function_declaration_for(tool) }
15
- }]
16
- end
17
-
18
- def format_tool_call(msg) # rubocop:disable Metrics/PerceivedComplexity
19
- parts = []
20
-
21
- if msg.content && !(msg.content.respond_to?(:empty?) && msg.content.empty?)
22
- formatted_content = Media.format_content(msg.content)
23
- parts.concat(formatted_content.is_a?(Array) ? formatted_content : [formatted_content])
24
- end
25
-
26
- fallback_signature = msg.thinking&.signature
27
- used_fallback = false
28
-
29
- msg.tool_calls.each_value do |tool_call|
30
- part = {
31
- functionCall: {
32
- name: tool_call.name,
33
- args: tool_call.arguments
34
- }
35
- }
36
-
37
- signature = tool_call.thought_signature
38
- if signature.nil? && fallback_signature && !used_fallback
39
- signature = fallback_signature
40
- used_fallback = true
41
- end
42
- part[:thoughtSignature] = signature if signature
43
- parts << part
44
- end
45
-
46
- parts
47
- end
48
-
49
- def format_tool_result(msg, function_name = nil)
50
- function_name ||= msg.tool_call_id
51
- content = msg.content
52
- content = '(no output)' if content.nil? || (content.respond_to?(:empty?) && content.empty?)
53
-
54
- [{
55
- functionResponse: {
56
- name: function_name,
57
- response: {
58
- name: function_name,
59
- content: Media.format_content(content)
60
- }
61
- }
62
- }]
63
- end
64
-
65
- def extract_tool_calls(data) # rubocop:disable Metrics/PerceivedComplexity
66
- return nil unless data
67
-
68
- candidate = data.is_a?(Hash) ? data.dig('candidates', 0) : nil
69
- return nil unless candidate
70
-
71
- parts = candidate.dig('content', 'parts')
72
- return nil unless parts.is_a?(Array)
73
-
74
- tool_calls = parts.each_with_object({}) do |part, result|
75
- function_data = part['functionCall']
76
- next unless function_data
77
-
78
- id = SecureRandom.uuid
79
- thought_signature = part['thoughtSignature'] || part['thought_signature']
80
-
81
- result[id] = ToolCall.new(
82
- id:,
83
- name: function_data['name'],
84
- arguments: function_data['args'] || {},
85
- thought_signature: thought_signature
86
- )
87
- end
88
-
89
- tool_calls.empty? ? nil : tool_calls
90
- end
91
-
92
- private
93
-
94
- def function_declaration_for(tool)
95
- parameters_schema = tool.params_schema ||
96
- RubyLLM::Tool::SchemaDefinition.from_parameters(tool.parameters)&.json_schema
97
-
98
- declaration = {
99
- name: tool.name,
100
- description: tool.description
101
- }
102
-
103
- declaration[:parameters] = convert_tool_schema_to_gemini(parameters_schema) if parameters_schema
104
-
105
- return declaration if tool.provider_params.empty?
106
-
107
- RubyLLM::Utils.deep_merge(declaration, tool.provider_params)
108
- end
109
-
110
- def convert_tool_schema_to_gemini(schema)
111
- return nil unless schema
112
-
113
- schema = RubyLLM::Utils.deep_stringify_keys(schema)
114
-
115
- raise ArgumentError, 'Gemini tool parameters must be objects' unless schema['type'] == 'object'
116
-
117
- {
118
- type: 'OBJECT',
119
- properties: schema.fetch('properties', {}).transform_values { |property| convert_property(property) },
120
- required: (schema['required'] || []).map(&:to_s)
121
- }
122
- end
123
-
124
- def convert_property(property_schema) # rubocop:disable Metrics/PerceivedComplexity
125
- normalized_schema = normalize_any_of_schema(property_schema)
126
- working_schema = normalized_schema || property_schema
127
-
128
- type = param_type_for_gemini(working_schema['type'])
129
-
130
- property = {
131
- type: type
132
- }
133
-
134
- copy_common_attributes(property, property_schema)
135
- copy_common_attributes(property, working_schema)
136
-
137
- case type
138
- when 'ARRAY'
139
- items_schema = working_schema['items'] || property_schema['items'] || { 'type' => 'string' }
140
- property[:items] = convert_property(items_schema)
141
- copy_tool_attributes(property, working_schema, %w[minItems maxItems])
142
- copy_tool_attributes(property, property_schema, %w[minItems maxItems])
143
- when 'OBJECT'
144
- nested_properties = working_schema.fetch('properties', {}).transform_values do |child|
145
- convert_property(child)
146
- end
147
- property[:properties] = nested_properties
148
- required = working_schema['required'] || property_schema['required']
149
- property[:required] = required.map(&:to_s) if required
150
- end
151
-
152
- property
153
- end
154
-
155
- def copy_common_attributes(target, source)
156
- copy_tool_attributes(target, source, %w[description enum format nullable maximum minimum multipleOf])
157
- end
158
-
159
- def copy_tool_attributes(target, source, attributes)
160
- attributes.each do |attribute|
161
- value = schema_value(source, attribute)
162
- next if value.nil?
163
-
164
- target[attribute.to_sym] = value
165
- end
166
- end
167
-
168
- def normalize_any_of_schema(schema) # rubocop:disable Metrics/PerceivedComplexity
169
- any_of = schema['anyOf'] || schema[:anyOf]
170
- return nil unless any_of.is_a?(Array) && any_of.any?
171
-
172
- null_entries, non_null_entries = any_of.partition { |entry| schema_type(entry).to_s == 'null' }
173
-
174
- if non_null_entries.size == 1 && null_entries.any?
175
- normalized = RubyLLM::Utils.deep_dup(non_null_entries.first)
176
- normalized['nullable'] = true
177
- normalized
178
- elsif non_null_entries.any?
179
- RubyLLM::Utils.deep_dup(non_null_entries.first)
180
- else
181
- { 'type' => 'string', 'nullable' => true }
182
- end
183
- end
184
-
185
- def schema_type(schema)
186
- schema['type'] || schema[:type]
187
- end
188
-
189
- def schema_value(source, attribute) # rubocop:disable Metrics/PerceivedComplexity
190
- case attribute
191
- when 'multipleOf'
192
- source['multipleOf'] || source[:multipleOf] || source['multiple_of'] || source[:multiple_of]
193
- when 'minItems'
194
- source['minItems'] || source[:minItems] || source['min_items'] || source[:min_items]
195
- when 'maxItems'
196
- source['maxItems'] || source[:maxItems] || source['max_items'] || source[:max_items]
197
- else
198
- source[attribute] || source[attribute.to_sym]
199
- end
200
- end
201
-
202
- def param_type_for_gemini(type)
203
- case type.to_s.downcase
204
- when 'integer' then 'INTEGER'
205
- when 'number', 'float', 'double' then 'NUMBER'
206
- when 'boolean' then 'BOOLEAN'
207
- when 'array' then 'ARRAY'
208
- when 'object' then 'OBJECT'
209
- else 'STRING'
210
- end
211
- end
212
-
213
- def build_tool_config(tool_choice)
214
- {
215
- functionCallingConfig: {
216
- mode: forced_tool_choice?(tool_choice) ? 'any' : tool_choice
217
- }.tap do |config|
218
- # Use allowedFunctionNames to simulate specific tool choice
219
- config[:allowedFunctionNames] = [tool_choice] if specific_tool_choice?(tool_choice)
220
- end
221
- }
222
- end
223
-
224
- def forced_tool_choice?(tool_choice)
225
- tool_choice == :required || specific_tool_choice?(tool_choice)
226
- end
227
-
228
- def specific_tool_choice?(tool_choice)
229
- !%i[auto none required].include?(tool_choice)
230
- end
231
- end
232
- end
233
- end
234
- 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,236 +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: format_message_content(msg),
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_message_content(msg)
136
- content = format_content(msg.content)
137
- return '' if content.nil? && thinking_only_assistant_message?(msg)
138
-
139
- content
140
- end
141
-
142
- def thinking_only_assistant_message?(msg)
143
- msg.role == :assistant && msg.thinking && !msg.tool_call?
144
- end
145
-
146
- def format_content(content)
147
- Media.format_content(content)
148
- end
149
-
150
- def format_role(role)
151
- case role
152
- when :system
153
- @config.openai_use_system_role ? 'system' : 'developer'
154
- else
155
- role.to_s
156
- end
157
- end
158
-
159
- def resolve_effort(thinking)
160
- return nil unless thinking
161
-
162
- thinking.respond_to?(:effort) ? thinking.effort : thinking
163
- end
164
-
165
- def format_thinking(msg)
166
- return {} unless msg.role == :assistant
167
-
168
- thinking = msg.thinking
169
- return {} unless thinking
170
-
171
- payload = {}
172
- if thinking.text
173
- payload[:reasoning] = thinking.text
174
- payload[:reasoning_content] = thinking.text
175
- end
176
- payload[:reasoning_signature] = thinking.signature if thinking.signature
177
- payload
178
- end
179
-
180
- def extract_thinking_text(message_data)
181
- candidate = message_data['reasoning_content'] || message_data['reasoning'] || message_data['thinking']
182
- candidate.is_a?(String) ? candidate : nil
183
- end
184
-
185
- def extract_thinking_signature(message_data)
186
- candidate = message_data['reasoning_signature'] || message_data['signature']
187
- candidate.is_a?(String) ? candidate : nil
188
- end
189
-
190
- def extract_content_and_thinking(content)
191
- return extract_think_tag_content(content) if content.is_a?(String)
192
- return [content, nil] unless content.is_a?(Array)
193
-
194
- text = extract_text_from_blocks(content)
195
- thinking = extract_thinking_from_blocks(content)
196
-
197
- [text.empty? ? nil : text, thinking.empty? ? nil : thinking]
198
- end
199
-
200
- def extract_text_from_blocks(blocks)
201
- blocks.filter_map do |block|
202
- block['text'] if block['type'] == 'text' && block['text'].is_a?(String)
203
- end.join
204
- end
205
-
206
- def extract_thinking_from_blocks(blocks)
207
- blocks.filter_map do |block|
208
- next unless block['type'] == 'thinking'
209
-
210
- extract_thinking_text_from_block(block)
211
- end.join
212
- end
213
-
214
- def extract_thinking_text_from_block(block)
215
- thinking_block = block['thinking']
216
- return thinking_block if thinking_block.is_a?(String)
217
-
218
- if thinking_block.is_a?(Array)
219
- return thinking_block.filter_map { |item| item['text'] if item['type'] == 'text' }.join
220
- end
221
-
222
- block['text'] if block['text'].is_a?(String)
223
- end
224
-
225
- def extract_think_tag_content(text)
226
- return [text, nil] unless text.include?('<think>')
227
-
228
- thinking = text.scan(%r{<think>(.*?)</think>}m).join
229
- content = text.gsub(%r{<think>.*?</think>}m, '').strip
230
-
231
- [content.empty? ? nil : content, thinking.empty? ? nil : thinking]
232
- end
233
- end
234
- end
235
- end
236
- 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