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
@@ -0,0 +1,28 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../base_generator"
4
+
5
+ module Raif
6
+ module Generators
7
+ class EvalSetGenerator < BaseGenerator
8
+ source_root File.expand_path("templates", __dir__)
9
+
10
+ def create_eval_set_file
11
+ template "eval_set.rb.tt", eval_set_file_path
12
+ end
13
+
14
+ def show_instructions
15
+ say "\nEval set created!"
16
+ say "To run this eval set: bundle exec raif evals ./#{eval_set_file_path}"
17
+ say "To run all eval sets: bundle exec raif evals"
18
+ say ""
19
+ end
20
+
21
+ private
22
+
23
+ def eval_set_file_path
24
+ File.join("raif_evals", "eval_sets", class_path, "#{file_name}_eval_set.rb")
25
+ end
26
+ end
27
+ end
28
+ end
@@ -0,0 +1,21 @@
1
+ <% raif_module_namespacing(["Evals"]) do -%>
2
+ class <%= class_name.demodulize %>EvalSet < Raif::Evals::EvalSet
3
+ # Run this eval set with:
4
+ # bundle exec raif evals ./<%= eval_set_file_path %>
5
+
6
+ # Setup method runs before each eval
7
+ setup do
8
+ # Common setup code
9
+ end
10
+
11
+ # Teardown runs after each eval
12
+ teardown do
13
+ # Cleanup code
14
+ end
15
+
16
+ eval "description of your eval" do
17
+ # Your eval code here
18
+ # expect_tool_invocation, expect_no_tool_invocation, expect, etc.
19
+ end
20
+ end
21
+ <% end -%>
@@ -0,0 +1,47 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "rails/generators"
4
+
5
+ module Raif
6
+ module Generators
7
+ module Evals
8
+ class SetupGenerator < Rails::Generators::Base
9
+ source_root File.expand_path("templates", __dir__)
10
+
11
+ def create_directories
12
+ empty_directory "raif_evals"
13
+ empty_directory "raif_evals/eval_sets"
14
+ empty_directory "raif_evals/files"
15
+ empty_directory "raif_evals/results"
16
+ end
17
+
18
+ def create_setup_file
19
+ create_file "raif_evals/setup.rb", <<~EOS
20
+ #
21
+ # This file is loaded at the start of a run of your evals.
22
+ #
23
+ # Add any setup code that should run before your evals.
24
+ #
25
+ EOS
26
+ end
27
+
28
+ def create_gitignore
29
+ create_file "raif_evals/results/.gitignore", <<~EOS
30
+ *
31
+ !.gitignore
32
+ EOS
33
+ end
34
+
35
+ def show_instructions
36
+ say "\nRaif evals setup complete!", :green
37
+ say "You can create evals with: rails g raif:eval_set ExampleName"
38
+ say ""
39
+ say "Run evals with:"
40
+ say " bundle exec raif evals # Run all evals"
41
+ say " bundle exec raif evals ./raif_evals/eval_sets/my_eval_set.rb # Run specific eval set"
42
+ say ""
43
+ end
44
+ end
45
+ end
46
+ end
47
+ end
@@ -24,8 +24,23 @@ module Raif
24
24
  end
25
25
 
26
26
  def add_engine_route
27
+ routes_file = "config/routes.rb"
28
+
29
+ if File.exist?(routes_file)
30
+ routes_content = File.read(routes_file)
31
+ if routes_content.include?("mount Raif::Engine")
32
+ say "Raif is already mounted in #{routes_file}, skipping route", :yellow
33
+ return
34
+ end
35
+ end
36
+
27
37
  route 'mount Raif::Engine => "/raif"'
28
38
  end
39
+
40
+ def setup_evals
41
+ say "\nSetting up Raif evals...", :green
42
+ generate "raif:evals:setup"
43
+ end
29
44
  end
30
45
  end
31
46
  end
@@ -10,6 +10,22 @@ Raif.configure do |config|
10
10
  # Whether OpenAI embedding models are enabled.
11
11
  # config.open_ai_embedding_models_enabled = ENV["OPENAI_API_KEY"].present?
12
12
 
