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,39 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- # Sources for model registry data.
5
- module ModelRegistry
6
- # Reads model registry data from the configured JSON file.
7
- class JsonSource
8
- def initialize(file = nil)
9
- @file = file
10
- end
11
-
12
- def read
13
- Models.read_from_json(@file || RubyLLM.config.model_registry_file)
14
- end
15
- end
16
-
17
- # Reads model registry data from the configured Active Record model class.
18
- class ActiveRecordSource
19
- def read
20
- model_class = resolve_model_class
21
- return [] unless model_class.respond_to?(:table_exists?) && model_class.table_exists?
22
-
23
- model_class.all.map(&:to_llm)
24
- rescue StandardError => e
25
- RubyLLM.logger.debug { "Failed to load models from database: #{e.message}, falling back to JSON" }
26
- []
27
- end
28
-
29
- private
30
-
31
- def resolve_model_class
32
- model_class = RubyLLM.config.model_registry_class
33
- return model_class unless model_class.is_a?(String)
34
-
35
- model_class.split('::').inject(Object) { |scope, name| scope.const_get(name) }
36
- end
37
- end
38
- end
39
- end
@@ -1,171 +0,0 @@
1
- {
2
- "title": "RubyLLM Models Schema",
3
- "description": "Schema for validating the structure of models.json",
4
- "type": "array",
5
- "items": {
6
- "type": "object",
7
- "required": ["id", "name", "provider", "context_window", "max_output_tokens"],
8
- "properties": {
9
- "id": {
10
- "type": "string",
11
- "description": "Unique identifier for the model"
12
- },
13
- "name": {
14
- "type": "string",
15
- "description": "Display name of the model"
16
- },
17
- "provider": {
18
- "type": "string",
19
- "description": "Provider of the model (e.g., openai, anthropic, mistral)"
20
- },
21
- "family": {
22
- "type": ["string", "null"],
23
- "description": "Model family (e.g., gpt-4, claude-3)"
24
- },
25
- "created_at": {
26
- "type": ["null", {"type": "string", "format": "date-time"}],
27
- "description": "Creation date of the model"
28
- },
29
- "context_window": {
30
- "type": ["null", {"type": "integer", "minimum": 0}],
31
- "description": "Maximum context window size"
32
- },
33
- "max_output_tokens": {
34
- "type": ["null", {"type": "integer", "minimum": 0}],
35
- "description": "Maximum output tokens"
36
- },
37
- "knowledge_cutoff": {
38
- "type": ["null", {"type": "string", "format": "date"}],
39
- "description": "Knowledge cutoff date"
40
- },
41
- "modalities": {
42
- "type": "object",
43
- "required": ["input", "output"],
44
- "properties": {
45
- "input": {
46
- "type": "array",
47
- "items": {
48
- "type": "string",
49
- "enum": ["text", "image", "audio", "pdf", "video", "file"]
50
- },
51
- "uniqueItems": true,
52
- "description": "Supported input modalities"
53
- },
54
- "output": {
55
- "type": "array",
56
- "items": {
57
- "type": "string",
58
- "enum": ["text", "image", "audio", "video", "embeddings", "moderation"]
59
- },
60
- "uniqueItems": true,
61
- "description": "Supported output modalities"
62
- }
63
- }
64
- },
65
- "capabilities": {
66
- "type": "array",
67
- "items": {
68
- "type": "string",
69
- "enum": [
70
- "streaming", "function_calling", "structured_output", "predicted_outputs",
71
- "distillation", "fine_tuning", "batch", "realtime", "image_generation",
72
- "speech_generation", "transcription", "translation", "citations", "reasoning",
73
- "caching", "moderation", "json_mode", "vision"
74
- ]
75
- },
76
- "uniqueItems": true,
77
- "description": "Model capabilities"
78
- },
79
- "pricing": {
80
- "type": "object",
81
- "properties": {
82
- "text_tokens": {
83
- "type": "object",
84
- "required": ["standard"],
85
- "properties": {
86
- "standard": {
87
- "type": "object",
88
- "properties": {
89
- "input_per_million": {"type": "number", "minimum": 0},
90
- "cache_read_input_per_million": {"type": "number", "minimum": 0},
91
- "cache_write_input_per_million": {"type": "number", "minimum": 0},
92
- "cached_input_per_million": {"type": "number", "minimum": 0},
93
- "cache_creation_input_per_million": {"type": "number", "minimum": 0},
94
- "output_per_million": {"type": "number", "minimum": 0},
95
- "reasoning_output_per_million": {"type": "number", "minimum": 0}
96
- }
97
- },
98
- "batch": {
99
- "type": "object",
100
- "properties": {
101
- "input_per_million": {"type": "number", "minimum": 0},
102
- "output_per_million": {"type": "number", "minimum": 0}
103
- }
104
- }
105
- }
106
- },
107
- "images": {
108
- "type": "object",
109
- "properties": {
110
- "standard": {
111
- "type": "object",
112
- "properties": {
113
- "input": {"type": "number", "minimum": 0},
114
- "output": {"type": "number", "minimum": 0}
115
- }
116
- },
117
- "batch": {
118
- "type": "object",
119
- "properties": {
120
- "input": {"type": "number", "minimum": 0},
121
- "output": {"type": "number", "minimum": 0}
122
- }
123
- }
124
- }
125
- },
126
- "audio_tokens": {
127
- "type": "object",
128
- "properties": {
129
- "standard": {
130
- "type": "object",
131
- "properties": {
132
- "input_per_million": {"type": "number", "minimum": 0},
133
- "output_per_million": {"type": "number", "minimum": 0}
134
- }
135
- },
136
- "batch": {
137
- "type": "object",
138
- "properties": {
139
- "input_per_million": {"type": "number", "minimum": 0},
140
- "output_per_million": {"type": "number", "minimum": 0}
141
- }
142
- }
143
- }
144
- },
145
- "embeddings": {
146
- "type": "object",
147
- "properties": {
148
- "standard": {
149
- "type": "object",
150
- "properties": {
151
- "input_per_million": {"type": "number", "minimum": 0}
152
- }
153
- },
154
- "batch": {
155
- "type": "object",
156
- "properties": {
157
- "input_per_million": {"type": "number", "minimum": 0}
158
- }
159
- }
160
- }
161
- }
162
- },
163
- "description": "Pricing information for the model"
164
- },
165
- "metadata": {
166
- "type": "object",
167
- "description": "Additional metadata about the model"
168
- }
169
- }
170
- }
171
- }
@@ -1,291 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class Anthropic
6
- # Chat methods for the Anthropic API implementation
7
- module Chat
8
- module_function
9
-
10
- def completion_url
11
- 'v1/messages'
12
- end
13
-
14
- # rubocop:disable Metrics/ParameterLists
15
- def render_payload(messages, tools:, temperature:, model:, stream: false,
16
- schema: nil, thinking: nil, tool_prefs: nil)
17
- tool_prefs ||= {}
18
- system_messages, chat_messages = separate_messages(messages)
19
- system_content = build_system_content(system_messages)
20
-
21
- build_base_payload(chat_messages, model, stream, thinking).tap do |payload|
22
- add_optional_fields(payload, system_content:, tools:, tool_prefs:, temperature:, schema:)
23
- end
24
- end
25
- # rubocop:enable Metrics/ParameterLists
26
-
27
- def separate_messages(messages)
28
- messages.partition { |msg| msg.role == :system }
29
- end
30
-
31
- def build_system_content(system_messages)
32
- return [] if system_messages.empty?
33
-
34
- # Anthropic's `system` parameter accepts an array of text content blocks
35
- # (each optionally with cache_control); each :system message becomes its
36
- # own block in the resulting array.
37
- system_messages.flat_map do |msg|
38
- content = msg.content
39
-
40
- if content.is_a?(RubyLLM::Content::Raw)
41
- content.value
42
- else
43
- Media.format_content(content)
44
- end
45
- end
46
- end
47
-
48
- def build_base_payload(chat_messages, model, stream, thinking)
49
- payload = {
50
- model: model.id,
51
- messages: chat_messages.map { |msg| format_message(msg, thinking: thinking) },
52
- stream: stream,
53
- max_tokens: model.max_tokens || 4096
54
- }
55
-
56
- add_thinking_fields(payload, thinking, model)
57
-
58
- payload
59
- end
60
-
61
- def add_optional_fields(payload, system_content:, tools:, tool_prefs:, temperature:, schema: nil) # rubocop:disable Metrics/ParameterLists
62
- if tools.any?
63
- payload[:tools] = tools.values.map { |t| Tools.function_for(t) }
64
- unless tool_prefs[:choice].nil? && tool_prefs[:calls].nil?
65
- payload[:tool_choice] = Tools.build_tool_choice(tool_prefs)
66
- end
67
- end
68
- payload[:system] = system_content unless system_content.empty?
69
- payload[:temperature] = temperature unless temperature.nil?
70
- payload[:output_config] = payload.fetch(:output_config, {}).merge(build_output_config(schema)) if schema
71
- end
72
-
73
- def build_output_config(schema)
74
- normalized = RubyLLM::Utils.deep_dup(schema[:schema])
75
- normalized.delete(:strict)
76
- normalized.delete('strict')
77
- { format: { type: 'json_schema', schema: normalized } }
78
- end
79
-
80
- def parse_completion_response(response)
81
- data = response.body
82
- content_blocks = data['content'] || []
83
-
84
- text_content = extract_text_content(content_blocks)
85
- thinking_content = extract_thinking_content(content_blocks)
86
- thinking_signature = extract_thinking_signature(content_blocks)
87
- tool_use_blocks = Tools.find_tool_uses(content_blocks)
88
-
89
- build_message(data, text_content, thinking_content, thinking_signature, tool_use_blocks, response)
90
- end
91
-
92
- def extract_text_content(blocks)
93
- text_blocks = blocks.select { |c| c['type'] == 'text' }
94
- text_blocks.map { |c| c['text'] }.join
95
- end
96
-
97
- def extract_thinking_content(blocks)
98
- thinking_blocks = blocks.select { |c| c['type'] == 'thinking' }
99
- thoughts = thinking_blocks.map { |c| c['thinking'] || c['text'] }.join
100
- thoughts.empty? ? nil : thoughts
101
- end
102
-
103
- def extract_thinking_signature(blocks)
104
- thinking_block = blocks.find { |c| c['type'] == 'thinking' } ||
105
- blocks.find { |c| c['type'] == 'redacted_thinking' }
106
- thinking_block&.dig('signature') || thinking_block&.dig('data')
107
- end
108
-
109
- def build_message(data, content, thinking, thinking_signature, tool_use_blocks, response) # rubocop:disable Metrics/ParameterLists
110
- usage = data['usage'] || {}
111
- cached_tokens = usage['cache_read_input_tokens']
112
- cache_creation_tokens = usage['cache_creation_input_tokens']
113
- if cache_creation_tokens.nil? && usage['cache_creation'].is_a?(Hash)
114
- cache_creation_tokens = usage['cache_creation'].values.compact.sum
115
- end
116
- thinking_tokens = usage.dig('output_tokens_details', 'thinking_tokens') ||
117
- usage.dig('output_tokens_details', 'reasoning_tokens') ||
118
- usage['thinking_tokens'] ||
119
- usage['reasoning_tokens']
120
-
121
- Message.new(
122
- role: :assistant,
123
- content: content,
124
- thinking: Thinking.build(text: thinking, signature: thinking_signature),
125
- tool_calls: Tools.parse_tool_calls(tool_use_blocks),
126
- input_tokens: usage['input_tokens'],
127
- output_tokens: usage['output_tokens'],
128
- cached_tokens: cached_tokens,
129
- cache_creation_tokens: cache_creation_tokens,
130
- thinking_tokens: thinking_tokens,
131
- model_id: data['model'],
132
- raw: response
133
- )
134
- end
135
-
136
- def format_message(msg, thinking: nil)
137
- thinking_enabled = thinking&.enabled?
138
-
139
- if msg.tool_call?
140
- format_tool_call_with_thinking(msg, thinking_enabled)
141
- elsif msg.tool_result?
142
- Tools.format_tool_result(msg)
143
- else
144
- format_basic_message_with_thinking(msg, thinking_enabled)
145
- end
146
- end
147
-
148
- def format_basic_message_with_thinking(msg, thinking_enabled)
149
- content_blocks = []
150
-
151
- if msg.role == :assistant && thinking_enabled
152
- thinking_block = build_thinking_block(msg.thinking)
153
- content_blocks << thinking_block if thinking_block
154
- end
155
-
156
- append_formatted_content(content_blocks, msg.content)
157
-
158
- {
159
- role: convert_role(msg.role),
160
- content: content_blocks
161
- }
162
- end
163
-
164
- def format_tool_call_with_thinking(msg, thinking_enabled)
165
- if msg.content.is_a?(RubyLLM::Content::Raw)
166
- content_blocks = msg.content.value
167
- content_blocks = [content_blocks] unless content_blocks.is_a?(Array)
168
- content_blocks = prepend_thinking_block(content_blocks, msg, thinking_enabled)
169
-
170
- return { role: 'assistant', content: content_blocks }
171
- end
172
-
173
- content_blocks = prepend_thinking_block([], msg, thinking_enabled)
174
- append_formatted_content(content_blocks, msg.content) unless msg.content.nil? || msg.content.empty?
175
-
176
- msg.tool_calls.each_value do |tool_call|
177
- content_blocks << {
178
- type: 'tool_use',
179
- id: tool_call.id,
180
- name: tool_call.name,
181
- input: tool_call.arguments
182
- }
183
- end
184
-
185
- {
186
- role: 'assistant',
187
- content: content_blocks
188
- }
189
- end
190
-
191
- def prepend_thinking_block(content_blocks, msg, thinking_enabled)
192
- return content_blocks unless thinking_enabled
193
-
194
- thinking_block = build_thinking_block(msg.thinking)
195
- content_blocks.unshift(thinking_block) if thinking_block
196
-
197
- content_blocks
198
- end
199
-
200
- def build_thinking_block(thinking)
201
- return nil unless thinking
202
-
203
- if thinking.text
204
- {
205
- type: 'thinking',
206
- thinking: thinking.text,
207
- signature: thinking.signature
208
- }.compact
209
- elsif thinking.signature
210
- {
211
- type: 'redacted_thinking',
212
- data: thinking.signature
213
- }
214
- end
215
- end
216
-
217
- def append_formatted_content(content_blocks, content)
218
- formatted_content = Media.format_content(content)
219
- if formatted_content.is_a?(Array)
220
- content_blocks.concat(formatted_content)
221
- else
222
- content_blocks << formatted_content
223
- end
224
- end
225
-
226
- def convert_role(role)
227
- case role
228
- when :tool, :user then 'user'
229
- else 'assistant'
230
- end
231
- end
232
-
233
- def add_thinking_fields(payload, thinking, model)
234
- thinking_payload = build_thinking_payload(thinking, model)
235
- return unless thinking_payload
236
-
237
- payload[:thinking] = thinking_payload[:thinking] if thinking_payload[:thinking]
238
- return unless thinking_payload[:output_config]
239
-
240
- payload[:output_config] = payload.fetch(:output_config, {}).merge(thinking_payload[:output_config])
241
- end
242
-
243
- def build_thinking_payload(thinking, model)
244
- return nil unless thinking&.enabled?
245
-
246
- effort = resolve_effort(thinking)
247
- return nil if effort == 'none'
248
-
249
- budget = resolve_budget(thinking)
250
- if budget
251
- return enabled_thinking_payload(budget) if model.reasoning_option('budget_tokens')
252
-
253
- raise ArgumentError, "Anthropic thinking budget is not supported for #{model.id}"
254
- end
255
-
256
- raise ArgumentError, 'Anthropic adaptive thinking requires an effort' if effort.nil?
257
- return adaptive_thinking_payload(effort) if model.reasoning_option('effort')
258
-
259
- raise ArgumentError, "Anthropic thinking effort is not supported for #{model.id}"
260
- end
261
-
262
- def enabled_thinking_payload(budget)
263
- {
264
- thinking: {
265
- type: 'enabled',
266
- budget_tokens: budget
267
- }
268
- }
269
- end
270
-
271
- def adaptive_thinking_payload(effort)
272
- {
273
- thinking: { type: 'adaptive' },
274
- output_config: { effort: effort }
275
- }
276
- end
277
-
278
- def resolve_effort(thinking)
279
- effort = thinking.respond_to?(:effort) ? thinking.effort : nil
280
- effort = effort.to_s if effort
281
- effort.nil? || effort.empty? ? nil : effort
282
- end
283
-
284
- def resolve_budget(thinking)
285
- budget = thinking.respond_to?(:budget) ? thinking.budget : thinking
286
- budget.is_a?(Integer) ? budget : nil
287
- end
288
- end
289
- end
290
- end
291
- end
@@ -1,44 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class Anthropic
6
- # Helper for constructing Anthropic native content blocks.
7
- class Content
8
- class << self
9
- def new(text = nil, cache: false, cache_control: nil, parts: nil, **extras)
10
- payload = resolve_payload(
11
- text: text,
12
- parts: parts,
13
- cache: cache,
14
- cache_control: cache_control,
15
- extras: extras
16
- )
17
-
18
- RubyLLM::Content::Raw.new(payload)
19
- end
20
-
21
- private
22
-
23
- def resolve_payload(text:, parts:, cache:, cache_control:, extras:)
24
- return Array(parts) if parts
25
-
26
- raise ArgumentError, 'text or parts must be provided' if text.nil?
27
-
28
- block = { type: 'text', text: text }.merge(extras)
29
- control = determine_cache_control(cache_control, cache)
30
- block[:cache_control] = control if control
31
-
32
- [block]
33
- end
34
-
35
- def determine_cache_control(cache_control, cache_flag)
36
- return cache_control if cache_control
37
-
38
- { type: 'ephemeral' } if cache_flag
39
- end
40
- end
41
- end
42
- end
43
- end
44
- end
@@ -1,20 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class Anthropic
6
- # Embeddings methods of the Anthropic API integration
7
- module Embeddings
8
- private
9
-
10
- def embed
11
- raise Error "Anthropic doesn't support embeddings"
12
- end
13
-
14
- alias render_embedding_payload embed
15
- alias embedding_url embed
16
- alias parse_embedding_response embed
17
- end
18
- end
19
- end
20
- end
@@ -1,92 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module RubyLLM
4
- module Providers
5
- class Anthropic
6
- # Handles formatting of media content (images, PDFs, audio) for Anthropic
7
- module Media
8
- module_function
9
-
10
- def format_content(content) # rubocop:disable Metrics/PerceivedComplexity
11
- return content.value if content.is_a?(RubyLLM::Content::Raw)
12
- return [format_text(content.to_json)] if content.is_a?(Hash) || content.is_a?(Array)
13
- return [format_text(content)] unless content.is_a?(RubyLLM::Content)
14
-
15
- parts = []
16
- parts << format_text(content.text) if content.text
17
-
18
- content.attachments.each do |attachment|
19
- case attachment.type
20
- when :image
21
- parts << format_image(attachment)
22
- when :pdf
23
- parts << format_pdf(attachment)
24
- when :text
25
- parts << format_text_file(attachment)
26
- else
27
- raise UnsupportedAttachmentError, attachment.mime_type
28
- end
29
- end
30
-
31
- parts
32
- end
33
-
34
- def format_text(text)
35
- {
36
- type: 'text',
37
- text: text
38
- }
39
- end
40
-
41
- def format_image(image)
42
- if image.url?
43
- {
44
- type: 'image',
45
- source: {
46
- type: 'url',
47
- url: image.source.to_s
48
- }
49
- }
50
- else
51
- {
52
- type: 'image',
53
- source: {
54
- type: 'base64',
55
- media_type: image.mime_type,
56
- data: image.encoded
57
- }
58
- }
59
- end
60
- end
61
-
62
- def format_pdf(pdf)
63
- if pdf.url?
64
- {
65
- type: 'document',
66
- source: {
67
- type: 'url',
68
- url: pdf.source.to_s
69
- }
70
- }
71
- else
72
- {
73
- type: 'document',
74
- source: {
75
- type: 'base64',
76
- media_type: pdf.mime_type,
77
- data: pdf.encoded
78
- }
79
- }
80
- end
81
- end
82
-
83
- def format_text_file(text_file)
84
- {
85
- type: 'text',
86
- text: text_file.for_llm
87
- }
88
- end
89
- end
90
- end
91
- end
92
- end