raif 1.3.0 → 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 (130) 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/json_schema_definition.rb +40 -5
  25. data/app/models/raif/concerns/llms/anthropic/message_formatting.rb +28 -0
  26. data/app/models/raif/concerns/llms/anthropic/response_tool_calls.rb +24 -0
  27. data/app/models/raif/concerns/llms/anthropic/tool_formatting.rb +4 -0
  28. data/app/models/raif/concerns/llms/bedrock/message_formatting.rb +36 -0
  29. data/app/models/raif/concerns/llms/bedrock/response_tool_calls.rb +26 -0
  30. data/app/models/raif/concerns/llms/bedrock/tool_formatting.rb +4 -0
  31. data/app/models/raif/concerns/llms/google/message_formatting.rb +109 -0
  32. data/app/models/raif/concerns/llms/google/response_tool_calls.rb +32 -0
  33. data/app/models/raif/concerns/llms/google/tool_formatting.rb +72 -0
  34. data/app/models/raif/concerns/llms/message_formatting.rb +11 -5
  35. data/app/models/raif/concerns/llms/open_ai/json_schema_validation.rb +3 -3
  36. data/app/models/raif/concerns/llms/open_ai_completions/message_formatting.rb +22 -0
  37. data/app/models/raif/concerns/llms/open_ai_completions/response_tool_calls.rb +22 -0
  38. data/app/models/raif/concerns/llms/open_ai_completions/tool_formatting.rb +4 -0
  39. data/app/models/raif/concerns/llms/open_ai_responses/message_formatting.rb +17 -0
  40. data/app/models/raif/concerns/llms/open_ai_responses/response_tool_calls.rb +26 -0
  41. data/app/models/raif/concerns/llms/open_ai_responses/tool_formatting.rb +4 -0
  42. data/app/models/raif/concerns/run_with.rb +127 -0
  43. data/app/models/raif/conversation.rb +91 -8
  44. data/app/models/raif/conversation_entry.rb +32 -1
  45. data/app/models/raif/embedding_model.rb +2 -1
  46. data/app/models/raif/embedding_models/open_ai.rb +1 -1
  47. data/app/models/raif/llm.rb +27 -2
  48. data/app/models/raif/llms/anthropic.rb +7 -19
  49. data/app/models/raif/llms/bedrock.rb +6 -20
  50. data/app/models/raif/llms/google.rb +140 -0
  51. data/app/models/raif/llms/open_ai_base.rb +19 -5
  52. data/app/models/raif/llms/open_ai_completions.rb +6 -11
  53. data/app/models/raif/llms/open_ai_responses.rb +6 -16
  54. data/app/models/raif/llms/open_router.rb +7 -13
  55. data/app/models/raif/model_completion.rb +61 -0
  56. data/app/models/raif/model_tool.rb +10 -2
  57. data/app/models/raif/model_tool_invocation.rb +38 -6
  58. data/app/models/raif/model_tools/agent_final_answer.rb +2 -7
  59. data/app/models/raif/model_tools/provider_managed/code_execution.rb +4 -0
  60. data/app/models/raif/model_tools/provider_managed/image_generation.rb +4 -0
  61. data/app/models/raif/model_tools/provider_managed/web_search.rb +4 -0
  62. data/app/models/raif/streaming_responses/google.rb +71 -0
  63. data/app/models/raif/task.rb +55 -12
  64. data/app/models/raif/user_tool_invocation.rb +19 -0
  65. data/app/views/layouts/raif/admin.html.erb +12 -1
  66. data/app/views/raif/admin/agents/_agent.html.erb +8 -0
  67. data/app/views/raif/admin/agents/_conversation_message.html.erb +28 -6
  68. data/app/views/raif/admin/agents/index.html.erb +2 -0
  69. data/app/views/raif/admin/agents/show.html.erb +46 -1
  70. data/app/views/raif/admin/configs/show.html.erb +117 -0
  71. data/app/views/raif/admin/conversations/_conversation_entry.html.erb +29 -34
  72. data/app/views/raif/admin/conversations/show.html.erb +2 -0
  73. data/app/views/raif/admin/model_completions/_model_completion.html.erb +9 -0
  74. data/app/views/raif/admin/model_completions/index.html.erb +26 -0
  75. data/app/views/raif/admin/model_completions/show.html.erb +124 -61
  76. data/app/views/raif/admin/model_tool_invocations/index.html.erb +22 -1
  77. data/app/views/raif/admin/model_tools/_list.html.erb +16 -0
  78. data/app/views/raif/admin/model_tools/_model_tool.html.erb +36 -0
  79. data/app/views/raif/admin/stats/_stats_tile.html.erb +34 -0
  80. data/app/views/raif/admin/stats/index.html.erb +71 -88
  81. data/app/views/raif/admin/stats/model_tool_invocations/index.html.erb +43 -0
  82. data/app/views/raif/admin/stats/tasks/index.html.erb +20 -6
  83. data/app/views/raif/admin/tasks/index.html.erb +6 -1
  84. data/app/views/raif/admin/tasks/show.html.erb +36 -3
  85. data/app/views/raif/conversation_entries/_form.html.erb +3 -0
  86. data/app/views/raif/conversations/_conversation.html.erb +10 -0
  87. data/app/views/raif/conversations/_entry_processed.turbo_stream.erb +12 -0
  88. data/app/views/raif/conversations/index.html.erb +23 -0
  89. data/config/locales/admin.en.yml +33 -1
  90. data/config/locales/en.yml +33 -4
  91. data/config/routes.rb +2 -0
  92. data/db/migrate/20250904194456_add_generating_entry_response_to_raif_conversations.rb +7 -0
  93. data/db/migrate/20250911125234_add_source_to_raif_tasks.rb +7 -0
  94. data/db/migrate/20251020005853_add_source_to_raif_agents.rb +7 -0
  95. data/db/migrate/20251020011346_rename_task_run_args_to_run_with.rb +7 -0
  96. data/db/migrate/20251020011405_add_run_with_to_raif_agents.rb +13 -0
  97. data/db/migrate/20251024160119_add_llm_messages_max_length_to_raif_conversations.rb +14 -0
  98. data/db/migrate/20251124185033_add_provider_tool_call_id_to_raif_model_tool_invocations.rb +7 -0
  99. data/db/migrate/20251128202941_add_tool_choice_to_raif_model_completions.rb +7 -0
  100. data/db/migrate/20260118144846_add_source_to_raif_conversations.rb +7 -0
  101. data/db/migrate/20260119000000_add_failure_tracking_to_raif_model_completions.rb +10 -0
  102. data/db/migrate/20260119000001_add_completed_at_to_raif_model_completions.rb +8 -0
  103. data/db/migrate/20260119000002_add_started_at_to_raif_model_completions.rb +8 -0
  104. data/lib/generators/raif/agent/templates/agent.rb.tt +1 -1
  105. data/lib/generators/raif/agent/templates/application_agent.rb.tt +1 -1
  106. data/lib/generators/raif/conversation/templates/conversation.rb.tt +6 -0
  107. data/lib/generators/raif/install/templates/initializer.rb +78 -10
  108. data/lib/generators/raif/task/templates/task.rb.tt +1 -1
  109. data/lib/raif/configuration.rb +37 -2
  110. data/lib/raif/engine.rb +8 -0
  111. data/lib/raif/errors/instance_dependent_schema_error.rb +8 -0
  112. data/lib/raif/errors/streaming_error.rb +6 -3
  113. data/lib/raif/errors.rb +1 -0
  114. data/lib/raif/evals/llm_judge.rb +2 -2
  115. data/lib/raif/evals/llm_judges/binary.rb +3 -3
  116. data/lib/raif/evals/llm_judges/comparative.rb +3 -3
  117. data/lib/raif/evals/llm_judges/scored.rb +1 -1
  118. data/lib/raif/evals/llm_judges/summarization.rb +2 -2
  119. data/lib/raif/evals/run.rb +1 -0
  120. data/lib/raif/json_schema_builder.rb +14 -0
  121. data/lib/raif/llm_registry.rb +207 -37
  122. data/lib/raif/messages.rb +180 -0
  123. data/lib/raif/version.rb +1 -1
  124. data/lib/raif.rb +9 -0
  125. data/lib/tasks/annotate_rb.rake +10 -0
  126. data/spec/support/rspec_helpers.rb +8 -8
  127. metadata +44 -9
  128. data/app/models/raif/agents/re_act_agent.rb +0 -127
  129. data/app/models/raif/agents/re_act_step.rb +0 -32
  130. data/app/models/raif/concerns/task_run_args.rb +0 -62
