active_genie 0.30.1 → 0.30.8

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 (59) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +7 -5
  3. data/VERSION +1 -1
  4. data/lib/active_genie/comparator/debate.rb +13 -31
  5. data/lib/active_genie/comparator/fight.rb +3 -3
  6. data/lib/active_genie/comparator.rb +0 -2
  7. data/lib/active_genie/configs/base_config.rb +25 -0
  8. data/lib/active_genie/configs/extractor_config.rb +6 -14
  9. data/lib/active_genie/configs/lister_config.rb +6 -10
  10. data/lib/active_genie/configs/llm_config.rb +12 -25
  11. data/lib/active_genie/configs/log_config.rb +19 -16
  12. data/lib/active_genie/configs/providers/anthropic_config.rb +10 -16
  13. data/lib/active_genie/configs/providers/deepseek_config.rb +4 -19
  14. data/lib/active_genie/configs/providers/google_config.rb +4 -19
  15. data/lib/active_genie/configs/providers/openai_config.rb +4 -19
  16. data/lib/active_genie/configs/providers/provider_base.rb +21 -67
  17. data/lib/active_genie/configs/providers_config.rb +39 -20
  18. data/lib/active_genie/configs/ranker_config.rb +6 -12
  19. data/lib/active_genie/configuration.rb +23 -60
  20. data/lib/active_genie/{ranker/entities → entities}/player.rb +4 -0
  21. data/lib/active_genie/{ranker/entities → entities}/players.rb +1 -1
  22. data/lib/active_genie/entities/result.rb +29 -0
  23. data/lib/active_genie/errors/invalid_model_error.rb +42 -0
  24. data/lib/active_genie/errors/invalid_provider_error.rb +1 -1
  25. data/lib/active_genie/errors/provider_server_error.rb +26 -0
  26. data/lib/active_genie/errors/without_available_provider_error.rb +39 -0
  27. data/lib/active_genie/extractor/data.json +9 -0
  28. data/lib/active_genie/extractor/data.prompt.md +12 -0
  29. data/lib/active_genie/extractor/data.rb +71 -0
  30. data/lib/active_genie/extractor/explanation.rb +22 -41
  31. data/lib/active_genie/extractor/litote.rb +10 -9
  32. data/lib/active_genie/extractor.rb +5 -0
  33. data/lib/active_genie/lister/feud.json +5 -1
  34. data/lib/active_genie/lister/feud.rb +12 -17
  35. data/lib/active_genie/lister/juries.rb +18 -16
  36. data/lib/active_genie/logger.rb +16 -28
  37. data/lib/active_genie/providers/anthropic_provider.rb +12 -6
  38. data/lib/active_genie/providers/base_provider.rb +15 -18
  39. data/lib/active_genie/providers/deepseek_provider.rb +18 -10
  40. data/lib/active_genie/providers/google_provider.rb +11 -5
  41. data/lib/active_genie/providers/openai_provider.rb +8 -6
  42. data/lib/active_genie/providers/unified_provider.rb +58 -3
  43. data/lib/active_genie/ranker/elo.rb +41 -36
  44. data/lib/active_genie/ranker/free_for_all.rb +45 -28
  45. data/lib/active_genie/ranker/scoring.rb +20 -11
  46. data/lib/active_genie/ranker/tournament.rb +23 -35
  47. data/lib/active_genie/scorer/jury_bench.rb +18 -23
  48. data/lib/active_genie/utils/base_module.rb +34 -0
  49. data/lib/active_genie/utils/call_wrapper.rb +20 -0
  50. data/lib/active_genie/utils/deep_merge.rb +12 -0
  51. data/lib/active_genie/utils/fiber_by_batch.rb +2 -2
  52. data/lib/active_genie/utils/text_case.rb +18 -0
  53. data/lib/active_genie.rb +16 -18
  54. data/lib/tasks/benchmark.rake +1 -3
  55. data/lib/tasks/templates/active_genie.rb +0 -3
  56. data/lib/tasks/test.rake +62 -1
  57. metadata +25 -15
  58. data/lib/active_genie/configs/comparator_config.rb +0 -10
  59. data/lib/active_genie/configs/scorer_config.rb +0 -10
@@ -1,22 +1,34 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'base_config'
4
+ require_relative 'providers/openai_config'
5
+ require_relative 'providers/google_config'
6
+ require_relative 'providers/anthropic_config'
7
+ require_relative 'providers/deepseek_config'
8
+
3
9
  module ActiveGenie
