raif 1.2.1 → 1.3.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 (71) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +29 -935
  3. data/app/assets/builds/raif_admin.css +5 -1
  4. data/app/assets/images/raif-logo-white.svg +8 -0
  5. data/app/assets/stylesheets/raif_admin.scss +4 -0
  6. data/app/jobs/raif/conversation_entry_job.rb +1 -1
  7. data/app/models/raif/agents/re_act_step.rb +1 -2
  8. data/app/models/raif/concerns/has_llm.rb +1 -1
  9. data/app/models/raif/concerns/task_run_args.rb +62 -0
  10. data/app/models/raif/conversation.rb +8 -0
  11. data/app/models/raif/conversation_entry.rb +6 -9
  12. data/app/models/raif/llm.rb +1 -1
  13. data/app/models/raif/llms/open_router.rb +47 -4
  14. data/app/models/raif/task.rb +22 -9
  15. data/app/views/layouts/raif/admin.html.erb +3 -1
  16. data/app/views/raif/conversation_entries/_form.html.erb +1 -1
  17. data/app/views/raif/conversations/_full_conversation.html.erb +3 -6
  18. data/app/views/raif/conversations/_initial_chat_message.html.erb +5 -0
  19. data/config/locales/en.yml +8 -0
  20. data/db/migrate/20250804013843_add_task_run_args_to_raif_tasks.rb +13 -0
  21. data/db/migrate/20250811171150_make_raif_task_creator_optional.rb +8 -0
  22. data/exe/raif +7 -0
  23. data/lib/generators/raif/agent/agent_generator.rb +22 -7
  24. data/lib/generators/raif/agent/templates/agent.rb.tt +20 -24
  25. data/lib/generators/raif/agent/templates/agent_eval_set.rb.tt +48 -0
  26. data/lib/generators/raif/agent/templates/application_agent.rb.tt +0 -2
  27. data/lib/generators/raif/base_generator.rb +19 -0
  28. data/lib/generators/raif/conversation/conversation_generator.rb +21 -2
  29. data/lib/generators/raif/conversation/templates/application_conversation.rb.tt +0 -2
  30. data/lib/generators/raif/conversation/templates/conversation.rb.tt +29 -33
  31. data/lib/generators/raif/conversation/templates/conversation_eval_set.rb.tt +70 -0
  32. data/lib/generators/raif/eval_set/eval_set_generator.rb +28 -0
  33. data/lib/generators/raif/eval_set/templates/eval_set.rb.tt +21 -0
  34. data/lib/generators/raif/evals/setup/setup_generator.rb +47 -0
  35. data/lib/generators/raif/install/install_generator.rb +15 -0
  36. data/lib/generators/raif/install/templates/initializer.rb +14 -3
  37. data/lib/generators/raif/model_tool/model_tool_generator.rb +5 -2
  38. data/lib/generators/raif/model_tool/templates/model_tool.rb.tt +78 -76
  39. data/lib/generators/raif/model_tool/templates/model_tool_invocation_partial.html.erb.tt +10 -0
  40. data/lib/generators/raif/task/task_generator.rb +22 -3
  41. data/lib/generators/raif/task/templates/application_task.rb.tt +0 -2
  42. data/lib/generators/raif/task/templates/task.rb.tt +55 -59
  43. data/lib/generators/raif/task/templates/task_eval_set.rb.tt +54 -0
  44. data/lib/raif/cli/base.rb +39 -0
  45. data/lib/raif/cli/evals.rb +47 -0
  46. data/lib/raif/cli/evals_setup.rb +27 -0
  47. data/lib/raif/cli.rb +67 -0
  48. data/lib/raif/configuration.rb +23 -9
  49. data/lib/raif/engine.rb +2 -1
  50. data/lib/raif/evals/eval.rb +30 -0
  51. data/lib/raif/evals/eval_set.rb +111 -0
  52. data/lib/raif/evals/eval_sets/expectations.rb +53 -0
  53. data/lib/raif/evals/eval_sets/llm_judge_expectations.rb +255 -0
  54. data/lib/raif/evals/expectation_result.rb +39 -0
  55. data/lib/raif/evals/llm_judge.rb +32 -0
  56. data/lib/raif/evals/llm_judges/binary.rb +94 -0
  57. data/lib/raif/evals/llm_judges/comparative.rb +89 -0
  58. data/lib/raif/evals/llm_judges/scored.rb +63 -0
  59. data/lib/raif/evals/llm_judges/summarization.rb +166 -0
  60. data/lib/raif/evals/run.rb +201 -0
  61. data/lib/raif/evals/scoring_rubric.rb +174 -0
  62. data/lib/raif/evals.rb +26 -0
  63. data/lib/raif/llm_registry.rb +33 -0
  64. data/lib/raif/migration_checker.rb +3 -3
  65. data/lib/raif/utils/colors.rb +23 -0
  66. data/lib/raif/utils.rb +1 -0
  67. data/lib/raif/version.rb +1 -1
  68. data/lib/raif.rb +4 -0
  69. data/spec/support/current_temperature_test_tool.rb +34 -0
  70. data/spec/support/test_conversation.rb +1 -1
  71. metadata +37 -3
