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,59 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'time'
4
-
5
- module RubyLLM
6
- module Providers
7
- class Anthropic
8
- # Models methods of the Anthropic API integration
9
- module Models
10
- module_function
11
-
12
- def models_url
13
- 'v1/models'
14
- end
15
-
16
- def parse_list_models_response(response, slug, _capabilities)
17
- Array(response.body['data']).map do |model_data|
18
- model_id = model_data['id']
19
-
20
- Model::Info.new(
21
- id: model_id,
22
- name: model_data['display_name'] || model_id,
23
- provider: slug,
24
- created_at: Time.parse(model_data['created_at']),
25
- metadata: {}
26
- )
27
- end
28
- end
29
-
30
- def extract_model_id(data)
31
- data.dig('message', 'model')
32
- end
33
-
34
- def extract_input_tokens(data)
35
- data.dig('message', 'usage', 'input_tokens')
36
- end
37
-
38
- def extract_output_tokens(data)
39
- data.dig('message', 'usage', 'output_tokens') || data.dig('usage', 'output_tokens')
40
- end
41
-
42
- def extract_cached_tokens(data)
43
- data.dig('message', 'usage', 'cache_read_input_tokens') || data.dig('usage', 'cache_read_input_tokens')
44
- end
45
-
46
- def extract_cache_creation_tokens(data)
47
- direct = data.dig('message', 'usage',
48
- 'cache_creation_input_tokens') || data.dig('usage', 'cache_creation_input_tokens')
49
- return direct if direct
50
-
51
- breakdown = data.dig('message', 'usage', 'cache_creation') || data.dig('usage', 'cache_creation')
52
- return unless breakdown.is_a?(Hash)
53
-
54
- breakdown.values.compact.sum
55
- end
56
- end
57
- end
58
- end
59
- end
@@ -1,71 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
-
5
- module RubyLLM
6
- module Providers
7
- class Anthropic
8
- # Streaming methods of the Anthropic API integration
9
- module Streaming
10
- private
11
-
12
- def stream_url
13
- completion_url
14
- end
15
-
16
- def build_chunk(data)
17
- delta_type = data.dig('delta', 'type')
18
-
19
- Chunk.new(
20
- role: :assistant,
21
- model_id: extract_model_id(data),
22
- content: extract_content_delta(data, delta_type),
23
- thinking: Thinking.build(
24
- text: extract_thinking_delta(data, delta_type),
25
- signature: extract_signature_delta(data, delta_type)
26
- ),
27
- input_tokens: extract_input_tokens(data),
28
- output_tokens: extract_output_tokens(data),
29
- cached_tokens: extract_cached_tokens(data),
30
- cache_creation_tokens: extract_cache_creation_tokens(data),
31
- tool_calls: extract_tool_calls(data)
32
- )
33
- end
34
-
35
- def extract_content_delta(data, delta_type)
36
- return data.dig('delta', 'text') if delta_type == 'text_delta'
37
-
38
- nil
39
- end
40
-
41
- def extract_thinking_delta(data, delta_type)
42
- return data.dig('delta', 'thinking') if delta_type == 'thinking_delta'
43
-
44
- nil
45
- end
46
-
47
- def extract_signature_delta(data, delta_type)
48
- return data.dig('delta', 'signature') if delta_type == 'signature_delta'
49
-
50
- nil
51
- end
52
-
53
- def json_delta?(data)
54
- data['type'] == 'content_block_delta' && data.dig('delta', 'type') == 'input_json_delta'
55
- end
56
-
57
- def parse_streaming_error(data)
58
- error_data = JSON.parse(data)
59
- return unless error_data['type'] == 'error'
60
-
61
- case error_data.dig('error', 'type')
62
- when 'overloaded_error'
63
- [529, error_data['error']['message']]
64
- else
65
- [500, error_data['error']['message']]
66
- end
67
- end
68
- end
69
- end
70
- end
71
- end
@@ -1,405 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require 'json'
4
-
5
- module RubyLLM
6
- module Providers
7
- class Bedrock
8
- # Chat methods for Bedrock Converse API.
9
- module Chat
10
- module_function
11
-
12
- def completion_url
13
- "/model/#{@model.id}/converse"
14
- end
15
-
16
- # rubocop:disable Metrics/ParameterLists,Lint/UnusedMethodArgument
17
- def render_payload(messages, tools:, temperature:, model:, stream: false,
18
- schema: nil, thinking: nil, tool_prefs: nil)
19
- tool_prefs ||= {}
20
- @model = model
21
- @used_document_names = {}
22
- system_messages, chat_messages = messages.partition { |msg| msg.role == :system }
23
- payload = {
24
- messages: render_messages(chat_messages)
25
- }
26
-
27
- system_blocks = render_system(system_messages)
28
- payload[:system] = system_blocks unless system_blocks.empty?
29
-
30
- payload[:inferenceConfig] = render_inference_config(model, temperature)
31
-
32
- tool_config = render_tool_config(tools, tool_prefs)
33
- if tool_config
34
- payload[:toolConfig] = tool_config
35
- payload[:tools] = tool_config[:tools] # Internal mirror for shared payload inspections in specs.
36
- end
37
-
38
- additional_fields = render_additional_model_request_fields(thinking)
39
- payload[:additionalModelRequestFields] = additional_fields if additional_fields
40
-
41
- output_config = build_output_config(schema)
42
- payload[:outputConfig] = output_config if output_config
43
-
44
- payload
45
- end
46
- # rubocop:enable Metrics/ParameterLists,Lint/UnusedMethodArgument
47
-
48
- def parse_completion_response(response)
49
- data = response.body
50
- return if data.nil? || data.empty?
51
-
52
- content_blocks = data.dig('output', 'message', 'content') || []
53
- usage = data['usage'] || {}
54
- thinking_text, thinking_signature = parse_thinking(content_blocks)
55
-
56
- Message.new(
57
- role: :assistant,
58
- content: parse_text_content(content_blocks),
59
- thinking: Thinking.build(text: thinking_text, signature: thinking_signature),
60
- tool_calls: parse_tool_calls(content_blocks),
61
- input_tokens: input_tokens(usage),
62
- output_tokens: usage['outputTokens'],
63
- cached_tokens: usage['cacheReadInputTokens'],
64
- cache_creation_tokens: usage['cacheWriteInputTokens'],
65
- thinking_tokens: usage['reasoningTokens'],
66
- model_id: data['modelId'],
67
- raw: response
68
- )
69
- end
70
-
71
- def input_tokens(usage)
72
- input_tokens = usage['inputTokens']
73
- return unless input_tokens
74
-
75
- [input_tokens.to_i - usage['cacheReadInputTokens'].to_i - usage['cacheWriteInputTokens'].to_i, 0].max
76
- end
77
-
78
- def render_messages(messages)
79
- rendered = []
80
- tool_result_blocks = []
81
-
82
- messages.each do |msg|
83
- if msg.tool_result?
84
- tool_result_blocks << render_tool_result_block(msg)
85
- next
86
- end
87
-
88
- unless tool_result_blocks.empty?
89
- rendered << { role: 'user', content: tool_result_blocks }
90
- tool_result_blocks = []
91
- end
92
-
93
- message = render_non_tool_message(msg)
94
- rendered << message if message
95
- end
96
-
97
- rendered << { role: 'user', content: tool_result_blocks } unless tool_result_blocks.empty?
98
- rendered
99
- end
100
-
101
- def render_non_tool_message(msg)
102
- content = render_message_content(msg)
103
- return nil if content.empty?
104
-
105
- {
106
- role: render_role(msg.role),
107
- content: content
108
- }
109
- end
110
-
111
- def render_message_content(msg)
112
- if msg.content.is_a?(RubyLLM::Content::Raw)
113
- return render_raw_content(msg.content) if msg.role == :assistant
114
-
115
- return sanitize_non_assistant_raw_blocks(render_raw_content(msg.content))
116
- end
117
-
118
- blocks = []
119
-
120
- thinking_block = render_thinking_block(msg.thinking)
121
- blocks << thinking_block if msg.role == :assistant && thinking_block
122
-
123
- text_and_media_blocks = Media.render_content(msg.content, used_document_names: @used_document_names)
124
- blocks.concat(text_and_media_blocks) if text_and_media_blocks
125
-
126
- if msg.tool_call?
127
- msg.tool_calls.each_value do |tool_call|
128
- blocks << {
129
- toolUse: {
130
- toolUseId: tool_call.id,
131
- name: tool_call.name,
132
- input: tool_call.arguments
133
- }
134
- }
135
- end
136
- end
137
-
138
- blocks
139
- end
140
-
141
- def render_raw_content(content)
142
- value = content.value
143
- value.is_a?(Array) ? value : [value]
144
- end
145
-
146
- def sanitize_non_assistant_raw_blocks(blocks)
147
- blocks.filter_map do |block|
148
- next unless block.is_a?(Hash)
149
- next if block.key?(:reasoningContent) || block.key?('reasoningContent')
150
-
151
- block
152
- end
153
- end
154
-
155
- def render_tool_result_block(msg)
156
- {
157
- toolResult: {
158
- toolUseId: msg.tool_call_id,
159
- content: render_tool_result_content(msg.content)
160
- }
161
- }
162
- end
163
-
164
- def render_tool_result_content(content)
165
- return render_raw_tool_result_content(content.value) if content.is_a?(RubyLLM::Content::Raw)
166
- return [{ json: content }] if content.is_a?(Hash) || content.is_a?(Array)
167
- return render_content_tool_result_content(content) if content.is_a?(RubyLLM::Content)
168
-
169
- [text_tool_result_block(content)]
170
- end
171
-
172
- def render_content_tool_result_content(content)
173
- blocks = []
174
- blocks << text_tool_result_block(content.text) unless content.text.to_s.empty?
175
- content.attachments.each { |attachment| blocks << text_tool_result_block(attachment.for_llm) }
176
- blocks.empty? ? [text_tool_result_block(nil)] : blocks
177
- end
178
-
179
- def text_tool_result_block(text)
180
- text = text.to_s
181
- text = '(no output)' if text.empty?
182
- { text: text }
183
- end
184
-
185
- def render_raw_tool_result_content(raw_value)
186
- blocks = raw_value.is_a?(Array) ? raw_value : [raw_value]
187
-
188
- normalized = blocks.filter_map do |block|
189
- normalize_tool_result_block(block)
190
- end
191
-
192
- normalized.empty? ? [{ text: raw_value.to_s }] : normalized
193
- end
194
-
195
- def normalize_tool_result_block(block)
196
- return nil unless block.is_a?(Hash)
197
- return block if tool_result_content_block?(block)
198
-
199
- nil
200
- end
201
-
202
- def tool_result_content_block?(block)
203
- %w[text json document image].any? do |key|
204
- block.key?(key) || block.key?(key.to_sym)
205
- end
206
- end
207
-
208
- def render_role(role)
209
- case role
210
- when :assistant then 'assistant'
211
- else 'user'
212
- end
213
- end
214
-
215
- def render_system(messages)
216
- messages.flat_map { |msg| Media.render_content(msg.content, used_document_names: @used_document_names) }
217
- end
218
-
219
- def render_inference_config(_model, temperature)
220
- config = {}
221
- config[:temperature] = temperature unless temperature.nil?
222
- config
223
- end
224
-
225
- def render_tool_config(tools, tool_prefs)
226
- return nil if tools.empty?
227
-
228
- config = {
229
- tools: tools.values.map { |tool| render_tool(tool) }
230
- }
231
-
232
- return config if tool_prefs.nil? || tool_prefs[:choice].nil?
233
-
234
- tool_choice = render_tool_choice(tool_prefs[:choice])
235
- config[:toolChoice] = tool_choice if tool_choice
236
- config
237
- end
238
-
239
- def render_tool_choice(choice)
240
- case choice
241
- when :auto
242
- { auto: {} }
243
- when :none
244
- nil
245
- when :required
246
- { any: {} }
247
- else
248
- { tool: { name: choice.to_s } }
249
- end
250
- end
251
-
252
- def render_tool(tool)
253
- input_schema = tool.params_schema || RubyLLM::Tool::SchemaDefinition.from_parameters(tool.parameters)&.json_schema
254
-
255
- tool_spec = {
256
- toolSpec: {
257
- name: tool.name,
258
- description: tool.description,
259
- inputSchema: {
260
- json: input_schema || default_input_schema
261
- }
262
- }
263
- }
264
-
265
- return tool_spec if tool.provider_params.empty?
266
-
267
- RubyLLM::Utils.deep_merge(tool_spec, tool.provider_params)
268
- end
269
-
270
- def render_additional_model_request_fields(thinking)
271
- fields = {}
272
-
273
- reasoning_fields = render_reasoning_fields(thinking)
274
- fields = RubyLLM::Utils.deep_merge(fields, reasoning_fields) if reasoning_fields
275
-
276
- fields.empty? ? nil : fields
277
- end
278
-
279
- def build_output_config(schema)
280
- return nil unless schema
281
-
282
- cleaned = RubyLLM::Utils.deep_dup(schema[:schema])
283
- cleaned.delete(:strict)
284
- cleaned.delete('strict')
285
-
286
- {
287
- textFormat: {
288
- type: 'json_schema',
289
- structure: {
290
- jsonSchema: {
291
- schema: JSON.generate(cleaned),
292
- name: schema[:name]
293
- }
294
- }
295
- }
296
- }
297
- end
298
-
299
- def render_reasoning_fields(thinking)
300
- return nil unless thinking&.enabled?
301
-
302
- effort_config = effort_reasoning_config(thinking)
303
- return effort_config if effort_config
304
-
305
- budget_reasoning_config(thinking)
306
- end
307
-
308
- def effort_reasoning_config(thinking)
309
- effort = thinking.respond_to?(:effort) ? thinking.effort : nil
310
- effort = effort.to_s if effort
311
- return nil if effort.nil? || effort.empty? || effort == 'none'
312
-
313
- if reasoning_embedded?(@model)
314
- { reasoning_config: { type: 'enabled', reasoning_effort: effort } }
315
- else
316
- { reasoning_effort: effort }
317
- end
318
- end
319
-
320
- def budget_reasoning_config(thinking)
321
- budget = thinking.respond_to?(:budget) ? thinking.budget : thinking
322
- return nil unless budget.is_a?(Integer)
323
-
324
- { reasoning_config: { type: 'enabled', budget_tokens: budget } }
325
- end
326
-
327
- def render_thinking_block(thinking)
328
- return nil unless thinking
329
-
330
- if thinking.text
331
- {
332
- reasoningContent: {
333
- reasoningText: {
334
- text: thinking.text,
335
- signature: thinking.signature
336
- }.compact
337
- }
338
- }
339
- elsif thinking.signature
340
- {
341
- reasoningContent: {
342
- redactedContent: thinking.signature
343
- }
344
- }
345
- end
346
- end
347
-
348
- def parse_text_content(content_blocks)
349
- text = content_blocks.filter_map { |block| block['text'] if block['text'].is_a?(String) }.join
350
- text.empty? ? nil : text
351
- end
352
-
353
- def parse_thinking(content_blocks)
354
- text = +''
355
- signature = nil
356
-
357
- content_blocks.each do |block|
358
- chunk_text, chunk_signature = parse_reasoning_content_block(block)
359
- text << chunk_text if chunk_text
360
- signature ||= chunk_signature
361
- end
362
-
363
- [text.empty? ? nil : text, signature]
364
- end
365
-
366
- def parse_reasoning_content_block(block)
367
- reasoning_content = block['reasoningContent']
368
- return [nil, nil] unless reasoning_content.is_a?(Hash)
369
-
370
- reasoning_text = reasoning_content['reasoningText'] || {}
371
- text = reasoning_text['text'].is_a?(String) ? reasoning_text['text'] : nil
372
- signature = reasoning_text['signature'] if reasoning_text['signature'].is_a?(String)
373
- signature ||= reasoning_content['redactedContent'] if reasoning_content['redactedContent'].is_a?(String)
374
- [text, signature]
375
- end
376
-
377
- def parse_tool_calls(content_blocks)
378
- tool_calls = {}
379
-
380
- content_blocks.each do |block|
381
- tool_use = block['toolUse']
382
- next unless tool_use
383
-
384
- tool_call_id = tool_use['toolUseId']
385
- tool_calls[tool_call_id] = ToolCall.new(
386
- id: tool_call_id,
387
- name: tool_use['name'],
388
- arguments: tool_use['input'] || {}
389
- )
390
- end
391
-
392
- tool_calls.empty? ? nil : tool_calls
393
- end
394
-
395
- def default_input_schema
396
- {
397
- 'type' => 'object',
398
- 'properties' => {},
399
- 'required' => []
400
- }
401
- end
402
- end
403
- end
404
- end
405
- end
@@ -1,108 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class Bedrock
6
- # Media formatting for Bedrock Converse content blocks.
7
- module Media
8
- module_function
9
-
10
- def render_content(content, used_document_names: nil)
11
- return [] if empty_content?(content)
12
- return render_raw_content(content) if content.is_a?(RubyLLM::Content::Raw)
13
- return [{ text: content.to_json }] if content.is_a?(Hash) || content.is_a?(Array)
14
- return [{ text: content }] unless content.is_a?(RubyLLM::Content)
15
-
16
- render_content_object(content, used_document_names || {})
17
- end
18
-
19
- def empty_content?(content)
20
- content.nil? || (content.respond_to?(:empty?) && content.empty?)
21
- end
22
-
23
- def render_content_object(content, used_document_names)
24
- blocks = []
25
- blocks << { text: content.text } if content.text
26
- content.attachments.each do |attachment|
27
- blocks << render_attachment(attachment, used_document_names:)
28
- end
29
- blocks
30
- end
31
-
32
- def render_raw_content(content)
33
- value = content.value
34
- value.is_a?(Array) ? value : [value]
35
- end
36
-
37
- def render_attachment(attachment, used_document_names:)
38
- case attachment.type
39
- when :image
40
- render_image_attachment(attachment)
41
- when :pdf, :document
42
- render_document_attachment(attachment, used_document_names:)
43
- when :text
44
- render_text_attachment(attachment)
45
- else
46
- raise UnsupportedAttachmentError, attachment.mime_type
47
- end
48
- end
49
-
50
- SUPPORTED_DOCUMENT_FORMATS = %w[pdf csv doc docx xls xlsx html txt md].freeze
51
-
52
- def render_image_attachment(attachment)
53
- {
54
- image: {
55
- format: attachment.format,
56
- source: {
57
- bytes: attachment.encoded
58
- }
59
- }
60
- }
61
- end
62
-
63
- def render_text_attachment(attachment)
64
- { text: attachment.for_llm }
65
- end
66
-
67
- def render_document_attachment(attachment, used_document_names:)
68
- format = document_format(attachment)
69
-
70
- raise UnsupportedAttachmentError, attachment.mime_type unless supported_document_format?(attachment)
71
-
72
- document_name = unique_document_name(sanitize_document_name(attachment.filename), used_document_names)
73
- {
74
- document: {
75
- format: format,
76
- name: document_name,
77
- source: {
78
- bytes: attachment.encoded
79
- }
80
- }
81
- }
82
- end
83
-
84
- def supported_document_format?(attachment)
85
- SUPPORTED_DOCUMENT_FORMATS.include?(document_format(attachment))
86
- end
87
-
88
- def document_format(attachment)
89
- attachment.extension || attachment.format
90
- end
91
-
92
- def sanitize_document_name(filename)
93
- base = File.basename(filename.to_s, '.*')
94
- safe = base.gsub(/[^a-zA-Z0-9_-]/, '_')
95
- safe.empty? ? 'document' : safe
96
- end
97
-
98
- def unique_document_name(base_name, used_names)
99
- count = used_names[base_name].to_i
100
- used_names[base_name] = count + 1
101
- return base_name if count.zero?
102
-
103
- "#{base_name}_#{count + 1}"
104
- end
105
- end
106
- end
107
- end
108
- end