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.
- checksums.yaml +4 -4
- data/README.md +29 -935
- data/app/assets/builds/raif_admin.css +5 -1
- data/app/assets/images/raif-logo-white.svg +8 -0
- data/app/assets/stylesheets/raif_admin.scss +4 -0
- data/app/jobs/raif/conversation_entry_job.rb +1 -1
- data/app/models/raif/agents/re_act_step.rb +1 -2
- data/app/models/raif/concerns/has_llm.rb +1 -1
- data/app/models/raif/concerns/task_run_args.rb +62 -0
- data/app/models/raif/conversation.rb +8 -0
- data/app/models/raif/conversation_entry.rb +6 -9
- data/app/models/raif/llm.rb +1 -1
- data/app/models/raif/llms/open_router.rb +47 -4
- data/app/models/raif/task.rb +22 -9
- data/app/views/layouts/raif/admin.html.erb +3 -1
- data/app/views/raif/conversation_entries/_form.html.erb +1 -1
- data/app/views/raif/conversations/_full_conversation.html.erb +3 -6
- data/app/views/raif/conversations/_initial_chat_message.html.erb +5 -0
- data/config/locales/en.yml +8 -0
- data/db/migrate/20250804013843_add_task_run_args_to_raif_tasks.rb +13 -0
- data/db/migrate/20250811171150_make_raif_task_creator_optional.rb +8 -0
- data/exe/raif +7 -0
- data/lib/generators/raif/agent/agent_generator.rb +22 -7
- data/lib/generators/raif/agent/templates/agent.rb.tt +20 -24
- data/lib/generators/raif/agent/templates/agent_eval_set.rb.tt +48 -0
- data/lib/generators/raif/agent/templates/application_agent.rb.tt +0 -2
- data/lib/generators/raif/base_generator.rb +19 -0
- data/lib/generators/raif/conversation/conversation_generator.rb +21 -2
- data/lib/generators/raif/conversation/templates/application_conversation.rb.tt +0 -2
- data/lib/generators/raif/conversation/templates/conversation.rb.tt +29 -33
- data/lib/generators/raif/conversation/templates/conversation_eval_set.rb.tt +70 -0
- data/lib/generators/raif/eval_set/eval_set_generator.rb +28 -0
- data/lib/generators/raif/eval_set/templates/eval_set.rb.tt +21 -0
- data/lib/generators/raif/evals/setup/setup_generator.rb +47 -0
- data/lib/generators/raif/install/install_generator.rb +15 -0
- data/lib/generators/raif/install/templates/initializer.rb +14 -3
- data/lib/generators/raif/model_tool/model_tool_generator.rb +5 -2
- data/lib/generators/raif/model_tool/templates/model_tool.rb.tt +78 -76
- data/lib/generators/raif/model_tool/templates/model_tool_invocation_partial.html.erb.tt +10 -0
- data/lib/generators/raif/task/task_generator.rb +22 -3
- data/lib/generators/raif/task/templates/application_task.rb.tt +0 -2
- data/lib/generators/raif/task/templates/task.rb.tt +55 -59
- data/lib/generators/raif/task/templates/task_eval_set.rb.tt +54 -0
- data/lib/raif/cli/base.rb +39 -0
- data/lib/raif/cli/evals.rb +47 -0
- data/lib/raif/cli/evals_setup.rb +27 -0
- data/lib/raif/cli.rb +67 -0
- data/lib/raif/configuration.rb +23 -9
- data/lib/raif/engine.rb +2 -1
- data/lib/raif/evals/eval.rb +30 -0
- data/lib/raif/evals/eval_set.rb +111 -0
- data/lib/raif/evals/eval_sets/expectations.rb +53 -0
- data/lib/raif/evals/eval_sets/llm_judge_expectations.rb +255 -0
- data/lib/raif/evals/expectation_result.rb +39 -0
- data/lib/raif/evals/llm_judge.rb +32 -0
- data/lib/raif/evals/llm_judges/binary.rb +94 -0
- data/lib/raif/evals/llm_judges/comparative.rb +89 -0
- data/lib/raif/evals/llm_judges/scored.rb +63 -0
- data/lib/raif/evals/llm_judges/summarization.rb +166 -0
- data/lib/raif/evals/run.rb +201 -0
- data/lib/raif/evals/scoring_rubric.rb +174 -0
- data/lib/raif/evals.rb +26 -0
- data/lib/raif/llm_registry.rb +33 -0
- data/lib/raif/migration_checker.rb +3 -3
- data/lib/raif/utils/colors.rb +23 -0
- data/lib/raif/utils.rb +1 -0
- data/lib/raif/version.rb +1 -1
- data/lib/raif.rb +4 -0
- data/spec/support/current_temperature_test_tool.rb +34 -0
- data/spec/support/test_conversation.rb +1 -1
- metadata +37 -3
@@ -1,87 +1,89 @@
|
|
1
|
-
|
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
|
-
|
4
|
-
|
5
|
-
|
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
|
-
|
13
|
-
|
14
|
-
|
15
|
-
#
|
16
|
-
# All attributes will be required and additionalProperties will be set to false.
|
17
|
-
tool_arguments_schema do
|
18
|
-
# 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.
|
19
14
|
#
|
20
|
-
#
|
21
|
-
# boolean :is_red, description: "Whether the widget is red"
|
22
|
-
# integer :rating, description: "A rating of the widget from 1 to 10", minimum: 1, maximum: 10
|
23
|
-
# array :tags, description: "Associated tags" do
|
24
|
-
# items type: "string"
|
25
|
-
# end
|
26
|
-
# end
|
15
|
+
# All attributes will be required and additionalProperties will be set to false.
|
27
16
|
#
|
28
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
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
|
36
37
|
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
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
|
45
46
|
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
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?
|
54
55
|
|
55
|
-
|
56
|
-
|
56
|
+
JSON.pretty_generate(tool_invocation.result)
|
57
|
+
end
|
57
58
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
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
|
63
86
|
|
64
|
-
# When the LLM invokes your tool, this method will be called with a `Raif::ModelToolInvocation` record as an argument.
|
65
|
-
# It should handle the actual execution of the tool.
|
66
|
-
# For example, if you are implementing a GoogleSearch tool, this method should run the actual search
|
67
|
-
# and store the results in the tool_invocation's result JSON column.
|
68
|
-
def process_invocation(tool_invocation)
|
69
|
-
# Extract arguments from tool_invocation.tool_arguments
|
70
|
-
# query = tool_invocation.tool_arguments["query"]
|
71
|
-
#
|
72
|
-
# Process the invocation and perform the desired action
|
73
|
-
# ...
|
74
|
-
#
|
75
|
-
# Store the results in the tool_invocation
|
76
|
-
# tool_invocation.update!(
|
77
|
-
# result: {
|
78
|
-
# # Your result data structure
|
79
|
-
# }
|
80
|
-
# )
|
81
|
-
#
|
82
|
-
# Return the result
|
83
|
-
# tool_invocation.result
|
84
87
|
end
|
85
88
|
end
|
86
|
-
|
87
|
-
end
|
89
|
+
<% end -%>
|
@@ -0,0 +1,10 @@
|
|
1
|
+
<%%#
|
2
|
+
This partial is used to render a model tool invocation to the user in the conversation interface.
|
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
|
+
%>
|
5
|
+
|
6
|
+
<div class="raif-model-tool-invocation">
|
7
|
+
<h5><%%= <%= file_name %>.tool_type.demodulize.titleize %> Result</h5>
|
8
|
+
<pre><%%= JSON.pretty_generate(<%= file_name %>.result || {}) %></pre>
|
9
|
+
<p>Edit this file in <code><%%= __FILE__ %></code> to customize the display of the tool invocation.</p>
|
10
|
+
</div>
|
@@ -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 <
|
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
|
24
|
-
|
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,63 +1,59 @@
|
|
1
|
-
|
2
|
-
|
3
|
-
|
4
|
-
|
5
|
-
|
6
|
-
|
7
|
-
|
8
|
-
|
9
|
-
|
10
|
-
|
11
|
-
|
12
|
-
|
13
|
-
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
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
|
+
# task_run_arg :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
|
-
#
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
37
|
-
|
38
|
-
|
39
|
-
|
40
|
-
|
41
|
-
|
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 -%>
|
@@ -0,0 +1,54 @@
|
|
1
|
+
<% raif_module_namespacing(["Evals", "Tasks"]) 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 "<%= class_name %> produces expected output" do
|
17
|
+
# task = Raif::Tasks::<%= class_name %>.run(
|
18
|
+
# Add your task parameters here that produce the expected output
|
19
|
+
# )
|
20
|
+
|
21
|
+
# The return value of the block determines if the expectation passes or fails
|
22
|
+
# expect "task completes successfully" do
|
23
|
+
# task.completed?
|
24
|
+
# end
|
25
|
+
|
26
|
+
# expect "contains the word 'hello' in the output" do
|
27
|
+
# task.parsed_response.include?("hello")
|
28
|
+
# end
|
29
|
+
|
30
|
+
# Add more specific expectations based on your task's behavior
|
31
|
+
end
|
32
|
+
|
33
|
+
eval "properly handles refusals" do
|
34
|
+
# task = Raif::Tasks::<%= class_name %>.run(
|
35
|
+
# Add your task parameters here to trigger a refusal
|
36
|
+
# )
|
37
|
+
|
38
|
+
# expect "returns exactly the text 'I'm sorry, I can't do that.'" do
|
39
|
+
# task.parsed_response == "I'm sorry, I can't do that."
|
40
|
+
# end
|
41
|
+
end
|
42
|
+
|
43
|
+
eval "<%= class_name %> uses appropriate LLM tools" do
|
44
|
+
# Test that the task uses the expected tools if applicable
|
45
|
+
# task = Raif::Tasks::<%= class_name %>.run(
|
46
|
+
# Add parameters that trigger the use of the expected tools
|
47
|
+
# )
|
48
|
+
|
49
|
+
# Example tool invocation expectations (if your task uses tools):
|
50
|
+
# expect_tool_invocation(task, "tool_name", with: { param: "value" })
|
51
|
+
# expect_no_tool_invocation(task, "unwanted_tool")
|
52
|
+
end
|
53
|
+
end
|
54
|
+
<% end -%>
|
@@ -0,0 +1,39 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Raif
|
4
|
+
module CLI
|
5
|
+
class Base
|
6
|
+
attr_reader :args, :options
|
7
|
+
|
8
|
+
def initialize(args = [])
|
9
|
+
@args = args
|
10
|
+
@options = {}
|
11
|
+
end
|
12
|
+
|
13
|
+
protected
|
14
|
+
|
15
|
+
def find_rails_root
|
16
|
+
current = Dir.pwd
|
17
|
+
|
18
|
+
until File.exist?(File.join(current, "config", "environment.rb"))
|
19
|
+
parent = File.dirname(current)
|
20
|
+
if parent == current
|
21
|
+
puts "Error: Could not find Rails application root"
|
22
|
+
puts "Please run this command from within a Rails application directory"
|
23
|
+
exit 1
|
24
|
+
end
|
25
|
+
|
26
|
+
current = parent
|
27
|
+
end
|
28
|
+
|
29
|
+
current
|
30
|
+
end
|
31
|
+
|
32
|
+
def load_rails_application
|
33
|
+
rails_root = find_rails_root
|
34
|
+
Dir.chdir(rails_root)
|
35
|
+
require File.join(rails_root, "config", "environment")
|
36
|
+
end
|
37
|
+
end
|
38
|
+
end
|
39
|
+
end
|
@@ -0,0 +1,47 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
require_relative "base"
|
5
|
+
|
6
|
+
module Raif
|
7
|
+
module CLI
|
8
|
+
class Evals < Base
|
9
|
+
def run
|
10
|
+
# Set test environment by default for evals
|
11
|
+
ENV["RAILS_ENV"] ||= "test"
|
12
|
+
ENV["RAIF_RUNNING_EVALS"] = "true"
|
13
|
+
|
14
|
+
OptionParser.new do |opts|
|
15
|
+
opts.banner = "Usage: raif evals [options] [FILE_PATHS]"
|
16
|
+
|
17
|
+
opts.on("-e", "--environment ENV", "Rails environment (default: test)") do |env|
|
18
|
+
ENV["RAILS_ENV"] = env
|
19
|
+
end
|
20
|
+
|
21
|
+
opts.on("-h", "--help", "Show this help message") do
|
22
|
+
puts opts
|
23
|
+
exit
|
24
|
+
end
|
25
|
+
end.parse!(args)
|
26
|
+
|
27
|
+
# Parse file paths with optional line numbers
|
28
|
+
file_paths = args.map do |arg|
|
29
|
+
if arg.include?(":")
|
30
|
+
file_path, line_number = arg.split(":", 2)
|
31
|
+
{ file_path: file_path, line_number: line_number.to_i }
|
32
|
+
else
|
33
|
+
{ file_path: arg, line_number: nil }
|
34
|
+
end
|
35
|
+
end if args.any?
|
36
|
+
|
37
|
+
# Find and load Rails application
|
38
|
+
load_rails_application
|
39
|
+
|
40
|
+
require "raif/evals"
|
41
|
+
|
42
|
+
run = Raif::Evals::Run.new(file_paths: file_paths)
|
43
|
+
run.execute
|
44
|
+
end
|
45
|
+
end
|
46
|
+
end
|
47
|
+
end
|
@@ -0,0 +1,27 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require "optparse"
|
4
|
+
require_relative "base"
|
5
|
+
|
6
|
+
module Raif
|
7
|
+
module CLI
|
8
|
+
class EvalsSetup < Base
|
9
|
+
def run
|
10
|
+
OptionParser.new do |opts|
|
11
|
+
opts.banner = "Usage: raif evals:setup [options]"
|
12
|
+
opts.on("-h", "--help", "Show this help message") do
|
13
|
+
puts opts
|
14
|
+
exit
|
15
|
+
end
|
16
|
+
end.parse!(args)
|
17
|
+
|
18
|
+
# Load Rails application to use generators
|
19
|
+
load_rails_application
|
20
|
+
|
21
|
+
# Invoke the Rails generator
|
22
|
+
require "rails/generators"
|
23
|
+
Rails::Generators.invoke("raif:evals:setup", args)
|
24
|
+
end
|
25
|
+
end
|
26
|
+
end
|
27
|
+
end
|
data/lib/raif/cli.rb
ADDED
@@ -0,0 +1,67 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative "cli/base"
|
4
|
+
require_relative "cli/evals"
|
5
|
+
require_relative "cli/evals_setup"
|
6
|
+
|
7
|
+
module Raif
|
8
|
+
module CLI
|
9
|
+
COMMANDS = {
|
10
|
+
"evals" => "Run Raif evaluation sets",
|
11
|
+
"evals:setup" => "Setup Raif evals directory structure",
|
12
|
+
"version" => "Show Raif version",
|
13
|
+
"help" => "Show this help message"
|
14
|
+
}.freeze
|
15
|
+
|
16
|
+
class Runner
|
17
|
+
def initialize(args)
|
18
|
+
@args = args
|
19
|
+
@command = args.shift
|
20
|
+
end
|
21
|
+
|
22
|
+
def run
|
23
|
+
case @command
|
24
|
+
when "evals"
|
25
|
+
Evals.new(@args).run
|
26
|
+
when "evals:setup"
|
27
|
+
EvalsSetup.new(@args).run
|
28
|
+
when "version", "--version", "-v"
|
29
|
+
show_version
|
30
|
+
when "help", "--help", "-h", nil
|
31
|
+
show_help
|
32
|
+
else
|
33
|
+
puts "Unknown command: #{@command}"
|
34
|
+
puts ""
|
35
|
+
show_help
|
36
|
+
exit 1
|
37
|
+
end
|
38
|
+
end
|
39
|
+
|
40
|
+
private
|
41
|
+
|
42
|
+
def show_version
|
43
|
+
require_relative "../raif/version"
|
44
|
+
puts "Raif #{Raif::VERSION}"
|
45
|
+
end
|
46
|
+
|
47
|
+
def show_help
|
48
|
+
puts "Usage: raif COMMAND [options]"
|
49
|
+
puts ""
|
50
|
+
puts "Commands:"
|
51
|
+
COMMANDS.each do |command, description|
|
52
|
+
puts format(" %-12s %s", command, description)
|
53
|
+
end
|
54
|
+
puts ""
|
55
|
+
puts "For help on a specific command:"
|
56
|
+
puts " raif COMMAND --help"
|
57
|
+
puts ""
|
58
|
+
puts "Examples:"
|
59
|
+
puts " raif evals:setup # Setup eval directory structure"
|
60
|
+
puts " raif evals # Run all eval sets in test environment"
|
61
|
+
puts " raif evals CustomerSupportEvalSet # Run specific eval set"
|
62
|
+
puts " raif evals -e development # Run evals in development environment"
|
63
|
+
puts " raif version # Show Raif version"
|
64
|
+
end
|
65
|
+
end
|
66
|
+
end
|
67
|
+
end
|