@@ -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",
@@ -113,27 +148,6 @@ module Raif
113
148
  output_token_cost: 4.4 / 1_000_000,
114
149
  model_provider_settings: { supports_temperature: false },
115
150
  },
116
- {
117
- key: :open_ai_gpt_5,
118
- api_name: "gpt-5",
119
- input_token_cost: 1.25 / 1_000_000,
120
- output_token_cost: 10.0 / 1_000_000,
121
- model_provider_settings: { supports_temperature: false },
122
- },
123
- {
124
- key: :open_ai_gpt_5_mini,
125
- api_name: "gpt-5-mini",
126
- input_token_cost: 0.25 / 1_000_000,
127
- output_token_cost: 2.0 / 1_000_000,
128
- model_provider_settings: { supports_temperature: false },
129
- },
130
- {
131
- key: :open_ai_gpt_5_nano,
132
- api_name: "gpt-5-nano",
133
- input_token_cost: 0.05 / 1_000_000,
134
- output_token_cost: 0.4 / 1_000_000,
135
- model_provider_settings: { supports_temperature: false },
136
- }
137
151
  ]
138
152
 
139
153
  open_ai_responses_models = open_ai_models.dup.map.with_index do |model, _index|
@@ -172,11 +186,33 @@ module Raif
172
186
  Raif::Llms::OpenAiResponses => open_ai_responses_models,
