ruby_llm 1.15.0 → 2.0.0.rc1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (470) hide show
  1. checksums.yaml +4 -4
  2. data/.rdoc_options +25 -0
  3. data/README.md +87 -33
  4. data/exe/ruby_llm +8 -0
  5. data/lib/generators/ruby_llm/agent/templates/agent.rb.tt +0 -1
  6. data/lib/generators/ruby_llm/chat_ui/chat_ui_generator.rb +3 -43
  7. data/lib/generators/ruby_llm/chat_ui/templates/controllers/chats_controller.rb.tt +11 -3
  8. data/lib/generators/ruby_llm/chat_ui/templates/controllers/messages_controller.rb.tt +8 -0
  9. data/lib/generators/ruby_llm/chat_ui/templates/controllers/models_controller.rb.tt +4 -4
  10. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_chat.html.erb.tt +1 -1
  11. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/_form.html.erb.tt +1 -1
  12. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/index.html.erb.tt +1 -1
  13. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/chats/show.html.erb.tt +2 -2
  14. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_assistant.html.erb.tt +1 -1
  15. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_system.html.erb.tt +1 -1
  16. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool.html.erb.tt +1 -1
  17. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_tool_calls.html.erb.tt +6 -4
  18. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/_user.html.erb.tt +1 -1
  19. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/messages/tool_calls/_default.html.erb.tt +2 -2
  20. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/_model.html.erb.tt +5 -6
  21. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/index.html.erb.tt +2 -2
  22. data/lib/generators/ruby_llm/chat_ui/templates/tailwind/views/models/show.html.erb.tt +5 -5
  23. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_chat.html.erb.tt +1 -1
  24. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/_form.html.erb.tt +1 -1
  25. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/index.html.erb.tt +1 -1
  26. data/lib/generators/ruby_llm/chat_ui/templates/views/chats/show.html.erb.tt +2 -2
  27. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_assistant.html.erb.tt +1 -1
  28. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_system.html.erb.tt +1 -1
  29. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool.html.erb.tt +1 -1
  30. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_tool_calls.html.erb.tt +6 -4
  31. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/_user.html.erb.tt +1 -1
  32. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/create.turbo_stream.erb.tt +4 -6
  33. data/lib/generators/ruby_llm/chat_ui/templates/views/messages/tool_calls/_default.html.erb.tt +2 -2
  34. data/lib/generators/ruby_llm/chat_ui/templates/views/models/_model.html.erb.tt +5 -6
  35. data/lib/generators/ruby_llm/chat_ui/templates/views/models/index.html.erb.tt +2 -2
  36. data/lib/generators/ruby_llm/chat_ui/templates/views/models/show.html.erb.tt +3 -3
  37. data/lib/generators/ruby_llm/generator_helpers.rb +106 -62
  38. data/lib/generators/ruby_llm/install/install_generator.rb +3 -11
  39. data/lib/generators/ruby_llm/install/templates/create_chats_migration.rb.tt +2 -0
  40. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +14 -6
  41. data/lib/generators/ruby_llm/install/templates/create_ruby_llm_records_migration.rb.tt +117 -0
  42. data/lib/generators/ruby_llm/install/templates/initializer.rb.tt +0 -8
  43. data/lib/generators/ruby_llm/provider/cli.rb +175 -0
  44. data/lib/generators/ruby_llm/provider/scaffold.rb +323 -0
  45. data/lib/generators/ruby_llm/provider/templates/core/provider.rb.erb +37 -0
  46. data/lib/generators/ruby_llm/provider/templates/core/provider_spec.rb.erb +34 -0
  47. data/lib/generators/ruby_llm/provider/templates/gem/archspec.rb.erb +14 -0
  48. data/lib/generators/ruby_llm/provider/templates/gem/bin/console.erb +15 -0
  49. data/lib/generators/ruby_llm/provider/templates/gem/bin/setup.erb +5 -0
  50. data/lib/generators/ruby_llm/provider/templates/gem/chat_schema_spec.rb.erb +30 -0
  51. data/lib/generators/ruby_llm/provider/templates/gem/chat_spec.rb.erb +35 -0
  52. data/lib/generators/ruby_llm/provider/templates/gem/chat_streaming_spec.rb.erb +22 -0
  53. data/lib/generators/ruby_llm/provider/templates/gem/chat_tools_spec.rb.erb +30 -0
  54. data/lib/generators/ruby_llm/provider/templates/gem/ci.yml.erb +32 -0
  55. data/lib/generators/ruby_llm/provider/templates/gem/embedding_spec.rb.erb +49 -0
  56. data/lib/generators/ruby_llm/provider/templates/gem/env.erb +2 -0
  57. data/lib/generators/ruby_llm/provider/templates/gem/fixtures_gitkeep.erb +1 -0
  58. data/lib/generators/ruby_llm/provider/templates/gem/flayignore.erb +1 -0
  59. data/lib/generators/ruby_llm/provider/templates/gem/gemfile.erb +25 -0
  60. data/lib/generators/ruby_llm/provider/templates/gem/gemspec.erb +28 -0
  61. data/lib/generators/ruby_llm/provider/templates/gem/gitignore.erb +7 -0
  62. data/lib/generators/ruby_llm/provider/templates/gem/gitleaks.yml.erb +22 -0
  63. data/lib/generators/ruby_llm/provider/templates/gem/image_spec.rb.erb +23 -0
  64. data/lib/generators/ruby_llm/provider/templates/gem/license.erb +21 -0
  65. data/lib/generators/ruby_llm/provider/templates/gem/models.rb.erb +19 -0
  66. data/lib/generators/ruby_llm/provider/templates/gem/models_spec.rb.erb +17 -0
  67. data/lib/generators/ruby_llm/provider/templates/gem/moderation_spec.rb.erb +22 -0
  68. data/lib/generators/ruby_llm/provider/templates/gem/overcommit.yml.erb +31 -0
  69. data/lib/generators/ruby_llm/provider/templates/gem/provider.rb.erb +52 -0
  70. data/lib/generators/ruby_llm/provider/templates/gem/provider_spec.rb.erb +38 -0
  71. data/lib/generators/ruby_llm/provider/templates/gem/rakefile.erb +38 -0
  72. data/lib/generators/ruby_llm/provider/templates/gem/readme.md.erb +50 -0
  73. data/lib/generators/ruby_llm/provider/templates/gem/release.yml.erb +36 -0
  74. data/lib/generators/ruby_llm/provider/templates/gem/rerank_spec.rb.erb +23 -0
  75. data/lib/generators/ruby_llm/provider/templates/gem/rspec.erb +2 -0
  76. data/lib/generators/ruby_llm/provider/templates/gem/rubocop.yml.erb +29 -0
  77. data/lib/generators/ruby_llm/provider/templates/gem/rubyllm_configuration.rb.erb +14 -0
  78. data/lib/generators/ruby_llm/provider/templates/gem/spec_helper.rb.erb +26 -0
  79. data/lib/generators/ruby_llm/provider/templates/gem/speech_spec.rb.erb +25 -0
  80. data/lib/generators/ruby_llm/provider/templates/gem/vcr_configuration.rb.erb +16 -0
  81. data/lib/generators/ruby_llm/provider/templates/gem/video_spec.rb.erb +27 -0
  82. data/lib/generators/ruby_llm/schema/schema_generator.rb +5 -1
  83. data/lib/generators/ruby_llm/schema/templates/schema.rb.tt +1 -1
  84. data/lib/generators/ruby_llm/tool/templates/tailwind/tool_call.html.erb.tt +13 -0
  85. data/lib/generators/ruby_llm/tool/templates/tailwind/tool_result.html.erb.tt +21 -0
  86. data/lib/generators/ruby_llm/tool/templates/tool.rb.tt +3 -3
  87. data/lib/generators/ruby_llm/tool/templates/tool_call.html.erb.tt +7 -12
  88. data/lib/generators/ruby_llm/tool/templates/tool_result.html.erb.tt +5 -2
  89. data/lib/generators/ruby_llm/tool/tool_generator.rb +25 -59
  90. data/lib/generators/ruby_llm/upgrade/templates/backfill_v2_data.rb.tt +461 -0
  91. data/lib/generators/ruby_llm/upgrade/templates/cleanup_v2_upgrade.rb.tt +100 -0
  92. data/lib/generators/ruby_llm/upgrade/templates/finish_v2_upgrade.rb.tt +215 -0
  93. data/lib/generators/ruby_llm/upgrade/templates/prepare_v2_upgrade.rb.tt +660 -0
  94. data/lib/generators/ruby_llm/upgrade/templates/ruby_llm_upgrade.rb.tt +222 -0
  95. data/lib/generators/ruby_llm/upgrade/templates/upgrade_initializer.rb.tt +13 -0
  96. data/lib/generators/ruby_llm/upgrade/upgrade_generator.rb +167 -0
  97. data/lib/generators/ruby_llm/upgrade/upgrade_migration.rb +344 -0
  98. data/lib/ruby_llm/accounting/usage.rb +245 -0
  99. data/lib/ruby_llm/active_record/acts_as.rb +94 -136
  100. data/lib/ruby_llm/active_record/attachment_helpers.rb +180 -0
  101. data/lib/ruby_llm/active_record/batch.rb +97 -0
  102. data/lib/ruby_llm/active_record/chat_methods.rb +823 -305
  103. data/lib/ruby_llm/active_record/message_methods.rb +119 -75
  104. data/lib/ruby_llm/active_record/model.rb +135 -0
  105. data/lib/ruby_llm/active_record/payload_helpers.rb +1 -2
  106. data/lib/ruby_llm/active_record/tool_call.rb +33 -0
  107. data/lib/ruby_llm/active_record/usage.rb +61 -0
  108. data/lib/ruby_llm/agent.rb +1065 -150
  109. data/lib/ruby_llm/aliases.json +338 -167
  110. data/lib/ruby_llm/attachment.rb +217 -61
  111. data/lib/ruby_llm/batch.rb +432 -0
  112. data/lib/ruby_llm/cached_content.rb +112 -0
  113. data/lib/ruby_llm/chat/tool_concurrency.rb +111 -0
  114. data/lib/ruby_llm/chat.rb +1208 -150
  115. data/lib/ruby_llm/chunk.rb +10 -0
  116. data/lib/ruby_llm/citation.rb +105 -0
  117. data/lib/ruby_llm/configuration.rb +274 -24
  118. data/lib/ruby_llm/context.rb +128 -6
  119. data/lib/ruby_llm/cost.rb +217 -80
  120. data/lib/ruby_llm/downloaded_file.rb +33 -0
  121. data/lib/ruby_llm/embedding.rb +141 -7
  122. data/lib/ruby_llm/embedding_request.rb +53 -0
  123. data/lib/ruby_llm/error.rb +161 -89
  124. data/lib/ruby_llm/fallback.rb +133 -0
  125. data/lib/ruby_llm/files/mime_type.rb +97 -0
  126. data/lib/ruby_llm/image.rb +155 -32
  127. data/lib/ruby_llm/message.rb +233 -54
  128. data/lib/ruby_llm/model/modalities.rb +17 -4
  129. data/lib/ruby_llm/model/pricing.rb +43 -14
  130. data/lib/ruby_llm/model/pricing_category.rb +103 -14
  131. data/lib/ruby_llm/model/pricing_tier.rb +66 -15
  132. data/lib/ruby_llm/model.rb +244 -2
  133. data/lib/ruby_llm/models/aliases.rb +41 -0
  134. data/lib/ruby_llm/models/registry.rb +165 -0
  135. data/lib/ruby_llm/models/schema.rb +99 -0
  136. data/lib/ruby_llm/models.json +70380 -33380
  137. data/lib/ruby_llm/models.rb +528 -201
  138. data/lib/ruby_llm/moderation.rb +139 -26
  139. data/lib/ruby_llm/ocr.rb +112 -0
  140. data/lib/ruby_llm/prompt.rb +79 -0
  141. data/lib/ruby_llm/protocol/binary_streaming.rb +65 -0
  142. data/lib/ruby_llm/protocol/stream_accumulator.rb +214 -0
  143. data/lib/ruby_llm/protocol/streaming.rb +230 -0
  144. data/lib/ruby_llm/protocol.rb +662 -0
  145. data/lib/ruby_llm/protocols/anthropic/batches.rb +73 -0
  146. data/lib/ruby_llm/protocols/anthropic/chat.rb +540 -0
  147. data/lib/ruby_llm/protocols/anthropic/embeddings.rb +14 -0
  148. data/lib/ruby_llm/protocols/anthropic/files.rb +38 -0
  149. data/lib/ruby_llm/protocols/anthropic/media.rb +141 -0
  150. data/lib/ruby_llm/protocols/anthropic/models.rb +129 -0
  151. data/lib/ruby_llm/protocols/anthropic/streaming.rb +166 -0
  152. data/lib/ruby_llm/{providers → protocols}/anthropic/tools.rb +43 -34
  153. data/lib/ruby_llm/protocols/anthropic.rb +100 -0
  154. data/lib/ruby_llm/protocols/azure/files.rb +16 -0
  155. data/lib/ruby_llm/protocols/bedrock/async_videos.rb +109 -0
  156. data/lib/ruby_llm/protocols/bedrock/batches.rb +129 -0
  157. data/lib/ruby_llm/protocols/bedrock/files.rb +110 -0
  158. data/lib/ruby_llm/protocols/bedrock/guardrails.rb +122 -0
  159. data/lib/ruby_llm/protocols/bedrock/rerank.rb +95 -0
  160. data/lib/ruby_llm/protocols/chat_completions/batches.rb +32 -0
  161. data/lib/ruby_llm/protocols/chat_completions/chat.rb +493 -0
  162. data/lib/ruby_llm/protocols/chat_completions/embedding_batches.rb +35 -0
  163. data/lib/ruby_llm/protocols/chat_completions/embeddings.rb +60 -0
  164. data/lib/ruby_llm/protocols/chat_completions/images.rb +127 -0
  165. data/lib/ruby_llm/protocols/chat_completions/media.rb +121 -0
  166. data/lib/ruby_llm/protocols/chat_completions/models.rb +39 -0
  167. data/lib/ruby_llm/protocols/chat_completions/moderation.rb +52 -0
  168. data/lib/ruby_llm/protocols/chat_completions/rerank.rb +56 -0
  169. data/lib/ruby_llm/protocols/chat_completions/speech.rb +40 -0
  170. data/lib/ruby_llm/protocols/chat_completions/streaming.rb +69 -0
  171. data/lib/ruby_llm/{providers/openai → protocols/chat_completions}/tools.rb +17 -17
  172. data/lib/ruby_llm/protocols/chat_completions/transcription.rb +150 -0
  173. data/lib/ruby_llm/protocols/chat_completions.rb +21 -0
  174. data/lib/ruby_llm/protocols/cohere/batch_requests.rb +75 -0
  175. data/lib/ruby_llm/protocols/cohere/batches.rb +98 -0
  176. data/lib/ruby_llm/protocols/cohere/chat.rb +227 -0
  177. data/lib/ruby_llm/protocols/cohere/datasets.rb +102 -0
  178. data/lib/ruby_llm/protocols/cohere/embeddings.rb +68 -0
  179. data/lib/ruby_llm/protocols/cohere/media.rb +77 -0
  180. data/lib/ruby_llm/protocols/cohere/models.rb +92 -0
  181. data/lib/ruby_llm/protocols/cohere/ocr.rb +63 -0
  182. data/lib/ruby_llm/protocols/cohere/rerank.rb +52 -0
  183. data/lib/ruby_llm/protocols/cohere/streaming.rb +105 -0
  184. data/lib/ruby_llm/protocols/cohere/tokenization.rb +22 -0
  185. data/lib/ruby_llm/protocols/cohere/tools.rb +132 -0
  186. data/lib/ruby_llm/protocols/cohere/transcription.rb +41 -0
  187. data/lib/ruby_llm/protocols/cohere.rb +21 -0
  188. data/lib/ruby_llm/protocols/converse/batches.rb +55 -0
  189. data/lib/ruby_llm/protocols/converse/chat.rb +685 -0
  190. data/lib/ruby_llm/protocols/converse/media.rb +178 -0
  191. data/lib/ruby_llm/protocols/converse/streaming.rb +424 -0
  192. data/lib/ruby_llm/protocols/converse.rb +54 -0
  193. data/lib/ruby_llm/protocols/deepgram/models.rb +74 -0
  194. data/lib/ruby_llm/protocols/deepgram/speech.rb +93 -0
  195. data/lib/ruby_llm/protocols/deepgram/streaming_transcription.rb +89 -0
  196. data/lib/ruby_llm/protocols/deepgram/transcription.rb +96 -0
  197. data/lib/ruby_llm/protocols/deepgram.rb +19 -0
  198. data/lib/ruby_llm/protocols/deepseek/files.rb +31 -0
  199. data/lib/ruby_llm/protocols/elevenlabs/assets.rb +36 -0
  200. data/lib/ruby_llm/protocols/elevenlabs/flows/images.rb +74 -0
  201. data/lib/ruby_llm/protocols/elevenlabs/flows/media.rb +42 -0
  202. data/lib/ruby_llm/protocols/elevenlabs/flows/videos.rb +93 -0
  203. data/lib/ruby_llm/protocols/elevenlabs/flows.rb +14 -0
  204. data/lib/ruby_llm/protocols/elevenlabs/models.rb +64 -0
  205. data/lib/ruby_llm/protocols/elevenlabs/speech.rb +67 -0
  206. data/lib/ruby_llm/protocols/elevenlabs/streaming_transcription.rb +127 -0
  207. data/lib/ruby_llm/protocols/elevenlabs/transcription.rb +61 -0
  208. data/lib/ruby_llm/protocols/elevenlabs.rb +15 -0
  209. data/lib/ruby_llm/protocols/files.rb +119 -0
  210. data/lib/ruby_llm/protocols/gemini/batches.rb +162 -0
  211. data/lib/ruby_llm/protocols/gemini/caches.rb +59 -0
  212. data/lib/ruby_llm/protocols/gemini/chat.rb +453 -0
  213. data/lib/ruby_llm/protocols/gemini/embedding_batches.rb +86 -0
  214. data/lib/ruby_llm/protocols/gemini/embeddings.rb +70 -0
  215. data/lib/ruby_llm/protocols/gemini/file_transcription.rb +32 -0
  216. data/lib/ruby_llm/protocols/gemini/files.rb +115 -0
  217. data/lib/ruby_llm/protocols/gemini/images.rb +183 -0
  218. data/lib/ruby_llm/protocols/gemini/live_transcription.rb +140 -0
  219. data/lib/ruby_llm/{providers → protocols}/gemini/media.rb +33 -20
  220. data/lib/ruby_llm/protocols/gemini/models.rb +71 -0
  221. data/lib/ruby_llm/protocols/gemini/speech.rb +56 -0
  222. data/lib/ruby_llm/protocols/gemini/streaming.rb +96 -0
  223. data/lib/ruby_llm/protocols/gemini/tools.rb +157 -0
  224. data/lib/ruby_llm/{providers → protocols}/gemini/transcription.rb +22 -22
  225. data/lib/ruby_llm/protocols/gemini/videos.rb +103 -0
  226. data/lib/ruby_llm/protocols/gemini.rb +35 -0
  227. data/lib/ruby_llm/protocols/gpustack/responses.rb +111 -0
  228. data/lib/ruby_llm/protocols/gpustack/tokenization.rb +22 -0
  229. data/lib/ruby_llm/protocols/gpustack/videos.rb +96 -0
  230. data/lib/ruby_llm/protocols/interactions/chat.rb +145 -0
  231. data/lib/ruby_llm/protocols/interactions/content.rb +90 -0
  232. data/lib/ruby_llm/protocols/interactions/streaming.rb +91 -0
  233. data/lib/ruby_llm/protocols/interactions/tools.rb +51 -0
  234. data/lib/ruby_llm/protocols/interactions/transcription.rb +58 -0
  235. data/lib/ruby_llm/protocols/interactions.rb +29 -0
  236. data/lib/ruby_llm/protocols/invoke_model/cohere_embeddings.rb +51 -0
  237. data/lib/ruby_llm/protocols/invoke_model/embedding_batches.rb +111 -0
  238. data/lib/ruby_llm/protocols/invoke_model/nova_embeddings.rb +50 -0
  239. data/lib/ruby_llm/protocols/invoke_model/stability_images.rb +103 -0
  240. data/lib/ruby_llm/protocols/invoke_model/titan_multimodal_embeddings.rb +33 -0
  241. data/lib/ruby_llm/protocols/invoke_model/titan_text_embeddings.rb +44 -0
  242. data/lib/ruby_llm/protocols/invoke_model.rb +57 -0
  243. data/lib/ruby_llm/protocols/mistral/content.rb +49 -0
  244. data/lib/ruby_llm/protocols/mistral/conversations/chat.rb +160 -0
  245. data/lib/ruby_llm/protocols/mistral/conversations/images.rb +43 -0
  246. data/lib/ruby_llm/protocols/mistral/conversations/streaming.rb +83 -0
  247. data/lib/ruby_llm/protocols/mistral/conversations.rb +30 -0
  248. data/lib/ruby_llm/protocols/mistral/files.rb +36 -0
  249. data/lib/ruby_llm/protocols/mistral/multi_completion.rb +160 -0
  250. data/lib/ruby_llm/protocols/openai/batches.rb +126 -0
  251. data/lib/ruby_llm/protocols/openai/files.rb +42 -0
  252. data/lib/ruby_llm/protocols/openrouter/batches.rb +147 -0
  253. data/lib/ruby_llm/protocols/openrouter/files.rb +24 -0
  254. data/lib/ruby_llm/protocols/openrouter/responses.rb +53 -0
  255. data/lib/ruby_llm/protocols/openrouter/transcription.rb +51 -0
  256. data/lib/ruby_llm/protocols/perplexity/files.rb +48 -0
  257. data/lib/ruby_llm/protocols/perplexity/router.rb +59 -0
  258. data/lib/ruby_llm/protocols/responses/approvals.rb +32 -0
  259. data/lib/ruby_llm/protocols/responses/batches.rb +32 -0
  260. data/lib/ruby_llm/protocols/responses/chat.rb +476 -0
  261. data/lib/ruby_llm/protocols/responses/compaction.rb +29 -0
  262. data/lib/ruby_llm/protocols/responses/media.rb +61 -0
  263. data/lib/ruby_llm/protocols/responses/streaming.rb +117 -0
  264. data/lib/ruby_llm/protocols/responses/token_counting.rb +26 -0
  265. data/lib/ruby_llm/protocols/responses/tools.rb +39 -0
  266. data/lib/ruby_llm/protocols/responses.rb +35 -0
  267. data/lib/ruby_llm/protocols/vertexai/batch_prediction.rb +155 -0
  268. data/lib/ruby_llm/protocols/vertexai/embedding_prediction/requests.rb +85 -0
  269. data/lib/ruby_llm/protocols/vertexai/embedding_prediction/results.rb +74 -0
  270. data/lib/ruby_llm/protocols/vertexai/embedding_prediction.rb +56 -0
  271. data/lib/ruby_llm/protocols/vertexai/files.rb +101 -0
  272. data/lib/ruby_llm/protocols/vertexai/ranking.rb +69 -0
  273. data/lib/ruby_llm/protocols/vertexai/research.rb +193 -0
  274. data/lib/ruby_llm/protocols/xai/files.rb +30 -0
  275. data/lib/ruby_llm/protocols/xai/streaming_transcription.rb +120 -0
  276. data/lib/ruby_llm/protocols/xai/tokenization.rb +23 -0
  277. data/lib/ruby_llm/provider.rb +565 -124
  278. data/lib/ruby_llm/providers/anthropic/capabilities.rb +5 -7
  279. data/lib/ruby_llm/providers/anthropic.rb +4 -6
  280. data/lib/ruby_llm/providers/azure/audio.rb +18 -0
  281. data/lib/ruby_llm/providers/azure/capabilities.rb +16 -0
  282. data/lib/ruby_llm/providers/azure/chat.rb +2 -9
  283. data/lib/ruby_llm/providers/azure/chat_completions/batches.rb +29 -0
  284. data/lib/ruby_llm/providers/azure/chat_completions.rb +80 -0
  285. data/lib/ruby_llm/providers/azure/cohere.rb +33 -0
  286. data/lib/ruby_llm/providers/azure/embeddings.rb +3 -2
  287. data/lib/ruby_llm/providers/azure/images.rb +22 -0
  288. data/lib/ruby_llm/providers/azure/media.rb +6 -15
  289. data/lib/ruby_llm/providers/azure/models.rb +35 -0
  290. data/lib/ruby_llm/providers/azure/responses.rb +26 -0
  291. data/lib/ruby_llm/providers/azure/videos.rb +64 -0
  292. data/lib/ruby_llm/providers/azure.rb +77 -78
  293. data/lib/ruby_llm/providers/bedrock/auth.rb +61 -41
  294. data/lib/ruby_llm/providers/bedrock/capabilities.rb +18 -0
  295. data/lib/ruby_llm/providers/bedrock/mantle/anthropic.rb +39 -0
  296. data/lib/ruby_llm/providers/bedrock/mantle/chat_completions.rb +23 -0
  297. data/lib/ruby_llm/providers/bedrock/mantle/responses.rb +24 -0
  298. data/lib/ruby_llm/providers/bedrock/mantle/voxtral.rb +96 -0
  299. data/lib/ruby_llm/providers/bedrock/mantle.rb +57 -0
  300. data/lib/ruby_llm/providers/bedrock/models.rb +194 -42
  301. data/lib/ruby_llm/providers/bedrock.rb +217 -46
  302. data/lib/ruby_llm/providers/cohere.rb +31 -0
  303. data/lib/ruby_llm/providers/deepgram.rb +37 -0
  304. data/lib/ruby_llm/providers/deepseek/capabilities.rb +4 -8
  305. data/lib/ruby_llm/providers/deepseek/chat.rb +56 -0
  306. data/lib/ruby_llm/providers/deepseek/responses.rb +68 -0
  307. data/lib/ruby_llm/providers/deepseek.rb +9 -2
  308. data/lib/ruby_llm/providers/elevenlabs.rb +35 -0
  309. data/lib/ruby_llm/providers/gemini/capabilities.rb +8 -107
  310. data/lib/ruby_llm/providers/gemini.rb +15 -8
  311. data/lib/ruby_llm/providers/gpustack/chat.rb +2 -9
  312. data/lib/ruby_llm/providers/gpustack/embeddings.rb +28 -0
  313. data/lib/ruby_llm/providers/gpustack/media.rb +17 -16
  314. data/lib/ruby_llm/providers/gpustack/models.rb +72 -60
  315. data/lib/ruby_llm/providers/gpustack/speech.rb +15 -0
  316. data/lib/ruby_llm/providers/gpustack/transcription.rb +29 -0
  317. data/lib/ruby_llm/providers/gpustack.rb +35 -11
  318. data/lib/ruby_llm/providers/mistral/capabilities.rb +7 -155
  319. data/lib/ruby_llm/providers/mistral/chat.rb +37 -61
  320. data/lib/ruby_llm/providers/mistral/chat_completions/batches.rb +120 -0
  321. data/lib/ruby_llm/providers/mistral/chat_completions.rb +21 -0
  322. data/lib/ruby_llm/providers/mistral/conversations.rb +12 -0
  323. data/lib/ruby_llm/providers/mistral/embeddings.rb +6 -4
  324. data/lib/ruby_llm/providers/mistral/media.rb +43 -0
  325. data/lib/ruby_llm/providers/mistral/models.rb +57 -21
  326. data/lib/ruby_llm/providers/mistral/ocr.rb +47 -0
  327. data/lib/ruby_llm/providers/mistral/speech.rb +51 -0
  328. data/lib/ruby_llm/providers/mistral/transcription.rb +62 -0
  329. data/lib/ruby_llm/providers/mistral.rb +18 -6
  330. data/lib/ruby_llm/providers/ollama/chat.rb +9 -8
  331. data/lib/ruby_llm/providers/ollama/media.rb +6 -15
  332. data/lib/ruby_llm/providers/ollama/models.rb +50 -9
  333. data/lib/ruby_llm/providers/ollama.rb +9 -8
  334. data/lib/ruby_llm/providers/ollama_cloud/models.rb +14 -0
  335. data/lib/ruby_llm/providers/ollama_cloud.rb +40 -0
  336. data/lib/ruby_llm/providers/openai/capabilities.rb +54 -259
  337. data/lib/ruby_llm/providers/openai/models.rb +23 -23
  338. data/lib/ruby_llm/providers/openai/responses.rb +13 -0
  339. data/lib/ruby_llm/providers/openai.rb +92 -11
  340. data/lib/ruby_llm/providers/openrouter/chat.rb +130 -104
  341. data/lib/ruby_llm/providers/openrouter/embeddings.rb +51 -0
  342. data/lib/ruby_llm/providers/openrouter/images.rb +44 -43
  343. data/lib/ruby_llm/providers/openrouter/media.rb +34 -0
  344. data/lib/ruby_llm/providers/openrouter/models.rb +50 -11
  345. data/lib/ruby_llm/providers/openrouter/speech.rb +32 -0
  346. data/lib/ruby_llm/providers/openrouter/streaming.rb +31 -38
  347. data/lib/ruby_llm/providers/openrouter/videos.rb +81 -0
  348. data/lib/ruby_llm/providers/openrouter.rb +78 -20
  349. data/lib/ruby_llm/providers/perplexity/chat.rb +4 -0
  350. data/lib/ruby_llm/providers/perplexity/embeddings.rb +32 -0
  351. data/lib/ruby_llm/providers/perplexity/media.rb +46 -0
  352. data/lib/ruby_llm/providers/perplexity/models.rb +80 -13
  353. data/lib/ruby_llm/providers/perplexity.rb +29 -21
  354. data/lib/ruby_llm/providers/vertexai/anthropic/batches.rb +52 -0
  355. data/lib/ruby_llm/providers/vertexai/anthropic.rb +34 -0
  356. data/lib/ruby_llm/providers/vertexai/capabilities.rb +19 -0
  357. data/lib/ruby_llm/providers/vertexai/chat_completions/batches.rb +54 -0
  358. data/lib/ruby_llm/providers/vertexai/chat_completions.rb +15 -0
  359. data/lib/ruby_llm/providers/vertexai/embed_content.rb +42 -0
  360. data/lib/ruby_llm/providers/vertexai/embeddings.rb +22 -7
  361. data/lib/ruby_llm/providers/vertexai/gemini/batches.rb +42 -0
  362. data/lib/ruby_llm/providers/vertexai/gemini.rb +69 -0
  363. data/lib/ruby_llm/providers/vertexai/live_transcription.rb +24 -0
  364. data/lib/ruby_llm/providers/vertexai/mistral.rb +28 -0
  365. data/lib/ruby_llm/providers/vertexai/models.rb +145 -43
  366. data/lib/ruby_llm/providers/vertexai/transcription.rb +49 -4
  367. data/lib/ruby_llm/providers/vertexai/videos.rb +61 -0
  368. data/lib/ruby_llm/providers/vertexai.rb +164 -17
  369. data/lib/ruby_llm/providers/xai/capabilities.rb +18 -0
  370. data/lib/ruby_llm/providers/xai/chat.rb +10 -0
  371. data/lib/ruby_llm/providers/xai/chat_completions/batches.rb +108 -0
  372. data/lib/ruby_llm/providers/xai/chat_completions.rb +19 -0
  373. data/lib/ruby_llm/providers/xai/images.rb +91 -0
  374. data/lib/ruby_llm/providers/xai/models.rb +32 -48
  375. data/lib/ruby_llm/providers/xai/reported_cost.rb +18 -0
  376. data/lib/ruby_llm/providers/xai/responses.rb +52 -0
  377. data/lib/ruby_llm/providers/xai/speech.rb +45 -0
  378. data/lib/ruby_llm/providers/xai/transcription.rb +48 -0
  379. data/lib/ruby_llm/providers/xai/videos.rb +87 -0
  380. data/lib/ruby_llm/providers/xai.rb +17 -7
  381. data/lib/ruby_llm/railtie.rb +11 -16
  382. data/lib/ruby_llm/rerank.rb +105 -0
  383. data/lib/ruby_llm/research_job.rb +241 -0
  384. data/lib/ruby_llm/search_results.rb +68 -0
  385. data/lib/ruby_llm/server_tool_call.rb +73 -0
  386. data/lib/ruby_llm/speech.rb +159 -0
  387. data/lib/ruby_llm/speech_chunk.rb +33 -0
  388. data/lib/ruby_llm/support/deprecator.rb +22 -0
  389. data/lib/ruby_llm/support/inspectable.rb +49 -0
  390. data/lib/ruby_llm/support/instrumentation.rb +41 -0
  391. data/lib/ruby_llm/support/utils.rb +147 -0
  392. data/lib/ruby_llm/thinking.rb +127 -20
  393. data/lib/ruby_llm/tokenization.rb +59 -0
  394. data/lib/ruby_llm/tokens.rb +103 -33
  395. data/lib/ruby_llm/tool.rb +266 -91
  396. data/lib/ruby_llm/tool_call.rb +36 -3
  397. data/lib/ruby_llm/tools/server_tools.rb +109 -0
  398. data/lib/ruby_llm/transcription/wav_audio.rb +62 -0
  399. data/lib/ruby_llm/transcription.rb +139 -13
  400. data/lib/ruby_llm/transcription_chunk.rb +68 -0
  401. data/lib/ruby_llm/transport/connection.rb +193 -0
  402. data/lib/ruby_llm/transport/error_middleware.rb +131 -0
  403. data/lib/ruby_llm/transport/usage_middleware.rb +28 -0
  404. data/lib/ruby_llm/transport/websocket_connection.rb +220 -0
  405. data/lib/ruby_llm/uploaded_file.rb +144 -0
  406. data/lib/ruby_llm/version.rb +2 -1
  407. data/lib/ruby_llm/video.rb +136 -0
  408. data/lib/ruby_llm/video_job.rb +150 -0
  409. data/lib/ruby_llm/workflow.rb +91 -0
  410. data/lib/ruby_llm.rb +385 -4
  411. data/lib/tasks/ruby_llm.rake +21 -16
  412. data/skills/rubyllm/SKILL.md +81 -0
  413. data/skills/rubyllm/agents/openai.yaml +4 -0
  414. metadata +340 -92
  415. data/lib/generators/ruby_llm/install/templates/add_references_to_chats_tool_calls_and_messages_migration.rb.tt +0 -9
  416. data/lib/generators/ruby_llm/install/templates/create_models_migration.rb.tt +0 -39
  417. data/lib/generators/ruby_llm/install/templates/create_tool_calls_migration.rb.tt +0 -21
  418. data/lib/generators/ruby_llm/install/templates/model_model.rb.tt +0 -3
  419. data/lib/generators/ruby_llm/install/templates/tool_call_model.rb.tt +0 -3
  420. data/lib/generators/ruby_llm/upgrade_to_v1_10/templates/add_v1_10_message_columns.rb.tt +0 -19
  421. data/lib/generators/ruby_llm/upgrade_to_v1_10/upgrade_to_v1_10_generator.rb +0 -50
  422. data/lib/generators/ruby_llm/upgrade_to_v1_14/templates/add_v1_14_tool_call_columns.rb.tt +0 -7
  423. data/lib/generators/ruby_llm/upgrade_to_v1_14/upgrade_to_v1_14_generator.rb +0 -49
  424. data/lib/generators/ruby_llm/upgrade_to_v1_7/templates/migration.rb.tt +0 -145
  425. data/lib/generators/ruby_llm/upgrade_to_v1_7/upgrade_to_v1_7_generator.rb +0 -122
  426. data/lib/generators/ruby_llm/upgrade_to_v1_9/templates/add_v1_9_message_columns.rb.tt +0 -15
  427. data/lib/generators/ruby_llm/upgrade_to_v1_9/upgrade_to_v1_9_generator.rb +0 -49
  428. data/lib/ruby_llm/active_record/acts_as_legacy.rb +0 -530
  429. data/lib/ruby_llm/active_record/model_methods.rb +0 -82
  430. data/lib/ruby_llm/active_record/tool_call_methods.rb +0 -18
  431. data/lib/ruby_llm/aliases.rb +0 -38
  432. data/lib/ruby_llm/connection.rb +0 -130
  433. data/lib/ruby_llm/content.rb +0 -77
  434. data/lib/ruby_llm/mime_type.rb +0 -71
  435. data/lib/ruby_llm/model/info.rb +0 -130
  436. data/lib/ruby_llm/models_schema.json +0 -171
  437. data/lib/ruby_llm/providers/anthropic/chat.rb +0 -257
  438. data/lib/ruby_llm/providers/anthropic/content.rb +0 -44
  439. data/lib/ruby_llm/providers/anthropic/embeddings.rb +0 -20
  440. data/lib/ruby_llm/providers/anthropic/media.rb +0 -92
  441. data/lib/ruby_llm/providers/anthropic/models.rb +0 -57
  442. data/lib/ruby_llm/providers/anthropic/streaming.rb +0 -69
  443. data/lib/ruby_llm/providers/bedrock/chat.rb +0 -403
  444. data/lib/ruby_llm/providers/bedrock/media.rb +0 -90
  445. data/lib/ruby_llm/providers/bedrock/streaming.rb +0 -322
  446. data/lib/ruby_llm/providers/gemini/chat.rb +0 -543
  447. data/lib/ruby_llm/providers/gemini/embeddings.rb +0 -37
  448. data/lib/ruby_llm/providers/gemini/images.rb +0 -47
  449. data/lib/ruby_llm/providers/gemini/models.rb +0 -38
  450. data/lib/ruby_llm/providers/gemini/streaming.rb +0 -96
  451. data/lib/ruby_llm/providers/gemini/tools.rb +0 -232
  452. data/lib/ruby_llm/providers/gpustack/capabilities.rb +0 -20
  453. data/lib/ruby_llm/providers/ollama/capabilities.rb +0 -20
  454. data/lib/ruby_llm/providers/openai/chat.rb +0 -221
  455. data/lib/ruby_llm/providers/openai/embeddings.rb +0 -33
  456. data/lib/ruby_llm/providers/openai/images.rb +0 -90
  457. data/lib/ruby_llm/providers/openai/media.rb +0 -84
  458. data/lib/ruby_llm/providers/openai/moderation.rb +0 -34
  459. data/lib/ruby_llm/providers/openai/streaming.rb +0 -53
  460. data/lib/ruby_llm/providers/openai/temperature.rb +0 -28
  461. data/lib/ruby_llm/providers/openai/transcription.rb +0 -70
  462. data/lib/ruby_llm/providers/perplexity/capabilities.rb +0 -72
  463. data/lib/ruby_llm/providers/vertexai/chat.rb +0 -14
  464. data/lib/ruby_llm/providers/vertexai/streaming.rb +0 -14
  465. data/lib/ruby_llm/stream_accumulator.rb +0 -203
  466. data/lib/ruby_llm/streaming.rb +0 -175
  467. data/lib/ruby_llm/utils.rb +0 -91
  468. data/lib/tasks/models.rake +0 -565
  469. data/lib/tasks/release.rake +0 -67
  470. data/lib/tasks/vcr.rake +0 -124
