raif 1.2.2 → 1.4.0

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 (167) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +6 -5
  3. data/app/assets/builds/raif.css +4 -1
  4. data/app/assets/builds/raif_admin.css +13 -1
  5. data/app/assets/javascript/raif/controllers/conversations_controller.js +1 -1
  6. data/app/assets/stylesheets/raif/admin/conversation.scss +16 -0
  7. data/app/assets/stylesheets/raif/conversations.scss +3 -0
  8. data/app/assets/stylesheets/raif.scss +2 -1
  9. data/app/controllers/raif/admin/application_controller.rb +16 -0
  10. data/app/controllers/raif/admin/configs_controller.rb +94 -0
  11. data/app/controllers/raif/admin/model_completions_controller.rb +18 -1
  12. data/app/controllers/raif/admin/model_tool_invocations_controller.rb +7 -1
  13. data/app/controllers/raif/admin/stats/model_tool_invocations_controller.rb +21 -0
  14. data/app/controllers/raif/admin/stats/tasks_controller.rb +15 -6
  15. data/app/controllers/raif/admin/stats_controller.rb +32 -3
  16. data/app/controllers/raif/conversation_entries_controller.rb +1 -0
  17. data/app/controllers/raif/conversations_controller.rb +10 -2
  18. data/app/jobs/raif/conversation_entry_job.rb +8 -6
  19. data/app/models/raif/admin/task_stat.rb +7 -0
  20. data/app/models/raif/agent.rb +63 -2
  21. data/app/models/raif/agents/native_tool_calling_agent.rb +101 -56
  22. data/app/models/raif/application_record.rb +18 -0
  23. data/app/models/raif/concerns/agent_inference_stats.rb +35 -0
  24. data/app/models/raif/concerns/has_llm.rb +1 -1
  25. data/app/models/raif/concerns/json_schema_definition.rb +40 -5
  26. data/app/models/raif/concerns/llms/anthropic/message_formatting.rb +28 -0
  27. data/app/models/raif/concerns/llms/anthropic/response_tool_calls.rb +24 -0
  28. data/app/models/raif/concerns/llms/anthropic/tool_formatting.rb +4 -0
  29. data/app/models/raif/concerns/llms/bedrock/message_formatting.rb +36 -0
  30. data/app/models/raif/concerns/llms/bedrock/response_tool_calls.rb +26 -0
  31. data/app/models/raif/concerns/llms/bedrock/tool_formatting.rb +4 -0
  32. data/app/models/raif/concerns/llms/google/message_formatting.rb +109 -0
  33. data/app/models/raif/concerns/llms/google/response_tool_calls.rb +32 -0
  34. data/app/models/raif/concerns/llms/google/tool_formatting.rb +72 -0
  35. data/app/models/raif/concerns/llms/message_formatting.rb +11 -5
  36. data/app/models/raif/concerns/llms/open_ai/json_schema_validation.rb +3 -3
  37. data/app/models/raif/concerns/llms/open_ai_completions/message_formatting.rb +22 -0
  38. data/app/models/raif/concerns/llms/open_ai_completions/response_tool_calls.rb +22 -0
  39. data/app/models/raif/concerns/llms/open_ai_completions/tool_formatting.rb +4 -0
  40. data/app/models/raif/concerns/llms/open_ai_responses/message_formatting.rb +17 -0
  41. data/app/models/raif/concerns/llms/open_ai_responses/response_tool_calls.rb +26 -0
  42. data/app/models/raif/concerns/llms/open_ai_responses/tool_formatting.rb +4 -0
  43. data/app/models/raif/concerns/run_with.rb +127 -0
  44. data/app/models/raif/conversation.rb +96 -9
  45. data/app/models/raif/conversation_entry.rb +37 -8
  46. data/app/models/raif/embedding_model.rb +2 -1
  47. data/app/models/raif/embedding_models/open_ai.rb +1 -1
  48. data/app/models/raif/llm.rb +28 -3
  49. data/app/models/raif/llms/anthropic.rb +7 -19
  50. data/app/models/raif/llms/bedrock.rb +6 -20
  51. data/app/models/raif/llms/google.rb +140 -0
  52. data/app/models/raif/llms/open_ai_base.rb +19 -5
  53. data/app/models/raif/llms/open_ai_completions.rb +6 -11
  54. data/app/models/raif/llms/open_ai_responses.rb +6 -16
  55. data/app/models/raif/llms/open_router.rb +10 -14
  56. data/app/models/raif/model_completion.rb +61 -0
  57. data/app/models/raif/model_tool.rb +10 -2
  58. data/app/models/raif/model_tool_invocation.rb +38 -6
  59. data/app/models/raif/model_tools/agent_final_answer.rb +2 -7
  60. data/app/models/raif/model_tools/provider_managed/code_execution.rb +4 -0
  61. data/app/models/raif/model_tools/provider_managed/image_generation.rb +4 -0
  62. data/app/models/raif/model_tools/provider_managed/web_search.rb +4 -0
  63. data/app/models/raif/streaming_responses/google.rb +71 -0
  64. data/app/models/raif/task.rb +74 -18
  65. data/app/models/raif/user_tool_invocation.rb +19 -0
  66. data/app/views/layouts/raif/admin.html.erb +12 -1
  67. data/app/views/raif/admin/agents/_agent.html.erb +8 -0
  68. data/app/views/raif/admin/agents/_conversation_message.html.erb +28 -6
  69. data/app/views/raif/admin/agents/index.html.erb +2 -0
  70. data/app/views/raif/admin/agents/show.html.erb +46 -1
  71. data/app/views/raif/admin/configs/show.html.erb +117 -0
  72. data/app/views/raif/admin/conversations/_conversation_entry.html.erb +29 -34
  73. data/app/views/raif/admin/conversations/show.html.erb +2 -0
  74. data/app/views/raif/admin/model_completions/_model_completion.html.erb +9 -0
  75. data/app/views/raif/admin/model_completions/index.html.erb +26 -0
  76. data/app/views/raif/admin/model_completions/show.html.erb +124 -61
  77. data/app/views/raif/admin/model_tool_invocations/index.html.erb +22 -1
  78. data/app/views/raif/admin/model_tools/_list.html.erb +16 -0
  79. data/app/views/raif/admin/model_tools/_model_tool.html.erb +36 -0
  80. data/app/views/raif/admin/stats/_stats_tile.html.erb +34 -0
  81. data/app/views/raif/admin/stats/index.html.erb +71 -88
  82. data/app/views/raif/admin/stats/model_tool_invocations/index.html.erb +43 -0
  83. data/app/views/raif/admin/stats/tasks/index.html.erb +20 -6
  84. data/app/views/raif/admin/tasks/index.html.erb +6 -1
  85. data/app/views/raif/admin/tasks/show.html.erb +36 -3
  86. data/app/views/raif/conversation_entries/_form.html.erb +4 -1
  87. data/app/views/raif/conversations/_conversation.html.erb +10 -0
  88. data/app/views/raif/conversations/_entry_processed.turbo_stream.erb +12 -0
  89. data/app/views/raif/conversations/_full_conversation.html.erb +3 -6
  90. data/app/views/raif/conversations/_initial_chat_message.html.erb +5 -0
  91. data/app/views/raif/conversations/index.html.erb +23 -0
  92. data/config/locales/admin.en.yml +33 -1
  93. data/config/locales/en.yml +41 -4
  94. data/config/routes.rb +2 -0
  95. data/db/migrate/20250804013843_add_task_run_args_to_raif_tasks.rb +13 -0
  96. data/db/migrate/20250811171150_make_raif_task_creator_optional.rb +8 -0
  97. data/db/migrate/20250904194456_add_generating_entry_response_to_raif_conversations.rb +7 -0
  98. data/db/migrate/20250911125234_add_source_to_raif_tasks.rb +7 -0
  99. data/db/migrate/20251020005853_add_source_to_raif_agents.rb +7 -0
  100. data/db/migrate/20251020011346_rename_task_run_args_to_run_with.rb +7 -0
  101. data/db/migrate/20251020011405_add_run_with_to_raif_agents.rb +13 -0
  102. data/db/migrate/20251024160119_add_llm_messages_max_length_to_raif_conversations.rb +14 -0
  103. data/db/migrate/20251124185033_add_provider_tool_call_id_to_raif_model_tool_invocations.rb +7 -0
  104. data/db/migrate/20251128202941_add_tool_choice_to_raif_model_completions.rb +7 -0
  105. data/db/migrate/20260118144846_add_source_to_raif_conversations.rb +7 -0
  106. data/db/migrate/20260119000000_add_failure_tracking_to_raif_model_completions.rb +10 -0
  107. data/db/migrate/20260119000001_add_completed_at_to_raif_model_completions.rb +8 -0
  108. data/db/migrate/20260119000002_add_started_at_to_raif_model_completions.rb +8 -0
  109. data/exe/raif +7 -0
  110. data/lib/generators/raif/agent/agent_generator.rb +22 -7
  111. data/lib/generators/raif/agent/templates/agent.rb.tt +20 -24
  112. data/lib/generators/raif/agent/templates/agent_eval_set.rb.tt +48 -0
  113. data/lib/generators/raif/agent/templates/application_agent.rb.tt +1 -3
  114. data/lib/generators/raif/base_generator.rb +19 -0
  115. data/lib/generators/raif/conversation/conversation_generator.rb +21 -2
  116. data/lib/generators/raif/conversation/templates/application_conversation.rb.tt +0 -2
  117. data/lib/generators/raif/conversation/templates/conversation.rb.tt +34 -32
  118. data/lib/generators/raif/conversation/templates/conversation_eval_set.rb.tt +70 -0
  119. data/lib/generators/raif/eval_set/eval_set_generator.rb +28 -0
  120. data/lib/generators/raif/eval_set/templates/eval_set.rb.tt +21 -0
  121. data/lib/generators/raif/evals/setup/setup_generator.rb +47 -0
  122. data/lib/generators/raif/install/install_generator.rb +15 -0
  123. data/lib/generators/raif/install/templates/initializer.rb +89 -10
  124. data/lib/generators/raif/model_tool/model_tool_generator.rb +5 -5
  125. data/lib/generators/raif/model_tool/templates/model_tool.rb.tt +78 -78
  126. data/lib/generators/raif/model_tool/templates/model_tool_invocation_partial.html.erb.tt +1 -1
  127. data/lib/generators/raif/task/task_generator.rb +22 -3
  128. data/lib/generators/raif/task/templates/application_task.rb.tt +0 -2
  129. data/lib/generators/raif/task/templates/task.rb.tt +55 -59
  130. data/lib/generators/raif/task/templates/task_eval_set.rb.tt +54 -0
  131. data/lib/raif/cli/base.rb +39 -0
  132. data/lib/raif/cli/evals.rb +47 -0
  133. data/lib/raif/cli/evals_setup.rb +27 -0
  134. data/lib/raif/cli.rb +67 -0
  135. data/lib/raif/configuration.rb +57 -8
  136. data/lib/raif/engine.rb +8 -0
  137. data/lib/raif/errors/instance_dependent_schema_error.rb +8 -0
  138. data/lib/raif/errors/streaming_error.rb +6 -3
  139. data/lib/raif/errors.rb +1 -0
  140. data/lib/raif/evals/eval.rb +30 -0
  141. data/lib/raif/evals/eval_set.rb +111 -0
  142. data/lib/raif/evals/eval_sets/expectations.rb +53 -0
  143. data/lib/raif/evals/eval_sets/llm_judge_expectations.rb +255 -0
  144. data/lib/raif/evals/expectation_result.rb +39 -0
  145. data/lib/raif/evals/llm_judge.rb +32 -0
  146. data/lib/raif/evals/llm_judges/binary.rb +94 -0
  147. data/lib/raif/evals/llm_judges/comparative.rb +89 -0
  148. data/lib/raif/evals/llm_judges/scored.rb +63 -0
  149. data/lib/raif/evals/llm_judges/summarization.rb +166 -0
  150. data/lib/raif/evals/run.rb +202 -0
  151. data/lib/raif/evals/scoring_rubric.rb +174 -0
  152. data/lib/raif/evals.rb +26 -0
  153. data/lib/raif/json_schema_builder.rb +14 -0
  154. data/lib/raif/llm_registry.rb +218 -15
  155. data/lib/raif/messages.rb +180 -0
  156. data/lib/raif/migration_checker.rb +3 -3
  157. data/lib/raif/utils/colors.rb +23 -0
  158. data/lib/raif/utils.rb +1 -0
  159. data/lib/raif/version.rb +1 -1
  160. data/lib/raif.rb +13 -0
  161. data/lib/tasks/annotate_rb.rake +10 -0
  162. data/spec/support/current_temperature_test_tool.rb +34 -0
  163. data/spec/support/rspec_helpers.rb +8 -8
  164. data/spec/support/test_conversation.rb +1 -1
  165. metadata +77 -10
  166. data/app/models/raif/agents/re_act_agent.rb +0 -127
  167. data/app/models/raif/agents/re_act_step.rb +0 -33