173
187
  Raif::Llms::Anthropic => [
174
188
  {
175
- key: :anthropic_claude_4_sonnet,
176
- api_name: "claude-sonnet-4-20250514",
189
+ key: :anthropic_claude_4_5_sonnet,
190
+ api_name: "claude-sonnet-4-5",
177
191
  input_token_cost: 3.0 / 1_000_000,
178
192
  output_token_cost: 15.0 / 1_000_000,
179
- 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,
180
216
  supported_provider_managed_tools: [
181
217
  Raif::ModelTools::ProviderManaged::WebSearch,
182
218
  Raif::ModelTools::ProviderManaged::CodeExecution
@@ -193,6 +229,17 @@ module Raif
193
229
  Raif::ModelTools::ProviderManaged::CodeExecution
194
230
  ]
195
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
+ },
196
243
  {
197
244
  key: :anthropic_claude_3_7_sonnet,
198
245
  api_name: "claude-3-7-sonnet-latest",
@@ -235,6 +282,27 @@ module Raif
235
282
  },
236
283
  ],
237
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
+ },
238
306
  {
239
307
  key: :bedrock_claude_4_sonnet,
240
308
  api_name: "anthropic.claude-sonnet-4-20250514-v1:0",
@@ -250,15 +318,15 @@ module Raif
250
318
  max_completion_tokens: 8192
251
319
  },
252
320
  {
253
- key: :bedrock_claude_3_5_sonnet,
254
- 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",
255
323
  input_token_cost: 0.003 / 1000,
256
324
  output_token_cost: 0.015 / 1000,
257
325
  max_completion_tokens: 8192
258
326
  },
259
327
  {
260
- key: :bedrock_claude_3_7_sonnet,
261
- 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",
262
330
  input_token_cost: 0.003 / 1000,
263
331
  output_token_cost: 0.015 / 1000,
264
332
  max_completion_tokens: 8192
@@ -306,6 +374,60 @@ module Raif
306
374
  input_token_cost: 3.0 / 1_000_000,
307
375
  output_token_cost: 15.0 / 1_000_000,
308
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
+ },
309
431
  {
310
432
  key: :open_router_llama_3_3_70b_instruct,
311
433
  api_name: "meta-llama/llama-3.3-70b-instruct",
@@ -331,16 +453,22 @@ module Raif
331
453
  output_token_cost: 0.30 / 1_000_000,
332
454
  },
333
455
  {
334
- key: :open_router_gemini_2_0_flash,
335
- api_name: "google/gemini-2.0-flash-001",
336
- input_token_cost: 0.1 / 1_000_000,
337
- 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,
338
460
  },
339
461
  {
340
- key: :open_router_deepseek_chat_v3,
341
- api_name: "deepseek/deepseek-chat-v3-0324",
342
- input_token_cost: 0.27 / 1_000_000,
343
- 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,
344
472
  },
345
473
  {
346
474
  key: :open_router_open_ai_gpt_oss_120b,
@@ -353,7 +481,49 @@ module Raif
353
481
  api_name: "gpt-oss-20b",
354
482
  input_token_cost: 0.05 / 1_000_000,
355
483
  output_token_cost: 0.2 / 1_000_000,
356
- }
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
+ ]
526
+ },
357
527
  ]
358
528
  }
359
529
  end
@@ -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
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.3.0"
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"
@@ -41,4 +42,12 @@ module Raif
41
42
  def self.running_evals?
42
43
  ENV["RAIF_RUNNING_EVALS"] == "true"
43
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
44
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
@@ -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