4
10
  module Config
5
- class ProvidersConfig
6
- def initialize
7
- @all = {}
8
- @default = nil
11
+ class ProvidersConfig < BaseConfig
12
+ def initialize(**args)
13
+ @all = {
14
+ openai: Providers::OpenaiConfig.new(**args.fetch(:openai, {})),
15
+ google: Providers::GoogleConfig.new(**args.fetch(:google, {})),
16
+ anthropic: Providers::AnthropicConfig.new(**args.fetch(:anthropic, {})),
17
+ deepseek: Providers::DeepseekConfig.new(**args.fetch(:deepseek, {}))
18
+ }
19
+ super
9
20
  end
10
21
 
11
- attr_reader :all
12
-
13
22
  def default
14
- @default || ENV.fetch('PROVIDER_NAME', nil) || valid.keys.first
23
+ @default ||= ENV.fetch('PROVIDER_NAME', nil)&.to_s&.downcase&.strip
15
24
  end
16
25
 
17
26
  def default=(provider)
18
- normalized_provider = provider.to_s.downcase.strip
19
- @default = normalized_provider.size.positive? ? normalized_provider : valid.keys.first
27
+ @default = provider&.to_s&.downcase&.strip
28
+ end
29
+
30
+ def all
31
+ @all ||= {}
20
32
  end
21
33
 
22
34
  def valid
@@ -24,31 +36,29 @@ module ActiveGenie
24
36
  @all.slice(*valid_provider_keys)
25
37
  end
26
38
 
27
- def add(provider_classes)
39
+ def add(name, provider_configs)
28
40
  @all ||= {}
29
- Array(provider_classes).each do |provider|
30
- name = provider::NAME
41
+ Array(provider_configs).each do |provider_config|
31
42
  remove([name]) if @all.key?(name)
32
43
 
33
- @all[name] = provider.new
44
+ @all[name] = provider_config.new
34
45
  end
35
46
 
36
47
  self
37
48
  end
38
49
 
39
- def remove(provider_classes)
40
- Array(provider_classes).each do |provider|
50
+ def remove(provider_configs)
51
+ Array(provider_configs).each do |provider|
41
52
  @all.delete(provider::NAME)
42
53
  end
43
54
 
44
55
  self
45
56
  end
46
57
 
47
- def merge(config_params = {})
48
- dup.tap do |config|
49
- config.add(config_params[:providers]) if config_params[:providers]
50
- config.default = config_params[:default] if config_params[:default]
51
- end
58
+ def provider_name_by_model(model)
59
+ return nil if model.nil?
60
+
61
+ valid.find { |_, config| config.valid_model?(model) }&.first
52
62
  end
53
63
 
54
64
  def method_missing(method_name, *args, &)
@@ -58,6 +68,15 @@ module ActiveGenie
58
68
  def respond_to_missing?(method_name, include_private = false)
59
69
  @all.key?(method_name) || super
60
70
  end
71
+
72
+ def to_h
73
+ h = {}
74
+ @all.each do |key, config|
75
+ h[key] = config.to_h
76
+ end
77
+
78
+ super.merge(h)
79
+ end
61
80
  end
62
81
  end
63
82
  end
@@ -1,20 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative 'base_config'
4
+
3
5
  module ActiveGenie
4
6
  module Config
5
- class RankerConfig
6
- attr_accessor :score_variation_threshold
7
-
8
- def initialize
9
- @score_variation_threshold = 30
10
- end
7
+ class RankerConfig < BaseConfig
8
+ attr_writer :score_variation_threshold
11
9
 
12
- def merge(config_params = {})
13
- dup.tap do |config|
14
- if config_params[:score_variation_threshold]
15
- config.score_variation_threshold = config_params[:score_variation_threshold]
16
- end
17
- end
10
+ def score_variation_threshold
11
+ @score_variation_threshold ||= 30
18
12
  end
19
13
  end
20
14
  end
@@ -1,90 +1,53 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'configs/comparator_config'
4
3
  require_relative 'configs/extractor_config'
5
4
  require_relative 'configs/lister_config'
6
5
  require_relative 'configs/llm_config'
7
6
  require_relative 'configs/log_config'
8
7
  require_relative 'configs/providers_config'
9
8
  require_relative 'configs/ranker_config'
10
- require_relative 'configs/scorer_config'
11
9
 