@@ -1,28 +1,24 @@
1
- # frozen_string_literal: true
1
+ <% raif_module_namespacing(["Agents"]) do -%>
2
+ class <%= class_name.demodulize %> < Raif::ApplicationAgent
3
+ # If you want to always include a certain set of model tools with this agent type,
4
+ # uncomment this callback to populate the available_model_tools attribute with your desired model tools.
5
+ # def populate_default_model_tools
6
+ # self.available_model_tools = [
7
+ # Raif::ModelTools::WikipediaSearch,
8
+ # Raif::ModelTools::FetchUrl
9
+ # ]
10
+ # end
2
11
 
3
- module Raif
4
- module Agents
5
- class <%= class_name %> < Raif::ApplicationAgent
6
- # If you want to always include a certain set of model tools with this agent type,
7
- # uncomment this callback to populate the available_model_tools attribute with your desired model tools.
8
- # def populate_default_model_tools
9
- # self.available_model_tools ||= [
10
- # Raif::ModelTools::WikipediaSearchTool,
11
- # Raif::ModelTools::FetchUrlTool
12
- # ]
13
- # end
14
-
15
- # Enter your agent's system prompt here. Alternatively, you can change your agent's superclass
16
- # to an existing agent types (like Raif::Agents::ReActAgent) to utilize an existing system prompt.
17
- def build_system_prompt
18
- # TODO: Implement your system prompt here
19
- end
12
+ # Enter your agent's system prompt here. Alternatively, you can change your agent's superclass
13
+ # to an existing agent types (like Raif::Agents::ReActAgent) to utilize an existing system prompt.
14
+ def build_system_prompt
15
+ # TODO: Implement your system prompt here
16
+ end
20
17
 
21
- # Each iteration of the agent loop will generate a new Raif::ModelCompletion record and
22
- # then call this method with it as an argument.
23
- def process_iteration_model_completion(model_completion)
24
- # TODO: Implement your iteration processing here
25
- end
18
+ # Each iteration of the agent loop will generate a new Raif::ModelCompletion record and
19
+ # then call this method with it as an argument.
20
+ def process_iteration_model_completion(model_completion)
21
+ # TODO: Implement your iteration processing here
26
22
  end
27
23
  end
