raif 1.0.0 → 1.1.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 +200 -41
- data/app/assets/stylesheets/raif/admin/stats.scss +12 -0
- data/app/controllers/raif/admin/application_controller.rb +14 -0
- data/app/controllers/raif/admin/stats/tasks_controller.rb +25 -0
- data/app/controllers/raif/admin/stats_controller.rb +19 -0
- data/app/controllers/raif/admin/tasks_controller.rb +18 -2
- data/app/controllers/raif/conversations_controller.rb +5 -1
- data/app/models/raif/agent.rb +11 -9
- data/app/models/raif/agents/native_tool_calling_agent.rb +11 -1
- data/app/models/raif/agents/re_act_agent.rb +6 -0
- data/app/models/raif/concerns/has_available_model_tools.rb +1 -1
- data/app/models/raif/concerns/json_schema_definition.rb +28 -0
- data/app/models/raif/concerns/llm_response_parsing.rb +23 -1
- data/app/models/raif/concerns/llm_temperature.rb +17 -0
- data/app/models/raif/concerns/llms/anthropic/message_formatting.rb +51 -0
- data/app/models/raif/concerns/llms/bedrock_claude/message_formatting.rb +70 -0
- data/app/models/raif/concerns/llms/message_formatting.rb +41 -0
- data/app/models/raif/concerns/llms/open_ai/message_formatting.rb +41 -0
- data/app/models/raif/conversation.rb +11 -3
- data/app/models/raif/conversation_entry.rb +22 -6
- data/app/models/raif/embedding_model.rb +22 -0
- data/app/models/raif/embedding_models/bedrock_titan.rb +34 -0
- data/app/models/raif/embedding_models/open_ai.rb +40 -0
- data/app/models/raif/llm.rb +39 -6
- data/app/models/raif/llms/anthropic.rb +23 -28
- data/app/models/raif/llms/bedrock_claude.rb +33 -19
- data/app/models/raif/llms/open_ai.rb +14 -17
- data/app/models/raif/llms/open_router.rb +93 -0
- data/app/models/raif/model_completion.rb +21 -2
- data/app/models/raif/model_file_input.rb +113 -0
- data/app/models/raif/model_image_input.rb +4 -0
- data/app/models/raif/model_tool.rb +77 -51
- data/app/models/raif/model_tool_invocation.rb +8 -6
- data/app/models/raif/model_tools/agent_final_answer.rb +18 -27
- data/app/models/raif/model_tools/fetch_url.rb +27 -36
- data/app/models/raif/model_tools/wikipedia_search.rb +46 -55
- data/app/models/raif/task.rb +71 -16
- data/app/views/layouts/raif/admin.html.erb +10 -0
- data/app/views/raif/admin/agents/show.html.erb +3 -1
- data/app/views/raif/admin/conversations/_conversation.html.erb +1 -1
- data/app/views/raif/admin/conversations/show.html.erb +3 -1
- data/app/views/raif/admin/model_completions/_model_completion.html.erb +1 -0
- data/app/views/raif/admin/model_completions/index.html.erb +1 -0
- data/app/views/raif/admin/model_completions/show.html.erb +30 -3
- data/app/views/raif/admin/stats/index.html.erb +128 -0
- data/app/views/raif/admin/stats/tasks/index.html.erb +45 -0
- data/app/views/raif/admin/tasks/_task.html.erb +5 -4
- data/app/views/raif/admin/tasks/index.html.erb +20 -2
- data/app/views/raif/admin/tasks/show.html.erb +3 -1
- data/app/views/raif/conversation_entries/_conversation_entry.html.erb +18 -14
- data/app/views/raif/conversation_entries/_form.html.erb +1 -1
- data/app/views/raif/conversation_entries/_form_with_available_tools.html.erb +4 -4
- data/app/views/raif/conversation_entries/_message.html.erb +10 -3
- data/config/locales/admin.en.yml +14 -0
- data/config/locales/en.yml +25 -3
- data/config/routes.rb +6 -0
- data/db/migrate/20250421202149_add_response_format_to_raif_conversations.rb +7 -0
- data/db/migrate/20250424200755_add_cost_columns_to_raif_model_completions.rb +14 -0
- data/db/migrate/20250424232946_add_created_at_indexes.rb +11 -0
- data/db/migrate/20250502155330_add_status_indexes_to_raif_tasks.rb +14 -0
- data/db/migrate/20250507155314_add_retry_count_to_raif_model_completions.rb +7 -0
- data/lib/generators/raif/agent/agent_generator.rb +22 -12
- data/lib/generators/raif/agent/templates/agent.rb.tt +3 -3
- data/lib/generators/raif/agent/templates/application_agent.rb.tt +7 -0
- data/lib/generators/raif/conversation/conversation_generator.rb +10 -0
- data/lib/generators/raif/conversation/templates/application_conversation.rb.tt +7 -0
- data/lib/generators/raif/conversation/templates/conversation.rb.tt +13 -11
- data/lib/generators/raif/install/templates/initializer.rb +50 -6
- data/lib/generators/raif/model_tool/model_tool_generator.rb +0 -5
- data/lib/generators/raif/model_tool/templates/model_tool.rb.tt +69 -56
- data/lib/generators/raif/task/templates/task.rb.tt +34 -23
- data/lib/raif/configuration.rb +40 -3
- data/lib/raif/embedding_model_registry.rb +83 -0
- data/lib/raif/engine.rb +34 -1
- data/lib/raif/errors/{open_ai/api_error.rb → invalid_model_file_input_error.rb} +1 -3
- data/lib/raif/errors/{anthropic/api_error.rb → invalid_model_image_input_error.rb} +1 -3
- data/lib/raif/errors/unsupported_feature_error.rb +8 -0
- data/lib/raif/errors.rb +3 -2
- data/lib/raif/json_schema_builder.rb +104 -0
- data/lib/raif/llm_registry.rb +205 -0
- data/lib/raif/version.rb +1 -1
- data/lib/raif.rb +5 -32
- data/lib/tasks/raif_tasks.rake +9 -4
- metadata +32 -19
- data/lib/raif/default_llms.rb +0 -37
@@ -1,74 +1,87 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
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/
|
6
|
-
# Fetch URL Tool: https://github.com/CultivateLabs/raif/blob/main/app/models/raif/model_tools/
|
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
|
11
|
+
|
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
|
+
tool_arguments_schema do
|
18
|
+
# string :title, description: "The title of the operation", minLength: 3
|
19
|
+
#
|
20
|
+
# object :widget, description: "A widget's description" do
|
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
|
27
|
+
#
|
28
|
+
# array :products, description: "List of products" do
|
29
|
+
# object do
|
30
|
+
# integer :id, description: "Product identifier"
|
31
|
+
# string :name, description: "Product name"
|
32
|
+
# number :price, description: "Product price", minimum: 0
|
33
|
+
# end
|
34
|
+
# end
|
35
|
+
end
|
7
36
|
|
8
37
|
# An example of how the LLM should invoke your tool. This should return a hash with name and arguments keys.
|
9
38
|
# `to_json` will be called on it and provided to the LLM as an example of how to invoke your tool.
|
10
|
-
|
39
|
+
example_model_invocation do
|
11
40
|
{
|
12
41
|
"name": tool_name,
|
13
42
|
"arguments": { }
|
14
43
|
}
|
15
44
|
end
|
16
45
|
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
# For example
|
22
|
-
#
|
23
|
-
|
24
|
-
|
25
|
-
# required: ["query"],
|
26
|
-
# properties: {
|
27
|
-
# query: {
|
28
|
-
# type: "string",
|
29
|
-
# description: "The query to search for"
|
30
|
-
# }
|
31
|
-
# }
|
32
|
-
# }
|
33
|
-
# Would expect the model to invoke your tool with an arguments JSON object like:
|
34
|
-
# { "query" : "some query here" }
|
35
|
-
end
|
46
|
+
class << self
|
47
|
+
# When your tool is invoked by the LLM in a Raif::Agent loop,
|
48
|
+
# the results of the tool invocation are provided back to the LLM as an observation.
|
49
|
+
# This method should return whatever you want provided to the LLM.
|
50
|
+
# For example, if you were implementing a GoogleSearch tool, this might return a JSON
|
51
|
+
# object containing search results for the query.
|
52
|
+
def observation_for_invocation(tool_invocation)
|
53
|
+
return "No results found" unless tool_invocation.result.present?
|
36
54
|
|
37
|
-
|
38
|
-
|
39
|
-
end
|
55
|
+
JSON.pretty_generate(tool_invocation.result)
|
56
|
+
end
|
40
57
|
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
def self.observation_for_invocation(tool_invocation)
|
47
|
-
return "No results found" unless tool_invocation.result.present?
|
48
|
-
|
49
|
-
JSON.pretty_generate(tool_invocation.result)
|
50
|
-
end
|
58
|
+
# When your tool is invoked in a Raif::Conversation, should the result be automatically provided back to the model?
|
59
|
+
# When true, observation_for_invocation will be used to produce the observation provided to the model
|
60
|
+
def triggers_observation_to_model?
|
61
|
+
false
|
62
|
+
end
|
51
63
|
|
52
|
-
|
53
|
-
|
54
|
-
|
55
|
-
|
56
|
-
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
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
|
+
end
|
72
85
|
end
|
73
86
|
|
74
87
|
end
|
@@ -6,39 +6,50 @@ module Raif
|
|
6
6
|
# Set the response format for the task. Options are :html, :text, or :json.
|
7
7
|
llm_response_format :<%= options[:response_format] %>
|
8
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
|
+
|
9
20
|
# Define any attributes that are needed for the task.
|
10
21
|
# You can then pass them when running the task and they will be available in build_prompt:
|
11
22
|
# Raif::Tasks::<%= task_class_name %>.run(your_attribute: "some value")
|
12
23
|
# attr_accessor :your_attribute
|
13
24
|
|
14
25
|
<%- if options[:response_format] == "json" -%>
|
15
|
-
# Define a JSON schema that the response should adhere to
|
16
|
-
#
|
17
|
-
#
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
24
|
-
|
25
|
-
|
26
|
-
|
27
|
-
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
32
|
-
|
33
|
-
|
34
|
-
|
35
|
-
|
36
|
-
|
26
|
+
# Define a JSON schema that the model's response should adhere to
|
27
|
+
#
|
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
|
37
48
|
<%- end -%>
|
38
49
|
|
39
50
|
def build_prompt
|
40
51
|
# Implement the LLM prompt for this task.
|
41
|
-
raise NotImplementedError, "Implement #build_prompt in #{self.class}"
|
52
|
+
raise NotImplementedError, "Implement #build_prompt in #{self.class.name}"
|
42
53
|
end
|
43
54
|
|
44
55
|
# Optional: Override build_system_prompt if you need custom system instructions.
|
data/lib/raif/configuration.rb
CHANGED
@@ -10,16 +10,25 @@ module Raif
|
|
10
10
|
:authorize_controller_action,
|
11
11
|
:aws_bedrock_model_name_prefix,
|
12
12
|
:aws_bedrock_region,
|
13
|
+
:aws_bedrock_titan_embedding_models_enabled,
|
13
14
|
:conversation_entries_controller,
|
14
15
|
:conversation_system_prompt_intro,
|
15
16
|
:conversation_types,
|
16
17
|
:conversations_controller,
|
17
18
|
:current_user_method,
|
19
|
+
:default_embedding_model_key,
|
18
20
|
:default_llm_model_key,
|
19
21
|
:llm_api_requests_enabled,
|
22
|
+
:llm_request_max_retries,
|
23
|
+
:llm_request_retriable_exceptions,
|
20
24
|
:model_superclass,
|
21
25
|
:open_ai_api_key,
|
26
|
+
:open_ai_embedding_models_enabled,
|
22
27
|
:open_ai_models_enabled,
|
28
|
+
:open_router_api_key,
|
29
|
+
:open_router_models_enabled,
|
30
|
+
:open_router_app_name,
|
31
|
+
:open_router_site_url,
|
23
32
|
:task_system_prompt_intro,
|
24
33
|
:user_tool_types
|
25
34
|
|
@@ -27,23 +36,36 @@ module Raif
|
|
27
36
|
# Set default config
|
28
37
|
@agent_types = Set.new(["Raif::Agents::ReActAgent", "Raif::Agents::NativeToolCallingAgent"])
|
29
38
|
@anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
|
30
|
-
@anthropic_bedrock_models_enabled =
|
31
|
-
@anthropic_models_enabled =
|
39
|
+
@anthropic_bedrock_models_enabled = false
|
40
|
+
@anthropic_models_enabled = ENV["ANTHROPIC_API_KEY"].present?
|
32
41
|
@authorize_admin_controller_action = ->{ false }
|
33
42
|
@authorize_controller_action = ->{ false }
|
34
43
|
@aws_bedrock_region = "us-east-1"
|
35
44
|
@aws_bedrock_model_name_prefix = "us"
|
45
|
+
@aws_bedrock_titan_embedding_models_enabled = false
|
36
46
|
@task_system_prompt_intro = "You are a helpful assistant."
|
37
47
|
@conversation_entries_controller = "Raif::ConversationEntriesController"
|
38
48
|
@conversation_system_prompt_intro = "You are a helpful assistant who is collaborating with a teammate."
|
39
49
|
@conversation_types = Set.new(["Raif::Conversation"])
|
40
50
|
@conversations_controller = "Raif::ConversationsController"
|
41
51
|
@current_user_method = :current_user
|
52
|
+
@default_embedding_model_key = "open_ai_text_embedding_3_small"
|
42
53
|
@default_llm_model_key = "open_ai_gpt_4o"
|
43
54
|
@llm_api_requests_enabled = true
|
55
|
+
@llm_request_max_retries = 2
|
56
|
+
@llm_request_retriable_exceptions = [
|
57
|
+
Faraday::ConnectionFailed,
|
58
|
+
Faraday::TimeoutError,
|
59
|
+
Faraday::ServerError,
|
60
|
+
]
|
44
61
|
@model_superclass = "ApplicationRecord"
|
45
62
|
@open_ai_api_key = ENV["OPENAI_API_KEY"]
|
46
|
-
@
|
63
|
+
@open_ai_embedding_models_enabled = ENV["OPENAI_API_KEY"].present?
|
64
|
+
@open_ai_models_enabled = ENV["OPENAI_API_KEY"].present?
|
65
|
+
@open_router_api_key = ENV["OPENROUTER_API_KEY"]
|
66
|
+
@open_router_models_enabled = ENV["OPENROUTER_API_KEY"].present?
|
67
|
+
@open_router_app_name = nil
|
68
|
+
@open_router_site_url = nil
|
47
69
|
@user_tool_types = []
|
48
70
|
end
|
49
71
|
|
@@ -53,6 +75,11 @@ module Raif
|
|
53
75
|
"Raif.config.default_llm_model_key was set to #{default_llm_model_key}, but must be one of: #{Raif.available_llm_keys.join(", ")}"
|
54
76
|
end
|
55
77
|
|
78
|
+
unless Raif.available_embedding_model_keys.include?(default_embedding_model_key.to_sym)
|
79
|
+
raise Raif::Errors::InvalidConfigError,
|
80
|
+
"Raif.config.default_embedding_model_key was set to #{default_embedding_model_key}, but must be one of: #{Raif.available_embedding_model_keys.join(", ")}" # rubocop:disable Layout/LineLength
|
81
|
+
end
|
82
|
+
|
56
83
|
if authorize_controller_action.respond_to?(:call)
|
57
84
|
authorize_controller_action.freeze
|
58
85
|
else
|
@@ -72,10 +99,20 @@ module Raif
|
|
72
99
|
"Raif.config.open_ai_api_key is required when Raif.config.open_ai_models_enabled is true. Set it via Raif.config.open_ai_api_key or ENV[\"OPENAI_API_KEY\"]" # rubocop:disable Layout/LineLength
|
73
100
|
end
|
74
101
|
|
102
|
+
if open_ai_embedding_models_enabled && open_ai_api_key.blank?
|
103
|
+
raise Raif::Errors::InvalidConfigError,
|
104
|
+
"Raif.config.open_ai_api_key is required when Raif.config.open_ai_embedding_models_enabled is true. Set it via Raif.config.open_ai_api_key or ENV[\"OPENAI_API_KEY\"]" # rubocop:disable Layout/LineLength
|
105
|
+
end
|
106
|
+
|
75
107
|
if anthropic_models_enabled && anthropic_api_key.blank?
|
76
108
|
raise Raif::Errors::InvalidConfigError,
|
77
109
|
"Raif.config.anthropic_api_key is required when Raif.config.anthropic_models_enabled is true. Set it via Raif.config.anthropic_api_key or ENV['ANTHROPIC_API_KEY']" # rubocop:disable Layout/LineLength
|
78
110
|
end
|
111
|
+
|
112
|
+
if open_router_models_enabled && open_router_api_key.blank?
|
113
|
+
raise Raif::Errors::InvalidConfigError,
|
114
|
+
"Raif.config.open_router_api_key is required when Raif.config.open_router_models_enabled is true. Set it via Raif.config.open_router_api_key or ENV['OPENROUTER_API_KEY']" # rubocop:disable Layout/LineLength
|
115
|
+
end
|
79
116
|
end
|
80
117
|
|
81
118
|
end
|
@@ -0,0 +1,83 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Raif
|
4
|
+
class << self
|
5
|
+
attr_accessor :embedding_model_registry
|
6
|
+
end
|
7
|
+
|
8
|
+
def self.generate_embedding!(input, dimensions: nil)
|
9
|
+
embedding_model = embedding_model(default_embedding_model_key.to_sym)
|
10
|
+
embedding_model.generate_embedding!(input, dimensions:)
|
11
|
+
end
|
12
|
+
|
13
|
+
def self.default_embedding_model_key
|
14
|
+
Rails.env.test? ? :raif_test_embedding_model : Raif.config.default_embedding_model_key
|
15
|
+
end
|
16
|
+
|
17
|
+
def self.register_embedding_model(embedding_model_class, embedding_model_config)
|
18
|
+
embedding_model = embedding_model_class.new(**embedding_model_config)
|
19
|
+
|
20
|
+
unless embedding_model.valid?
|
21
|
+
raise ArgumentError, "The embedding model you tried to register is invalid: #{embedding_model.errors.full_messages.join(", ")}"
|
22
|
+
end
|
23
|
+
|
24
|
+
@embedding_model_registry ||= {}
|
25
|
+
@embedding_model_registry[embedding_model.key] = embedding_model_config.merge(embedding_model_class: embedding_model_class)
|
26
|
+
end
|
27
|
+
|
28
|
+
def self.embedding_model(model_key)
|
29
|
+
embedding_model_config = embedding_model_registry[model_key]
|
30
|
+
|
31
|
+
if embedding_model_config.nil?
|
32
|
+
raise ArgumentError, "No embedding model found for model key: #{model_key}. Available models: #{available_embedding_model_keys.join(", ")}"
|
33
|
+
end
|
34
|
+
|
35
|
+
embedding_model_class = embedding_model_config[:embedding_model_class]
|
36
|
+
embedding_model_class.new(**embedding_model_config.except(:embedding_model_class))
|
37
|
+
end
|
38
|
+
|
39
|
+
def self.available_embedding_models
|
40
|
+
embedding_model_registry.values
|
41
|
+
end
|
42
|
+
|
43
|
+
def self.available_embedding_model_keys
|
44
|
+
embedding_model_registry.keys
|
45
|
+
end
|
46
|
+
|
47
|
+
def self.embedding_model_config(model_key)
|
48
|
+
embedding_model_registry[model_key]
|
49
|
+
end
|
50
|
+
|
51
|
+
def self.default_embedding_models
|
52
|
+
{
|
53
|
+
Raif::EmbeddingModels::OpenAi => [
|
54
|
+
{
|
55
|
+
key: :open_ai_text_embedding_3_large,
|
56
|
+
api_name: "text-embedding-3-large",
|
57
|
+
input_token_cost: 0.13 / 1_000_000,
|
58
|
+
default_output_vector_size: 3072,
|
59
|
+
},
|
60
|
+
{
|
61
|
+
key: :open_ai_text_embedding_3_small,
|
62
|
+
api_name: "text-embedding-3-small",
|
63
|
+
input_token_cost: 0.02 / 1_000_000,
|
64
|
+
default_output_vector_size: 1536,
|
65
|
+
},
|
66
|
+
{
|
67
|
+
key: :open_ai_text_embedding_ada_002,
|
68
|
+
api_name: "text-embedding-ada-002",
|
69
|
+
input_token_cost: 0.01 / 1_000_000,
|
70
|
+
default_output_vector_size: 1536,
|
71
|
+
},
|
72
|
+
],
|
73
|
+
Raif::EmbeddingModels::BedrockTitan => [
|
74
|
+
{
|
75
|
+
key: :bedrock_titan_embed_text_v2,
|
76
|
+
api_name: "amazon.titan-embed-text-v2:0",
|
77
|
+
input_token_cost: 0.01 / 1_000_000,
|
78
|
+
default_output_vector_size: 1024,
|
79
|
+
},
|
80
|
+
]
|
81
|
+
}
|
82
|
+
end
|
83
|
+
end
|
data/lib/raif/engine.rb
CHANGED
@@ -34,6 +34,14 @@ module Raif
|
|
34
34
|
end
|
35
35
|
end
|
36
36
|
|
37
|
+
config.after_initialize do
|
38
|
+
next unless Raif.config.open_ai_embedding_models_enabled
|
39
|
+
|
40
|
+
Raif.default_embedding_models[Raif::EmbeddingModels::OpenAi].each do |embedding_model_config|
|
41
|
+
Raif.register_embedding_model(Raif::EmbeddingModels::OpenAi, **embedding_model_config)
|
42
|
+
end
|
43
|
+
end
|
44
|
+
|
37
45
|
config.after_initialize do
|
38
46
|
next unless Raif.config.anthropic_models_enabled
|
39
47
|
|
@@ -45,7 +53,6 @@ module Raif
|
|
45
53
|
config.after_initialize do
|
46
54
|
next unless Raif.config.anthropic_bedrock_models_enabled
|
47
55
|
|
48
|
-
require "aws-sdk-bedrock"
|
49
56
|
require "aws-sdk-bedrockruntime"
|
50
57
|
|
51
58
|
Raif.default_llms[Raif::Llms::BedrockClaude].each do |llm_config|
|
@@ -53,6 +60,24 @@ module Raif
|
|
53
60
|
end
|
54
61
|
end
|
55
62
|
|
63
|
+
config.after_initialize do
|
64
|
+
next unless Raif.config.open_router_models_enabled
|
65
|
+
|
66
|
+
Raif.default_llms[Raif::Llms::OpenRouter].each do |llm_config|
|
67
|
+
Raif.register_llm(Raif::Llms::OpenRouter, **llm_config)
|
68
|
+
end
|
69
|
+
end
|
70
|
+
|
71
|
+
config.after_initialize do
|
72
|
+
next unless Raif.config.aws_bedrock_titan_embedding_models_enabled
|
73
|
+
|
74
|
+
require "aws-sdk-bedrockruntime"
|
75
|
+
|
76
|
+
Raif.default_embedding_models[Raif::EmbeddingModels::BedrockTitan].each do |embedding_model_config|
|
77
|
+
Raif.register_embedding_model(Raif::EmbeddingModels::BedrockTitan, **embedding_model_config)
|
78
|
+
end
|
79
|
+
end
|
80
|
+
|
56
81
|
config.after_initialize do
|
57
82
|
next unless Rails.env.test?
|
58
83
|
|
@@ -60,6 +85,14 @@ module Raif
|
|
60
85
|
|
61
86
|
require "#{Raif::Engine.root}/spec/support/test_llm"
|
62
87
|
Raif.register_llm(Raif::Llms::Test, key: :raif_test_llm, api_name: "raif-test-llm")
|
88
|
+
|
89
|
+
require "#{Raif::Engine.root}/spec/support/test_embedding_model"
|
90
|
+
Raif.register_embedding_model(
|
91
|
+
Raif::EmbeddingModels::Test,
|
92
|
+
key: :raif_test_embedding_model,
|
93
|
+
api_name: "raif-test-embedding-model",
|
94
|
+
default_output_vector_size: 1536
|
95
|
+
)
|
63
96
|
end
|
64
97
|
|
65
98
|
config.after_initialize do
|
data/lib/raif/errors.rb
CHANGED
@@ -5,5 +5,6 @@ require "raif/errors/action_not_authorized_error"
|
|
5
5
|
require "raif/errors/invalid_user_tool_type_error"
|
6
6
|
require "raif/errors/invalid_conversation_type_error"
|
7
7
|
require "raif/errors/open_ai/json_schema_error"
|
8
|
-
require "raif/errors/
|
9
|
-
require "raif/errors/
|
8
|
+
require "raif/errors/invalid_model_image_input_error"
|
9
|
+
require "raif/errors/invalid_model_file_input_error"
|
10
|
+
require "raif/errors/unsupported_feature_error"
|
@@ -0,0 +1,104 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Raif
|
4
|
+
class JsonSchemaBuilder
|
5
|
+
attr_reader :properties, :required_properties, :items_schema
|
6
|
+
|
7
|
+
def initialize
|
8
|
+
@properties = {}
|
9
|
+
@required_properties = []
|
10
|
+
@items_schema = nil
|
11
|
+
end
|
12
|
+
|
13
|
+
def string(name, options = {})
|
14
|
+
add_property(name, "string", options)
|
15
|
+
end
|
16
|
+
|
17
|
+
def integer(name, options = {})
|
18
|
+
add_property(name, "integer", options)
|
19
|
+
end
|
20
|
+
|
21
|
+
def number(name, options = {})
|
22
|
+
add_property(name, "number", options)
|
23
|
+
end
|
24
|
+
|
25
|
+
def boolean(name, options = {})
|
26
|
+
add_property(name, "boolean", options)
|
27
|
+
end
|
28
|
+
|
29
|
+
def object(name = nil, options = {}, &block)
|
30
|
+
schema = {}
|
31
|
+
|
32
|
+
if block_given?
|
33
|
+
nested_builder = self.class.new
|
34
|
+
nested_builder.instance_eval(&block)
|
35
|
+
|
36
|
+
schema[:properties] = nested_builder.properties
|
37
|
+
schema[:additionalProperties] = false
|
38
|
+
|
39
|
+
# We currently use strict mode, which means that all properties are required
|
40
|
+
schema[:required] = nested_builder.required_properties
|
41
|
+
end
|
42
|
+
|
43
|
+
# If name is nil, we're inside an array and should return the schema directly
|
44
|
+
if name.nil?
|
45
|
+
@items_schema = { type: "object" }.merge(schema)
|
46
|
+
else
|
47
|
+
add_property(name, "object", options.merge(schema))
|
48
|
+
end
|
49
|
+
end
|
50
|
+
|
51
|
+
def array(name, options = {}, &block)
|
52
|
+
items_schema = options.delete(:items) || {}
|
53
|
+
|
54
|
+
if block_given?
|
55
|
+
nested_builder = self.class.new
|
56
|
+
nested_builder.instance_eval(&block)
|
57
|
+
|
58
|
+
# If items were directly set using the items method
|
59
|
+
if nested_builder.items_schema.present?
|
60
|
+
items_schema = nested_builder.items_schema
|
61
|
+
# If there are properties defined, it's an object schema
|
62
|
+
elsif nested_builder.properties.any?
|
63
|
+
items_schema = {
|
64
|
+
type: "object",
|
65
|
+
properties: nested_builder.properties,
|
66
|
+
additionalProperties: false
|
67
|
+
}
|
68
|
+
|
69
|
+
# We currently use strict mode, which means that all properties are required
|
70
|
+
items_schema[:required] = nested_builder.required_properties
|
71
|
+
end
|
72
|
+
end
|
73
|
+
|
74
|
+
options[:items] = items_schema unless items_schema.empty?
|
75
|
+
add_property(name, "array", options)
|
76
|
+
end
|
77
|
+
|
78
|
+
# Allow setting array items directly
|
79
|
+
def items(options = {})
|
80
|
+
@items_schema = options
|
81
|
+
end
|
82
|
+
|
83
|
+
def to_schema
|
84
|
+
{
|
85
|
+
type: "object",
|
86
|
+
additionalProperties: false,
|
87
|
+
properties: @properties,
|
88
|
+
required: @required_properties
|
89
|
+
}
|
90
|
+
end
|
91
|
+
|
92
|
+
private
|
93
|
+
|
94
|
+
def add_property(name, type, options = {})
|
95
|
+
property = { type: type }
|
96
|
+
|
97
|
+
# We currently use strict mode, which means that all properties are required
|
98
|
+
@required_properties << name.to_s
|
99
|
+
|
100
|
+
property.merge!(options)
|
101
|
+
@properties[name] = property
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|