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,542 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'set'
4
- require 'rubygems/version'
5
-
6
- module RubyLLM
7
- module Providers
8
- class Gemini
9
- # Chat methods for the Gemini API implementation
10
- module Chat
11
- module_function
12
-
13
- def completion_url
14
- "models/#{@model}:generateContent"
15
- end
16
-
17
- # rubocop:disable Metrics/ParameterLists,Lint/UnusedMethodArgument
18
- def render_payload(messages, tools:, temperature:, model:, stream: false, schema: nil,
19
- thinking: nil, tool_prefs: nil)
20
- tool_prefs ||= {}
21
- @model = model.id
22
- payload = {
23
- contents: format_messages(messages),
24
- generationConfig: {}
25
- }
26
-
27
- payload[:generationConfig][:temperature] = temperature unless temperature.nil?
28
-
29
- payload[:generationConfig].merge!(structured_output_config(schema, model)) if schema
30
- payload[:generationConfig][:thinkingConfig] = build_thinking_config(model, thinking) if thinking&.enabled?
31
-
32
- if tools.any?
33
- payload[:tools] = format_tools(tools)
34
- # Gemini doesn't support controlling parallel tool calls
35
- payload[:toolConfig] = build_tool_config(tool_prefs[:choice]) unless tool_prefs[:choice].nil?
36
- end
37
-
38
- payload
39
- end
40
- # rubocop:enable Metrics/ParameterLists,Lint/UnusedMethodArgument
41
-
42
- def build_thinking_config(_model, thinking)
43
- config = { includeThoughts: true }
44
-
45
- config[:thinkingLevel] = resolve_effort_level(thinking) if thinking&.effort
46
- config[:thinkingBudget] = resolve_budget(thinking) if thinking&.budget
47
-
48
- config
49
- end
50
-
51
- def resolve_effort_level(thinking)
52
- thinking.respond_to?(:effort) ? thinking.effort : thinking
53
- end
54
-
55
- def resolve_budget(thinking)
56
- budget = thinking.respond_to?(:budget) ? thinking.budget : thinking
57
- budget.is_a?(Integer) ? budget : nil
58
- end
59
-
60
- private
61
-
62
- def format_messages(messages)
63
- formatter = MessageFormatter.new(
64
- messages,
65
- format_role: method(:format_role),
66
- format_parts: method(:format_parts),
67
- format_tool_result: method(:format_tool_result)
68
- )
69
- formatter.format
70
- end
71
-
72
- def format_role(role)
73
- case role
74
- when :assistant then 'model'
75
- when :system, :tool then 'user'
76
- else role.to_s
77
- end
78
- end
79
-
80
- def format_parts(msg)
81
- if msg.tool_call?
82
- format_tool_call(msg)
83
- elsif msg.tool_result?
84
- format_tool_result(msg)
85
- else
86
- format_message_parts(msg)
87
- end
88
- end
89
-
90
- def format_message_parts(msg)
91
- parts = []
92
-
93
- parts << build_thought_part(msg.thinking) if msg.role == :assistant && msg.thinking
94
-
95
- content_parts = Media.format_content(msg.content)
96
- parts.concat(content_parts.is_a?(Array) ? content_parts : [content_parts])
97
- parts
98
- end
99
-
100
- def build_thought_part(thinking)
101
- part = { thought: true }
102
- part[:text] = thinking.text if thinking.text
103
- part[:thoughtSignature] = thinking.signature if thinking.signature
104
- part
105
- end
106
-
107
- def parse_completion_response(response)
108
- data = response.body
109
- parts = data.dig('candidates', 0, 'content', 'parts') || []
110
- tool_calls = extract_tool_calls(data)
111
-
112
- Message.new(
113
- role: :assistant,
114
- content: extract_text_parts(parts) || parse_content(data),
115
- thinking: Thinking.build(
116
- text: extract_thought_parts(parts),
117
- signature: extract_thought_signature(parts)
118
- ),
119
- tool_calls: tool_calls,
120
- input_tokens: input_tokens(data),
121
- output_tokens: calculate_output_tokens(data),
122
- cached_tokens: data.dig('usageMetadata', 'cachedContentTokenCount'),
123
- thinking_tokens: data.dig('usageMetadata', 'thoughtsTokenCount'),
124
- model_id: data['modelVersion'] || response.env.url.path.split('/')[3].split(':')[0],
125
- raw: response
126
- )
127
- end
128
-
129
- def input_tokens(data)
130
- prompt_tokens = data.dig('usageMetadata', 'promptTokenCount')
131
- return unless prompt_tokens
132
-
133
- [prompt_tokens.to_i - data.dig('usageMetadata', 'cachedContentTokenCount').to_i, 0].max
134
- end
135
-
136
- def convert_schema_to_gemini(schema)
137
- return nil unless schema
138
-
139
- # Extract inner schema if wrapper format (e.g., from RubyLLM::Schema.to_json_schema)
140
- schema = schema[:schema] || schema
141
-
142
- GeminiSchema.new(schema).to_h
143
- end
144
-
145
- def parse_content(data)
146
- candidate = data.dig('candidates', 0)
147
- return '' unless candidate
148
-
149
- return '' if function_call?(candidate)
150
-
151
- parts = candidate.dig('content', 'parts')
152
- return '' unless parts&.any?
153
-
154
- non_thought_parts = parts.reject { |part| part['thought'] }
155
- return '' unless non_thought_parts.any?
156
-
157
- build_response_content(non_thought_parts)
158
- end
159
-
160
- def extract_text_parts(parts)
161
- text_parts = parts.reject { |p| p['thought'] }
162
- content = text_parts.filter_map { |p| p['text'] }.join
163
- content.empty? ? nil : content
164
- end
165
-
166
- def extract_thought_parts(parts)
167
- thought_parts = parts.select { |p| p['thought'] }
168
- thoughts = thought_parts.filter_map { |p| p['text'] }.join
169
- thoughts.empty? ? nil : thoughts
170
- end
171
-
172
- def extract_thought_signature(parts)
173
- parts.each do |part|
174
- signature = part['thoughtSignature'] ||
175
- part['thought_signature'] ||
176
- part.dig('functionCall', 'thoughtSignature') ||
177
- part.dig('functionCall', 'thought_signature')
178
- return signature if signature
179
- end
180
-
181
- nil
182
- end
183
-
184
- def function_call?(candidate)
185
- parts = candidate.dig('content', 'parts')
186
- parts&.any? { |p| p['functionCall'] }
187
- end
188
-
189
- def calculate_output_tokens(data)
190
- candidates = data.dig('usageMetadata', 'candidatesTokenCount') || 0
191
- thoughts = data.dig('usageMetadata', 'thoughtsTokenCount') || 0
192
- candidates + thoughts
193
- end
194
-
195
- def response_json_schema_supported?(model)
196
- version = gemini_version(model)
197
- version && version >= Gem::Version.new('2.5')
198
- end
199
-
200
- def build_json_schema(schema)
201
- normalized = RubyLLM::Utils.deep_dup(schema[:schema])
202
- normalized.delete(:strict)
203
- normalized.delete('strict')
204
- RubyLLM::Utils.deep_stringify_keys(normalized)
205
- end
206
-
207
- def gemini_version(model)
208
- return nil unless model
209
-
210
- candidates = [
211
- safe_string(model.id),
212
- safe_string(model.respond_to?(:family) ? model.family : nil),
213
- safe_string(model_metadata_value(model, :version)),
214
- safe_string(model_metadata_value(model, 'version')),
215
- safe_string(model_metadata_value(model, :description))
216
- ].compact
217
-
218
- candidates.each do |candidate|
219
- version = extract_version(candidate)
220
- return version if version
221
- end
222
-
223
- nil
224
- end
225
-
226
- def model_metadata_value(model, key)
227
- return unless model.respond_to?(:metadata)
228
-
229
- metadata = model.metadata
230
- return unless metadata.is_a?(Hash)
231
-
232
- metadata[key] || metadata[key.to_s]
233
- end
234
-
235
- def safe_string(value)
236
- value&.to_s
237
- end
238
-
239
- def extract_version(text)
240
- return nil unless text
241
-
242
- match = text.match(/(\d+\.\d+|\d+)/)
243
- return nil unless match
244
-
245
- Gem::Version.new(match[1])
246
- rescue ArgumentError
247
- nil
248
- end
249
-
250
- def structured_output_config(schema, model)
251
- {
252
- responseMimeType: 'application/json'
253
- }.tap do |config|
254
- if response_json_schema_supported?(model)
255
- config[:responseJsonSchema] = build_json_schema(schema)
256
- else
257
- config[:responseSchema] = convert_schema_to_gemini(schema)
258
- end
259
- end
260
- end
261
-
262
- # formats a message
263
- class MessageFormatter
264
- def initialize(messages, format_role:, format_parts:, format_tool_result:)
265
- @messages = messages
266
- @index = 0
267
- @tool_call_names = {}
268
- @format_role = format_role
269
- @format_parts = format_parts
270
- @format_tool_result = format_tool_result
271
- end
272
-
273
- def format
274
- formatted = []
275
-
276
- while current_message
277
- if tool_message?(current_message)
278
- tool_parts, next_index = collect_tool_parts
279
- formatted << build_tool_response(tool_parts)
280
- @index = next_index
281
- else
282
- remember_tool_calls if current_message.tool_call?
283
- formatted << build_standard_message(current_message)
284
- @index += 1
285
- end
286
- end
287
-
288
- formatted
289
- end
290
-
291
- private
292
-
293
- def current_message
294
- @messages[@index]
295
- end
296
-
297
- def tool_message?(message)
298
- message&.role == :tool
299
- end
300
-
301
- def collect_tool_parts
302
- parts = []
303
- index = @index
304
-
305
- while tool_message?(@messages[index])
306
- tool_message = @messages[index]
307
- tool_name = @tool_call_names.delete(tool_message.tool_call_id)
308
- parts.concat(format_tool_result(tool_message, tool_name))
309
- index += 1
310
- end
311
-
312
- [parts, index]
313
- end
314
-
315
- def build_tool_response(parts)
316
- { role: 'user', parts: parts }
317
- end
318
-
319
- def remember_tool_calls
320
- current_message.tool_calls.each do |tool_call_id, tool_call|
321
- @tool_call_names[tool_call_id] = tool_call.name
322
- end
323
- end
324
-
325
- def build_standard_message(message)
326
- {
327
- role: @format_role.call(message.role),
328
- parts: @format_parts.call(message)
329
- }
330
- end
331
-
332
- def format_tool_result(message, tool_name)
333
- @format_tool_result.call(message, tool_name)
334
- end
335
- end
336
-
337
- # converts json schema to gemini
338
- class GeminiSchema
339
- def initialize(schema)
340
- @raw_schema = RubyLLM::Utils.deep_dup(schema)
341
- @definitions = {}
342
- end
343
-
344
- def to_h
345
- return nil unless @raw_schema
346
-
347
- symbolized = symbolize_and_extract_definitions(@raw_schema)
348
- convert(symbolized, Set.new)
349
- end
350
-
351
- private
352
-
353
- attr_reader :definitions
354
-
355
- def symbolize_and_extract_definitions(value)
356
- case value
357
- when Hash
358
- value.each_with_object({}) do |(key, val), hash|
359
- key_sym = begin
360
- key.to_sym
361
- rescue StandardError
362
- key
363
- end
364
-
365
- if definition_key?(key_sym)
366
- merge_definitions(val)
367
- else
368
- hash[key_sym] = symbolize_and_extract_definitions(val)
369
- end
370
- end
371
- when Array
372
- value.map { |item| symbolize_and_extract_definitions(item) }
373
- else
374
- value
375
- end
376
- end
377
-
378
- def definition_key?(key)
379
- %i[$defs definitions].include?(key)
380
- end
381
-
382
- def merge_definitions(raw_defs)
383
- return unless raw_defs
384
-
385
- symbolized = symbolize_and_extract_definitions(raw_defs)
386
- @definitions = if definitions.empty?
387
- symbolized
388
- else
389
- RubyLLM::Utils.deep_merge(definitions, symbolized)
390
- end
391
- end
392
-
393
- def convert(schema, visited_refs)
394
- return default_string_schema unless schema.is_a?(Hash)
395
-
396
- schema = strip_unsupported_keys(schema)
397
-
398
- if schema[:$ref]
399
- resolved = resolve_reference(schema, visited_refs)
400
- return resolved if resolved
401
- end
402
-
403
- schema = normalize_any_of(schema)
404
-
405
- result = case schema[:type].to_s
406
- when 'object'
407
- build_object(schema, visited_refs)
408
- when 'array'
409
- build_array(schema, visited_refs)
410
- when 'number'
411
- build_scalar('NUMBER', schema, %i[format minimum maximum enum nullable multipleOf])
412
- when 'integer'
413
- build_scalar('INTEGER', schema, %i[format minimum maximum enum nullable multipleOf])
414
- when 'boolean'
415
- build_scalar('BOOLEAN', schema, %i[nullable])
416
- else
417
- build_scalar('STRING', schema, %i[enum format nullable])
418
- end
419
-
420
- apply_description(result, schema)
421
- result
422
- end
423
-
424
- def strip_unsupported_keys(schema)
425
- schema.dup.tap do |copy|
426
- copy.delete(:strict)
427
- copy.delete(:additionalProperties)
428
- end
429
- end
430
-
431
- def resolve_reference(schema, visited_refs)
432
- ref = schema[:$ref]
433
- return unless ref
434
- return if visited_refs.include?(ref)
435
-
436
- referenced = lookup_definition(ref)
437
- return unless referenced
438
-
439
- overrides = schema.except(:$ref)
440
- visited_refs.add(ref)
441
- merged = RubyLLM::Utils.deep_merge(referenced, overrides)
442
- convert(merged, visited_refs)
443
- ensure
444
- visited_refs.delete(ref)
445
- end
446
-
447
- def lookup_definition(ref) # rubocop:disable Metrics/PerceivedComplexity
448
- segments = ref.to_s.split('/').reject(&:empty?)
449
- return nil if segments.empty?
450
-
451
- segments.shift if segments.first == '#'
452
- segments.shift if %w[$defs definitions].include?(segments.first)
453
-
454
- current = definitions
455
-
456
- segments.each do |segment|
457
- break current = nil unless current.is_a?(Hash)
458
-
459
- key = begin
460
- segment.to_sym
461
- rescue StandardError
462
- segment
463
- end
464
- current = current[key]
465
- end
466
-
467
- current ? RubyLLM::Utils.deep_dup(current) : nil
468
- end
469
-
470
- def normalize_any_of(schema)
471
- any_of = schema[:anyOf]
472
- return schema unless any_of
473
-
474
- options = Array(any_of).map { |option| RubyLLM::Utils.deep_symbolize_keys(option) }
475
- nullables, non_null = options.partition { |option| schema_type(option) == 'null' }
476
-
477
- base = RubyLLM::Utils.deep_symbolize_keys(non_null.first || { type: 'string' })
478
- base[:nullable] = true if nullables.any?
479
-
480
- without_any_of = schema.each_with_object({}) do |(key, value), result|
481
- result[key] = value unless key == :anyOf
482
- end
483
-
484
- without_any_of.merge(base)
485
- end
486
-
487
- def schema_type(option)
488
- (option[:type] || option['type']).to_s.downcase
489
- end
490
-
491
- def build_object(schema, visited_refs)
492
- properties = schema.fetch(:properties, {}).transform_values do |child|
493
- convert(child, visited_refs)
494
- end
495
-
496
- {
497
- type: 'OBJECT',
498
- properties: properties
499
- }.tap do |object|
500
- required = Array(schema[:required]).map(&:to_s).uniq
501
- object[:required] = required if required.any?
502
- object[:propertyOrdering] = schema[:propertyOrdering] if schema[:propertyOrdering]
503
- copy_attribute(object, schema, :nullable)
504
- end
505
- end
506
-
507
- def build_array(schema, visited_refs)
508
- items_schema = schema[:items] ? convert(schema[:items], visited_refs) : default_string_schema
509
-
510
- {
511
- type: 'ARRAY',
512
- items: items_schema
513
- }.tap do |array|
514
- copy_attribute(array, schema, :minItems)
515
- copy_attribute(array, schema, :maxItems)
516
- copy_attribute(array, schema, :nullable)
517
- end
518
- end
519
-
520
- def build_scalar(type, schema, allowed_keys)
521
- { type: type }.tap do |result|
522
- allowed_keys.each { |key| copy_attribute(result, schema, key) }
523
- end
524
- end
525
-
526
- def apply_description(target, schema)
527
- description = schema[:description]
528
- target[:description] = description if description
529
- end
530
-
531
- def copy_attribute(target, source, key)
532
- target[key] = source[key] if source.key?(key)
533
- end
534
-
535
- def default_string_schema
536
- { type: 'STRING' }
537
- end
538
- end
539
- end
540
- end
541
- end
542
- end
@@ -1,37 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class Gemini
6
- # Embeddings methods for the Gemini API integration
7
- module Embeddings
8
- module_function
9
-
10
- def embedding_url(model:)
11
- "models/#{model}:batchEmbedContents"
12
- end
13
-
14
- def render_embedding_payload(text, model:, dimensions:)
15
- { requests: [text].flatten.map { |t| single_embedding_payload(t, model:, dimensions:) } }
16
- end
17
-
18
- def parse_embedding_response(response, model:, text:)
19
- vectors = response.body['embeddings']&.map { |e| e['values'] }
20
- vectors = vectors.first if vectors&.length == 1 && !text.is_a?(Array)
21
-
22
- Embedding.new(vectors:, model:, input_tokens: 0)
23
- end
24
-
25
- private
26
-
27
- def single_embedding_payload(text, model:, dimensions:)
28
- {
29
- model: "models/#{model}",
30
- content: { parts: [{ text: text.to_s }] },
31
- outputDimensionality: dimensions
32
- }.compact
33
- end
34
- end
35
- end
36
- end
37
- end
@@ -1,47 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class Gemini
6
- # Image generation methods for the Gemini API implementation
7
- module Images
8
- def images_url(with: nil, mask: nil) # rubocop:disable Lint/UnusedMethodArgument
9
- "models/#{@model}:predict"
10
- end
11
-
12
- def render_image_payload(prompt, model:, size:, with: nil, mask: nil, params: {}) # rubocop:disable Lint/UnusedMethodArgument,Metrics/ParameterLists
13
- RubyLLM.logger.debug { "Ignoring size #{size}. Gemini does not support image size customization." }
14
- @model = model
15
- {
16
- instances: [
17
- {
18
- prompt: prompt
19
- }
20
- ],
21
- parameters: {
22
- sampleCount: 1
23
- }
24
- }
25
- end
26
-
27
- def parse_image_response(response, model:)
28
- data = response.body
29
- image_data = data['predictions']&.first
30
-
31
- unless image_data&.key?('bytesBase64Encoded')
32
- raise Error, 'Unexpected response format from Gemini image generation API'
33
- end
34
-
35
- mime_type = image_data['mimeType'] || 'image/png'
36
- base64_data = image_data['bytesBase64Encoded']
37
-
38
- Image.new(
39
- data: base64_data,
40
- mime_type: mime_type,
41
- model_id: model
42
- )
43
- end
44
- end
45
- end
46
- end
47
- end
@@ -1,38 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class Gemini
6
- # Models methods for the Gemini API integration
7
- module Models
8
- module_function
9
-
10
- def models_url
11
- 'models'
12
- end
13
-
14
- def parse_list_models_response(response, slug, capabilities)
15
- Array(response.body['models']).map do |model_data|
16
- model_id = model_data['name'].gsub('models/', '')
17
-
18
- Model::Info.new(
19
- id: model_id,
20
- name: model_data['displayName'] || model_id,
21
- provider: slug,
22
- created_at: nil,
23
- context_window: model_data['inputTokenLimit'] || capabilities.context_window_for(model_id),
24
- max_output_tokens: model_data['outputTokenLimit'] || capabilities.max_tokens_for(model_id),
25
- capabilities: capabilities.critical_capabilities_for(model_id),
26
- pricing: capabilities.pricing_for(model_id),
27
- metadata: {
28
- version: model_data['version'],
29
- description: model_data['description'],
30
- supported_generation_methods: model_data['supportedGenerationMethods']
31
- }
32
- )
33
- end
34
- end
35
- end
36
- end
37
- end
38
- end