13
+ # The base URL for OpenAI API requests.
14
+ # Set this if you want to use the OpenAI adapter with a different provider (e.g. for using Azure instead of OpenAI)
15
+ # config.open_ai_base_url = "https://api.openai.com/v1"
16
+
17
+ # The base URL for OpenAI embedding API requests.
18
+ # Set this if you want to use a different provider for embeddings (e.g. Ollama, vLLM, or other OpenAI-compatible APIs)
19
+ # config.open_ai_embedding_base_url = "https://api.openai.com/v1"
20
+
21
+ # When set, this will be included as an api-version parameter in any OpenAI API requests (e.g. for using Azure instead of OpenAI)
22
+ # config.open_ai_api_version = nil
23
+
24
+ # The authentication header style for OpenAI API requests. Defaults to :bearer
25
+ # Use :bearer for standard OpenAI API (Authorization: Bearer <token>)
26
+ # Use :api_key for Azure OpenAI API (api-key: <token>)
27
+ # config.open_ai_auth_header_style = :bearer
28
+
13
29
  # Your Anthropic API key. Defaults to ENV["ANTHROPIC_API_KEY"]
14
30
  # config.anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
15
31
 
@@ -40,24 +56,56 @@ Raif.configure do |config|
40
56
  # The site URL to include in OpenRouter API requests headers. Optional.
41
57
  # config.open_router_site_url = "https://myapp.com"
42
58
 
59
+ # Your Google AI API key. Defaults to ENV["GOOGLE_AI_API_KEY"].presence || ENV["GOOGLE_API_KEY"]
60
+ # config.google_api_key = ENV["GOOGLE_AI_API_KEY"].presence || ENV["GOOGLE_API_KEY"]
61
+
62
+ # Whether Google models are enabled.
63
+ # config.google_models_enabled = ENV["GOOGLE_API_KEY"].present?
64
+
43
65
  # The default LLM model to use. Defaults to "open_ai_gpt_4o"
44
66
  # Available keys:
45
- # open_ai_gpt_4_1
46
- # open_ai_gpt_4_1_mini
47
- # open_ai_gpt_4_1_nano
48
67
  # open_ai_gpt_4o_mini
49
68
  # open_ai_gpt_4o
50
69
  # open_ai_gpt_3_5_turbo
70
+ # open_ai_gpt_4_1
71
+ # open_ai_gpt_4_1_mini
72
+ # open_ai_gpt_4_1_nano
73
+ # open_ai_o1
74
+ # open_ai_o1_mini
75
+ # open_ai_o3
76
+ # open_ai_o3_mini
77
+ # open_ai_o4_mini
78
+ # open_ai_gpt_5
79
+ # open_ai_gpt_5_mini
80
+ # open_ai_gpt_5_nano
81
+ # open_ai_responses_gpt_4o_mini
82
+ # open_ai_responses_gpt_4o
83
+ # open_ai_responses_gpt_3_5_turbo
51
84
  # open_ai_responses_gpt_4_1
52
85
  # open_ai_responses_gpt_4_1_mini
53
86
  # open_ai_responses_gpt_4_1_nano
54
- # open_ai_responses_gpt_4o_mini
55
- # open_ai_responses_gpt_4o
56
- # open_ai_gpt_3_5_turbo
87
+ # open_ai_responses_o1
88
+ # open_ai_responses_o1_mini
89
+ # open_ai_responses_o3
90
+ # open_ai_responses_o3_mini
91
+ # open_ai_responses_o4_mini
92
+ # open_ai_responses_gpt_5
93
+ # open_ai_responses_gpt_5_mini
94
+ # open_ai_responses_gpt_5_nano
95
+ # open_ai_responses_o1_pro
96
+ # open_ai_responses_o3_pro
97
+ # anthropic_claude_4_sonnet
98
+ # anthropic_claude_4_5_sonnet
99
+ # anthropic_claude_4_opus
100
+ # anthropic_claude_4_1_opus
57
101
  # anthropic_claude_3_7_sonnet
58
102
  # anthropic_claude_3_5_sonnet
59
103
  # anthropic_claude_3_5_haiku
60
104
  # anthropic_claude_3_opus