12
10
  module ActiveGenie
13
11
  class Configuration
14
- def log
15
- @log ||= Config::LogConfig.new
12
+ def initialize(initial_config = nil)
13
+ @initial_config = initial_config || {}
16
14
  end
17
15
 
18
16
  def providers
19
- @providers ||= Config::ProvidersConfig.new
20
- end
21
-
22
- def ranker
23
- @ranker ||= Config::RankerConfig.new
24
- end
25
-
26
- def scorer
27
- @scorer ||= Config::ScorerConfig.new
17
+ @providers ||= Config::ProvidersConfig.new(**@initial_config.fetch(:providers, {}))
28
18
  end
29
19
 
30
- def extractor
31
- @extractor ||= Config::ExtractorConfig.new
20
+ def llm
21
+ @llm ||= Config::LlmConfig.new(**@initial_config.fetch(:llm, {}))
32
22
  end
33
23
 
34
- def comparator
35
- @comparator ||= Config::ComparatorConfig.new
24
+ def log
25
+ @log ||= Config::LogConfig.new(**@initial_config.fetch(:log, {}))
36
26
  end
37
27
 
38
- def lister
39
- @lister ||= Config::ListerConfig.new
40
- end
28
+ # Modules
41
29
 
42
- def llm
43
- @llm ||= Config::LlmConfig.new
30
+ def ranker
31
+ @ranker ||= Config::RankerConfig.new(**@initial_config.fetch(:ranker, {}))
44
32
  end
45
33
 
46
- def logger
47
- @logger ||= ActiveGenie::Logger.new(log_config: log)
34
+ def extractor
35
+ @extractor ||= Config::ExtractorConfig.new(**@initial_config.fetch(:extractor, {}))
48
36
  end
49
37
 
50
- SUB_CONFIGS = %w[log providers llm ranker scorer extractor comparator lister].freeze
51
-
52
- def merge(config_params = {})
53
- return config_params if config_params.is_a?(Configuration)
54
-
55
- new_configuration = dup
56
-
57
- SUB_CONFIGS.each do |key|
58
- config = new_configuration.send(key)
59
-
60
- next unless config.respond_to?(:merge)
61
-
62
- new_config = sub_config_merge(config, key, config_params)
63
-
64
- new_configuration.send("#{key}=", new_config)
65
- end
66
- @logger = nil
67
-
68
- new_configuration
38
+ def lister
39
+ @lister ||= Config::ListerConfig.new(**@initial_config.fetch(:lister, {}))
69
40
  end
70
41
 
71
- # Merges a sub config with the config_params.
72
- # Examples:
73
- # config.merge({ 'log' => { file_path: 'log/active_genie.log' } })
74
- # config.merge({ log: { file_path: 'log/active_genie.log' } })
75
- # config.merge({ file_path: 'log/active_genie.log' })
76
- # these are all the same
77
-
78
- def sub_config_merge(config, key, config_params)
79
- if config_params&.key?(key.to_s)
80
- config.merge(config_params[key.to_s])
81
- elsif config_params&.key?(key.to_sym)
82
- config.merge(config_params[key.to_sym])
83
- else
84
- config.merge(config_params || {})
85
- end
42
+ def to_h
43
+ {
44
+ providers: providers.to_h,
45
+ llm: llm.to_h,
46
+ log: log.to_h,
47
+ ranker: ranker.to_h,
48
+ extractor: extractor.to_h,
49
+ lister: lister.to_h
50
+ }
86
51
  end
87
-
88
- attr_writer(*SUB_CONFIGS, :logger)
89
52
  end
90
53
  end
@@ -92,6 +92,10 @@ module ActiveGenie
92
92
  }
93
93
  end
94
94
 
95
+ def to_s
96
+ content.to_s
97
+ end
98
+
95
99
  def method_missing(method_name, *args, &)
96
100
  if method_name == :[] && args.size == 1
97
101
  attr_name = args.first.to_sym
@@ -90,7 +90,7 @@ module ActiveGenie
90
90
  def tier_size
91
91
  size = (eligible_size / 3).ceil
92
92
 
93
- if eligible_size < 10
93
+ if eligible_size < 10 * 2
94
94
  (eligible_size / 2).ceil
95
95
  else
96
96
  size.clamp(10, eligible_size - 10)
