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.
Files changed (116) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +346 -43
  3. data/app/assets/builds/raif.css +26 -1
  4. data/app/assets/stylesheets/raif/admin/stats.scss +12 -0
  5. data/app/assets/stylesheets/raif/loader.scss +27 -1
  6. data/app/controllers/raif/admin/application_controller.rb +14 -0
  7. data/app/controllers/raif/admin/stats/tasks_controller.rb +25 -0
  8. data/app/controllers/raif/admin/stats_controller.rb +19 -0
  9. data/app/controllers/raif/admin/tasks_controller.rb +18 -2
  10. data/app/controllers/raif/conversations_controller.rb +5 -1
  11. data/app/models/raif/agent.rb +11 -9
  12. data/app/models/raif/agents/native_tool_calling_agent.rb +11 -1
  13. data/app/models/raif/agents/re_act_agent.rb +6 -0
  14. data/app/models/raif/concerns/has_available_model_tools.rb +1 -1
  15. data/app/models/raif/concerns/json_schema_definition.rb +28 -0
  16. data/app/models/raif/concerns/llm_response_parsing.rb +42 -14
  17. data/app/models/raif/concerns/llm_temperature.rb +17 -0
  18. data/app/models/raif/concerns/llms/anthropic/message_formatting.rb +51 -0
  19. data/app/models/raif/concerns/llms/anthropic/tool_formatting.rb +56 -0
  20. data/app/models/raif/concerns/llms/bedrock/message_formatting.rb +70 -0
  21. data/app/models/raif/concerns/llms/bedrock/tool_formatting.rb +37 -0
  22. data/app/models/raif/concerns/llms/message_formatting.rb +42 -0
  23. data/app/models/raif/concerns/llms/open_ai/json_schema_validation.rb +138 -0
  24. data/app/models/raif/concerns/llms/open_ai_completions/message_formatting.rb +41 -0
  25. data/app/models/raif/concerns/llms/open_ai_completions/tool_formatting.rb +26 -0
  26. data/app/models/raif/concerns/llms/open_ai_responses/message_formatting.rb +43 -0
  27. data/app/models/raif/concerns/llms/open_ai_responses/tool_formatting.rb +42 -0
  28. data/app/models/raif/conversation.rb +28 -7
  29. data/app/models/raif/conversation_entry.rb +40 -8
  30. data/app/models/raif/embedding_model.rb +22 -0
  31. data/app/models/raif/embedding_models/bedrock.rb +34 -0
  32. data/app/models/raif/embedding_models/open_ai.rb +40 -0
  33. data/app/models/raif/llm.rb +108 -9
  34. data/app/models/raif/llms/anthropic.rb +72 -57
  35. data/app/models/raif/llms/bedrock.rb +165 -0
  36. data/app/models/raif/llms/open_ai_base.rb +66 -0
  37. data/app/models/raif/llms/open_ai_completions.rb +100 -0
  38. data/app/models/raif/llms/open_ai_responses.rb +144 -0
  39. data/app/models/raif/llms/open_router.rb +88 -0
  40. data/app/models/raif/model_completion.rb +23 -2
  41. data/app/models/raif/model_file_input.rb +113 -0
  42. data/app/models/raif/model_image_input.rb +4 -0
  43. data/app/models/raif/model_tool.rb +82 -52
  44. data/app/models/raif/model_tool_invocation.rb +8 -6
  45. data/app/models/raif/model_tools/agent_final_answer.rb +18 -27
  46. data/app/models/raif/model_tools/fetch_url.rb +27 -36
  47. data/app/models/raif/model_tools/provider_managed/base.rb +9 -0
  48. data/app/models/raif/model_tools/provider_managed/code_execution.rb +5 -0
  49. data/app/models/raif/model_tools/provider_managed/image_generation.rb +5 -0
  50. data/app/models/raif/model_tools/provider_managed/web_search.rb +5 -0
  51. data/app/models/raif/model_tools/wikipedia_search.rb +46 -55
  52. data/app/models/raif/streaming_responses/anthropic.rb +63 -0
  53. data/app/models/raif/streaming_responses/bedrock.rb +89 -0
  54. data/app/models/raif/streaming_responses/open_ai_completions.rb +76 -0
  55. data/app/models/raif/streaming_responses/open_ai_responses.rb +54 -0
  56. data/app/models/raif/task.rb +71 -16
  57. data/app/views/layouts/raif/admin.html.erb +10 -0
  58. data/app/views/raif/admin/agents/show.html.erb +3 -1
  59. data/app/views/raif/admin/conversations/_conversation.html.erb +1 -1
  60. data/app/views/raif/admin/conversations/_conversation_entry.html.erb +48 -0
  61. data/app/views/raif/admin/conversations/show.html.erb +4 -2
  62. data/app/views/raif/admin/model_completions/_model_completion.html.erb +8 -0
  63. data/app/views/raif/admin/model_completions/index.html.erb +2 -0
  64. data/app/views/raif/admin/model_completions/show.html.erb +58 -3
  65. data/app/views/raif/admin/stats/index.html.erb +128 -0
  66. data/app/views/raif/admin/stats/tasks/index.html.erb +45 -0
  67. data/app/views/raif/admin/tasks/_task.html.erb +5 -4
  68. data/app/views/raif/admin/tasks/index.html.erb +20 -2
  69. data/app/views/raif/admin/tasks/show.html.erb +3 -1
  70. data/app/views/raif/conversation_entries/_citations.html.erb +9 -0
  71. data/app/views/raif/conversation_entries/_conversation_entry.html.erb +22 -14
  72. data/app/views/raif/conversation_entries/_form.html.erb +1 -1
  73. data/app/views/raif/conversation_entries/_form_with_available_tools.html.erb +4 -4
  74. data/app/views/raif/conversation_entries/_message.html.erb +14 -3
  75. data/config/locales/admin.en.yml +16 -0
  76. data/config/locales/en.yml +47 -3
  77. data/config/routes.rb +6 -0
  78. data/db/migrate/20250224234252_create_raif_tables.rb +1 -1
  79. data/db/migrate/20250421202149_add_response_format_to_raif_conversations.rb +7 -0
  80. data/db/migrate/20250424200755_add_cost_columns_to_raif_model_completions.rb +14 -0
  81. data/db/migrate/20250424232946_add_created_at_indexes.rb +11 -0
  82. data/db/migrate/20250502155330_add_status_indexes_to_raif_tasks.rb +14 -0
  83. data/db/migrate/20250507155314_add_retry_count_to_raif_model_completions.rb +7 -0
  84. data/db/migrate/20250527213016_add_response_id_and_response_array_to_model_completions.rb +14 -0
  85. data/db/migrate/20250603140622_add_citations_to_raif_model_completions.rb +13 -0
  86. data/db/migrate/20250603202013_add_stream_response_to_raif_model_completions.rb +7 -0
  87. data/lib/generators/raif/agent/agent_generator.rb +22 -12
  88. data/lib/generators/raif/agent/templates/agent.rb.tt +3 -3
  89. data/lib/generators/raif/agent/templates/application_agent.rb.tt +7 -0
  90. data/lib/generators/raif/conversation/conversation_generator.rb +10 -0
  91. data/lib/generators/raif/conversation/templates/application_conversation.rb.tt +7 -0
  92. data/lib/generators/raif/conversation/templates/conversation.rb.tt +16 -14
  93. data/lib/generators/raif/install/templates/initializer.rb +62 -6
  94. data/lib/generators/raif/model_tool/model_tool_generator.rb +0 -5
  95. data/lib/generators/raif/model_tool/templates/model_tool.rb.tt +69 -56
  96. data/lib/generators/raif/task/templates/task.rb.tt +34 -23
  97. data/lib/raif/configuration.rb +63 -4
  98. data/lib/raif/embedding_model_registry.rb +83 -0
  99. data/lib/raif/engine.rb +56 -7
  100. data/lib/raif/errors/{open_ai/api_error.rb → invalid_model_file_input_error.rb} +1 -3
  101. data/lib/raif/errors/{anthropic/api_error.rb → invalid_model_image_input_error.rb} +1 -3
  102. data/lib/raif/errors/streaming_error.rb +18 -0
  103. data/lib/raif/errors/unsupported_feature_error.rb +8 -0
  104. data/lib/raif/errors.rb +4 -2
  105. data/lib/raif/json_schema_builder.rb +104 -0
  106. data/lib/raif/llm_registry.rb +315 -0
  107. data/lib/raif/migration_checker.rb +74 -0
  108. data/lib/raif/utils/html_fragment_processor.rb +169 -0
  109. data/lib/raif/utils.rb +1 -0
  110. data/lib/raif/version.rb +1 -1
  111. data/lib/raif.rb +7 -32
  112. data/lib/tasks/raif_tasks.rake +9 -4
  113. metadata +62 -12
  114. data/app/models/raif/llms/bedrock_claude.rb +0 -134
  115. data/app/models/raif/llms/open_ai.rb +0 -259
  116. 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/wikipedia_search_tool.rb