105
+ # bedrock_claude_4_sonnet
106
+ # bedrock_claude_4_5_sonnet
107
+ # bedrock_claude_4_opus
108
+ # bedrock_claude_4_1_opus
61
109
  # bedrock_claude_3_5_sonnet
62
110
  # bedrock_claude_3_7_sonnet
63
111
  # bedrock_claude_3_5_haiku
@@ -66,10 +114,21 @@ Raif.configure do |config|
66
114
  # bedrock_amazon_nova_lite
67
115
  # bedrock_amazon_nova_pro
68
116
  # open_router_claude_3_7_sonnet
69
- # open_router_llama_3_3_70b_instruct
70
- # open_router_llama_3_1_8b_instruct
71
- # open_router_gemini_2_0_flash
72
117
  # open_router_deepseek_chat_v3
118
+ # open_router_deepseek_v3_1
119
+ # open_router_gemini_2_0_flash
120
+ # open_router_gemini_2_5_pro
121
+ # open_router_grok_4
122
+ # open_router_llama_3_1_8b_instruct
123
+ # open_router_llama_3_3_70b_instruct
124
+ # open_router_llama_4_maverick
125
+ # open_router_llama_4_scout
126
+ # open_router_open_ai_gpt_oss_120b
127
+ # open_router_open_ai_gpt_oss_20b
128
+ # google_gemini_2_5_pro
129
+ # google_gemini_2_5_flash
130
+ # google_gemini_3_0_pro
131
+ # google_gemini_3_0_flash
73
132
  #
74
133
  # config.default_llm_model_key = "open_ai_gpt_4o"
75
134
 
@@ -98,6 +157,9 @@ Raif.configure do |config|
98
157
  # Or you can use a lambda to return a dynamic system prompt intro:
99
158
  # config.task_system_prompt_intro = ->(task){ "You are a helpful assistant. Today's date is #{Date.today.strftime('%B %d, %Y')}." }
100
159
 
160
+ # Whether the creator association is optional for Raif::Task. Defaults to true.
161
+ # config.task_creator_optional = true
162
+
101
163
  # The system prompt intro for Raif::Conversation instances. Defaults to "You are a helpful assistant who is collaborating with a teammate."
102
164
  # config.conversation_system_prompt_intro = "You are a helpful assistant who is collaborating with a teammate."
103
165
  # Or you can use a lambda to return a dynamic system prompt intro:
@@ -115,10 +177,14 @@ Raif.configure do |config|
115
177
  # If you want to use a custom controller that inherits from Raif::ConversationEntriesController, you can set it here.
116
178
  # config.conversation_entries_controller = "Raif::ConversationEntriesController"
117
179
 
180
+ # The default maximum number of conversation entries to include in LLM messages. Defaults to 50.
181
+ # Set to nil to include all entries. Each conversation can override this with its own llm_messages_max_length attribute.
182
+ # config.conversation_llm_messages_max_length_default = 50
183
+
118
184
  # The method to call to get the current user. Defaults to :current_user
119
185
  # config.current_user_method = :current_user
120
186
 
121
- # The agent types that are available. Defaults to Set.new(["Raif::Agents::ReActAgent", "Raif::Agents::NativeToolCallingAgent"])
187
+ # The agent types that are available. Defaults to Set.new(["Raif::Agents::NativeToolCallingAgent"])
122
188
  # If you want to use custom agent types that inherits from Raif::Agent, you can add them here.
123
189
  # config.agent_types += ["MyAgent"]
124
190
 
@@ -134,4 +200,17 @@ Raif.configure do |config|
134
200
  # Whether LLM API requests are enabled. Defaults to true.
135
201
  # Use this to globally disable requests to LLM APIs.
136
202
  # config.llm_api_requests_enabled = true
