raif 1.0.0 → 1.2.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 +346 -43
- data/app/assets/builds/raif.css +26 -1
- data/app/assets/stylesheets/raif/admin/stats.scss +12 -0
- data/app/assets/stylesheets/raif/loader.scss +27 -1
- 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 +42 -14
- 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/anthropic/tool_formatting.rb +56 -0
- data/app/models/raif/concerns/llms/bedrock/message_formatting.rb +70 -0
- data/app/models/raif/concerns/llms/bedrock/tool_formatting.rb +37 -0
- data/app/models/raif/concerns/llms/message_formatting.rb +42 -0
- data/app/models/raif/concerns/llms/open_ai/json_schema_validation.rb +138 -0
- data/app/models/raif/concerns/llms/open_ai_completions/message_formatting.rb +41 -0
- data/app/models/raif/concerns/llms/open_ai_completions/tool_formatting.rb +26 -0
- data/app/models/raif/concerns/llms/open_ai_responses/message_formatting.rb +43 -0
- data/app/models/raif/concerns/llms/open_ai_responses/tool_formatting.rb +42 -0
- data/app/models/raif/conversation.rb +28 -7
- data/app/models/raif/conversation_entry.rb +40 -8
- data/app/models/raif/embedding_model.rb +22 -0
- data/app/models/raif/embedding_models/bedrock.rb +34 -0
- data/app/models/raif/embedding_models/open_ai.rb +40 -0
- data/app/models/raif/llm.rb +108 -9
- data/app/models/raif/llms/anthropic.rb +72 -57
- data/app/models/raif/llms/bedrock.rb +165 -0
- data/app/models/raif/llms/open_ai_base.rb +66 -0
- data/app/models/raif/llms/open_ai_completions.rb +100 -0
- data/app/models/raif/llms/open_ai_responses.rb +144 -0
- data/app/models/raif/llms/open_router.rb +88 -0
- data/app/models/raif/model_completion.rb +23 -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 +82 -52
- 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/provider_managed/base.rb +9 -0
- data/app/models/raif/model_tools/provider_managed/code_execution.rb +5 -0
- data/app/models/raif/model_tools/provider_managed/image_generation.rb +5 -0
- data/app/models/raif/model_tools/provider_managed/web_search.rb +5 -0
- data/app/models/raif/model_tools/wikipedia_search.rb +46 -55
- data/app/models/raif/streaming_responses/anthropic.rb +63 -0
- data/app/models/raif/streaming_responses/bedrock.rb +89 -0
- data/app/models/raif/streaming_responses/open_ai_completions.rb +76 -0
- data/app/models/raif/streaming_responses/open_ai_responses.rb +54 -0
- 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/_conversation_entry.html.erb +48 -0
- data/app/views/raif/admin/conversations/show.html.erb +4 -2
- data/app/views/raif/admin/model_completions/_model_completion.html.erb +8 -0
- data/app/views/raif/admin/model_completions/index.html.erb +2 -0
- data/app/views/raif/admin/model_completions/show.html.erb +58 -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/_citations.html.erb +9 -0
- data/app/views/raif/conversation_entries/_conversation_entry.html.erb +22 -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 +14 -3
- data/config/locales/admin.en.yml +16 -0
- data/config/locales/en.yml +47 -3
- data/config/routes.rb +6 -0
- data/db/migrate/20250224234252_create_raif_tables.rb +1 -1
- 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/db/migrate/20250527213016_add_response_id_and_response_array_to_model_completions.rb +14 -0
- data/db/migrate/20250603140622_add_citations_to_raif_model_completions.rb +13 -0
- data/db/migrate/20250603202013_add_stream_response_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 +16 -14
- data/lib/generators/raif/install/templates/initializer.rb +62 -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 +63 -4
- data/lib/raif/embedding_model_registry.rb +83 -0
- data/lib/raif/engine.rb +56 -7
- 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/streaming_error.rb +18 -0
- data/lib/raif/errors/unsupported_feature_error.rb +8 -0
- data/lib/raif/errors.rb +4 -2
- data/lib/raif/json_schema_builder.rb +104 -0
- data/lib/raif/llm_registry.rb +315 -0
- data/lib/raif/migration_checker.rb +74 -0
- data/lib/raif/utils/html_fragment_processor.rb +169 -0
- data/lib/raif/utils.rb +1 -0
- data/lib/raif/version.rb +1 -1
- data/lib/raif.rb +7 -32
- data/lib/tasks/raif_tasks.rake +9 -4
- metadata +62 -12
- data/app/models/raif/llms/bedrock_claude.rb +0 -134
- data/app/models/raif/llms/open_ai.rb +0 -259
- 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
@@ -4,55 +4,104 @@ module Raif
|
|
4
4
|
class Configuration
|
5
5
|
attr_accessor :agent_types,
|
6
6
|
:anthropic_api_key,
|
7
|
-
:
|
7
|
+
:bedrock_models_enabled,
|
8
8
|
:anthropic_models_enabled,
|
9
9
|
:authorize_admin_controller_action,
|
10
10
|
:authorize_controller_action,
|
11
11
|
:aws_bedrock_model_name_prefix,
|
12
12
|
:aws_bedrock_region,
|
13
|
+
:bedrock_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,
|
32
|
+
:streaming_update_chunk_size_threshold,
|
23
33
|
:task_system_prompt_intro,
|
24
34
|
:user_tool_types
|
25
35
|
|
36
|
+
alias_method :anthropic_bedrock_models_enabled, :bedrock_models_enabled
|
37
|
+
alias_method :anthropic_bedrock_models_enabled=, :bedrock_models_enabled=
|
38
|
+
|
39
|
+
alias_method :aws_bedrock_titan_embedding_models_enabled, :bedrock_embedding_models_enabled
|
40
|
+
alias_method :aws_bedrock_titan_embedding_models_enabled=, :bedrock_embedding_models_enabled=
|
41
|
+
|
26
42
|
def initialize
|
27
43
|
# Set default config
|
28
44
|
@agent_types = Set.new(["Raif::Agents::ReActAgent", "Raif::Agents::NativeToolCallingAgent"])
|
29
45
|
@anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
|
30
|
-
@
|
31
|
-
@anthropic_models_enabled =
|
46
|
+
@bedrock_models_enabled = false
|
47
|
+
@anthropic_models_enabled = ENV["ANTHROPIC_API_KEY"].present?
|
32
48
|
@authorize_admin_controller_action = ->{ false }
|
33
49
|
@authorize_controller_action = ->{ false }
|
34
50
|
@aws_bedrock_region = "us-east-1"
|
35
51
|
@aws_bedrock_model_name_prefix = "us"
|
52
|
+
@bedrock_embedding_models_enabled = false
|
36
53
|
@task_system_prompt_intro = "You are a helpful assistant."
|
37
54
|
@conversation_entries_controller = "Raif::ConversationEntriesController"
|
38
55
|
@conversation_system_prompt_intro = "You are a helpful assistant who is collaborating with a teammate."
|
39
56
|
@conversation_types = Set.new(["Raif::Conversation"])
|
40
57
|
@conversations_controller = "Raif::ConversationsController"
|
41
58
|
@current_user_method = :current_user
|
59
|
+
@default_embedding_model_key = "open_ai_text_embedding_3_small"
|
42
60
|
@default_llm_model_key = "open_ai_gpt_4o"
|
43
61
|
@llm_api_requests_enabled = true
|
62
|
+
@llm_request_max_retries = 2
|
63
|
+
@llm_request_retriable_exceptions = [
|
64
|
+
Faraday::ConnectionFailed,
|
65
|
+
Faraday::TimeoutError,
|
66
|
+
Faraday::ServerError,
|
67
|
+
]
|
44
68
|
@model_superclass = "ApplicationRecord"
|
45
69
|
@open_ai_api_key = ENV["OPENAI_API_KEY"]
|
46
|
-
@
|
70
|
+
@open_ai_embedding_models_enabled = ENV["OPENAI_API_KEY"].present?
|
71
|
+
@open_ai_models_enabled = ENV["OPENAI_API_KEY"].present?
|
72
|
+
@open_router_api_key = ENV["OPENROUTER_API_KEY"]
|
73
|
+
@open_router_models_enabled = ENV["OPENROUTER_API_KEY"].present?
|
74
|
+
@open_router_app_name = nil
|
75
|
+
@open_router_site_url = nil
|
76
|
+
@streaming_update_chunk_size_threshold = 25
|
47
77
|
@user_tool_types = []
|
48
78
|
end
|
49
79
|
|
50
80
|
def validate!
|
81
|
+
if Raif.llm_registry.blank?
|
82
|
+
puts <<~EOS
|
83
|
+
|
84
|
+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
85
|
+
No LLMs are enabled in Raif. Make sure you have an API key configured for at least one LLM provider. You can do this by setting an API key in your environment variables or in config/initializers/raif.rb (e.g. ENV["OPENAI_API_KEY"], ENV["ANTHROPIC_API_KEY"], ENV["OPENROUTER_API_KEY"]).
|
86
|
+
|
87
|
+
See the README for more information: https://github.com/CultivateLabs/raif#setup
|
88
|
+
!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!
|
89
|
+
|
90
|
+
EOS
|
91
|
+
|
92
|
+
return
|
93
|
+
end
|
94
|
+
|
51
95
|
unless Raif.available_llm_keys.include?(default_llm_model_key.to_sym)
|
52
96
|
raise Raif::Errors::InvalidConfigError,
|
53
97
|
"Raif.config.default_llm_model_key was set to #{default_llm_model_key}, but must be one of: #{Raif.available_llm_keys.join(", ")}"
|
54
98
|
end
|
55
99
|
|
100
|
+
if Raif.embedding_model_registry.present? && !Raif.available_embedding_model_keys.include?(default_embedding_model_key.to_sym)
|
101
|
+
raise Raif::Errors::InvalidConfigError,
|
102
|
+
"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
|
103
|
+
end
|
104
|
+
|
56
105
|
if authorize_controller_action.respond_to?(:call)
|
57
106
|
authorize_controller_action.freeze
|
58
107
|
else
|
@@ -72,10 +121,20 @@ module Raif
|
|
72
121
|
"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
122
|
end
|
74
123
|
|
124
|
+
if open_ai_embedding_models_enabled && open_ai_api_key.blank?
|
125
|
+
raise Raif::Errors::InvalidConfigError,
|
126
|
+
"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
|
127
|
+
end
|
128
|
+
|
75
129
|
if anthropic_models_enabled && anthropic_api_key.blank?
|
76
130
|
raise Raif::Errors::InvalidConfigError,
|
77
131
|
"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
132
|
end
|
133
|
+
|
134
|
+
if open_router_models_enabled && open_router_api_key.blank?
|
135
|
+
raise Raif::Errors::InvalidConfigError,
|
136
|
+
"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
|
137
|
+
end
|
79
138
|
end
|
80
139
|
|
81
140
|
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::Bedrock => [
|
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
@@ -29,8 +29,20 @@ module Raif
|
|
29
29
|
config.after_initialize do
|
30
30
|
next unless Raif.config.open_ai_models_enabled
|
31
31
|
|
32
|
-
Raif.default_llms[Raif::Llms::
|
33
|
-
Raif.register_llm(Raif::Llms::
|
32
|
+
Raif.default_llms[Raif::Llms::OpenAiCompletions].each do |llm_config|
|
33
|
+
Raif.register_llm(Raif::Llms::OpenAiCompletions, **llm_config)
|
34
|
+
end
|
35
|
+
|
36
|
+
Raif.default_llms[Raif::Llms::OpenAiResponses].each do |llm_config|
|
37
|
+
Raif.register_llm(Raif::Llms::OpenAiResponses, **llm_config)
|
38
|
+
end
|
39
|
+
end
|
40
|
+
|
41
|
+
config.after_initialize do
|
42
|
+
next unless Raif.config.open_ai_embedding_models_enabled
|
43
|
+
|
44
|
+
Raif.default_embedding_models[Raif::EmbeddingModels::OpenAi].each do |embedding_model_config|
|
45
|
+
Raif.register_embedding_model(Raif::EmbeddingModels::OpenAi, **embedding_model_config)
|
34
46
|
end
|
35
47
|
end
|
36
48
|
|
@@ -43,13 +55,30 @@ module Raif
|
|
43
55
|
end
|
44
56
|
|
45
57
|
config.after_initialize do
|
46
|
-
next unless Raif.config.
|
58
|
+
next unless Raif.config.bedrock_models_enabled
|
59
|
+
|
60
|
+
require "aws-sdk-bedrockruntime"
|
61
|
+
|
62
|
+
Raif.default_llms[Raif::Llms::Bedrock].each do |llm_config|
|
63
|
+
Raif.register_llm(Raif::Llms::Bedrock, **llm_config)
|
64
|
+
end
|
65
|
+
end
|
66
|
+
|
67
|
+
config.after_initialize do
|
68
|
+
next unless Raif.config.open_router_models_enabled
|
69
|
+
|
70
|
+
Raif.default_llms[Raif::Llms::OpenRouter].each do |llm_config|
|
71
|
+
Raif.register_llm(Raif::Llms::OpenRouter, **llm_config)
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
config.after_initialize do
|
76
|
+
next unless Raif.config.bedrock_embedding_models_enabled
|
47
77
|
|
48
|
-
require "aws-sdk-bedrock"
|
49
78
|
require "aws-sdk-bedrockruntime"
|
50
79
|
|
51
|
-
Raif.
|
52
|
-
Raif.
|
80
|
+
Raif.default_embedding_models[Raif::EmbeddingModels::Bedrock].each do |embedding_model_config|
|
81
|
+
Raif.register_embedding_model(Raif::EmbeddingModels::Bedrock, **embedding_model_config)
|
53
82
|
end
|
54
83
|
end
|
55
84
|
|
@@ -59,13 +88,33 @@ module Raif
|
|
59
88
|
Raif.config.conversation_types += ["Raif::TestConversation"]
|
60
89
|
|
61
90
|
require "#{Raif::Engine.root}/spec/support/test_llm"
|
62
|
-
Raif.register_llm(Raif::Llms::
|
91
|
+
Raif.register_llm(Raif::Llms::TestLlm, key: :raif_test_llm, api_name: "raif-test-llm")
|
92
|
+
|
93
|
+
require "#{Raif::Engine.root}/spec/support/test_embedding_model"
|
94
|
+
Raif.register_embedding_model(
|
95
|
+
Raif::EmbeddingModels::Test,
|
96
|
+
key: :raif_test_embedding_model,
|
97
|
+
api_name: "raif-test-embedding-model",
|
98
|
+
default_output_vector_size: 1536
|
99
|
+
)
|
63
100
|
end
|
64
101
|
|
65
102
|
config.after_initialize do
|
66
103
|
Raif.config.validate!
|
67
104
|
end
|
68
105
|
|
106
|
+
config.after_initialize do
|
107
|
+
# Check to see if the host app is missing any of our migrations
|
108
|
+
# and print a warning if they are
|
109
|
+
next unless Rails.env.development?
|
110
|
+
next if File.basename($PROGRAM_NAME) == "rake"
|
111
|
+
|
112
|
+
# Skip if we're running inside the engine's own dummy app
|
113
|
+
next if Rails.root.to_s.include?("raif/spec/dummy")
|
114
|
+
|
115
|
+
Raif::MigrationChecker.check_and_warn!
|
116
|
+
end
|
117
|
+
|
69
118
|
initializer "raif.assets" do
|
70
119
|
if Rails.application.config.respond_to?(:assets)
|
71
120
|
Rails.application.config.assets.precompile += [
|
@@ -0,0 +1,18 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module Raif
|
4
|
+
module Errors
|
5
|
+
class StreamingError < StandardError
|
6
|
+
attr_reader :message, :type, :code, :event
|
7
|
+
|
8
|
+
def initialize(message:, type:, event:, code: nil)
|
9
|
+
super
|
10
|
+
|
11
|
+
@message = message
|
12
|
+
@type = type
|
13
|
+
@code = code
|
14
|
+
@event = event
|
15
|
+
end
|
16
|
+
end
|
17
|
+
end
|
18
|
+
end
|
data/lib/raif/errors.rb
CHANGED
@@ -5,5 +5,7 @@ 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"
|
11
|
+
require "raif/errors/streaming_error"
|