@@ -0,0 +1,29 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveGenie
4
+ class Result
5
+ attr_reader :data, :reasoning, :metadata
6
+
7
+ def initialize(data:, metadata:, reasoning: nil)
8
+ @data = data
9
+ @reasoning = reasoning
10
+ @metadata = metadata
11
+ end
12
+
13
+ def explanation
14
+ @reasoning
15
+ end
16
+
17
+ def to_h
18
+ { data: @data, reasoning: @reasoning, metadata: @metadata }
19
+ end
20
+
21
+ def to_s
22
+ to_h.to_s
23
+ end
24
+
25
+ def to_json(...)
26
+ to_h.to_json(...)
27
+ end
28
+ end
29
+ end
@@ -0,0 +1,42 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveGenie
4
+ class InvalidModelError < StandardError
5
+ TEXT = <<~TEXT
6
+ Invalid model: %<model>s
7
+
8
+ To configure ActiveGenie, you can either:
9
+ 1. Set up global configuration:
10
+ ```ruby
11
+ ActiveGenie.configure do |config|
12
+ config.providers.openai.api_key = 'your_api_key'
13
+ config.llm.model = 'gpt-5'
14
+ # ... other configuration options
15
+ end
16
+ ```
17
+
18
+ 2. Or pass configuration directly to the method call:
19
+ ```ruby
20
+ ActiveGenie::Extraction.call(
21
+ arg1,
22
+ arg2,
23
+ config: {
24
+ providers: {
25
+ openai: {
26
+ api_key: 'your_api_key'
27
+ }
28
+ },
29
+ llm: {
30
+ model: 'gpt-5'
31
+ }
32
+ }
33
+ )
34
+ ```
35
+
36
+ TEXT
37
+
38
+ def initialize(model)
39
+ super(format(TEXT, model:))
40
+ end
41
+ end
42
+ end
@@ -39,7 +39,7 @@ module ActiveGenie
39
39
  end
40
40
 
41
41
  def available_providers
42
- ActiveGenie.configuration.providers.all.keys.join(', ')
42
+ ActiveGenie.configuration.providers.valid.keys.join(', ')
43
43
  end
44
44
  end
45
45
  end
