ruby_llm 2.0.0 → 2.1.1

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 (287) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +67 -8
  3. data/lib/generators/ruby_llm/generator_helpers.rb +68 -6
  4. data/lib/generators/ruby_llm/install/templates/create_messages_migration.rb.tt +1 -0
  5. data/lib/generators/ruby_llm/install/templates/create_ruby_llm_records_migration.rb.tt +36 -3
  6. data/lib/generators/ruby_llm/provider/scaffold.rb +32 -22
  7. data/lib/generators/ruby_llm/provider/templates/gem/ci.yml.erb +1 -5
  8. data/lib/generators/ruby_llm/provider/templates/gem/gemfile.erb +1 -1
  9. data/lib/generators/ruby_llm/provider/templates/gem/gemspec.erb +1 -1
  10. data/lib/generators/ruby_llm/provider/templates/gem/rubocop.yml.erb +1 -1
  11. data/lib/generators/ruby_llm/upgrade/templates/upgrade_ruby_llm_to_2_1.rb.tt +78 -0
  12. data/lib/generators/ruby_llm/upgrade/upgrade_generator.rb +6 -163
  13. data/lib/ruby_llm/accounting/usage.rb +86 -49
  14. data/lib/ruby_llm/active_record/acts_as.rb +3 -0
  15. data/lib/ruby_llm/active_record/attachment_helpers.rb +50 -25
  16. data/lib/ruby_llm/active_record/batch.rb +1 -1
  17. data/lib/ruby_llm/active_record/chat_methods.rb +143 -43
  18. data/lib/ruby_llm/active_record/mcp_credential.rb +41 -0
  19. data/lib/ruby_llm/active_record/message_methods.rb +32 -7
  20. data/lib/ruby_llm/active_record/model.rb +1 -1
  21. data/lib/ruby_llm/active_record/provider_file.rb +64 -0
  22. data/lib/ruby_llm/active_record/record.rb +19 -0
  23. data/lib/ruby_llm/active_record/tool_call.rb +1 -1
  24. data/lib/ruby_llm/active_record/usage.rb +53 -3
  25. data/lib/ruby_llm/agent.rb +198 -33
  26. data/lib/ruby_llm/aliases.json +66 -8
  27. data/lib/ruby_llm/attachment.rb +54 -10
  28. data/lib/ruby_llm/batch.rb +26 -3
  29. data/lib/ruby_llm/chat/tool_concurrency.rb +47 -23
  30. data/lib/ruby_llm/chat/tool_search.rb +26 -0
  31. data/lib/ruby_llm/chat.rb +492 -82
  32. data/lib/ruby_llm/choice.rb +38 -0
  33. data/lib/ruby_llm/configuration.rb +36 -11
  34. data/lib/ruby_llm/context.rb +45 -32
  35. data/lib/ruby_llm/cost.rb +42 -1
  36. data/lib/ruby_llm/embedding.rb +9 -2
  37. data/lib/ruby_llm/error.rb +34 -2
  38. data/lib/ruby_llm/evaluation/assertions.rb +40 -0
  39. data/lib/ruby_llm/evaluation/case.rb +52 -0
  40. data/lib/ruby_llm/evaluation/dataset.rb +65 -0
  41. data/lib/ruby_llm/evaluation/evaluator.rb +126 -0
  42. data/lib/ruby_llm/evaluation/evidence.rb +125 -0
  43. data/lib/ruby_llm/evaluation/minitest.rb +26 -0
  44. data/lib/ruby_llm/evaluation/report.rb +103 -0
  45. data/lib/ruby_llm/evaluation/result.rb +75 -0
  46. data/lib/ruby_llm/evaluation/reviewer.rb +20 -0
  47. data/lib/ruby_llm/evaluation/rspec.rb +23 -0
  48. data/lib/ruby_llm/evaluation/runner.rb +93 -0
  49. data/lib/ruby_llm/evaluation/trial.rb +101 -0
  50. data/lib/ruby_llm/evaluation.rb +289 -0
  51. data/lib/ruby_llm/image.rb +12 -4
  52. data/lib/ruby_llm/judge/builder.rb +51 -0
  53. data/lib/ruby_llm/judge/data.rb +43 -0
  54. data/lib/ruby_llm/judge/question.rb +101 -0
  55. data/lib/ruby_llm/judge.rb +179 -0
  56. data/lib/ruby_llm/judgment.rb +125 -0
  57. data/lib/ruby_llm/mcp/apps.rb +25 -0
  58. data/lib/ruby_llm/mcp/client.rb +226 -0
  59. data/lib/ruby_llm/mcp/collection.rb +56 -0
  60. data/lib/ruby_llm/mcp/content.rb +44 -0
  61. data/lib/ruby_llm/mcp/error.rb +27 -0
  62. data/lib/ruby_llm/mcp/http.rb +379 -0
  63. data/lib/ruby_llm/mcp/input_request.rb +118 -0
  64. data/lib/ruby_llm/mcp/input_required_error.rb +29 -0
  65. data/lib/ruby_llm/mcp/listener.rb +168 -0
  66. data/lib/ruby_llm/mcp/oauth/key.rb +78 -0
  67. data/lib/ruby_llm/mcp/oauth/memory_store.rb +28 -0
  68. data/lib/ruby_llm/mcp/oauth/proofs.rb +39 -0
  69. data/lib/ruby_llm/mcp/oauth.rb +653 -0
  70. data/lib/ruby_llm/mcp/param_headers.rb +83 -0
  71. data/lib/ruby_llm/mcp/prompt.rb +71 -0
  72. data/lib/ruby_llm/mcp/resource.rb +84 -0
  73. data/lib/ruby_llm/mcp/resource_template.rb +99 -0
  74. data/lib/ruby_llm/mcp/result.rb +76 -0
  75. data/lib/ruby_llm/mcp/session_expired.rb +10 -0
  76. data/lib/ruby_llm/mcp/stdio.rb +250 -0
  77. data/lib/ruby_llm/mcp/task.rb +151 -0
  78. data/lib/ruby_llm/mcp/tool.rb +130 -0
  79. data/lib/ruby_llm/mcp.rb +1101 -0
  80. data/lib/ruby_llm/message.rb +41 -6
  81. data/lib/ruby_llm/model.rb +2 -1
  82. data/lib/ruby_llm/models/aliases.rb +5 -2
  83. data/lib/ruby_llm/models/lookup.rb +80 -0
  84. data/lib/ruby_llm/models/registry.rb +2 -2
  85. data/lib/ruby_llm/models/schema.rb +1 -1
  86. data/lib/ruby_llm/models.json +23082 -10158
  87. data/lib/ruby_llm/models.rb +20 -69
  88. data/lib/ruby_llm/moderation.rb +7 -2
  89. data/lib/ruby_llm/ocr.rb +7 -2
  90. data/lib/ruby_llm/open_telemetry/attributes.rb +111 -0
  91. data/lib/ruby_llm/open_telemetry.rb +82 -0
  92. data/lib/ruby_llm/probability.rb +30 -0
  93. data/lib/ruby_llm/progress.rb +44 -0
  94. data/lib/ruby_llm/prompt/context.rb +55 -0
  95. data/lib/ruby_llm/prompt.rb +1 -1
  96. data/lib/ruby_llm/protocol/binary_streaming.rb +1 -1
  97. data/lib/ruby_llm/protocol/request_shapes/analysis.rb +190 -0
  98. data/lib/ruby_llm/protocol/request_shapes.rb +183 -0
  99. data/lib/ruby_llm/protocol/stored_uploads.rb +62 -0
  100. data/lib/ruby_llm/protocol/stream_accumulator.rb +9 -25
  101. data/lib/ruby_llm/protocol/streaming.rb +38 -31
  102. data/lib/ruby_llm/protocol.rb +196 -40
  103. data/lib/ruby_llm/protocols/anthropic/chat.rb +39 -32
  104. data/lib/ruby_llm/protocols/anthropic/media.rb +6 -2
  105. data/lib/ruby_llm/protocols/anthropic/models.rb +16 -1
  106. data/lib/ruby_llm/protocols/anthropic/request_shapes.rb +70 -0
  107. data/lib/ruby_llm/protocols/anthropic/streaming.rb +23 -9
  108. data/lib/ruby_llm/protocols/anthropic/tools.rb +43 -0
  109. data/lib/ruby_llm/protocols/anthropic.rb +20 -1
  110. data/lib/ruby_llm/protocols/bedrock/async_videos.rb +9 -17
  111. data/lib/ruby_llm/protocols/bedrock/guardrails.rb +8 -9
  112. data/lib/ruby_llm/protocols/bedrock/rerank.rb +1 -17
  113. data/lib/ruby_llm/protocols/chat_completions/chat.rb +5 -7
  114. data/lib/ruby_llm/protocols/chat_completions/embedding_batches.rb +21 -0
  115. data/lib/ruby_llm/protocols/chat_completions/media.rb +34 -8
  116. data/lib/ruby_llm/protocols/chat_completions/request_shapes.rb +121 -0
  117. data/lib/ruby_llm/protocols/chat_completions/streaming.rb +3 -0
  118. data/lib/ruby_llm/protocols/chat_completions.rb +1 -0
  119. data/lib/ruby_llm/protocols/cohere/batch_requests.rb +0 -5
  120. data/lib/ruby_llm/protocols/cohere/batches.rb +3 -7
  121. data/lib/ruby_llm/protocols/cohere/chat.rb +8 -0
  122. data/lib/ruby_llm/protocols/cohere/embeddings.rb +7 -4
  123. data/lib/ruby_llm/protocols/cohere/media.rb +6 -2
  124. data/lib/ruby_llm/protocols/cohere/ocr.rb +1 -1
  125. data/lib/ruby_llm/protocols/cohere/streaming.rb +1 -0
  126. data/lib/ruby_llm/protocols/cohere/tools.rb +5 -1
  127. data/lib/ruby_llm/protocols/cohere.rb +1 -0
  128. data/lib/ruby_llm/protocols/converse/chat.rb +22 -131
  129. data/lib/ruby_llm/protocols/converse/media.rb +16 -2
  130. data/lib/ruby_llm/protocols/converse/reasoning.rb +194 -0
  131. data/lib/ruby_llm/protocols/converse/request_shapes.rb +99 -0
  132. data/lib/ruby_llm/protocols/converse/streaming.rb +2 -2
  133. data/lib/ruby_llm/protocols/converse/thinking_stream.rb +2 -1
  134. data/lib/ruby_llm/protocols/converse.rb +3 -5
  135. data/lib/ruby_llm/protocols/deepseek/files.rb +2 -9
  136. data/lib/ruby_llm/protocols/elevenlabs/flows/images.rb +1 -7
  137. data/lib/ruby_llm/protocols/elevenlabs/flows/videos.rb +11 -17
  138. data/lib/ruby_llm/protocols/elevenlabs/streaming_transcription.rb +1 -4
  139. data/lib/ruby_llm/protocols/gemini/chat.rb +50 -21
  140. data/lib/ruby_llm/protocols/gemini/embeddings.rb +2 -2
  141. data/lib/ruby_llm/protocols/gemini/files.rb +1 -1
  142. data/lib/ruby_llm/protocols/gemini/images.rb +5 -1
  143. data/lib/ruby_llm/protocols/gemini/media.rb +15 -3
  144. data/lib/ruby_llm/protocols/gemini/request_shapes.rb +111 -0
  145. data/lib/ruby_llm/protocols/gemini/streaming.rb +2 -1
  146. data/lib/ruby_llm/protocols/gemini/tools.rb +17 -11
  147. data/lib/ruby_llm/protocols/gemini/transcription.rb +13 -0
  148. data/lib/ruby_llm/protocols/gemini/videos.rb +7 -2
  149. data/lib/ruby_llm/protocols/gemini.rb +1 -0
  150. data/lib/ruby_llm/protocols/gpustack/videos.rb +7 -9
  151. data/lib/ruby_llm/protocols/interactions/chat.rb +46 -18
  152. data/lib/ruby_llm/protocols/interactions/content.rb +9 -2
  153. data/lib/ruby_llm/protocols/interactions/request_shapes.rb +62 -0
  154. data/lib/ruby_llm/protocols/interactions/streaming.rb +15 -0
  155. data/lib/ruby_llm/protocols/interactions/tools.rb +28 -4
  156. data/lib/ruby_llm/protocols/interactions/transcription.rb +1 -11
  157. data/lib/ruby_llm/protocols/interactions.rb +1 -0
  158. data/lib/ruby_llm/protocols/invoke_model/cohere_embeddings.rb +2 -11
  159. data/lib/ruby_llm/protocols/invoke_model/embedding_batches.rb +2 -2
  160. data/lib/ruby_llm/protocols/invoke_model/stability_images.rb +2 -6
  161. data/lib/ruby_llm/protocols/invoke_model/titan_text_embeddings.rb +2 -8
  162. data/lib/ruby_llm/protocols/invoke_model.rb +1 -4
  163. data/lib/ruby_llm/protocols/mistral/content.rb +13 -0
  164. data/lib/ruby_llm/protocols/mistral/conversations/chat.rb +11 -1
  165. data/lib/ruby_llm/protocols/mistral/conversations/images.rb +5 -1
  166. data/lib/ruby_llm/protocols/mistral/conversations/request_shapes.rb +45 -0
  167. data/lib/ruby_llm/protocols/mistral/conversations.rb +1 -0
  168. data/lib/ruby_llm/protocols/mistral/multi_completion.rb +11 -1
  169. data/lib/ruby_llm/protocols/openai/decisions.rb +135 -0
  170. data/lib/ruby_llm/protocols/openai/files.rb +0 -7
  171. data/lib/ruby_llm/protocols/openrouter/batches.rb +13 -8
  172. data/lib/ruby_llm/protocols/openrouter/responses.rb +5 -1
  173. data/lib/ruby_llm/protocols/perplexity/agent.rb +116 -0
  174. data/lib/ruby_llm/protocols/perplexity/router.rb +2 -43
  175. data/lib/ruby_llm/protocols/responses/chat.rb +56 -22
  176. data/lib/ruby_llm/protocols/responses/media.rb +11 -7
  177. data/lib/ruby_llm/protocols/responses/request_shapes.rb +70 -0
  178. data/lib/ruby_llm/protocols/responses/streaming.rb +23 -7
  179. data/lib/ruby_llm/protocols/responses/token_counting.rb +2 -2
  180. data/lib/ruby_llm/protocols/responses/tools.rb +27 -0
  181. data/lib/ruby_llm/protocols/responses.rb +1 -0
  182. data/lib/ruby_llm/protocols/system_one/judgments.rb +42 -0
  183. data/lib/ruby_llm/protocols/system_one/models.rb +27 -0
  184. data/lib/ruby_llm/protocols/system_one/responses.rb +55 -0
  185. data/lib/ruby_llm/protocols/system_one.rb +27 -0
  186. data/lib/ruby_llm/protocols/vertexai/embedding_prediction/requests.rb +2 -2
  187. data/lib/ruby_llm/protocols/vertexai/files.rb +34 -6
  188. data/lib/ruby_llm/protocols/vertexai/ranking.rb +9 -30
  189. data/lib/ruby_llm/protocols/vertexai/research.rb +9 -41
  190. data/lib/ruby_llm/protocols/xai/streaming_transcription.rb +2 -5
  191. data/lib/ruby_llm/provider.rb +87 -15
  192. data/lib/ruby_llm/providers/anthropic.rb +23 -0
  193. data/lib/ruby_llm/providers/azure/chat.rb +4 -0
  194. data/lib/ruby_llm/providers/azure/images.rb +2 -2
  195. data/lib/ruby_llm/providers/azure/media.rb +8 -3
  196. data/lib/ruby_llm/providers/azure/videos.rb +7 -2
  197. data/lib/ruby_llm/providers/azure.rb +24 -4
  198. data/lib/ruby_llm/providers/bedrock/auth.rb +1 -1
  199. data/lib/ruby_llm/providers/bedrock/mantle/anthropic.rb +1 -1
  200. data/lib/ruby_llm/providers/bedrock/mantle/voxtral.rb +1 -3
  201. data/lib/ruby_llm/providers/bedrock/mantle.rb +5 -9
  202. data/lib/ruby_llm/providers/bedrock/models.rb +1 -2
  203. data/lib/ruby_llm/providers/bedrock.rb +38 -6
  204. data/lib/ruby_llm/providers/deepseek/chat.rb +6 -1
  205. data/lib/ruby_llm/providers/deepseek/responses.rb +6 -0
  206. data/lib/ruby_llm/providers/deepseek.rb +4 -0
  207. data/lib/ruby_llm/providers/gemini.rb +4 -0
  208. data/lib/ruby_llm/providers/gpustack/media.rb +8 -5
  209. data/lib/ruby_llm/providers/hetzner/chat.rb +39 -0
  210. data/lib/ruby_llm/providers/hetzner.rb +41 -0
  211. data/lib/ruby_llm/providers/mistral/chat.rb +16 -12
  212. data/lib/ruby_llm/providers/mistral/chat_completions/batches.rb +17 -3
  213. data/lib/ruby_llm/providers/mistral/media.rb +8 -4
  214. data/lib/ruby_llm/providers/mistral/ocr.rb +1 -5
  215. data/lib/ruby_llm/providers/ollama/judgments.rb +26 -0
  216. data/lib/ruby_llm/providers/ollama/media.rb +6 -2
  217. data/lib/ruby_llm/providers/ollama/models.rb +2 -0
  218. data/lib/ruby_llm/providers/ollama.rb +11 -0
  219. data/lib/ruby_llm/providers/openai/capabilities.rb +3 -1
  220. data/lib/ruby_llm/providers/openai.rb +14 -0
  221. data/lib/ruby_llm/providers/openrouter/chat.rb +20 -24
  222. data/lib/ruby_llm/providers/openrouter/embeddings.rb +2 -0
  223. data/lib/ruby_llm/providers/openrouter/media.rb +5 -1
  224. data/lib/ruby_llm/providers/openrouter/streaming.rb +4 -1
  225. data/lib/ruby_llm/providers/openrouter/videos.rb +10 -5
  226. data/lib/ruby_llm/providers/openrouter.rb +4 -0
  227. data/lib/ruby_llm/providers/perplexity/chat.rb +4 -0
  228. data/lib/ruby_llm/providers/perplexity/media.rb +5 -11
  229. data/lib/ruby_llm/providers/perplexity/models.rb +8 -4
  230. data/lib/ruby_llm/providers/perplexity.rb +11 -0
  231. data/lib/ruby_llm/providers/typesafe.rb +32 -0
  232. data/lib/ruby_llm/providers/vertexai/credentials.rb +63 -0
  233. data/lib/ruby_llm/providers/vertexai/gemini.rb +2 -2
  234. data/lib/ruby_llm/providers/vertexai/transcription.rb +1 -0
  235. data/lib/ruby_llm/providers/vertexai.rb +27 -28
  236. data/lib/ruby_llm/providers/xai/chat.rb +6 -0
  237. data/lib/ruby_llm/providers/xai/images.rb +9 -10
  238. data/lib/ruby_llm/providers/xai/responses.rb +10 -8
  239. data/lib/ruby_llm/providers/xai/videos.rb +11 -9
  240. data/lib/ruby_llm/providers/xai.rb +4 -0
  241. data/lib/ruby_llm/railtie.rb +11 -0
  242. data/lib/ruby_llm/request_shape/part.rb +117 -0
  243. data/lib/ruby_llm/request_shape/problem.rb +63 -0
  244. data/lib/ruby_llm/request_shape/tool_round.rb +64 -0
  245. data/lib/ruby_llm/request_shape/turn.rb +54 -0
  246. data/lib/ruby_llm/request_shape.rb +160 -0
  247. data/lib/ruby_llm/rerank.rb +9 -2
  248. data/lib/ruby_llm/research_job.rb +21 -5
  249. data/lib/ruby_llm/score.rb +42 -0
  250. data/lib/ruby_llm/search_results.rb +2 -2
  251. data/lib/ruby_llm/server_tool_call.rb +10 -2
  252. data/lib/ruby_llm/speech.rb +8 -1
  253. data/lib/ruby_llm/support/cancellation.rb +39 -0
  254. data/lib/ruby_llm/support/inspectable.rb +3 -2
  255. data/lib/ruby_llm/support/instrumentation.rb +38 -1
  256. data/lib/ruby_llm/support/process_cache.rb +60 -0
  257. data/lib/ruby_llm/support/progress_reporter.rb +33 -0
  258. data/lib/ruby_llm/thinking.rb +2 -1
  259. data/lib/ruby_llm/tokens.rb +57 -14
  260. data/lib/ruby_llm/tool.rb +50 -3
  261. data/lib/ruby_llm/tool_call.rb +17 -2
  262. data/lib/ruby_llm/transcription.rb +10 -3
  263. data/lib/ruby_llm/transport/connection.rb +127 -44
  264. data/lib/ruby_llm/transport/error_middleware.rb +90 -24
  265. data/lib/ruby_llm/transport/event_stream_parser.rb +121 -0
  266. data/lib/ruby_llm/transport/json_response.rb +33 -0
  267. data/lib/ruby_llm/transport/signing_middleware.rb +29 -0
  268. data/lib/ruby_llm/version.rb +1 -1
  269. data/lib/ruby_llm/video.rb +5 -3
  270. data/lib/ruby_llm/video_job.rb +48 -5
  271. data/lib/ruby_llm.rb +106 -5
  272. data/lib/tasks/ruby_llm/evaluations.rb +43 -0
  273. data/lib/tasks/ruby_llm.rake +6 -9
  274. data/skills/rubyllm/SKILL.md +33 -12
  275. metadata +101 -37
  276. data/lib/generators/ruby_llm/upgrade/legacy_content_sql.rb +0 -34
  277. data/lib/generators/ruby_llm/upgrade/online_copy_migration/data.rb +0 -341
  278. data/lib/generators/ruby_llm/upgrade/online_copy_migration/journal.rb +0 -171
  279. data/lib/generators/ruby_llm/upgrade/online_copy_migration/verification.rb +0 -197
  280. data/lib/generators/ruby_llm/upgrade/online_copy_migration.rb +0 -174
  281. data/lib/generators/ruby_llm/upgrade/templates/backfill_v2_data.rb.tt +0 -468
  282. data/lib/generators/ruby_llm/upgrade/templates/cleanup_v2_upgrade.rb.tt +0 -101
  283. data/lib/generators/ruby_llm/upgrade/templates/finish_v2_upgrade.rb.tt +0 -247
  284. data/lib/generators/ruby_llm/upgrade/templates/prepare_v2_upgrade.rb.tt +0 -662
  285. data/lib/generators/ruby_llm/upgrade/templates/ruby_llm_upgrade.rb.tt +0 -251
  286. data/lib/generators/ruby_llm/upgrade/templates/upgrade_initializer.rb.tt +0 -13
  287. data/lib/generators/ruby_llm/upgrade/upgrade_migration.rb +0 -359
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 42dd9dfdb910f8e5df4eb6114ed59586a13e4a51797173d3f3bdb13b6d59c28b
4
- data.tar.gz: fa4e0aa374c915ab5e97146bcacb438fc2c3fca0c029ed51c1aea6c934769da8
3
+ metadata.gz: 8000141c827d6b8f899bcf2ec5a23153ecf83f8196fcd35cee7af8de2f9e58e4
4
+ data.tar.gz: '069b322ad8df77cd46ee2dd9b72ec02578281ff9e6c96b349e46aff911ee85b8'
5
5
  SHA512:
6
- metadata.gz: 3e5489b6fdd071de5c4281c9cc8eff9d345dd10876cb6773144873d2e58c3ec400109d208706d4c04c12d93328f5864f839907521fd751accc29aa9a5a753602
7
- data.tar.gz: 19c33af936f0626262ad3e469c955b98aee8599b15fe088ba745e31584f91ebbf166cabc981bef4bef526f6e6c96800aebe07dc133ca732313ca58598aa6273f
6
+ metadata.gz: 6d651244192242fc5f06cf8126932659b46dec1228ed97e3a9876dd7ee581f8972d42fbaca209698b1c42e1704ebb766ca07b450cfd7651d9192f8d46d3f8c42
7
+ data.tar.gz: 9511e7a1c10a8b73cebbefb59a26be1fd7dcec10c369f57d60b7a348d180a8037700826400d9da41b630d190b940aa73ba7c5cbdb7fbd0f4b6cffbcbe563d732
data/README.md CHANGED
@@ -9,7 +9,7 @@
9
9
 
10
10
  <p>RubyLLM is the Ruby-native AI framework. Work with models, tools, and agents through one consistent API, in plain Ruby or Rails.</p>
11
11
 
12
- [Website](https://rubyllm.com/) · [Getting Started](https://rubyllm.com/getting-started/) · [What's New in 2.0](https://rubyllm.com/whats-new-in-2-0/)
12
+ [Website](https://rubyllm.com/) · [Getting Started](https://rubyllm.com/getting-started/) · [What's New in 2.1](https://rubyllm.com/whats-new-in-2-1/)
13
13
 
14
14
  Battle tested at [<picture><source media="(prefers-color-scheme: dark)" srcset="https://chatwithwork.com/logotype-dark.svg"><img src="https://chatwithwork.com/logotype.svg" alt="Chat with Work" height="30" align="absmiddle"></picture>](https://chatwithwork.com) - *Fully private work AI*
15
15
 
@@ -21,12 +21,14 @@ Battle tested at [<picture><source media="(prefers-color-scheme: dark)" srcset="
21
21
  <a href="https://trendshift.io/repositories/13640" target="_blank"><img src="https://trendshift.io/api/badge/repositories/13640" alt="crmne%2Fruby_llm | Trendshift" style="width: 250px; height: 55px;" width="250" height="55"/></a>
22
22
  </div>
23
23
 
24
+ https://github.com/user-attachments/assets/519fcb20-ab8c-4af3-9185-0953b3ba7b81
25
+
24
26
  > [!NOTE]
25
27
  > Using RubyLLM? [Share your story](https://tally.so/r/3Na02p)! Takes 5 minutes.
26
28
 
27
29
  ---
28
30
 
29
- ## 17 providers. One Ruby API.
31
+ ## 19 providers. One Ruby API.
30
32
 
31
33
  Build with the models you want. Move between hosted and local providers without rewriting your application, or connect an OpenAI-compatible endpoint.
32
34
 
@@ -34,7 +36,7 @@ Build with the models you want. Move between hosted and local providers without
34
36
 
35
37
  ## Start with one line. Add files, tools, and agents
36
38
 
37
- These examples use **2.0.0**. Follow [Getting Started](https://rubyllm.com/getting-started/) to install it and configure the providers you want to try. For 1.x, use the [1.x docs](https://rubyllm.com/v1/).
39
+ These examples use **2.1.1**. Follow [Getting Started](https://rubyllm.com/getting-started/) to install it and configure the providers you want to try. For 1.x, use the [1.x docs](https://rubyllm.com/v1/).
38
40
 
39
41
  ```ruby
40
42
  # Just ask
@@ -111,6 +113,15 @@ puts document.markdown
111
113
  RubyLLM.moderate("Some user-generated content").flagged?
112
114
  ```
113
115
 
116
+ ```ruby
117
+ # Ask a typed question about your data
118
+ class Urgency < RubyLLM::Judge
119
+ probability :urgent, "Does this need attention today?"
120
+ end
121
+
122
+ Urgency.judge("Please refund my duplicate charge today.").urgent.probability
123
+ ```
124
+
114
125
  ```ruby
115
126
  # Let AI use your code
116
127
  class Weather < RubyLLM::Tool
@@ -150,9 +161,55 @@ response = chat.with_schema(ProductSchema).ask "Analyze this product", with: "pr
150
161
  response.parsed
151
162
  ```
152
163
 
164
+ ## Evaluate your agents
165
+
166
+ Keep questions and expected answers in a dataset. RubyLLM checks correctness by default. Run the same evaluation from Ruby, RSpec, Minitest, or Rake.
167
+
168
+ Evaluations are available on `main`. Follow the [development installation guide](https://rubyllm.com/next/getting-started/) to try them.
169
+
170
+ `app/agents/support_agent.rb`
171
+
172
+ ```ruby
173
+ class SupportAgent < RubyLLM::Agent
174
+ instructions "Accept returns of unopened items within 30 days."
175
+ end
176
+ ```
177
+
178
+ `app/evals/support_evaluation.rb`
179
+
180
+ ```ruby
181
+ class SupportEvaluation < RubyLLM::Evaluation
182
+ def perform(input)
183
+ SupportAgent.new.ask(input)
184
+ end
185
+ end
186
+ ```
187
+
188
+ `app/evals/support_evaluation.yml`
189
+
190
+ ```yaml
191
+ cases:
192
+ - name: within_return_window
193
+ inputs: Can I return an unopened item after 14 days?
194
+ expected_output: Yes, unopened items can be returned within 30 days.
195
+ - name: past_return_window
196
+ inputs: Can I return an unopened item after 45 days?
197
+ expected_output: No, the 30-day return window has passed.
198
+ ```
199
+
200
+ Run the evaluation:
201
+
202
+ ```ruby
203
+ report = SupportEvaluation.run
204
+ report.passed?
205
+ report.cost.total
206
+ ```
207
+
208
+ The model evaluator checks meaning, so answers can use different wording. Declare your own criteria, add Ruby assertions, or choose a configured evaluator agent or judgment model. [Read the evaluation guides](https://rubyllm.com/next/evaluations/).
209
+
153
210
  ## A complete AI framework for Ruby
154
211
 
155
- Agents, workflows, RAG, images, audio, and video. Built in, with usage tracking and Rails integration to bring them into your app.
212
+ Agents, evaluations, workflows, RAG, images, audio, and video. Built in, with usage tracking and Rails integration to bring them into your app.
156
213
 
157
214
  * **Chat:** Conversational AI with `RubyLLM.chat`
158
215
  * **Vision:** Analyze images and videos
@@ -164,11 +221,13 @@ Agents, workflows, RAG, images, audio, and video. Built in, with usage tracking
164
221
  * **Embeddings:** Generate embeddings with `RubyLLM.embed`
165
222
  * **Reranking:** Order retrieval candidates by relevance with `RubyLLM.rerank`
166
223
  * **Moderation:** Content flags, categories, and scores with `RubyLLM.moderate`
224
+ * **Judgments:** Probabilities, choices, and scores with `RubyLLM::Judge` and `RubyLLM.judge`
167
225
  * **Tools:** Let AI call your Ruby methods
168
226
  * **Tool approval:** Park a run until a human approves with `requires_approval`
169
227
  * **The agentic loop:** Drive it yourself with `ask_later`, `step`, and `complete?`
170
228
  * **Provider tools:** Web search, code execution, and MCP connectors with `with_provider_tools`
171
229
  * **Agents:** Reusable assistants with `RubyLLM::Agent`
230
+ * **Evaluations (main):** Reusable datasets, model criteria, and Ruby assertions with [`RubyLLM::Evaluation`](https://rubyllm.com/next/evaluations/)
172
231
  * **Prompt templates:** ERB prompts in `app/prompts`, rendered with `RubyLLM.render_prompt`
173
232
  * **Workflows:** Correlate multi-agent runs in your telemetry with `RubyLLM.workflow`
174
233
  * **Structured output:** Define a Ruby schema and read the result with `response.parsed`
@@ -185,14 +244,14 @@ Agents, workflows, RAG, images, audio, and video. Built in, with usage tracking
185
244
  * **Batches:** Provider-side batch processing with provider-specific discounts via `RubyLLM.batch`
186
245
  * **Compaction:** Let providers condense long conversations with `with_compaction`
187
246
  * **Token counting:** Count a request before you send it with `count_tokens`
188
- * **Providers:** OpenAI, Azure, xAI, Anthropic, Gemini, VertexAI, Bedrock, Cohere, DeepSeek, Mistral, Ollama, Ollama Cloud, OpenRouter, Perplexity, GPUStack, ElevenLabs, Deepgram, and any OpenAI-compatible API
247
+ * **Providers:** OpenAI, Azure, xAI, Anthropic, Gemini, VertexAI, Bedrock, Cohere, DeepSeek, Hetzner, Mistral, Ollama, Ollama Cloud, OpenRouter, Perplexity, GPUStack, ElevenLabs, Deepgram, TypeSafe, and any OpenAI-compatible API
189
248
 
190
249
  ## Installation
191
250
 
192
- Install RubyLLM 2.0:
251
+ Install RubyLLM 2.1:
193
252
 
194
253
  ```bash
195
- bundle add ruby_llm --version 2.0.0
254
+ bundle add ruby_llm --version 2.1.1
196
255
  ```
197
256
 
198
257
  Configure a provider in your script, or in `config/initializers/ruby_llm.rb` in Rails:
@@ -205,7 +264,7 @@ RubyLLM.configure do |config|
205
264
  end
206
265
  ```
207
266
 
208
- Configure the other providers used by the examples as needed: Gemini for files, xAI for video, Mistral for OCR, and Cohere for reranking. [Getting Started](https://rubyllm.com/getting-started/) shows each setup beside its example. If your app uses 1.16, follow the [upgrade guide](https://rubyllm.com/upgrading/) before deploying 2.0.
267
+ Configure the other providers used by the examples as needed: Gemini for files, xAI for video, Mistral for OCR, and Cohere for reranking. [Getting Started](https://rubyllm.com/getting-started/) shows each setup beside its example. Upgrading an existing app? Follow the [upgrade guide](https://rubyllm.com/upgrading/).
209
268
 
210
269
  ## Feels at home in Rails
211
270
 
@@ -149,15 +149,11 @@ module RubyLLM
149
149
  end
150
150
 
151
151
  def postgresql?
152
- ::ActiveRecord::Base.connection.adapter_name.downcase.include?('postgresql')
153
- rescue StandardError
154
- false
152
+ adapter_built_on?('ActiveRecord::ConnectionAdapters::PostgreSQLAdapter')
155
153
  end
156
154
 
157
155
  def mysql?
158
- ::ActiveRecord::Base.connection.adapter_name.downcase.include?('mysql')
159
- rescue StandardError
160
- false
156
+ adapter_built_on?('ActiveRecord::ConnectionAdapters::AbstractMysqlAdapter')
161
157
  end
162
158
 
163
159
  def table_exists?(table_name)
@@ -202,8 +198,74 @@ module RubyLLM
202
198
  end
203
199
  end
204
200
 
201
+ module ClassMethods # :nodoc:
202
+ def start(given_args = ARGV, config = {})
203
+ super(GeneratorHelpers.reorder_arguments(given_args, class_options), config)
204
+ end
205
+ end
206
+
207
+ def self.included(base)
208
+ base.extend(ClassMethods)
209
+ end
210
+
211
+ def self.reorder_arguments(args, options)
212
+ mappings = []
213
+ switches = []
214
+ index = 0
215
+
216
+ while index < args.length
217
+ argument = args[index]
218
+ return mappings + switches + args[index..] if argument == '--'
219
+
220
+ unless argument.is_a?(String) && argument.start_with?('-')
221
+ mappings << argument
222
+ index += 1
223
+ next
224
+ end
225
+
226
+ switches << argument
227
+ index += 1
228
+ next if argument.include?('=')
229
+
230
+ value = args[index]
231
+ next unless takes_value?(options, argument, value)
232
+
233
+ switches << value
234
+ index += 1
235
+ end
236
+
237
+ mappings + switches
238
+ end
239
+
240
+ def self.takes_value?(options, argument, value)
241
+ return false if value.nil? || value.to_s.start_with?('-')
242
+
243
+ option = option_for(options, argument)
244
+ return false unless option
245
+ return %w[true TRUE t T false FALSE f F].include?(value) if option.type == :boolean
246
+
247
+ !argument.tr('_', '-').match?(/\A--(?:no|skip)-/)
248
+ end
249
+
250
+ def self.option_for(options, argument)
251
+ return option_for(options, "-#{argument[-1]}") if argument.match?(/\A-[a-z]{2,}\z/i)
252
+
253
+ name = argument.sub(/\A--?/, '').tr('-', '_')
254
+ options[name.to_sym] || options[name] ||
255
+ options.values.find { |candidate| candidate.aliases.include?(argument) } ||
256
+ options[name.sub(/\A(?:no|skip)_/, '').to_sym] || options[name.sub(/\A(?:no|skip)_/, '')]
257
+ end
258
+
205
259
  private
206
260
 
261
+ # Compares class names because referencing an adapter constant loads
262
+ # its database driver, which the application may not have installed.
263
+ def adapter_built_on?(adapter_class_name)
264
+ ::ActiveRecord::Base.connection.class.ancestors.any? { |ancestor| ancestor.name == adapter_class_name }
265
+ rescue StandardError
266
+ false
267
+ end
268
+
207
269
  def add_association_params(params, default_assoc, table_name, model_name,
208
270
  owner_table:, owner_model_name:, plural: false)
209
271
  assoc = plural ? table_name.to_sym : table_name.singularize.to_sym
@@ -5,6 +5,7 @@ class <%= create_migration_class_name(message_table_name) %> < ActiveRecord::Mig
5
5
  t.string :role, null: false
6
6
  t.text :content
7
7
  t.boolean :cache_until_here, null: false, default: false
8
+ t.string :cache_ttl
8
9
  t.text :thinking_text
9
10
  t.text :thinking_signature
10
11
  <% if postgresql? %>
@@ -47,10 +47,16 @@ class <%= create_migration_class_name('ruby_llm_records') %> < ActiveRecord::Mig
47
47
  t.boolean :remote, default: false, null: false
48
48
  <% if postgresql? %>
49
49
  t.jsonb :arguments, default: {}
50
+ t.jsonb :mcp_state
51
+ t.jsonb :mcp_result
50
52
  <% elsif mysql? %>
51
53
  t.json :arguments
54
+ t.json :mcp_state
55
+ t.json :mcp_result
52
56
  <% else %>
53
57
  t.json :arguments, default: {}
58
+ t.json :mcp_state
59
+ t.json :mcp_result
54
60
  <% end %>
55
61
  t.timestamps
56
62
 
@@ -60,9 +66,19 @@ class <%= create_migration_class_name('ruby_llm_records') %> < ActiveRecord::Mig
60
66
  t.index :name
61
67
  end
62
68
 
69
+ create_table :ruby_llm_mcp_credentials, id: :<%= reference_type %> do |t|
70
+ t.references :owner, polymorphic: true, type: :<%= reference_type %>
71
+ t.string :key, null: false
72
+ t.text :data
73
+ t.timestamps
74
+
75
+ t.index :key, unique: true
76
+ end
77
+
63
78
  create_table :ruby_llm_usages, id: :<%= reference_type %> do |t|
64
- t.references :chat, polymorphic: true, null: false, type: :<%= reference_type %>, index: false
79
+ t.references :chat, polymorphic: true, type: :<%= reference_type %>, index: false
65
80
  t.references :message, polymorphic: true, type: :<%= reference_type %>, index: false
81
+ t.references :owner, polymorphic: true, type: :<%= reference_type %>, index: false
66
82
  t.string :operation, null: false
67
83
  t.string :provider, null: false
68
84
  t.string :model, null: false
@@ -72,6 +88,11 @@ class <%= create_migration_class_name('ruby_llm_records') %> < ActiveRecord::Mig
72
88
  t.integer :cache_read_tokens
73
89
  t.integer :cache_write_tokens
74
90
  t.integer :thinking_tokens
91
+ <% if postgresql? %>
92
+ t.jsonb :server_tool_use
93
+ <% else %>
94
+ t.json :server_tool_use
95
+ <% end %>
75
96
  t.decimal :input_cost, precision: 16, scale: 10
76
97
  t.decimal :output_cost, precision: 16, scale: 10
77
98
  t.decimal :cache_read_cost, precision: 16, scale: 10
@@ -82,9 +103,10 @@ class <%= create_migration_class_name('ruby_llm_records') %> < ActiveRecord::Mig
82
103
 
83
104
  t.index [ :chat_type, :chat_id ]
84
105
  t.index [ :message_type, :message_id ]
106
+ t.index [ :owner_type, :owner_id ]
85
107
  t.index :status
86
- t.check_constraint "operation IN (<%= usage_operations_sql %>)"
87
- t.check_constraint "status IN (<%= usage_statuses_sql %>)"
108
+ t.check_constraint "operation<%= '::text' if postgresql? %> IN (<%= usage_operations_sql %>)"
109
+ t.check_constraint "status<%= '::text' if postgresql? %> IN (<%= usage_statuses_sql %>)"
88
110
  end
89
111
 
90
112
  create_table :ruby_llm_batches, id: :<%= reference_type %> do |t|
@@ -113,5 +135,16 @@ class <%= create_migration_class_name('ruby_llm_records') %> < ActiveRecord::Mig
113
135
  t.index [ :provider, :provider_batch_id ], unique: true
114
136
  t.index :status
115
137
  end
138
+
139
+ create_table :ruby_llm_provider_files, id: :<%= reference_type %> do |t|
140
+ t.string :blob_key, null: false
141
+ t.string :provider, null: false
142
+ t.string :account, null: false
143
+ t.text :file_id, null: false
144
+ t.datetime :expires_at
145
+ t.timestamps
146
+
147
+ t.index [ :blob_key, :provider, :account ], unique: true, name: "index_ruby_llm_provider_files_uniqueness"
148
+ end
116
149
  end
117
150
  end
@@ -11,6 +11,9 @@ module RubyLLM
11
11
  # Generates first-party providers and standalone provider gems.
12
12
  class Scaffold # :nodoc: all
13
13
  SUPPORTED_MODES = %w[core gem].freeze
14
+ HASH_ENTRY_LINE = /^\s+'[^']+' => '[^']+',?$/
15
+ ENV_LINE = /^#?\s*[A-Z][A-Z0-9_]*=/
16
+ SPEC_CONFIG_LINE = /^ {6}config\.[a-z0-9_]+ = /
14
17
  SUPPORTED_DIALECTS = %w[chat_completions responses anthropic gemini converse ollama].freeze
15
18
  TEMPLATE_ROOT = File.expand_path('templates', __dir__)
16
19
 
@@ -157,7 +160,7 @@ module RubyLLM
157
160
  path = file_path('lib/ruby_llm.rb')
158
161
  return unless File.exist?(path)
159
162
 
160
- insert_sorted_line(path, " '#{slug}' => '#{class_name}',", /^\s+'[^']+' => '[^']+',?$/)
163
+ insert_sorted_entry(path, " '#{slug}' => '#{class_name}'")
161
164
  insert_sorted_line(
162
165
  path,
163
166
  "RubyLLM::Provider.register :#{slug}, RubyLLM::Providers::#{class_name}",
@@ -169,8 +172,8 @@ module RubyLLM
169
172
  path = file_path('.env.example')
170
173
  return unless File.exist?(path)
171
174
 
172
- append_unique_line(path, "#{api_key_env}=$(op read \"op://RubyLLM/#{class_name}/credential\")")
173
- append_unique_line(path, "#{api_base_env}=#{api_base}")
175
+ insert_sorted_line(path, "#{api_key_env}=$(op read \"op://RubyLLM/#{class_name}/credential\")", ENV_LINE)
176
+ insert_sorted_line(path, "#{api_base_env}=#{api_base}", ENV_LINE)
174
177
  end
175
178
 
176
179
  def update_core_spec_configuration
@@ -178,9 +181,9 @@ module RubyLLM
178
181
  return unless File.exist?(path)
179
182
 
180
183
  insert_sorted_line(path, " config.#{api_base_config} = ENV.fetch('#{api_base_env}', '#{api_base}')",
181
- /^\s+config\.[a-z0-9_]+_api_base =/)
184
+ SPEC_CONFIG_LINE)
182
185
  insert_sorted_line(path, " config.#{api_key_config} = ENV.fetch('#{api_key_env}', 'test')",
183
- /^\s+config\.[a-z0-9_]+_api_key =/)
186
+ SPEC_CONFIG_LINE)
184
187
  end
185
188
 
186
189
  def update_core_vcr_configuration
@@ -196,7 +199,7 @@ module RubyLLM
196
199
  path = file_path('lib/ruby_llm/models.rb')
197
200
  return unless File.exist?(path)
198
201
 
199
- insert_before_first(path, " '#{models_dev_provider}' => '#{slug}',", /^ \}\.freeze$/)
202
+ insert_sorted_entry(path, " '#{models_dev_provider}' => '#{slug}'")
200
203
  end
201
204
 
202
205
  def write_template(template, relative_path, executable: false)
@@ -218,38 +221,45 @@ module RubyLLM
218
221
  record(existed ? :updated : :written, relative_path)
219
222
  end
220
223
 
221
- def append_unique_line(path, line)
222
- content = File.exist?(path) ? File.read(path) : ''
223
- return record(:skipped, relative_path(path)) if content.include?(line)
224
-
225
- FileUtils.mkdir_p(File.dirname(path))
226
- File.write(path, "#{content.chomp}\n#{line}\n")
227
- record(:updated, relative_path(path))
228
- end
229
-
230
224
  def insert_sorted_line(path, line, matcher)
231
225
  content = File.read(path)
232
226
  return record(:skipped, relative_path(path)) if content.include?(line)
233
227
 
234
228
  lines = content.lines
235
- indexes = lines.each_index.select { |index| lines[index].match?(matcher) }
236
- insert_at = indexes.find { |index| line < lines[index].chomp } || indexes.last&.+(1) || lines.length
237
- lines.insert(insert_at, "#{line}\n")
229
+ lines.insert(sorted_line_index(lines, line, matcher), "#{line}\n")
238
230
  File.write(path, lines.join)
239
231
  record(:updated, relative_path(path))
240
232
  end
241
233
 
242
- def insert_before_first(path, line, matcher)
234
+ # Lands after the last smaller line, above any comment that describes the next one.
235
+ def sorted_line_index(lines, line, matcher)
236
+ indexes = lines.each_index.select { |index| lines[index].match?(matcher) }
237
+ index = indexes.find { |candidate| line < lines[candidate].chomp } || indexes.last&.+(1) || lines.length
238
+ index -= 1 while index.positive? && lines[index - 1].strip.start_with?('#')
239
+ index
240
+ end
241
+
242
+ # Inserts a `'key' => 'value'` entry into the first multiline hash literal of
243
+ # such entries, keeping it sorted and every entry but the last comma-terminated.
244
+ def insert_sorted_entry(path, entry)
243
245
  content = File.read(path)
244
- return record(:skipped, relative_path(path)) if content.include?(line)
246
+ return record(:skipped, relative_path(path)) if content.match?(/^#{Regexp.escape(entry)},?$/)
245
247
 
246
248
  lines = content.lines
247
- insert_at = lines.index { |candidate| candidate.match?(matcher) } || lines.length
248
- lines.insert(insert_at, "#{line}\n")
249
+ block = hash_entry_block(lines)
250
+ insert_at = block.find { |index| entry < lines[index].chomp.delete_suffix(',') } || (block.last + 1)
251
+ lines.insert(insert_at, "#{entry}\n")
252
+ hash_entry_block(lines)[0...-1].each { |index| lines[index] = "#{lines[index].chomp.delete_suffix(',')},\n" }
249
253
  File.write(path, lines.join)
250
254
  record(:updated, relative_path(path))
251
255
  end
252
256
 
257
+ def hash_entry_block(lines)
258
+ first = lines.index { |line| line.match?(HASH_ENTRY_LINE) }
259
+ last = (first...lines.length).take_while { |index| lines[index].match?(HASH_ENTRY_LINE) }.last
260
+ (first..last).to_a
261
+ end
262
+
253
263
  def record(action, path)
254
264
  @result.public_send(action) << path
255
265
  @result.actions << [action, path]
@@ -16,7 +16,7 @@ jobs:
16
16
  strategy:
17
17
  fail-fast: false
18
18
  matrix:
19
- ruby-version: ["3.1", "3.2", "3.3", "3.4", "4.0"]
19
+ ruby-version: ["3.2", "3.3", "3.4", "4.0"]
20
20
  steps:
21
21
  - uses: actions/checkout@v6
22
22
 
@@ -26,7 +26,3 @@ jobs:
26
26
  bundler-cache: true
27
27
 
28
28
  - run: bundle exec rake
29
- if: matrix.ruby-version != '3.1'
30
-
31
- - run: bundle exec rake rubocop flay spec
32
- if: matrix.ruby-version == '3.1'
@@ -5,7 +5,7 @@ source 'https://rubygems.org'
5
5
  gemspec
6
6
 
7
7
  group :development do
8
- gem 'archspec' if RUBY_ENGINE == 'ruby' && Gem::Version.new(RUBY_VERSION) >= Gem::Version.new('3.2')
8
+ gem 'archspec' if RUBY_ENGINE == 'ruby'
9
9
  gem 'bundler', '>= 2.0'
10
10
  gem 'dotenv'
11
11
  gem 'flay'
@@ -10,7 +10,7 @@ Gem::Specification.new do |spec|
10
10
  spec.description = 'Adds <%= class_name %> provider support to RubyLLM.'
11
11
  spec.homepage = 'https://github.com/<%= github_owner %>/<%= gem_name %>'
12
12
  spec.license = 'MIT'
13
- spec.required_ruby_version = '>= 3.1'
13
+ spec.required_ruby_version = '>= 3.2'
14
14
 
15
15
  spec.metadata['homepage_uri'] = spec.homepage
16
16
  spec.metadata['source_code_uri'] = spec.homepage
@@ -5,7 +5,7 @@ plugins:
5
5
 
6
6
  AllCops:
7
7
  NewCops: enable
8
- TargetRubyVersion: 3.1
8
+ TargetRubyVersion: 3.2
9
9
  Exclude:
10
10
  - Archspec.rb
11
11
  - vendor/**/*
@@ -0,0 +1,78 @@
1
+ class <%= 'upgrade_ruby_llm_to_2_1'.camelize %> < ActiveRecord::Migration<%= migration_version %>
2
+ def change
3
+ unless table_exists?(:ruby_llm_mcp_credentials)
4
+ create_table :ruby_llm_mcp_credentials, id: :<%= reference_type %> do |t|
5
+ t.references :owner, polymorphic: true, type: :<%= reference_type %>
6
+ t.string :key, null: false
7
+ t.text :data
8
+ t.timestamps
9
+
10
+ t.index :key, unique: true
11
+ end
12
+ end
13
+
14
+ unless column_exists?(:ruby_llm_tool_calls, :mcp_state)
15
+ if column_exists?(:ruby_llm_tool_calls, :pending_input)
16
+ rename_column :ruby_llm_tool_calls, :pending_input, :mcp_state
17
+ else
18
+ <% if postgresql? -%>
19
+ add_column :ruby_llm_tool_calls, :mcp_state, :jsonb
20
+ <% else -%>
21
+ add_column :ruby_llm_tool_calls, :mcp_state, :json
22
+ <% end -%>
23
+ end
24
+ end
25
+
26
+ unless column_exists?(:ruby_llm_tool_calls, :mcp_result)
27
+ <% if postgresql? -%>
28
+ add_column :ruby_llm_tool_calls, :mcp_result, :jsonb
29
+ <% else -%>
30
+ add_column :ruby_llm_tool_calls, :mcp_result, :json
31
+ <% end -%>
32
+ end
33
+
34
+ unless column_exists?(:ruby_llm_usages, :server_tool_use)
35
+ <% if postgresql? -%>
36
+ add_column :ruby_llm_usages, :server_tool_use, :jsonb
37
+ <% else -%>
38
+ add_column :ruby_llm_usages, :server_tool_use, :json
39
+ <% end -%>
40
+ end
41
+
42
+ if columns(:ruby_llm_usages).any? { |column| column.name == "chat_id" && !column.null }
43
+ change_column_null :ruby_llm_usages, :chat_type, true
44
+ change_column_null :ruby_llm_usages, :chat_id, true
45
+ end
46
+
47
+ unless column_exists?(:ruby_llm_usages, :owner_id)
48
+ add_reference :ruby_llm_usages, :owner, polymorphic: true, type: :<%= reference_type %>, index: true
49
+ end
50
+
51
+ reversible do |direction|
52
+ direction.up do
53
+ operations = check_constraints(:ruby_llm_usages).find { |constraint| constraint.expression.include?("operation") }
54
+ if operations && [<%= usage_operations_sql %>].any? { |operation| !operations.expression.include?("'#{operation}'") }
55
+ remove_check_constraint :ruby_llm_usages, name: operations.name
56
+ add_check_constraint :ruby_llm_usages, "operation<%= '::text' if postgresql? %> IN (<%= usage_operations_sql %>)"
57
+ end
58
+ end
59
+ end
60
+
61
+ unless column_exists?(:<%= message_table_name %>, :cache_ttl)
62
+ add_column :<%= message_table_name %>, :cache_ttl, :string
63
+ end
64
+
65
+ unless table_exists?(:ruby_llm_provider_files)
66
+ create_table :ruby_llm_provider_files, id: :<%= reference_type %> do |t|
67
+ t.string :blob_key, null: false
68
+ t.string :provider, null: false
69
+ t.string :account, null: false
70
+ t.text :file_id, null: false
71
+ t.datetime :expires_at
72
+ t.timestamps
73
+
74
+ t.index [ :blob_key, :provider, :account ], unique: true, name: "index_ruby_llm_provider_files_uniqueness"
75
+ end
76
+ end
77
+ end
78
+ end