6
- # Fetch URL Tool: https://github.com/CultivateLabs/raif/blob/main/app/models/raif/model_tools/fetch_url_tool.rb
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
- def self.example_model_invocation
39
+ example_model_invocation do
11
40
  {
12
41
  "name": tool_name,
13
42
  "arguments": { }
14
43
  }
15
44
  end
16
45
 
17
- # Define your tool's argument schema here. It should be a valid JSON schema.
18
- # When the model invokes your tool, the arguments it provides will be validated
19
- # against this schema using JSON::Validator from the json-schema gem.
20
- def self.tool_arguments_schema
21
- # For example:
22
- # {
23
- # type: "object",
24
- # additionalProperties: false,
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
- def self.tool_description
38
- "Description of your tool that will be provided to the LLM so it knows when to invoke it"
39
- end
55
+ JSON.pretty_generate(tool_invocation.result)
56
+ end
40
57
 
41
- # When your tool is invoked by the LLM in a Raif::Agent loop,
42
- # the results of the tool invocation are provided back to the LLM as an observation.
43
- # This method should return whatever you want provided to the LLM.
44
- # For example, if you were implementing a GoogleSearch tool, this might return a JSON
45
- # object containing search results for the query.
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
- # When the LLM invokes your tool, this method will be called with a `Raif::ModelToolInvocation` record as an argument.
53
- # It should handle the actual execution of the tool.
54
- # For example, if you are implementing a GoogleSearch tool, this method should run the actual search
55
- # and store the results in the tool_invocation's result JSON column.
56
- def self.process_invocation(tool_invocation)
57
- # Extract arguments from tool_invocation.tool_arguments
58
- # query = tool_invocation.tool_arguments["query"]
59
- #
60
- # Process the invocation and perform the desired action
61
- # ...
62
- #
63
- # Store the results in the tool_invocation
64
- # tool_invocation.update!(
65
- # result: {
66
- # # Your result data structure
67
- # }
68
- # )
69
- #
70
- # Return the result
71
- # tool_invocation.result
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
- # def self.json_response_schema
17
- # {
18
- # type: "object",
19
- # additionalProperties: false,
20
- # required: ["your_key_here"],
21
- # properties: {
22
- # your_key_here: {
23
- # type: "array",
24
- # items: {
25
- # type: "object",
26
- # additionalProperties: false,
27
- # required: ["query", "rationale"],
28
- # properties: {
29
- # query: { type: "string" },
30
- # rationale: { type: "string" }
31
- # }
32
- # }
33
- # }
34
- # }
35
- # }
36
- # end
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.
@@ -4,55 +4,104 @@ module Raif
4
4
  class Configuration