@@ -0,0 +1,26 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveGenie
4
+ class ProviderServerError < StandardError
5
+ TEXT = <<~TEXT
6
+ Provider server error: %<code>s
7
+ %<body>s
8
+
9
+ Providers errors are common and can occur for various reasons, such as:
10
+ - Invalid API key
11
+ - Exceeded usage limits
12
+ - Temporary server issues
13
+
14
+ Be ready to handle these errors gracefully in your application. We recommend implementing retry logic and exponential backoff strategies.
15
+ Usually async workers layer is the ideal place to handle such errors.
16
+ TEXT
17
+
18
+ def initialize(response)
19
+ super(format(
20
+ TEXT,
21
+ code: response&.code.to_s,
22
+ body: response&.body.to_s.strip.empty? ? '(no body)' : response&.body.to_s
23
+ ))
24
+ end
25
+ end
26
+ end
@@ -0,0 +1,39 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveGenie
4
+ class WithoutAvailableProviderError < StandardError
5
+ TEXT = <<~TEXT
6
+ Missing at least one credentialed provider to proceed.
7
+
8
+ To configure ActiveGenie, you can either:
9
+ 1. Set up global configuration:
10
+ ```ruby
11
+ ActiveGenie.configure do |config|
12
+ config.providers.default = 'openai'
13
+ config.providers.openai.api_key = 'your_api_key'
14
+ # ... other configuration options
15
+ end
16
+ ```
17
+
18
+ 2. Or pass configuration directly to the method call:
19
+ ```ruby
20
+ ActiveGenie::Extractor.call(
21
+ arg1,
22
+ arg2,
23
+ config: {
24
+ providers: {
25
+ default: 'openai',
26
+ openai: {
27
+ api_key: 'your_api_key'
28
+ }
29
+ }
30
+ }
31
+ )
32
+ ```
33
+ TEXT
34
+
35
+ def initialize
36
+ super(TEXT)
37
+ end
38
+ end
39
+ end
@@ -0,0 +1,9 @@
1
+ {
2
+ "name": "data_extractor",
3
+ "description": "Extract structured and typed data from text",
4
+ "parameters": {
5
+ "type": "object",
6
+ "properties": {},
7
+ "required": {}
8
+ }
9
+ }
@@ -0,0 +1,12 @@
1
+ Extract structured and typed data from user messages.
2
+ Identify relevant information within user messages and categorize it into predefined data fields with specific data types.
3
+
4
+ # Steps
5
+ 1. **Identify Data Types**: Determine the types of data to collect, such as names, dates, email addresses, phone numbers, etc.
6
+ 2. **Extract Information**: Use pattern recognition and language understanding to identify and extract the relevant pieces of data from the user message.
7
+ 3. **Categorize Data**: Assign the extracted data to the appropriate predefined fields.
8
+
9
+ # Notes
10
+ - Handle missing or partial information gracefully.
11
+ - Manage multiple occurrences of similar data points by prioritizing the first one unless specified otherwise.
12
+ - Be flexible to handle variations in data format and language clues.
@@ -0,0 +1,71 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../providers/unified_provider'
4
+
5
+ module ActiveGenie
6
+ module Extractor
7
+ class Data < ActiveGenie::BaseModule
8
+ # Extracts structured data from text based on a predefined schema.
9
+ #
10
+ # @param text [String] The input text to analyze and extract data from
11
+ # @param data_to_extract [Hash] Schema defining the data structure to extract.
12
+ # Each key in the hash represents a field to extract, and its value defines the expected type and constraints.
13
+ # @param config [Hash] Additional config for the extraction process
14
+ #
15
+ # @return [Hash] The extracted data matching the schema structure. Each field will include
16
+ # both the extracted value.
17
+ #
18
+ # @example Extract a person's details
19
+ # schema = {
20
+ # name: { type: 'string', description: 'Full name of the person' },
21
+ # age: { type: 'integer', description: 'Age in years' }
22
+ # }
23
+ # text = "John Doe is 25 years old"
24
+ # Extractor.with_explanation(text, schema)
25
+ # # => { name: "John Doe", age: 25 }
26
+ def initialize(text, data_to_extract, config: {})
27
+ @text = text
28
+ @data_to_extract = data_to_extract
29
+ super(config:)
30
+ end
31
+
32
+ def call
33
+ messages = [
34
+ { role: 'system', content: prompt },
35
+ { role: 'user', content: @text }
36
+ ]
37
+
38
+ function = JSON.parse(File.read(File.join(__dir__, 'data.json')), symbolize_names: true)
39
+ function[:parameters][:properties] = @data_to_extract
40
+ function[:parameters][:required] = @data_to_extract.keys
41
+
42
+ provider_response = ::ActiveGenie::Providers::UnifiedProvider.function_calling(
43
+ messages,
44
+ function,
45
+ config: config
46
+ )
47
+
48
+ response_formatted(provider_response)
49
+ end
50
+
51
+ private
52
+
53
+ def response_formatted(provider_response)
54
+ data = provider_response.compact
55
+
56
+ ActiveGenie::Result.new(
57
+ data:,
58
+ metadata: provider_response
59
+ )
60
+ end
61
+
62
+ def prompt
63
+ File.read(File.join(__dir__, 'data.prompt.md'))
64
+ end
65
+
66
+ def module_config
67
+ { llm: { recommended_model: 'deepseek-chat' } }
68
+ end
69
+ end
70
+ end
71
+ end
@@ -1,14 +1,11 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative '../providers/unified_provider'
4
+ require_relative '../utils/deep_merge'
4
5
 
5
6
  module ActiveGenie
6
7
  module Extractor
7
- class Explanation
8
- def self.call(...)
9
- new(...).call
10
- end
11
-
8
+ class Explanation < ActiveGenie::BaseModule
12
9
  # Extracts structured data from text based on a predefined schema.
13
10
  #
14
11
  # @param text [String] The input text to analyze and extract data from
@@ -31,7 +28,7 @@ module ActiveGenie
31
28
  def initialize(text, data_to_extract, config: {})
32
29
  @text = text
33
30
  @data_to_extract = data_to_extract
34
- @config = ActiveGenie.configuration.merge(config)
31
+ super(config:)
35
32
  end
36
33
 
37
34
  def call
@@ -46,16 +43,18 @@ module ActiveGenie
46
43
  function[:parameters][:properties] = properties
47
44
  function[:parameters][:required] = properties.keys
48
45
 
49
- response = function_calling(messages, function)
46
+ provider_response = ::ActiveGenie::Providers::UnifiedProvider.function_calling(
47
+ messages,
48
+ function,
49
+ config: config
50
+ )
50
51
 