@@ -1,543 +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 then 'user'
76
- when :tool then 'function'
77
- else role.to_s
78
- end
79
- end
80
-
81
- def format_parts(msg)
82
- if msg.tool_call?
83
- format_tool_call(msg)
84
- elsif msg.tool_result?
85
- format_tool_result(msg)
86
- else
87
- format_message_parts(msg)
88
- end
89
- end
90
-
91
- def format_message_parts(msg)
92
- parts = []
93
-
94
- parts << build_thought_part(msg.thinking) if msg.role == :assistant && msg.thinking
95
-
96
- content_parts = Media.format_content(msg.content)
97
- parts.concat(content_parts.is_a?(Array) ? content_parts : [content_parts])
98
- parts
99
- end
100
-
101
- def build_thought_part(thinking)
102
- part = { thought: true }
103
- part[:text] = thinking.text if thinking.text
104
- part[:thoughtSignature] = thinking.signature if thinking.signature
105
- part
106
- end
107
-
108
- def parse_completion_response(response)
109
- data = response.body
110
- parts = data.dig('candidates', 0, 'content', 'parts') || []
111
- tool_calls = extract_tool_calls(data)
112
-
113
- Message.new(
114
- role: :assistant,
115
- content: extract_text_parts(parts) || parse_content(data),
116
- thinking: Thinking.build(
117
- text: extract_thought_parts(parts),
118
- signature: extract_thought_signature(parts)
119
- ),
120
- tool_calls: tool_calls,
121
- input_tokens: input_tokens(data),
122
- output_tokens: calculate_output_tokens(data),
123
- cached_tokens: data.dig('usageMetadata', 'cachedContentTokenCount'),
124
- thinking_tokens: data.dig('usageMetadata', 'thoughtsTokenCount'),
125
- model_id: data['modelVersion'] || response.env.url.path.split('/')[3].split(':')[0],
126
- raw: response
127
- )
128
- end
129
-
130
- def input_tokens(data)
131
- prompt_tokens = data.dig('usageMetadata', 'promptTokenCount')
132
- return unless prompt_tokens
133
-
134
- [prompt_tokens.to_i - data.dig('usageMetadata', 'cachedContentTokenCount').to_i, 0].max
135
- end
136
-
137
- def convert_schema_to_gemini(schema)
138
- return nil unless schema
139
-
140
- # Extract inner schema if wrapper format (e.g., from RubyLLM::Schema.to_json_schema)
141
- schema = schema[:schema] || schema
142
-
143
- GeminiSchema.new(schema).to_h
144
- end
145
-
146
- def parse_content(data)
147
- candidate = data.dig('candidates', 0)
148
- return '' unless candidate
149
-
150
- return '' if function_call?(candidate)
151
-
152
- parts = candidate.dig('content', 'parts')
153
- return '' unless parts&.any?
154
-
155
- non_thought_parts = parts.reject { |part| part['thought'] }
156
- return '' unless non_thought_parts.any?
157
-
158
- build_response_content(non_thought_parts)
159
- end
160
-
161
- def extract_text_parts(parts)
162
- text_parts = parts.reject { |p| p['thought'] }
163
- content = text_parts.filter_map { |p| p['text'] }.join
164
- content.empty? ? nil : content
165
- end
166
-
167
- def extract_thought_parts(parts)
168
- thought_parts = parts.select { |p| p['thought'] }
169
- thoughts = thought_parts.filter_map { |p| p['text'] }.join
170
- thoughts.empty? ? nil : thoughts
171
- end
172
-
173
- def extract_thought_signature(parts)
174
- parts.each do |part|
175
- signature = part['thoughtSignature'] ||
176
- part['thought_signature'] ||
177
- part.dig('functionCall', 'thoughtSignature') ||
178
- part.dig('functionCall', 'thought_signature')
179
- return signature if signature
180
- end
181
-
182
- nil
183
- end
184
-
185
- def function_call?(candidate)
186
- parts = candidate.dig('content', 'parts')
187
- parts&.any? { |p| p['functionCall'] }
188
- end
189
-
190
- def calculate_output_tokens(data)
191
- candidates = data.dig('usageMetadata', 'candidatesTokenCount') || 0
192
- thoughts = data.dig('usageMetadata', 'thoughtsTokenCount') || 0
193
- candidates + thoughts
194
- end
195
-
196
- def response_json_schema_supported?(model)
197
- version = gemini_version(model)
198
- version && version >= Gem::Version.new('2.5')
199
- end
200
-
201
- def build_json_schema(schema)
202
- normalized = RubyLLM::Utils.deep_dup(schema[:schema])
203
- normalized.delete(:strict)
204
- normalized.delete('strict')
205
- RubyLLM::Utils.deep_stringify_keys(normalized)
206
- end
207
-
208
- def gemini_version(model)
209
- return nil unless model
210
-
211
- candidates = [
212
- safe_string(model.id),
213
- safe_string(model.respond_to?(:family) ? model.family : nil),
214
- safe_string(model_metadata_value(model, :version)),
215
- safe_string(model_metadata_value(model, 'version')),
216
- safe_string(model_metadata_value(model, :description))
217
- ].compact
218
-
219
- candidates.each do |candidate|
220
- version = extract_version(candidate)
221
- return version if version
222
- end
223
-
224
- nil
225
- end
226
-
227
- def model_metadata_value(model, key)
228
- return unless model.respond_to?(:metadata)
229
-
230
- metadata = model.metadata
231
- return unless metadata.is_a?(Hash)
232
-
233
- metadata[key] || metadata[key.to_s]
234
- end
235
-
236
- def safe_string(value)
237
- value&.to_s
238
- end
239
-
240
- def extract_version(text)
241
- return nil unless text
242
-
243
- match = text.match(/(\d+\.\d+|\d+)/)
244
- return nil unless match
245
-
246
- Gem::Version.new(match[1])
247
- rescue ArgumentError
248
- nil
249
- end
250
-
251
- def structured_output_config(schema, model)
252
- {
253
- responseMimeType: 'application/json'
254
- }.tap do |config|
255
- if response_json_schema_supported?(model)
256
- config[:responseJsonSchema] = build_json_schema(schema)
257
- else
258
- config[:responseSchema] = convert_schema_to_gemini(schema)
259
- end
260
- end
261
- end
262
-
263
- # formats a message
264
- class MessageFormatter
265
- def initialize(messages, format_role:, format_parts:, format_tool_result:)
266
- @messages = messages
267
- @index = 0
268
- @tool_call_names = {}
269
- @format_role = format_role
270
- @format_parts = format_parts
271
- @format_tool_result = format_tool_result
272
- end
273
-
274
- def format
275
- formatted = []
276
-
277
- while current_message
278
- if tool_message?(current_message)
279
- tool_parts, next_index = collect_tool_parts
280
- formatted << build_tool_response(tool_parts)
281
- @index = next_index
282
- else
283
- remember_tool_calls if current_message.tool_call?
284
- formatted << build_standard_message(current_message)
285
- @index += 1
286
- end
287
- end
288
-
289
- formatted
290
- end
291
-
292
- private
293
-
294
- def current_message
295
- @messages[@index]
296
- end
297
-
298
- def tool_message?(message)
299
- message&.role == :tool
300
- end
301
-
302
- def collect_tool_parts
303
- parts = []
304
- index = @index
305
-
306
- while tool_message?(@messages[index])
307
- tool_message = @messages[index]
308
- tool_name = @tool_call_names.delete(tool_message.tool_call_id)
309
- parts.concat(format_tool_result(tool_message, tool_name))
310
- index += 1
311
- end
312
-
313
- [parts, index]
314
- end
315
-
316
- def build_tool_response(parts)
317
- { role: 'function', parts: parts }
318
- end
319
-
320
- def remember_tool_calls
321
- current_message.tool_calls.each do |tool_call_id, tool_call|
322
- @tool_call_names[tool_call_id] = tool_call.name
323
- end
324
- end
325
-
326
- def build_standard_message(message)
327
- {
328
- role: @format_role.call(message.role),
329
- parts: @format_parts.call(message)
330
- }
331
- end
332
-
333
- def format_tool_result(message, tool_name)
334
- @format_tool_result.call(message, tool_name)
335
- end
336
- end
337
-
338
- # converts json schema to gemini
339
- class GeminiSchema
340
- def initialize(schema)
341
- @raw_schema = RubyLLM::Utils.deep_dup(schema)
342
- @definitions = {}
343
- end
344
-
345
- def to_h
346
- return nil unless @raw_schema
347
-
348
- symbolized = symbolize_and_extract_definitions(@raw_schema)
349
- convert(symbolized, Set.new)
350
- end
351
-
352
- private
353
-
354
- attr_reader :definitions
355
-
356
- def symbolize_and_extract_definitions(value)
357
- case value
358
- when Hash
359
- value.each_with_object({}) do |(key, val), hash|
360
- key_sym = begin
361
- key.to_sym
362
- rescue StandardError
363
- key
364
- end
365
-
366
- if definition_key?(key_sym)
367
- merge_definitions(val)
368
- else
369
- hash[key_sym] = symbolize_and_extract_definitions(val)
370
- end
371
- end
372
- when Array
373
- value.map { |item| symbolize_and_extract_definitions(item) }
374
- else
375
- value
376
- end
377
- end
378
-
379
- def definition_key?(key)
380
- %i[$defs definitions].include?(key)
381
- end
382
-
383
- def merge_definitions(raw_defs)
384
- return unless raw_defs
385
-
386
- symbolized = symbolize_and_extract_definitions(raw_defs)
387
- @definitions = if definitions.empty?
388
- symbolized
389
- else
390
- RubyLLM::Utils.deep_merge(definitions, symbolized)
391
- end
392
- end
393
-
394
- def convert(schema, visited_refs)
395
- return default_string_schema unless schema.is_a?(Hash)
396
-
397
- schema = strip_unsupported_keys(schema)
398
-
399
- if schema[:$ref]
400
- resolved = resolve_reference(schema, visited_refs)
401
- return resolved if resolved
402
- end
403
-
404
- schema = normalize_any_of(schema)
405
-
406
- result = case schema[:type].to_s
407
- when 'object'
408
- build_object(schema, visited_refs)
409
- when 'array'
410
- build_array(schema, visited_refs)
411
- when 'number'
412
- build_scalar('NUMBER', schema, %i[format minimum maximum enum nullable multipleOf])
413
- when 'integer'
414
- build_scalar('INTEGER', schema, %i[format minimum maximum enum nullable multipleOf])
415
- when 'boolean'
416
- build_scalar('BOOLEAN', schema, %i[nullable])
417
- else
418
- build_scalar('STRING', schema, %i[enum format nullable])
419
- end
420
-
421
- apply_description(result, schema)
422
- result
423
- end
424
-
425
- def strip_unsupported_keys(schema)
426
- schema.dup.tap do |copy|
427
- copy.delete(:strict)
428
- copy.delete(:additionalProperties)
429
- end
430
- end
431
-
432
- def resolve_reference(schema, visited_refs)
433
- ref = schema[:$ref]
434
- return unless ref
435
- return if visited_refs.include?(ref)
436
-
437
- referenced = lookup_definition(ref)
438
- return unless referenced
439
-
440
- overrides = schema.except(:$ref)
441
- visited_refs.add(ref)
442
- merged = RubyLLM::Utils.deep_merge(referenced, overrides)
443
- convert(merged, visited_refs)
444
- ensure
445
- visited_refs.delete(ref)
446
- end
447
-
448
- def lookup_definition(ref) # rubocop:disable Metrics/PerceivedComplexity
449
- segments = ref.to_s.split('/').reject(&:empty?)
450
- return nil if segments.empty?
451
-
452
- segments.shift if segments.first == '#'
453
- segments.shift if %w[$defs definitions].include?(segments.first)
454
-
455
- current = definitions
456
-
457
- segments.each do |segment|
458
- break current = nil unless current.is_a?(Hash)
459
-
460
- key = begin
461
- segment.to_sym
462
- rescue StandardError
463
- segment
464
- end
465
- current = current[key]
466
- end
467
-
468
- current ? RubyLLM::Utils.deep_dup(current) : nil
469
- end
470
-
471
- def normalize_any_of(schema)
472
- any_of = schema[:anyOf]
473
- return schema unless any_of
474
-
475
- options = Array(any_of).map { |option| RubyLLM::Utils.deep_symbolize_keys(option) }
476
- nullables, non_null = options.partition { |option| schema_type(option) == 'null' }
477
-
478
- base = RubyLLM::Utils.deep_symbolize_keys(non_null.first || { type: 'string' })
479
- base[:nullable] = true if nullables.any?
480
-
481
- without_any_of = schema.each_with_object({}) do |(key, value), result|
482
- result[key] = value unless key == :anyOf
483
- end
484
-
485
- without_any_of.merge(base)
486
- end
487
-
488
- def schema_type(option)
489
- (option[:type] || option['type']).to_s.downcase
490
- end
491
-
492
- def build_object(schema, visited_refs)
493
- properties = schema.fetch(:properties, {}).transform_values do |child|
494
- convert(child, visited_refs)
495
- end
496
-
497
- {
498
- type: 'OBJECT',
499
- properties: properties
500
- }.tap do |object|
501
- required = Array(schema[:required]).map(&:to_s).uniq
502
- object[:required] = required if required.any?
503
- object[:propertyOrdering] = schema[:propertyOrdering] if schema[:propertyOrdering]
504
- copy_attribute(object, schema, :nullable)
505
- end
506
- end
507
-
508
- def build_array(schema, visited_refs)
509
- items_schema = schema[:items] ? convert(schema[:items], visited_refs) : default_string_schema
510
-
511
- {
512
- type: 'ARRAY',
513
- items: items_schema
514
- }.tap do |array|
515
- copy_attribute(array, schema, :minItems)
516
- copy_attribute(array, schema, :maxItems)
517
- copy_attribute(array, schema, :nullable)
518
- end
519
- end
520
-
521
- def build_scalar(type, schema, allowed_keys)
522
- { type: type }.tap do |result|
523
- allowed_keys.each { |key| copy_attribute(result, schema, key) }
524
- end
525
- end
526
-
527
- def apply_description(target, schema)
528
- description = schema[:description]
529
- target[:description] = description if description
530
- end
531
-
532
- def copy_attribute(target, source, key)
533
- target[key] = source[key] if source.key?(key)
534
- end
535
-
536
- def default_string_schema
537
- { type: 'STRING' }
538
- end
539
- end
540
- end
541
- end
542
- end
543
- 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