raif 1.0.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 (121) hide show
  1. checksums.yaml +7 -0
  2. data/MIT-LICENSE +20 -0
  3. data/README.md +678 -0
  4. data/Rakefile +20 -0
  5. data/app/assets/builds/raif.css +74 -0
  6. data/app/assets/builds/raif_admin.css +266 -0
  7. data/app/assets/config/raif_manifest.js +1 -0
  8. data/app/assets/javascript/raif/controllers/conversations_controller.js +11 -0
  9. data/app/assets/javascript/raif/stream_actions/raif_scroll_to_bottom.js +12 -0
  10. data/app/assets/javascript/raif.js +10 -0
  11. data/app/assets/stylesheets/raif/admin/conversation.scss +64 -0
  12. data/app/assets/stylesheets/raif/loader.scss +85 -0
  13. data/app/assets/stylesheets/raif.scss +1 -0
  14. data/app/assets/stylesheets/raif_admin.scss +299 -0
  15. data/app/controllers/raif/admin/agents_controller.rb +17 -0
  16. data/app/controllers/raif/admin/application_controller.rb +20 -0
  17. data/app/controllers/raif/admin/conversations_controller.rb +17 -0
  18. data/app/controllers/raif/admin/model_completions_controller.rb +17 -0
  19. data/app/controllers/raif/admin/model_tool_invocations_controller.rb +17 -0
  20. data/app/controllers/raif/admin/tasks_controller.rb +23 -0
  21. data/app/controllers/raif/application_controller.rb +20 -0
  22. data/app/controllers/raif/conversation_entries_controller.rb +60 -0
  23. data/app/controllers/raif/conversations_controller.rb +58 -0
  24. data/app/helpers/raif/application_helper.rb +7 -0
  25. data/app/helpers/raif/shared/conversations_helper.rb +13 -0
  26. data/app/jobs/raif/application_job.rb +8 -0
  27. data/app/jobs/raif/conversation_entry_job.rb +30 -0
  28. data/app/models/raif/agent.rb +133 -0
  29. data/app/models/raif/agents/native_tool_calling_agent.rb +127 -0
  30. data/app/models/raif/agents/re_act_agent.rb +121 -0
  31. data/app/models/raif/agents/re_act_step.rb +33 -0
  32. data/app/models/raif/application_record.rb +14 -0
  33. data/app/models/raif/concerns/boolean_timestamp.rb +69 -0
  34. data/app/models/raif/concerns/has_available_model_tools.rb +13 -0
  35. data/app/models/raif/concerns/has_llm.rb +19 -0
  36. data/app/models/raif/concerns/has_requested_language.rb +20 -0
  37. data/app/models/raif/concerns/invokes_model_tools.rb +13 -0
  38. data/app/models/raif/concerns/llm_response_parsing.rb +44 -0
  39. data/app/models/raif/conversation.rb +67 -0
  40. data/app/models/raif/conversation_entry.rb +85 -0
  41. data/app/models/raif/llm.rb +88 -0
  42. data/app/models/raif/llms/anthropic.rb +120 -0
  43. data/app/models/raif/llms/bedrock_claude.rb +134 -0
  44. data/app/models/raif/llms/open_ai.rb +259 -0
  45. data/app/models/raif/model_completion.rb +28 -0
  46. data/app/models/raif/model_tool.rb +69 -0
  47. data/app/models/raif/model_tool_invocation.rb +43 -0
  48. data/app/models/raif/model_tools/agent_final_answer.rb +46 -0
  49. data/app/models/raif/model_tools/fetch_url.rb +57 -0
  50. data/app/models/raif/model_tools/wikipedia_search.rb +78 -0
  51. data/app/models/raif/task.rb +137 -0
  52. data/app/models/raif/user_tool_invocation.rb +29 -0
  53. data/app/views/layouts/raif/admin.html.erb +98 -0
  54. data/app/views/raif/admin/agents/_agent.html.erb +18 -0
  55. data/app/views/raif/admin/agents/_conversation_message.html.erb +15 -0
  56. data/app/views/raif/admin/agents/index.html.erb +33 -0
  57. data/app/views/raif/admin/agents/show.html.erb +131 -0
  58. data/app/views/raif/admin/conversations/_conversation.html.erb +7 -0
  59. data/app/views/raif/admin/conversations/_conversation_entry.html.erb +34 -0
  60. data/app/views/raif/admin/conversations/index.html.erb +32 -0
  61. data/app/views/raif/admin/conversations/show.html.erb +56 -0
  62. data/app/views/raif/admin/model_completions/_model_completion.html.erb +9 -0
  63. data/app/views/raif/admin/model_completions/index.html.erb +34 -0
  64. data/app/views/raif/admin/model_completions/show.html.erb +117 -0
  65. data/app/views/raif/admin/model_tool_invocations/_model_tool_invocation.html.erb +16 -0
  66. data/app/views/raif/admin/model_tool_invocations/index.html.erb +33 -0
  67. data/app/views/raif/admin/model_tool_invocations/show.html.erb +66 -0
  68. data/app/views/raif/admin/tasks/_task.html.erb +19 -0
  69. data/app/views/raif/admin/tasks/index.html.erb +49 -0
  70. data/app/views/raif/admin/tasks/show.html.erb +176 -0
  71. data/app/views/raif/conversation_entries/_conversation_entry.html.erb +26 -0
  72. data/app/views/raif/conversation_entries/_form.html.erb +25 -0
  73. data/app/views/raif/conversation_entries/_form_with_available_tools.html.erb +4 -0
  74. data/app/views/raif/conversation_entries/_form_with_user_tool_invocation.html.erb +18 -0
  75. data/app/views/raif/conversation_entries/_message.html.erb +17 -0
  76. data/app/views/raif/conversation_entries/_model_response_avatar.html.erb +1 -0
  77. data/app/views/raif/conversation_entries/_user_avatar.html.erb +1 -0
  78. data/app/views/raif/conversation_entries/create.turbo_stream.erb +11 -0
  79. data/app/views/raif/conversation_entries/new.turbo_stream.erb +6 -0
  80. data/app/views/raif/conversations/_available_user_tools.html.erb +11 -0
  81. data/app/views/raif/conversations/_full_conversation.html.erb +15 -0
  82. data/app/views/raif/conversations/show.html.erb +1 -0
  83. data/config/i18n-tasks.yml +181 -0
  84. data/config/importmap.rb +6 -0
  85. data/config/initializers/pagy.rb +14 -0
  86. data/config/locales/admin.en.yml +91 -0
  87. data/config/locales/en.yml +50 -0
  88. data/config/routes.rb +22 -0
  89. data/db/migrate/20250224234252_create_raif_tables.rb +114 -0
  90. data/lib/generators/raif/agent/agent_generator.rb +22 -0
  91. data/lib/generators/raif/agent/templates/agent.rb.tt +28 -0
  92. data/lib/generators/raif/conversation/conversation_generator.rb +27 -0
  93. data/lib/generators/raif/conversation/templates/conversation.rb.tt +37 -0
  94. data/lib/generators/raif/install/install_generator.rb +31 -0
  95. data/lib/generators/raif/install/templates/initializer.rb +81 -0
  96. data/lib/generators/raif/model_tool/model_tool_generator.rb +27 -0
  97. data/lib/generators/raif/model_tool/templates/model_tool.rb.tt +74 -0
  98. data/lib/generators/raif/task/task_generator.rb +28 -0
  99. data/lib/generators/raif/task/templates/application_task.rb.tt +7 -0
  100. data/lib/generators/raif/task/templates/task.rb.tt +52 -0
  101. data/lib/generators/raif/views_generator.rb +22 -0
  102. data/lib/raif/configuration.rb +82 -0
  103. data/lib/raif/default_llms.rb +37 -0
  104. data/lib/raif/engine.rb +86 -0
  105. data/lib/raif/errors/action_not_authorized_error.rb +8 -0
  106. data/lib/raif/errors/anthropic/api_error.rb +10 -0
  107. data/lib/raif/errors/invalid_config_error.rb +8 -0
  108. data/lib/raif/errors/invalid_conversation_type_error.rb +8 -0
  109. data/lib/raif/errors/invalid_user_tool_type_error.rb +8 -0
  110. data/lib/raif/errors/open_ai/api_error.rb +10 -0
  111. data/lib/raif/errors/open_ai/json_schema_error.rb +10 -0
  112. data/lib/raif/errors.rb +9 -0
  113. data/lib/raif/languages.rb +33 -0
  114. data/lib/raif/rspec.rb +7 -0
  115. data/lib/raif/utils/html_to_markdown_converter.rb +7 -0
  116. data/lib/raif/utils/readable_content_extractor.rb +61 -0
  117. data/lib/raif/utils.rb +6 -0
  118. data/lib/raif/version.rb +5 -0
  119. data/lib/raif.rb +65 -0
  120. data/lib/tasks/raif_tasks.rake +6 -0
  121. metadata +294 -0