5
5
  attr_accessor :agent_types,
6
6
  :anthropic_api_key,
7
- :anthropic_bedrock_models_enabled,
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
- @anthropic_bedrock_models_enabled = true
31
- @anthropic_models_enabled = true
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
- @open_ai_models_enabled = true
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::OpenAi].each do |llm_config|
33
- Raif.register_llm(Raif::Llms::OpenAi, **llm_config)
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.anthropic_bedrock_models_enabled
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.default_llms[Raif::Llms::BedrockClaude].each do |llm_config|
52
- Raif.register_llm(Raif::Llms::BedrockClaude, **llm_config)
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::Test, key: :raif_test_llm, api_name: "raif-test-llm")
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 += [
@@ -2,9 +2,7 @@
2
2
 
3
3
  module Raif
4
4
  module Errors
5
- module OpenAi
6
- class ApiError < StandardError
7
- end
5
+ class InvalidModelFileInputError < StandardError
8
6
  end
9
7
  end
10
8
  end
@@ -2,9 +2,7 @@
2
2
 
3
3
  module Raif
4
4
  module Errors
5
- module Anthropic
6
- class ApiError < StandardError
7
- end
5
+ class InvalidModelImageInputError < StandardError
8
6
  end
9
7
  end
10
8
  end
@@ -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
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ module Errors
5
+ class UnsupportedFeatureError < StandardError
6
+ end
7
+ end
8
+ 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/open_ai/api_error"
9
- require "raif/errors/anthropic/api_error"
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"