28
- end
24
+ <% end -%>
@@ -0,0 +1,48 @@
1
+ <% raif_module_namespacing(["Evals", "Agents"]) 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
+ # @user = User.create!(email: "test@example.com")
10
+ end
11
+
12
+ # Teardown runs after each eval
13
+ teardown do
14
+ # Cleanup code
15
+ end
16
+
17
+ eval "<%= class_name %> completes task successfully" do
18
+ # agent = Raif::Agents::<%= class_name %>.create!(
19
+ # creator: @user,
20
+ # task: "Your specific task here",
21
+ # available_model_tools: [] # Add your tools here if needed
22
+ # )
23
+
24
+ # agent.run!
25
+
26
+ # expect "agent completes successfully" do
27
+ # agent.completed?
28
+ # end
29
+
30
+ # expect "produces expected output" do
31
+ # agent.final_answer.include?("expected content")
32
+ # end
33
+ end
34
+
35
+ eval "<%= class_name %> uses tools correctly" do
36
+ # agent = Raif::Agents::<%= class_name %>.create!(
37
+ # creator: @user,
38
+ # task: "A task that requires tool usage",
39
+ # available_model_tools: ["expected_tool_name"]
40
+ # )
41
+
42
+ # agent.run!
43
+
44
+ # expect_tool_invocation(agent, "expected_tool_name")
45
+ end
46
+
47
+ end
48
+ <% end -%>
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Raif
4
2
  class ApplicationAgent < Raif::Agent
5
3
  # Add any shared agent behavior here
@@ -0,0 +1,19 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ class BaseGenerator < Rails::Generators::NamedBase
5
+ private
6
+
7
+ def raif_module_namespacing(intermediate_modules = [], &block)
8
+ content = capture(&block).rstrip
9
+
10
+ modules_names = intermediate_modules + class_path.map(&:camelize)
11
+ modules_names.reverse.each do |module_name|
12
+ content = indent "module #{module_name}\n#{content}\nend", 2
13
+ end
14
+
15
+ concat("module Raif\n#{content}\nend\n")
16
+ end
17
+
18
+ end
19
+ end
@@ -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 ConversationGenerator < Rails::Generators::NamedBase
7
+ class ConversationGenerator < BaseGenerator
6
8
  source_root File.expand_path("templates", __dir__)
7
9
 
8
10
  desc "Creates a new conversation type in the app/models/raif/conversations directory"
@@ -12,19 +14,30 @@ module Raif
12
14
  default: "text",
13
15
  desc: "Response format for the task (text, html, or json)"
14
16
 
17
+ class_option :skip_eval_set,
18
+ type: :boolean,
19
+ default: false,
20
+ desc: "Skip generating the corresponding eval set"
21
+
15
22
  def create_application_conversation
16
23
  template "application_conversation.rb.tt",
17
24
  "app/models/raif/application_conversation.rb" unless File.exist?("app/models/raif/application_conversation.rb")
18
25
  end
19
26
 
20
27
  def create_conversation_file
21
- template "conversation.rb.tt", File.join("app/models/raif/conversations", "#{file_name}.rb")
28
+ template "conversation.rb.tt", File.join("app/models/raif/conversations", class_path, "#{file_name}.rb")
22
29
  end
23
30
 
24
31
  def create_directory
25
32
  empty_directory "app/models/raif/conversations" unless File.directory?("app/models/raif/conversations")
26
33
  end
27
34
 
35
+ def create_eval_set
36
+ return if options[:skip_eval_set]
37
+
38
+ template "conversation_eval_set.rb.tt", eval_set_file_path
39
+ end
40
+
28
41
  def success_message
29
42
  say_status :success, "Conversation type created successfully", :green
30
43
  say "\nYou can now implement your conversation type in:"
@@ -32,6 +45,12 @@ module Raif
32
45
  say "\nDon't forget to add it to the config.conversation_types in your Raif configuration"
33
46
  say "For example: config.conversation_types += ['Raif::Conversations::#{class_name}']\n\n"
34
47
  end
48
+
49
+ private
50
+
51
+ def eval_set_file_path
52
+ File.join("raif_evals", "eval_sets", "conversations", class_path, "#{file_name}_eval_set.rb")
53
+ end
35
54
  end
36
55
  end
37
56
  end
@@ -1,5 +1,3 @@
1
- # frozen_string_literal: true
2
-
3
1
  module Raif
4
2
  class ApplicationConversation < Raif::Conversation
5
3
  # Add any shared conversation behavior here