203
+
204
+ # Timeout settings for LLM API requests (in seconds). All default to nil (use Faraday defaults).
205
+ # config.request_open_timeout = nil # Time to wait for a connection to be opened
206
+ # config.request_read_timeout = nil # Time to wait for data to be read
207
+ # config.request_write_timeout = nil # Time to wait for data to be written
208
+
209
+ # The default LLM model to use for LLM-as-judge evaluations.
210
+ # If not set, falls back to the default_llm_model_key.
211
+ # config.evals_default_llm_judge_model_key = ENV["RAIF_EVALS_DEFAULT_LLM_JUDGE_MODEL_KEY"].presence
212
+
213
+ # Whether to output verbose information during evaluation runs. Defaults to false.
214
+ # When true, provides more detailed output including individual test results.
215
+ # config.evals_verbose_output = false
137
216
  end
@@ -1,17 +1,17 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../base_generator"
4
+
3
5
  module Raif
4
6
  module Generators
5
- class ModelToolGenerator < Rails::Generators::NamedBase
7
+ class ModelToolGenerator < BaseGenerator
6
8
  source_root File.expand_path("templates", __dir__)
7
9
 
8
10
  desc "Creates a new model tool for the LLM to invoke in app/models/raif/model_tools"
9
11
 
10
12
  def create_model_tool_file
11
- template "model_tool.rb.tt", File.join("app/models/raif/model_tools", "#{file_name}.rb")
12
-
13
- # Generate the view partial for the tool invocation
14
- template "model_tool_invocation_partial.html.erb.tt", File.join("app/views/raif/model_tool_invocations", "_#{file_name}.html.erb")
13
+ template "model_tool.rb.tt", File.join("app/models/raif/model_tools", class_path, "#{file_name}.rb")
14
+ template "model_tool_invocation_partial.html.erb.tt", File.join("app/views/raif/model_tool_invocations", class_path, "_#{file_name}.html.erb")
15
15
  end
16
16
 
17
17
  def success_message
@@ -1,89 +1,89 @@
1
- # frozen_string_literal: true
1
+ <% raif_module_namespacing(["ModelTools"]) do -%>
2
+ class <%= class_name.demodulize %> < Raif::ModelTool
3
+ # For example tool implementations, see:
4
+ # Wikipedia Search Tool: https://github.com/CultivateLabs/raif/blob/main/app/models/raif/model_tools/wikipedia_search.rb
5
+ # Fetch URL Tool: https://github.com/CultivateLabs/raif/blob/main/app/models/raif/model_tools/fetch_url.rb
2
6
 
3
- class Raif::ModelTools::<%= class_name %> < Raif::ModelTool
4
- # For example tool implementations, see:
5
- # Wikipedia Search Tool: https://github.com/CultivateLabs/raif/blob/main/app/models/raif/model_tools/wikipedia_search.rb
6
- # Fetch URL Tool: https://github.com/CultivateLabs/raif/blob/main/app/models/raif/model_tools/fetch_url.rb
7
-
8
- tool_description do
9
- "Description of your tool that will be provided to the LLM so it knows when to invoke it"
10
- end
7
+ tool_description do
8
+ "Description of your tool that will be provided to the LLM so it knows when to invoke it"
9
+ end
11
10
 
12
- # Define the schema for the arguments that the LLM should use when invoking your tool.
13
- # It should be a valid JSON schema. When the model invokes your tool,
14
- # the arguments it provides will be validated against this schema using JSON::Validator from the json-schema gem.
15
- #
16
- # All attributes will be required and additionalProperties will be set to false.
17
- #
18
- # See https://docs.raif.ai/learn_more/json_schemas for more information about defining JSON schemas.
19
- tool_arguments_schema do
20
- # string :title, description: "The title of the operation", minLength: 3
11
+ # Define the schema for the arguments that the LLM should use when invoking your tool.
12
+ # It should be a valid JSON schema. When the model invokes your tool,
13
+ # the arguments it provides will be validated against this schema using JSON::Validator from the json-schema gem.
21
14
  #
22
- # object :widget, description: "A widget's description" do
23
- # boolean :is_red, description: "Whether the widget is red"
24
- # integer :rating, description: "A rating of the widget from 1 to 10", minimum: 1, maximum: 10
25
- # array :tags, description: "Associated tags" do
26
- # items type: "string"
27
- # end
28
- # end
15
+ # All attributes will be required and additionalProperties will be set to false.
29
16
  #