@@ -41,6 +41,41 @@ module Raif
41
41
 
42
42
  def self.default_llms
43
43
  open_ai_models = [
44
+ {
45
+ key: :open_ai_gpt_5_2,
46
+ api_name: "gpt-5.2",
47
+ input_token_cost: 1.75 / 1_000_000,
48
+ output_token_cost: 14.0 / 1_000_000,
49
+ model_provider_settings: { supports_temperature: false },
50
+ },
51
+ {
52
+ key: :open_ai_gpt_5_1,
53
+ api_name: "gpt-5.1",
54
+ input_token_cost: 1.25 / 1_000_000,
55
+ output_token_cost: 10.0 / 1_000_000,
56
+ model_provider_settings: { supports_temperature: false },
57
+ },
58
+ {
59
+ key: :open_ai_gpt_5,
60
+ api_name: "gpt-5",
61
+ input_token_cost: 1.25 / 1_000_000,
62
+ output_token_cost: 10.0 / 1_000_000,
63
+ model_provider_settings: { supports_temperature: false },
64
+ },
65
+ {
66
+ key: :open_ai_gpt_5_mini,
67
+ api_name: "gpt-5-mini",
68
+ input_token_cost: 0.25 / 1_000_000,
69
+ output_token_cost: 2.0 / 1_000_000,
70
+ model_provider_settings: { supports_temperature: false },
71
+ },
72
+ {
73
+ key: :open_ai_gpt_5_nano,
74
+ api_name: "gpt-5-nano",
75
+ input_token_cost: 0.05 / 1_000_000,
76
+ output_token_cost: 0.4 / 1_000_000,
77
+ model_provider_settings: { supports_temperature: false },
78
+ },
44
79
  {
45
80
  key: :open_ai_gpt_4o_mini,
46
81
  api_name: "gpt-4o-mini",
@@ -151,11 +186,33 @@ module Raif
151
186
  Raif::Llms::OpenAiResponses => open_ai_responses_models,
152
187
  Raif::Llms::Anthropic => [
153
188
  {
154
- key: :anthropic_claude_4_sonnet,
155
- api_name: "claude-sonnet-4-20250514",
189
+ key: :anthropic_claude_4_5_sonnet,
190
+ api_name: "claude-sonnet-4-5",
156
191
  input_token_cost: 3.0 / 1_000_000,
157
192
  output_token_cost: 15.0 / 1_000_000,
158
- max_completion_tokens: 8192,
193
+ max_completion_tokens: 64_000,
194
+ supported_provider_managed_tools: [
195
+ Raif::ModelTools::ProviderManaged::WebSearch,
196
+ Raif::ModelTools::ProviderManaged::CodeExecution
197
+ ]
198
+ },
199
+ {
200
+ key: :anthropic_claude_4_5_haiku,
201
+ api_name: "claude-haiku-4-5",
202
+ input_token_cost: 1.0 / 1_000_000,
203
+ output_token_cost: 5.0 / 1_000_000,
204
+ max_completion_tokens: 64_000,
205
+ supported_provider_managed_tools: [
206
+ Raif::ModelTools::ProviderManaged::WebSearch,
207
+ Raif::ModelTools::ProviderManaged::CodeExecution
208
+ ]
209
+ },
210
+ {
211
+ key: :anthropic_claude_4_1_opus,
212
+ api_name: "claude-opus-4-1",
213
+ input_token_cost: 15.0 / 1_000_000,
214
+ output_token_cost: 75.0 / 1_000_000,
215
+ max_completion_tokens: 32_000,
159
216
  supported_provider_managed_tools: [
160
217
  Raif::ModelTools::ProviderManaged::WebSearch,
161
218
  Raif::ModelTools::ProviderManaged::CodeExecution
@@ -172,6 +229,17 @@ module Raif
172
229
  Raif::ModelTools::ProviderManaged::CodeExecution
173
230
  ]
174
231
  },
232
+ {
233
+ key: :anthropic_claude_4_sonnet,
234
+ api_name: "claude-sonnet-4-20250514",
235
+ input_token_cost: 3.0 / 1_000_000,
236
+ output_token_cost: 15.0 / 1_000_000,
237
+ max_completion_tokens: 8192,
238
+ supported_provider_managed_tools: [
239
+ Raif::ModelTools::ProviderManaged::WebSearch,
240
+ Raif::ModelTools::ProviderManaged::CodeExecution
241
+ ]
242
+ },
175
243
  {
176
244
  key: :anthropic_claude_3_7_sonnet,
177
245
  api_name: "claude-3-7-sonnet-latest",
@@ -214,6 +282,27 @@ module Raif
214
282
  },
215
283
  ],
216
284
  Raif::Llms::Bedrock => [
285
+ {
286
+ key: :bedrock_claude_4_5_sonnet,
287
+ api_name: "anthropic.claude-sonnet-4-5-20250929-v1:0",
288
+ input_token_cost: 0.003 / 1000,
289
+ output_token_cost: 0.015 / 1000,
290
+ max_completion_tokens: 64_000
291
+ },
292
+ {
293
+ key: :bedrock_claude_4_5_haiku,
294
+ api_name: "anthropic.claude-haiku-4-5-20251001-v1:0",
295
+ input_token_cost: 0.001 / 1000,
296
+ output_token_cost: 0.005 / 1000,
297
+ max_completion_tokens: 64_000
298
+ },
299
+ {
300
+ key: :bedrock_claude_4_1_opus,
301
+ api_name: "anthropic.claude-opus-4-1-20250805-v1:0",
302
+ input_token_cost: 0.015 / 1000,
303
+ output_token_cost: 0.075 / 1000,
304
+ max_completion_tokens: 32_000
305
+ },
217
306
  {
218
307
  key: :bedrock_claude_4_sonnet,
219
308
  api_name: "anthropic.claude-sonnet-4-20250514-v1:0",
@@ -229,15 +318,15 @@ module Raif
229
318
  max_completion_tokens: 8192
230
319
  },
231
320
  {
232
- key: :bedrock_claude_3_5_sonnet,
233
- api_name: "anthropic.claude-3-5-sonnet-20241022-v2:0",
321
+ key: :bedrock_claude_3_7_sonnet,
322
+ api_name: "anthropic.claude-3-7-sonnet-20250219-v1:0",
234
323
  input_token_cost: 0.003 / 1000,
235
324
  output_token_cost: 0.015 / 1000,
236
325
  max_completion_tokens: 8192
237
326
  },
238
327
  {
239
- key: :bedrock_claude_3_7_sonnet,
240
- api_name: "anthropic.claude-3-7-sonnet-20250219-v1:0",
328
+ key: :bedrock_claude_3_5_sonnet,
329
+ api_name: "anthropic.claude-3-5-sonnet-20241022-v2:0",
241
330
  input_token_cost: 0.003 / 1000,
242
331
  output_token_cost: 0.015 / 1000,
243
332
  max_completion_tokens: 8192
@@ -285,6 +374,60 @@ module Raif
285
374
  input_token_cost: 3.0 / 1_000_000,
286
375
  output_token_cost: 15.0 / 1_000_000,
287
376
  },
377
+ {
378
+ key: :open_router_deepseek_chat_v3,
379
+ api_name: "deepseek/deepseek-chat-v3-0324",
380
+ input_token_cost: 0.27 / 1_000_000,
381
+ output_token_cost: 1.1 / 1_000_000,
382
+ },
383
+ {
384
+ key: :open_router_deepseek_v3_1,
385
+ api_name: "deepseek/deepseek-chat-v3.1",
386
+ input_token_cost: 0.25 / 1_000_000,
387
+ output_token_cost: 1.0 / 1_000_000,
388
+ },
389
+ {
390
+ key: :open_router_gemini_2_0_flash,
391
+ api_name: "google/gemini-2.0-flash-001",
392
+ input_token_cost: 0.1 / 1_000_000,
393
+ output_token_cost: 0.4 / 1_000_000,
394
+ },
395
+ {
396
+ key: :open_router_gemini_2_5_flash,
397
+ api_name: "google/gemini-2.5-flash",
398
+ input_token_cost: 0.3 / 1_000_000,
399
+ output_token_cost: 2.5 / 1_000_000,
400
+ },
401
+ {
402
+ key: :open_router_gemini_2_5_pro,
403
+ api_name: "google/gemini-2.5-pro",
404
+ input_token_cost: 1.25 / 1_000_000,
405
+ output_token_cost: 10.0 / 1_000_000,
406
+ },
407
+ {
408
+ key: :open_router_gemini_3_pro_preview,
409
+ api_name: "google/gemini-3-pro-preview",
410
+ input_token_cost: 2.0 / 1_000_000,
411
+ output_token_cost: 12.0 / 1_000_000,
412
+ },
413
+ {
414
+ key: :open_router_grok_4,
415
+ api_name: "x-ai/grok-4",
416
+ input_token_cost: 3.0 / 1_000_000,
417
+ output_token_cost: 15.0 / 1_000_000,
418
+ },
419
+ {
420
+ key: :open_router_grok_4_1_fast,
421
+ api_name: "x-ai/grok-4.1-fast",
422
+ input_token_cost: 0.2 / 1_000_000,
423
+ output_token_cost: 0.5 / 1_000_000,
424
+ },
425
+ {
426
+ key: :open_router_kimi_k2_thinking,
427
+ api_name: "moonshotai/kimi-k2-thinking",
428
+ input_token_cost: 0.45 / 1_000_000,
429
+ output_token_cost: 2.35 / 1_000_000,
430
+ },
288
431
  {
289
432
  key: :open_router_llama_3_3_70b_instruct,
290
433
  api_name: "meta-llama/llama-3.3-70b-instruct",
@@ -310,16 +453,76 @@ module Raif
310
453
  output_token_cost: 0.30 / 1_000_000,
311
454
  },
312
455
  {
313
- key: :open_router_gemini_2_0_flash,
314
- api_name: "google/gemini-2.0-flash-001",
315
- input_token_cost: 0.1 / 1_000_000,
316
- output_token_cost: 0.4 / 1_000_000,
456
+ key: :open_router_minimax_m2,
457
+ api_name: "minimax/minimax-m2",
458
+ input_token_cost: 0.255 / 1_000_000,
459
+ output_token_cost: 1.02 / 1_000_000,
317
460
  },
318
461
  {
319
- key: :open_router_deepseek_chat_v3,
320
- api_name: "deepseek/deepseek-chat-v3-0324",
321
- input_token_cost: 0.27 / 1_000_000,
322
- output_token_cost: 1.1 / 1_000_000,
462
+ key: :open_router_mistral_large_3_2512,
463
+ api_name: "mistralai/mistral-large-2512",
464
+ input_token_cost: 0.50 / 1_000_000,
465
+ output_token_cost: 1.5 / 1_000_000,
466
+ },
467
+ {
468
+ key: :open_router_mistral_small_3_2_24b,
469
+ api_name: "mistralai/mistral-small-3.2-24b-instruct",
470
+ input_token_cost: 0.06 / 1_000_000,
471
+ output_token_cost: 0.18 / 1_000_000,
472
+ },
473
+ {
474
+ key: :open_router_open_ai_gpt_oss_120b,
475
+ api_name: "gpt-oss-120b",
476
+ input_token_cost: 0.15 / 1_000_000,
477
+ output_token_cost: 0.6 / 1_000_000,
478
+ },
479
+ {
480
+ key: :open_router_open_ai_gpt_oss_20b,
481
+ api_name: "gpt-oss-20b",
482
+ input_token_cost: 0.05 / 1_000_000,
483
+ output_token_cost: 0.2 / 1_000_000,
484
+ },
485
+ ],
486
+ Raif::Llms::Google => [
487
+ {
488
+ key: :google_gemini_3_0_pro,
489
+ api_name: "gemini-3-pro-preview",
490
+ input_token_cost: 2.0 / 1_000_000,
491
+ output_token_cost: 12.0 / 1_000_000,
492
+ supported_provider_managed_tools: [
493
+ Raif::ModelTools::ProviderManaged::WebSearch,
494
+ Raif::ModelTools::ProviderManaged::CodeExecution
495
+ ]
496
+ },
497
+ {
498
+ key: :google_gemini_3_0_flash,
499
+ api_name: "gemini-3-flash-preview",
500
+ input_token_cost: 0.5 / 1_000_000,
501
+ output_token_cost: 3.0 / 1_000_000,
502
+ supported_provider_managed_tools: [
503
+ Raif::ModelTools::ProviderManaged::WebSearch,
504
+ Raif::ModelTools::ProviderManaged::CodeExecution
505
+ ]
506
+ },
507
+ {
508
+ key: :google_gemini_2_5_pro,
509
+ api_name: "gemini-2.5-pro-preview-06-05",
510
+ input_token_cost: 1.25 / 1_000_000,
511
+ output_token_cost: 10.0 / 1_000_000,
512
+ supported_provider_managed_tools: [
513
+ Raif::ModelTools::ProviderManaged::WebSearch,
514
+ Raif::ModelTools::ProviderManaged::CodeExecution
515
+ ]
516
+ },
517
+ {
518
+ key: :google_gemini_2_5_flash,
519
+ api_name: "gemini-2.5-flash",
520
+ input_token_cost: 0.3 / 1_000_000,
521
+ output_token_cost: 2.5 / 1_000_000,
522
+ supported_provider_managed_tools: [
523
+ Raif::ModelTools::ProviderManaged::WebSearch,
524
+ Raif::ModelTools::ProviderManaged::CodeExecution
525
+ ]
323
526
  },
324
527
  ]
325
528
  }
@@ -0,0 +1,180 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ # Message types for agent conversation_history and conversation llm_messages.
5
+ #
6
+ # These classes provide a structured API for creating messages that get stored
7
+ # as JSONB and passed to LLM providers. Each class has:
8
+ # - Named parameters for initialization
9
+ # - `to_h` for converting to hash format (for storage/API calls)
10
+ # - `from_h` class method for deserializing from stored hashes
11
+ #
12
+ # @example Creating messages
13
+ # message = Raif::Messages::ToolCall.new(
14
+ # name: "wikipedia_search",
15
+ # arguments: { query: "Ruby" },
16
+ # provider_tool_call_id: "call_123"
17
+ # )
18
+ # conversation_history << message.to_h
19
+ #
20
+ # @example Deserializing stored messages
21
+ # messages = Raif::Messages.from_array(agent.conversation_history)
22
+ # messages.each do |msg|
23
+ # case msg
24
+ # when Raif::Messages::ToolCall
25
+ # puts "Tool: #{msg.name}"
26
+ # when Raif::Messages::UserMessage
27
+ # puts "User: #{msg.content}"
28
+ # end
29
+ # end
30
+ module Messages
31
+ # User role message
32
+ class UserMessage
33
+ attr_reader :content
34
+
35
+ # @param content [String] The user's message content
36
+ def initialize(content:)
37
+ @content = content
38
+ end
39
+
40
+ # @return [Hash] Hash representation for JSONB storage and LLM APIs
41
+ def to_h
42
+ { "role" => "user", "content" => content }
43
+ end
44
+
45
+ # Deserialize from a hash
46
+ # @param hash [Hash] A hash with "content" key
47
+ # @return [UserMessage]
48
+ def self.from_h(hash)
49
+ new(content: hash["content"])
50
+ end
51
+ end
52
+
53
+ # Assistant role message
54
+ class AssistantMessage
55
+ attr_reader :content
56
+
57
+ # @param content [String] The assistant's message content
58
+ def initialize(content:)
59
+ @content = content
60
+ end
61
+
62
+ # @return [Hash] Hash representation for JSONB storage and LLM APIs
63
+ def to_h
64
+ { "role" => "assistant", "content" => content }
65
+ end
66
+
67
+ # Deserialize from a hash
68
+ # @param hash [Hash] A hash with "content" key
69
+ # @return [AssistantMessage]
70
+ def self.from_h(hash)
71
+ new(content: hash["content"])
72
+ end
73
+ end
74
+
75
+ # Tool invocation request from the assistant
76
+ class ToolCall
77
+ attr_reader :provider_tool_call_id, :name, :arguments, :assistant_message, :provider_metadata
78
+
79
+ # @param name [String] The tool name (snake_case)
80
+ # @param arguments [Hash] The arguments passed to the tool
81
+ # @param provider_tool_call_id [String, nil] Provider-assigned ID for the tool call
82
+ # @param assistant_message [String, nil] Optional assistant message accompanying the tool call
83
+ # @param provider_metadata [Hash, nil] Provider-specific metadata (e.g., Google's thoughtSignature)
84
+ def initialize(name:, arguments:, provider_tool_call_id: nil, assistant_message: nil, provider_metadata: nil)
85
+ @provider_tool_call_id = provider_tool_call_id
86
+ @name = name
87
+ @arguments = arguments
88
+ @assistant_message = assistant_message
89
+ @provider_metadata = provider_metadata
90
+ end
91
+
92
+ # @return [Hash] Hash representation for JSONB storage and LLM APIs
93
+ def to_h
94
+ {
95
+ "type" => "tool_call",
96
+ "provider_tool_call_id" => provider_tool_call_id,
97
+ "name" => name,
98
+ "arguments" => arguments,
99
+ "assistant_message" => assistant_message,
100
+ "provider_metadata" => provider_metadata
101
+ }.compact
102
+ end
103
+
104
+ # Deserialize from a hash
105
+ # @param hash [Hash] A hash with tool call fields
106
+ # @return [ToolCall]
107
+ def self.from_h(hash)
108
+ new(
109
+ name: hash["name"],
110
+ arguments: hash["arguments"],
111
+ provider_tool_call_id: hash["provider_tool_call_id"],
112
+ assistant_message: hash["assistant_message"],
113
+ provider_metadata: hash["provider_metadata"]
114
+ )
115
+ end
116
+ end
117
+
118
+ # Result of a tool invocation
119
+ class ToolCallResult
120
+ attr_reader :provider_tool_call_id, :name, :result
121
+
122
+ # @param result [Hash, String] The result returned by the tool
123
+ # @param provider_tool_call_id [String, nil] Provider-assigned ID matching the tool call
124
+ # @param name [String, nil] The tool name (required by some providers like Google)
125
+ def initialize(result:, provider_tool_call_id: nil, name: nil)
126
+ @provider_tool_call_id = provider_tool_call_id
127
+ @name = name
128
+ @result = result
129
+ end
130
+
131
+ # @return [Hash] Hash representation for JSONB storage and LLM APIs
132
+ def to_h
133
+ {
134
+ "type" => "tool_call_result",
135
+ "provider_tool_call_id" => provider_tool_call_id,
136
+ "name" => name,
137
+ "result" => result
138
+ }.compact
139
+ end
140
+
141
+ # Deserialize from a hash
142
+ # @param hash [Hash] A hash with tool call result fields
143
+ # @return [ToolCallResult]
144
+ def self.from_h(hash)
145
+ new(
146
+ provider_tool_call_id: hash["provider_tool_call_id"],
147
+ name: hash["name"],
148
+ result: hash["result"]
149
+ )
150
+ end
151
+ end
152
+
153
+ class << self
154
+ # Deserialize a single message hash into the appropriate message class
155
+ # @param hash [Hash] A message hash with either "role" or "type" key
156
+ # @return [UserMessage, AssistantMessage, ToolCall, ToolCallResult]
157
+ # @raise [ArgumentError] if the hash doesn't match a known message type
158
+ def from_h(hash)
159
+ if hash["type"] == "tool_call"
160
+ ToolCall.from_h(hash)
161
+ elsif hash["type"] == "tool_call_result"
162
+ ToolCallResult.from_h(hash)
163
+ elsif hash["role"] == "user"
164
+ UserMessage.from_h(hash)
165
+ elsif hash["role"] == "assistant"
166
+ AssistantMessage.from_h(hash)
167
+ else
168
+ raise ArgumentError, "Unknown message type: #{hash.inspect}"
169
+ end
170
+ end
171
+
172
+ # Deserialize an array of message hashes
173
+ # @param messages [Array<Hash>] Array of message hashes
174
+ # @return [Array<UserMessage, AssistantMessage, ToolCall, ToolCallResult>]
175
+ def from_array(messages)
176
+ messages.map { |msg| from_h(msg) }
177
+ end
178
+ end
179
+ end
180
+ end
@@ -53,8 +53,7 @@ module Raif
53
53
  end
54
54
 
55
55
  def build_warning_message(uninstalled_migration_names)
56
- <<~WARNING
57
- \e[33m
56
+ msg = <<~WARNING
58
57
  ⚠️ RAIF MIGRATION WARNING ⚠️
59
58
 
60
59
  The following Raif migrations have not been run in your application:
@@ -66,8 +65,9 @@ module Raif
66
65
  rails raif:install:migrations
67
66
  rails db:migrate
68
67
 
69
- \e[0m
70
68
  WARNING
69
+
70
+ Raif::Utils::Colors.yellow(msg)
71
71
  end
72
72
  end
73
73
  end
@@ -0,0 +1,23 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ module Utils
5
+ module Colors
6
+ def self.green(text)
7
+ "\e[32m#{text}\e[0m"
8
+ end
9
+
10
+ def self.red(text)
11
+ "\e[31m#{text}\e[0m"
12
+ end
13
+
14
+ def self.yellow(text)
15
+ "\e[33m#{text}\e[0m"
16
+ end
17
+
18
+ def self.blue(text)
19
+ "\e[34m#{text}\e[0m"
20
+ end
21
+ end
22
+ end
23
+ end
data/lib/raif/utils.rb CHANGED
@@ -4,4 +4,5 @@ module Raif::Utils
4
4
  require "raif/utils/readable_content_extractor"
5
5
  require "raif/utils/html_to_markdown_converter"
6
6
  require "raif/utils/html_fragment_processor"
7
+ require "raif/utils/colors"
7
8
  end
data/lib/raif/version.rb CHANGED
@@ -1,5 +1,5 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module Raif
4
- VERSION = "1.2.2"
4
+ VERSION = "1.4.0"
5
5
  end
data/lib/raif.rb CHANGED
@@ -10,6 +10,7 @@ require "raif/llm_registry"
10
10
  require "raif/embedding_model_registry"
11
11
  require "raif/json_schema_builder"
12
12
  require "raif/migration_checker"
13
+ require "raif/messages"
13
14
 
14
15
  require "faraday"
15
16
  require "event_stream_parser"
@@ -37,4 +38,16 @@ module Raif
37
38
  def self.logger
38
39
  @logger ||= Rails.logger
39
40
  end
41
+
42
+ def self.running_evals?
43
+ ENV["RAIF_RUNNING_EVALS"] == "true"
44
+ end
45
+
46
+ def self.default_request_options
47
+ {
48
+ open_timeout: config.request_open_timeout,
49
+ read_timeout: config.request_read_timeout,
50
+ write_timeout: config.request_write_timeout
51
+ }.compact
52
+ end
40
53
  end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ # This rake task was added by annotate_rb gem.
4
+
5
+ # Can set `ANNOTATERB_SKIP_ON_DB_TASKS` to be anything to skip this
6
+ if Rails.env.development? && ENV["ANNOTATERB_SKIP_ON_DB_TASKS"].nil?
7
+ require "annotate_rb"
8
+
9
+ AnnotateRb::Core.load_rake_tasks
10
+ end
@@ -0,0 +1,34 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Raif::ModelTools::CurrentTemperatureTestTool < Raif::ModelTool
4
+ tool_arguments_schema do
5
+ string :zip_code, description: "The zip code to get the current temperature for"
6
+ end
7
+
8
+ tool_description do
9
+ "A tool to get the current temperature for a given zip code"
10
+ end
11
+
12
+ class << self
13
+ def process_invocation(tool_invocation)
14
+ tool_invocation.update!(
15
+ result: {
16
+ temperature: 72
17
+ }
18
+ )
19
+
20
+ tool_invocation.result
21
+ end
22
+
23
+ def triggers_observation_to_model?
24
+ true
25
+ end
26
+
27
+ def observation_for_invocation(tool_invocation)
28
+ zip_code = tool_invocation.tool_arguments["zip_code"]
29
+ temperature = tool_invocation.result["temperature"]
30
+
31
+ "The current temperature for zip code #{zip_code} is #{temperature} degrees Fahrenheit."
32
+ end
33
+ end
34
+ end
@@ -3,11 +3,11 @@
3
3
  module Raif
4
4
  module RspecHelpers
5
5
 
6
- def stubbed_llm(llm_model_key, &block)
6
+ def stubbed_llm(llm_model_key, source_instance, &block)
7
7
  test_llm = Raif.llm(llm_model_key.to_sym)
8
8
 
9
9
  allow(test_llm).to receive(:perform_model_completion!) do |model_completion|
10
- result = block.call(model_completion.messages, model_completion)
10
+ result = block.call(model_completion.messages, model_completion, source_instance)
11
11
  model_completion.raw_response = result if result.is_a?(String)
12
12
  model_completion.completion_tokens = rand(100..2000)
13
13
  model_completion.prompt_tokens = rand(100..2000)
@@ -24,10 +24,10 @@ module Raif
24
24
  allow(Raif.config).to receive(:llm_api_requests_enabled){ true }
25
25
 
26
26
  if task.is_a?(Raif::Task)
27
- allow(task).to receive(:llm){ stubbed_llm(task.llm_model_key, &block) }
27
+ allow(task).to receive(:llm){ stubbed_llm(task.llm_model_key, task, &block) }
28
28
  else
29
29
  allow_any_instance_of(task).to receive(:llm) do |task_instance|
30
- stubbed_llm(task_instance.llm_model_key, &block)
30
+ stubbed_llm(task_instance.llm_model_key, task_instance, &block)
31
31
  end
32
32
  end
33
33
  end
@@ -36,10 +36,10 @@ module Raif
36
36
  allow(Raif.config).to receive(:llm_api_requests_enabled){ true }
37
37
 
38
38
  if conversation.is_a?(Raif::Conversation)
39
- allow(conversation).to receive(:llm){ stubbed_llm(conversation.llm_model_key, &block) }
39
+ allow(conversation).to receive(:llm){ stubbed_llm(conversation.llm_model_key, conversation, &block) }
40
40
  else
41
41
  allow_any_instance_of(conversation).to receive(:llm) do |conversation_instance|
42
- stubbed_llm(conversation_instance.llm_model_key, &block)
42
+ stubbed_llm(conversation_instance.llm_model_key, conversation_instance, &block)
43
43
  end
44
44
  end
45
45
  end
@@ -48,10 +48,10 @@ module Raif
48
48
  allow(Raif.config).to receive(:llm_api_requests_enabled){ true }
49
49
 
50
50
  if agent.is_a?(Raif::Agent)
51
- allow(agent).to receive(:llm){ stubbed_llm(agent.llm_model_key, &block) }
51
+ allow(agent).to receive(:llm){ stubbed_llm(agent.llm_model_key, agent, &block) }
52
52
  else
53
53
  allow_any_instance_of(agent).to receive(:llm) do |agent_instance|
54
- stubbed_llm(agent_instance.llm_model_key, &block)
54
+ stubbed_llm(agent_instance.llm_model_key, agent_instance, &block)
55
55
  end
56
56
  end
57
57
  end
@@ -12,7 +12,7 @@ class Raif::TestConversation < Raif::Conversation
12
12
  end
13
13
 
14
14
  def process_model_response_message(message:, entry:)
15
- message.gsub("jerk", "[REDACTED]")
15
+ message&.gsub("jerk", "[REDACTED]")
16
16
  end
17
17
 
18
18
  end