51
- simplify_response(response)
52
+ response_formatted(provider_response)
52
53
  end
53
54
 
54
55
  private
55
56
 
56
57
  def data_to_extract_with_explanation
57
- return @data_to_extract unless @config.extractor.with_explanation
58
-
59
58
  with_explanation = {}
60
59
 
61
60
  @data_to_extract.each do |key, value|
@@ -80,47 +79,29 @@ module ActiveGenie
80
79
  with_explanation
81
80
  end
82
81
 
83
- def function_calling(messages, function)
84
- response = ::ActiveGenie::Providers::UnifiedProvider.function_calling(
85
- messages,
86
- function,
87
- config: @config
88
- )
82
+ def response_formatted(provider_response)
83
+ data = provider_response.slice(*@data_to_extract.keys.map(&:to_s)).transform_keys(&:to_sym)
89
84
 
90
- @config.logger.call(
91
- {
92
- code: :extractor,
93
- text: @text[0..30],
94
- data_to_extract: function[:parameters][:properties],
95
- extracted_data: response
96
- }
97
- )
85
+ first_reasoning_key = provider_response["#{provider_response.keys.first}_explanation"]
98
86
 
99
- response
100
- end
101
-
102
- def simplify_response(response)
103
- return response if @config.extractor.verbose
104
-
105
- simplified_response = {}
106
-
107
- @data_to_extract.each_key do |key|
108
- next unless response.key?(key.to_s)
109
- next if response.key?("#{key}_accuracy") && response["#{key}_accuracy"] < min_accuracy
110
-
111
- simplified_response[key] = response[key.to_s]
112
- end
113
-
114
- simplified_response
87
+ ActiveGenie::Result.new(
88
+ data:,
89
+ reasoning: first_reasoning_key,
90
+ metadata: provider_response
91
+ )
115
92
  end
116
93
 
117
94
  def min_accuracy
118
- @config.extractor.min_accuracy # default 70
95
+ config.extractor.min_accuracy # default 70
119
96
  end
120
97
 
121
98
  def prompt
122
99
  File.read(File.join(__dir__, 'explanation.prompt.md'))
123
100
  end
101
+
102
+ def module_config
103
+ { llm: { recommended_model: 'deepseek-chat' } }
104
+ end
124
105
  end
125
106
  end
126
107
  end
@@ -4,11 +4,7 @@ require_relative 'explanation'
4
4
 
5
5
  module ActiveGenie
6
6
  module Extractor
7
- class Litote
8
- def self.call(...)
9
- new(...).call
10
- end
11
-
7
+ class Litote < ActiveGenie::BaseModule
12
8
  # Extracts data from informal text while also detecting litotes and their meanings.
13
9
  # This method extends the basic extraction by analyzing rhetorical devices.
14
10
  #
@@ -31,14 +27,15 @@ module ActiveGenie
31
27
  def initialize(text, data_to_extract, config: {})
32
28
  @text = text
33
29
  @data_to_extract = data_to_extract
34
- @config = ActiveGenie.configuration.merge(config)
30
+ super(config:)
35
31
  end
36
32
 
37
33
  def call
38
- response = Explanation.call(@text, extract_with_litote, config: @config)
34
+ response = Explanation.call(@text, extract_with_litote, config:)
39
35
 
40
- if response[:message_litote]
41
- response = Explanation.call(response[:litote_rephrased], @data_to_extract, config: @config)
36
+ if response.data[:message_litote]
37
+ response = Explanation.call(response.data[:litote_rephrased], @data_to_extract,
38
+ config:)
42
39
  end
43
40
 
44
41
  response
@@ -51,6 +48,10 @@ module ActiveGenie
51
48
 
52
49
  @data_to_extract.merge(parameters)
53
50
  end
51
+
52
+ def module_config
53
+ { llm: { recommended_model: 'deepseek-chat' } }
54
+ end
54
55
  end
55
56
  end
56
57
  end
@@ -1,6 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  require_relative 'extractor/explanation'
4
+ require_relative 'extractor/data'
4
5
  require_relative 'extractor/litote'
5
6
 
6
7
  module ActiveGenie
@@ -15,6 +16,10 @@ module ActiveGenie
15
16
  Explanation.call(...)
16
17
  end
17
18
 
19
+ def data(...)
20
+ Data.call(...)
21
+ end
22
+
18
23
  def with_litote(...)
19
24
  Litote.call(...)
20
25
  end