30
- # array :products, description: "List of products" do
31
- # object do
32
- # integer :id, description: "Product identifier"
33
- # string :name, description: "Product name"
34
- # number :price, description: "Product price", minimum: 0
35
- # end
36
- # end
37
- end
17
+ # See https://docs.raif.ai/learn_more/json_schemas for more information about defining JSON schemas.
18
+ tool_arguments_schema do
19
+ # string :title, description: "The title of the operation", minLength: 3
20
+ #
21
+ # object :widget, description: "A widget's description" do
22
+ # boolean :is_red, description: "Whether the widget is red"
23
+ # integer :rating, description: "A rating of the widget from 1 to 10", minimum: 1, maximum: 10
24
+ # array :tags, description: "Associated tags" do
25
+ # items type: "string"
26
+ # end
27
+ # end
28
+ #
29
+ # array :products, description: "List of products" do
30
+ # object do
31
+ # integer :id, description: "Product identifier"
32
+ # string :name, description: "Product name"
33
+ # number :price, description: "Product price", minimum: 0
34
+ # end
35
+ # end
36
+ end
38
37
 
39
- # An example of how the LLM should invoke your tool. This should return a hash with name and arguments keys.
40
- # `to_json` will be called on it and provided to the LLM as an example of how to invoke your tool.
41
- example_model_invocation do
42
- {
43
- "name": tool_name,
44
- "arguments": { }
45
- }
46
- end
38
+ # An example of how the LLM should invoke your tool. This should return a hash with name and arguments keys.
39
+ # `to_json` will be called on it and provided to the LLM as an example of how to invoke your tool.
40
+ example_model_invocation do
41
+ {
42
+ "name": tool_name,
43
+ "arguments": {}
44
+ }
45
+ end
47
46
 
48
- class << self
49
- # When your tool is invoked by the LLM in a Raif::Agent loop,
50
- # the results of the tool invocation are provided back to the LLM as an observation.
51
- # This method should return whatever you want provided to the LLM.
52
- # For example, if you were implementing a GoogleSearch tool, this might return a JSON
53
- # object containing search results for the query.
54
- def observation_for_invocation(tool_invocation)
55
- return "No results found" unless tool_invocation.result.present?
47
+ class << self
48
+ # When your tool is invoked by the LLM in a Raif::Agent loop,
49
+ # the results of the tool invocation are provided back to the LLM as an observation.
50
+ # This method should return whatever you want provided to the LLM.
51
+ # For example, if you were implementing a GoogleSearch tool, this might return a JSON
52
+ # object containing search results for the query.
53
+ def observation_for_invocation(tool_invocation)
54
+ return "No results found" unless tool_invocation.result.present?
56
55
 
57
- JSON.pretty_generate(tool_invocation.result)
58
- end
56
+ JSON.pretty_generate(tool_invocation.result)
57
+ end
59
58
 
60
- # When your tool is invoked in a Raif::Conversation, should the result be automatically provided back to the model?
61
- # When true, observation_for_invocation will be used to produce the observation provided to the model
62
- def triggers_observation_to_model?
63
- false
64
- end
59
+ # When your tool is invoked in a Raif::Conversation, should the result be automatically provided back to the model?
60
+ # When true, observation_for_invocation will be used to produce the observation provided to the model
61
+ def triggers_observation_to_model?
62
+ false
63
+ end
64
+
65
+ # When the LLM invokes your tool, this method will be called with a `Raif::ModelToolInvocation` record as an argument.
66
+ # It should handle the actual execution of the tool.
67
+ # For example, if you are implementing a GoogleSearch tool, this method should run the actual search
68
+ # and store the results in the tool_invocation's result JSON column.
69
+ def process_invocation(tool_invocation)
70
+ # Extract arguments from tool_invocation.tool_arguments
71
+ # query = tool_invocation.tool_arguments["query"]
72
+ #
73
+ # Process the invocation and perform the desired action
74
+ # ...
75
+ #
76
+ # Store the results in the tool_invocation
77
+ # tool_invocation.update!(
78
+ # result: {
79
+ # # Your result data structure
80
+ # }
81
+ # )
82
+ #
83
+ # Return the result
84
+ # tool_invocation.result
85
+ end
65
86
 