@@ -1,39 +1,35 @@
1
- # frozen_string_literal: true
1
+ <% raif_module_namespacing(["Conversations"]) do -%>
2
+ class <%= class_name.demodulize %> < Raif::ApplicationConversation
3
+ # Set the response format for the conversation. Options are :html, :text, or :json.
4
+ # If you set this to something other than :text, make sure to include instructions to the model in your system prompt
5
+ llm_response_format :<%= options[:response_format] %>
2
6
 
3
- module Raif
4
- module Conversations
5
- class <%= class_name %> < Raif::ApplicationConversation
6
- # Set the response format for the task. Options are :html, :text, or :json.
7
- # If you set this to something other than :text, make sure to include instructions to the model in your system prompt
8
- llm_response_format :<%= options[:response_format] %>
7
+ # If you want to always include a certain set of model tools with this conversation type,
8
+ # uncomment this callback to populate the available_model_tools attribute with your desired model tools.
9
+ # before_create -> { self.available_model_tools = ["Raif::ModelTools::Example"] }
9
10
 
10
- # If you want to always include a certain set of model tools with this conversation type,
11
- # uncomment this callback to populate the available_model_tools attribute with your desired model tools.
12
- # before_create -> { self.available_model_tools = ["Raif::ModelTools::Example"] }
11
+ # Override the methods below to customize the system prompt for this conversation type.
12
+ # def system_prompt_intro
13
+ # Raif.config.conversation_system_prompt_intro
14
+ # end
13
15
 
14
- # Override the methods below to customize the system prompt for this conversation type.
15
- # def system_prompt_intro
16
- # Raif.config.conversation_system_prompt_intro
17
- # end
16
+ # def build_system_prompt
17
+ # <<~PROMPT
18
+ # #{system_prompt_intro}
19
+ # #{system_prompt_language_preference}
20
+ # PROMPT
21
+ # end
18
22
 
19
- # def build_system_prompt
20
- # <<~PROMPT
21
- # #{system_prompt_intro}
22
- # #{system_prompt_language_preference}
23
- # PROMPT
24
- # end
23
+ # Override this method to set the initial message shown to the user.
24
+ # def initial_chat_message
25
+ # I18n.t("#{self.class.name.underscore.gsub("/", ".")}.initial_chat_message")
26
+ # end
25
27
 
26
- # Override this method to set the initial message shown to the user.
27
- # def initial_chat_message
28
- # I18n.t("#{self.class.name.underscore.gsub("/", ".")}.initial_chat_message")
29
- # end
30
-
31
- # This method will be called when receing a model response to a Raif::ConversationEntry
32
- # By default, it just passes the model response message through, but you can override
33
- # for custom response message processing
34
- # def process_model_response_message(message:, entry:)
35
- # message
36
- # end
37
- end
28
+ # This method will be called when receing a model response to a Raif::ConversationEntry
29
+ # By default, it just passes the model response message through, but you can override
30
+ # for custom response message processing
31
+ # def process_model_response_message(message:, entry:)
32
+ # message
33
+ # end
38
34
  end