@@ -0,0 +1,82 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ class Configuration
5
+ attr_accessor :agent_types,
6
+ :anthropic_api_key,
7
+ :anthropic_bedrock_models_enabled,
8
+ :anthropic_models_enabled,
9
+ :authorize_admin_controller_action,
10
+ :authorize_controller_action,
11
+ :aws_bedrock_model_name_prefix,
12
+ :aws_bedrock_region,
13
+ :conversation_entries_controller,
14
+ :conversation_system_prompt_intro,
15
+ :conversation_types,
16
+ :conversations_controller,
17
+ :current_user_method,
18
+ :default_llm_model_key,
19
+ :llm_api_requests_enabled,
20
+ :model_superclass,
21
+ :open_ai_api_key,
22
+ :open_ai_models_enabled,
23
+ :task_system_prompt_intro,
24
+ :user_tool_types
25
+
26
+ def initialize
27
+ # Set default config
28
+ @agent_types = Set.new(["Raif::Agents::ReActAgent", "Raif::Agents::NativeToolCallingAgent"])
29
+ @anthropic_api_key = ENV["ANTHROPIC_API_KEY"]
30
+ @anthropic_bedrock_models_enabled = true
31
+ @anthropic_models_enabled = true
32
+ @authorize_admin_controller_action = ->{ false }
33
+ @authorize_controller_action = ->{ false }
34
+ @aws_bedrock_region = "us-east-1"
35
+ @aws_bedrock_model_name_prefix = "us"
36
+ @task_system_prompt_intro = "You are a helpful assistant."
37
+ @conversation_entries_controller = "Raif::ConversationEntriesController"
38
+ @conversation_system_prompt_intro = "You are a helpful assistant who is collaborating with a teammate."
39
+ @conversation_types = Set.new(["Raif::Conversation"])
40
+ @conversations_controller = "Raif::ConversationsController"
41
+ @current_user_method = :current_user
42
+ @default_llm_model_key = "open_ai_gpt_4o"
43
+ @llm_api_requests_enabled = true
44
+ @model_superclass = "ApplicationRecord"
45
+ @open_ai_api_key = ENV["OPENAI_API_KEY"]
46
+ @open_ai_models_enabled = true
47
+ @user_tool_types = []
48
+ end
49
+
50
+ def validate!
51
+ unless Raif.available_llm_keys.include?(default_llm_model_key.to_sym)
52
+ raise Raif::Errors::InvalidConfigError,
53
+ "Raif.config.default_llm_model_key was set to #{default_llm_model_key}, but must be one of: #{Raif.available_llm_keys.join(", ")}"
54
+ end
55
+
56
+ if authorize_controller_action.respond_to?(:call)
57
+ authorize_controller_action.freeze
58
+ else
59
+ raise Raif::Errors::InvalidConfigError,
60
+ "Raif.config.authorize_controller_action must respond to :call and return a boolean"
61
+ end
62
+
63
+ if authorize_admin_controller_action.respond_to?(:call)
64
+ authorize_admin_controller_action.freeze
65
+ else
66
+ raise Raif::Errors::InvalidConfigError,
67
+ "Raif.config.authorize_admin_controller_action must respond to :call and return a boolean"
68
+ end
69
+
70
+ if open_ai_models_enabled && open_ai_api_key.blank?
71
+ raise Raif::Errors::InvalidConfigError,
72
+ "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
+ end
74
+
75
+ if anthropic_models_enabled && anthropic_api_key.blank?
76
+ raise Raif::Errors::InvalidConfigError,
77
+ "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
+ end
79
+ end
80
+
81
+ end
82
+ end
@@ -0,0 +1,37 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ def self.default_llms
5
+ {
6
+ Raif::Llms::OpenAi => [
7
+ {
8
+ key: :open_ai_gpt_4o_mini,
9
+ api_name: "gpt-4o-mini",
10
+ model_provider_settings: { supports_structured_outputs: true }
11
+ },
12
+ {
13
+ key: :open_ai_gpt_4o,
14
+ api_name: "gpt-4o",
15
+ model_provider_settings: { supports_structured_outputs: true }
16
+ },
17
+ {
18
+ key: :open_ai_gpt_3_5_turbo,
19
+ api_name: "gpt-3.5-turbo",
20
+ model_provider_settings: { supports_structured_outputs: false }
21
+ },
22
+ ],
23
+ Raif::Llms::Anthropic => [
24
+ { key: :anthropic_claude_3_7_sonnet, api_name: "claude-3-7-sonnet-latest", max_completion_tokens: 8192 },
25
+ { key: :anthropic_claude_3_5_sonnet, api_name: "claude-3-5-sonnet-latest", max_completion_tokens: 8192 },
26
+ { key: :anthropic_claude_3_5_haiku, api_name: "claude-3-5-haiku-latest", max_completion_tokens: 8192 },
27
+ { key: :anthropic_claude_3_opus, api_name: "claude-3-opus-latest", max_completion_tokens: 4096 },
28
+ ],
29
+ Raif::Llms::BedrockClaude => [
30
+ { key: :bedrock_claude_3_5_sonnet, api_name: "anthropic.claude-3-5-sonnet-20241022-v2:0", max_completion_tokens: 8192 },
31
+ { key: :bedrock_claude_3_7_sonnet, api_name: "anthropic.claude-3-7-sonnet-20250219-v1:0", max_completion_tokens: 8192 },
32
+ { key: :bedrock_claude_3_5_haiku, api_name: "anthropic.claude-3-5-haiku-20241022-v1:0", max_completion_tokens: 8192 },
33
+ { key: :bedrock_claude_3_opus, api_name: "anthropic.claude-3-opus-20240229-v1:0", max_completion_tokens: 4096 },
34
+ ]
35
+ }
36
+ end
37
+ end
@@ -0,0 +1,86 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "factory_bot_rails"
5
+ rescue LoadError # rubocop:disable Lint/SuppressedException
6
+ end
7
+
8
+ module Raif
9
+ class Engine < ::Rails::Engine
10
+ isolate_namespace Raif
11
+
12
+ # If the host app is using FactoryBot, add the factories to the host app so they can be used in host apptests
13
+ if defined?(FactoryBotRails)
14
+ config.factory_bot.definition_file_paths += [File.expand_path("../../../spec/factories/shared", __FILE__)]
15
+ end
16
+
17
+ config.generators do |g|
18
+ g.test_framework :rspec
19
+ g.fixture_replacement :factory_bot
20
+ g.factory_bot dir: "spec/factories"
21
+ end
22
+
23
+ config.after_initialize do
24
+ ActiveSupport.on_load(:action_view) do
25
+ include Raif::Shared::ConversationsHelper
26
+ end
27
+ end
28
+
29
+ config.after_initialize do
30
+ next unless Raif.config.open_ai_models_enabled
31
+
32
+ Raif.default_llms[Raif::Llms::OpenAi].each do |llm_config|
33
+ Raif.register_llm(Raif::Llms::OpenAi, **llm_config)
34
+ end
35
+ end
36
+
37
+ config.after_initialize do
38
+ next unless Raif.config.anthropic_models_enabled
39
+
40
+ Raif.default_llms[Raif::Llms::Anthropic].each do |llm_config|
41
+ Raif.register_llm(Raif::Llms::Anthropic, **llm_config)
42
+ end
43
+ end
44
+
45
+ config.after_initialize do
46
+ next unless Raif.config.anthropic_bedrock_models_enabled
47
+
48
+ require "aws-sdk-bedrock"
49
+ require "aws-sdk-bedrockruntime"
50
+
51
+ Raif.default_llms[Raif::Llms::BedrockClaude].each do |llm_config|
52
+ Raif.register_llm(Raif::Llms::BedrockClaude, **llm_config)
53
+ end
54
+ end
55
+
56
+ config.after_initialize do
57
+ next unless Rails.env.test?
58
+
59
+ Raif.config.conversation_types += ["Raif::TestConversation"]
60
+
61
+ require "#{Raif::Engine.root}/spec/support/test_llm"
62
+ Raif.register_llm(Raif::Llms::Test, key: :raif_test_llm, api_name: "raif-test-llm")
63
+ end
64
+
65
+ config.after_initialize do
66
+ Raif.config.validate!
67
+ end
68
+
69
+ initializer "raif.assets" do
70
+ if Rails.application.config.respond_to?(:assets)
71
+ Rails.application.config.assets.precompile += [
72
+ "raif.js",
73
+ "raif.css",
74
+ "raif_admin.css"
75
+ ]
76
+ end
77
+ end
78
+
79
+ initializer "raif.importmap", before: "importmap" do |app|
80
+ if Rails.application.respond_to?(:importmap)
81
+ app.config.importmap.paths << Raif::Engine.root.join("config/importmap.rb")
82
+ end
83
+ end
84
+
85
+ end
86
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ module Errors
5
+ class ActionNotAuthorizedError < StandardError
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ module Errors
5
+ module Anthropic
6
+ class ApiError < StandardError
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ module Errors
5
+ class InvalidConfigError < StandardError
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ module Errors
5
+ class InvalidConversationTypeError < StandardError
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,8 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ module Errors
5
+ class InvalidUserToolTypeError < StandardError
6
+ end
7
+ end
8
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ module Errors
5
+ module OpenAi
6
+ class ApiError < StandardError
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,10 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ module Errors
5
+ module OpenAi
6
+ class JsonSchemaError < StandardError
7
+ end
8
+ end
9
+ end
10
+ end
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "raif/errors/invalid_config_error"
4
+ require "raif/errors/action_not_authorized_error"
5
+ require "raif/errors/invalid_user_tool_type_error"
6
+ require "raif/errors/invalid_conversation_type_error"
7
+ require "raif/errors/open_ai/json_schema_error"
8
+ require "raif/errors/open_ai/api_error"
9
+ require "raif/errors/anthropic/api_error"
@@ -0,0 +1,33 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ SUPPORTED_LANGUAGES = [
5
+ "ar",
6
+ "da",
7
+ "de",
8
+ "en",
9
+ "es",
10
+ "fi",
11
+ "fr",
12
+ "he",
13
+ "hi",
14
+ "it",
15
+ "ja",
16
+ "ko",
17
+ "nl",
18
+ "no",
19
+ "pl",
20
+ "pt",
21
+ "ru",
22
+ "sv",
23
+ "th",
24
+ "tr",
25
+ "uk",
26
+ "vi",
27
+ "zh",
28
+ ].freeze
29
+
30
+ def self.supported_languages
31
+ SUPPORTED_LANGUAGES
32
+ end
33
+ end
data/lib/raif/rspec.rb ADDED
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative "../../spec/support/rspec_helpers"
4
+ require_relative "../../spec/support/test_model_tool"
5
+ require_relative "../../spec/support/test_conversation"
6
+ require_relative "../../spec/support/test_task"
7
+ require_relative "../../spec/support/test_llm"
@@ -0,0 +1,7 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Raif::Utils::HtmlToMarkdownConverter
4
+ def self.convert(html)
5
+ ReverseMarkdown.convert(html, unknown_tags: :bypass)
6
+ end
7
+ end
@@ -0,0 +1,61 @@
1
+ # frozen_string_literal: true
2
+
3
+ class Raif::Utils::ReadableContentExtractor
4
+ attr_reader :raw_html
5
+
6
+ def initialize(raw_html)
7
+ @raw_html = raw_html
8
+ end
9
+
10
+ TAG_REMOVE_LIST = [
11
+ "a",
12
+ "button",
13
+ "form",
14
+ "iframe",
15
+ "img",
16
+ "input",
17
+ "label",
18
+ "nav",
19
+ "noscript",
20
+ "script",
21
+ "style",
22
+ "svg",
23
+ "footer"
24
+ ]
25
+
26
+ # This will first remove all tags in TAG_REMOVE_LIST and their children.
27
+ # Things in TAG_REMOVE_LIST are things that we do not consider likely to contain readable content.
28
+ # We also call scrub!(:strip) to remove anything unsafe, but leave the text content.
29
+ def extract_readable_content
30
+ body_content = Loofah.html5_document(raw_html).at("body")
31
+ return raw_html unless body_content
32
+
33
+ scrubbed_html = body_content
34
+ .scrub!(readable_content_scrubber)
35
+ .scrub!(empty_node_scrubber)
36
+ .scrub!(:strip)
37
+
38
+ scrubbed_html.inner_html
39
+ end
40
+
41
+ def readable_content_scrubber
42
+ @readable_content_scrubber ||= Loofah::Scrubber.new do |node|
43
+ # if the node is something we don't consider readable content, remove it entirely
44
+ node.remove if TAG_REMOVE_LIST.include?(node.name)
45
+
46
+ # strip all attributes from the tags
47
+ node.attributes.each { |attr| node.remove_attribute(attr.first) }
48
+
49
+ # remove html comments
50
+ node.remove if node.comment?
51
+ end
52
+ end
53
+
54
+ def empty_node_scrubber
55
+ # remove empty nodes from the bottom up so any parents that have only empty children also get removed
56
+ @empty_node_scrubber ||= Loofah::Scrubber.new(direction: :bottom_up) do |node|
57
+ node.remove if node.children.empty? && node.text.strip.empty?
58
+ end
59
+ end
60
+
61
+ end
data/lib/raif/utils.rb ADDED
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif::Utils
4
+ require "raif/utils/readable_content_extractor"
5
+ require "raif/utils/html_to_markdown_converter"
6
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module Raif
4
+ VERSION = "1.0.0"
5
+ end
data/lib/raif.rb ADDED
@@ -0,0 +1,65 @@
1
+ # frozen_string_literal: true
2
+
3
+ require "raif/version"
4
+ require "raif/languages"
5
+ require "raif/engine"
6
+ require "raif/configuration"
7
+ require "raif/errors"
8
+ require "raif/utils"
9
+ require "raif/default_llms"
10
+ require "faraday"
11
+ require "loofah"
12
+ require "pagy"
13
+ require "reverse_markdown"
14
+ require "turbo-rails"
15
+
16
+ module Raif
17
+ class << self
18
+ attr_accessor :configuration
19
+ attr_accessor :llm_registry
20
+
21
+ attr_writer :logger
22
+ end
23
+
24
+ def self.config
25
+ @configuration ||= Raif::Configuration.new
26
+ end
27
+
28
+ def self.configure
29
+ yield(config)
30
+ end
31
+
32
+ def self.logger
33
+ @logger ||= Rails.logger
34
+ end
35
+
36
+ def self.register_llm(llm_class, llm_config)
37
+ llm = llm_class.new(**llm_config)
38
+
39
+ unless llm.valid?
40
+ raise ArgumentError, "The LLM you tried to register is invalid: #{llm.errors.full_messages.join(", ")}"
41
+ end
42
+
43
+ @llm_registry ||= {}
44
+ @llm_registry[llm.key] = llm_config.merge(llm_class: llm_class)
45
+ end
46
+
47
+ def self.llm(model_key)
48
+ llm_config = llm_registry[model_key]
49
+
50
+ if llm_config.nil?
51
+ raise ArgumentError, "No LLM found for model key: #{model_key}. Available models: #{available_llm_keys.join(", ")}"
52
+ end
53
+
54
+ llm_class = llm_config[:llm_class]
55
+ llm_class.new(**llm_config.except(:llm_class))
56
+ end
57
+
58
+ def self.available_llms
59
+ llm_registry.values
60
+ end
61
+
62
+ def self.available_llm_keys
63
+ llm_registry.keys
64
+ end
65
+ end
@@ -0,0 +1,6 @@
1
+ # frozen_string_literal: true
2
+
3
+ # desc "Explaining what the task does"
4
+ # task :raif do
5
+ # # Task goes here
6
+ # end