66
- # When the LLM invokes your tool, this method will be called with a `Raif::ModelToolInvocation` record as an argument.
67
- # It should handle the actual execution of the tool.
68
- # For example, if you are implementing a GoogleSearch tool, this method should run the actual search
69
- # and store the results in the tool_invocation's result JSON column.
70
- def process_invocation(tool_invocation)
71
- # Extract arguments from tool_invocation.tool_arguments
72
- # query = tool_invocation.tool_arguments["query"]
73
- #
74
- # Process the invocation and perform the desired action
75
- # ...
76
- #
77
- # Store the results in the tool_invocation
78
- # tool_invocation.update!(
79
- # result: {
80
- # # Your result data structure
81
- # }
82
- # )
83
- #
84
- # Return the result
85
- # tool_invocation.result
86
87
  end
87
88
  end
88
-
89
- end
89
+ <% end -%>
@@ -1,4 +1,4 @@
1
- <%#
1
+ <%%#
2
2
  This partial is used to render a model tool invocation to the user in the conversation interface.
3
3
  If you don't want the tool invocation to be displayed to the user, you can override the `renderable?` method in your model tool class to return false
4
4
  %>
@@ -1,8 +1,10 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative "../base_generator"
4
+
3
5
  module Raif
4
6
  module Generators
5
- class TaskGenerator < Rails::Generators::NamedBase
7
+ class TaskGenerator < BaseGenerator
6
8
  source_root File.expand_path("templates", __dir__)
7
9
 
8
10
  class_option :response_format,
@@ -10,6 +12,11 @@ module Raif
10
12
  default: "text",
11
13
  desc: "Response format for the task (text, html, or json)"
12
14
 
15
+ class_option :skip_eval_set,
16
+ type: :boolean,
17
+ default: false,
18
+ desc: "Skip generating the corresponding eval set"
19
+
13
20
  def create_application_task
14
21
  template "application_task.rb.tt", "app/models/raif/application_task.rb" unless File.exist?("app/models/raif/application_task.rb")
15
22
  end
@@ -18,11 +25,23 @@ module Raif
18
25
  template "task.rb.tt", File.join("app/models/raif/tasks", class_path, "#{file_name}.rb")
19
26
  end
20
27
 
28
+ def create_eval_set
29
+ return if options[:skip_eval_set]
30
+
31
+ template "task_eval_set.rb.tt", eval_set_file_path
32
+ end
33
+
34
+ def show_instructions
35
+ say "\nTask created!"
36
+ say ""
37
+ end
38
+
21
39
  private
22
40
 
23
- def task_class_name
24
- class_name
41
+ def eval_set_file_path
42
+ File.join("raif_evals", "eval_sets", "tasks", class_path, "#{file_name}_eval_set.rb")
25
43
  end
44
+
26
45
  end
27
46
  end
28
47
  end
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Raif
4
2
  class ApplicationTask < Raif::Task
5
3
  # Add any shared task behavior here