39
- end
35
+ <% end -%>
@@ -0,0 +1,70 @@
1
+ <% raif_module_namespacing(["Evals", "Conversations"]) 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
+ # @user = User.create!(email: "test@example.com")
10
+ # @conversation = Raif::Conversations::<%= class_name %>.create!(creator: @user)
11
+ end
12
+
13
+ # Teardown runs after each eval
14
+ teardown do
15
+ # Cleanup code
16
+ end
17
+
18
+ eval "<%= class_name %> responds appropriately to user greeting" do
19
+ # entry = @conversation.entries.create!(
20
+ # user_message: "Hello, how are you?",
21
+ # creator: @user
22
+ # )
23
+
24
+ # entry.process_entry!
25
+
26
+ # expect "generates a response" do
27
+ # entry.model_response_message.present?
28
+ # end
29
+
30
+ # expect "response is friendly" do
31
+ # entry.model_response_message.match?(/hello|hi|greetings/i)
32
+ # end
33
+ end
34
+
35
+ eval "<%= class_name %> maintains conversation context" do
36
+ # First message establishes context
37
+ # first_entry = @conversation.entries.create!(
38
+ # user_message: "My name is Alice",
39
+ # creator: @user
40
+ # )
41
+ # first_entry.process_entry!
42
+
43
+ # Second message references context
44
+ # second_entry = @conversation.entries.create!(
45
+ # user_message: "What's my name?",
46
+ # creator: @user
47
+ # )
48
+ # second_entry.process_entry!
49
+
50
+ # expect "remembers the user's name" do
51
+ # second_entry.model_response_message.include?("Alice")
52
+ # end
53
+ end
54
+
55
+ eval "<%= class_name %> handles tool invocations correctly" do
56
+ # Test if your conversation uses tools
57
+ # @conversation.update!(available_model_tools: [ "Raif::ModelTools::FetchUrl" ])
58
+
59
+ # entry = @conversation.entries.create!(
60
+ # user_message: "What can you tell me about the content of https://en.wikipedia.org/wiki/Moon",
61
+ # creator: @user
62
+ # )
63
+
64
+ # entry.process_entry!
65
+
66
+ # expect_tool_invocation(entry, "Raif::ModelTools::FetchUrl", with: { url: "https://en.wikipedia.org/wiki/Moon" })
67
+ end
68
+
69
+ end
70
+ <% end -%>
@@ -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
@@ -28,11 +28,11 @@ Raif.configure do |config|
28
28
  # Whether Titan embedding models are enabled. Defaults to false
29
29
  # config.bedrock_embedding_models_enabled = false
30
30
 
31
- # Your OpenRouter API key. Defaults to ENV["OPENROUTER_API_KEY"]
32
- # config.open_router_api_key = ENV["OPENROUTER_API_KEY"]
31
+ # Your OpenRouter API key. Defaults to ENV["OPEN_ROUTER_API_KEY"]
32
+ # config.open_router_api_key = ENV["OPEN_ROUTER_API_KEY"]
33
33
 
34
34
  # Whether OpenRouter models are enabled.
35
- # config.open_router_models_enabled = ENV["OPENROUTER_API_KEY"].present?
35
+ # config.open_router_models_enabled = ENV["OPEN_ROUTER_API_KEY"].present?
36
36
 
37
37
  # The app name to include in OpenRouter API requests headers. Optional.
38
38
  # config.open_router_app_name = "My App"
@@ -98,6 +98,9 @@ Raif.configure do |config|
98
98
  # Or you can use a lambda to return a dynamic system prompt intro:
99
99
  # config.task_system_prompt_intro = ->(task){ "You are a helpful assistant. Today's date is #{Date.today.strftime('%B %d, %Y')}." }
100
100
 
101
+ # Whether the creator association is optional for Raif::Task. Defaults to true.
102
+ # config.task_creator_optional = true
103
+
101
104
  # The system prompt intro for Raif::Conversation instances. Defaults to "You are a helpful assistant who is collaborating with a teammate."
102
105
  # config.conversation_system_prompt_intro = "You are a helpful assistant who is collaborating with a teammate."
103
106
  # Or you can use a lambda to return a dynamic system prompt intro:
@@ -134,4 +137,12 @@ Raif.configure do |config|
134
137
  # Whether LLM API requests are enabled. Defaults to true.
135
138
  # Use this to globally disable requests to LLM APIs.
136
139
  # config.llm_api_requests_enabled = true
140
+
141
+ # The default LLM model to use for LLM-as-judge evaluations.
142
+ # If not set, falls back to the default_llm_model_key.
143
+ # config.evals_default_llm_judge_model_key = ENV["RAIF_EVALS_DEFAULT_LLM_JUDGE_MODEL_KEY"].presence
144
+
145
+ # Whether to output verbose information during evaluation runs. Defaults to false.
146
+ # When true, provides more detailed output including individual test results.
147
+ # config.evals_verbose_output = false
137
148
  end
@@ -1,14 +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")
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")
12
15
  end
13
16
 
14
17
  def success_message