@@ -1,63 +1,59 @@
1
- # frozen_string_literal: true
2
-
3
- module Raif
4
- module Tasks
5
- class <%= task_class_name %> < Raif::ApplicationTask
6
- # Set the response format for the task. Options are :html, :text, or :json.
7
- llm_response_format :<%= options[:response_format] %>
8
-
9
- # Set the temperature for the task
10
- # llm_temperature 0.7
11
-
12
- # Optional: Set the allowed tags for the task. Only relevant if response_format is :html.
13
- # Defaults to Rails::HTML5::SafeListSanitizer.allowed_tags
14
- # llm_response_allowed_tags %w[p b i div strong]
15
-
16
- # Optional: Set the allowed attributes for the task. Only relevant if response_format is :html.
17
- # Defaults to Rails::HTML5::SafeListSanitizer.allowed_attributes
18
- # llm_response_allowed_attributes %w[style]
19
-
20
- # Define any attributes that are needed for the task.
21
- # You can then pass them when running the task and they will be available in build_prompt:
22
- # Raif::Tasks::<%= task_class_name %>.run(your_attribute: "some value")
23
- # attr_accessor :your_attribute
24
-
25
- <%- if options[:response_format] == "json" -%>
26
- # Define a JSON schema that the model's response should adhere to
1
+ <% raif_module_namespacing(["Tasks"]) do -%>
2
+ class <%= class_name.demodulize %> < Raif::ApplicationTask
3
+ # Set the response format for the task. Options are :html, :text, or :json.
4
+ llm_response_format :<%= options[:response_format] %>
5
+
6
+ # Set the temperature for the task
7
+ # llm_temperature 0.7
8
+
9
+ # Optional: Set the allowed tags for the task. Only relevant if response_format is :html.
10
+ # Defaults to Rails::HTML5::SafeListSanitizer.allowed_tags
11
+ # llm_response_allowed_tags %w[p b i div strong]
12
+
13
+ # Optional: Set the allowed attributes for the task. Only relevant if response_format is :html.
14
+ # Defaults to Rails::HTML5::SafeListSanitizer.allowed_attributes
15
+ # llm_response_allowed_attributes %w[style]
16
+
17
+ # Define any attributes that are needed for the task.
18
+ # You can then pass them when running the task and they will be available in build_prompt:
19
+ # Raif::Tasks::<%= class_name %>.run(your_attribute: "some value")
20
+ # run_with :your_attribute
21
+ <%- if options[:response_format] == "json" -%>
22
+
23
+ # Define a JSON schema that the model's response should adhere to
24
+ #
25
+ # All attributes will be required and additionalProperties will be set to false.
26
+ json_response_schema do
27
+ # string :title, description: "The title of the operation", minLength: 3
27
28
  #
28
- # All attributes will be required and additionalProperties will be set to false.
29
- json_response_schema do
30
- # string :title, description: "The title of the operation", minLength: 3
31
- #
32
- # object :widget, description: "A widget's description" do
33
- # boolean :is_red, description: "Whether the widget is red"
34
- # integer :rating, description: "A rating of the widget from 1 to 10", minimum: 1, maximum: 10
35
- # array :tags, description: "Associated tags" do
36
- # items type: "string"
37
- # end
38
- # end
39
- #
40
- # array :products, description: "List of products" do
41
- # object do
42
- # integer :id, description: "Product identifier"
43
- # string :name, description: "Product name"
44
- # number :price, description: "Product price", minimum: 0
45
- # end
46
- # end
47
- end
48
- <%- end -%>
49
-
50
- def build_prompt
51
- # Implement the LLM prompt for this task.
52
- raise NotImplementedError, "Implement #build_prompt in #{self.class.name}"
53
- end
54
-
55
- # Optional: Override build_system_prompt if you need custom system instructions.
56
- # The default implementation, which you'll get if you call super, will use Raif.config.task_system_prompt_intro
57
- # and append the system_prompt_language_preference if the task's requested_language_key is set.
58
- # def build_system_prompt
59
- # super + "\nAdditional system instructions..."
29
+ # object :widget, description: "A widget's description" do
30
+ # boolean :is_red, description: "Whether the widget is red"
31
+ # integer :rating, description: "A rating of the widget from 1 to 10", minimum: 1, maximum: 10
32
+ # array :tags, description: "Associated tags" do
33
+ # items type: "string"
34
+ # end
35
+ # end
36
+ #
37
+ # array :products, description: "List of products" do
38
+ # object do
39
+ # integer :id, description: "Product identifier"
40
+ # string :name, description: "Product name"
41
+ # number :price, description: "Product price", minimum: 0
42
+ # end
60
43
  # end
61
44
  end
45
+ <%- end -%>
46
+
47
+ def build_prompt
48
+ # Implement the LLM prompt for this task.
49
+ raise NotImplementedError, "Implement #build_prompt in #{self.class.name}"
50
+ end
51
+
52
+ # Optional: Override build_system_prompt if you need custom system instructions.
53
+ # The default implementation, which you'll get if you call super, will use Raif.config.task_system_prompt_intro
54
+ # and append the system_prompt_language_preference if the task's requested_language_key is set.
55
+ # def build_system_prompt
56
+ # super + "\nAdditional system instructions..."
57
+ # end
62
58
  end
63
- end
59
+ <% end -%>