active_genie 0.30.3 → 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 (55) 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 -36
  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 -26
  11. data/lib/active_genie/configs/log_config.rb +19 -16
  12. data/lib/active_genie/configs/providers/anthropic_config.rb +16 -1
  13. data/lib/active_genie/configs/providers/deepseek_config.rb +8 -2
  14. data/lib/active_genie/configs/providers/google_config.rb +8 -2
  15. data/lib/active_genie/configs/providers/openai_config.rb +8 -2
  16. data/lib/active_genie/configs/providers/provider_base.rb +22 -31
  17. data/lib/active_genie/configs/providers_config.rb +35 -16
  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/provider_server_error.rb +8 -5
  24. data/lib/active_genie/errors/without_available_provider_error.rb +39 -0
  25. data/lib/active_genie/extractor/data.json +9 -0
  26. data/lib/active_genie/extractor/data.prompt.md +12 -0
  27. data/lib/active_genie/extractor/data.rb +71 -0
  28. data/lib/active_genie/extractor/explanation.rb +19 -47
  29. data/lib/active_genie/extractor/litote.rb +8 -14
  30. data/lib/active_genie/extractor.rb +5 -0
  31. data/lib/active_genie/lister/feud.json +5 -1
  32. data/lib/active_genie/lister/feud.rb +10 -24
  33. data/lib/active_genie/lister/juries.rb +14 -20
  34. data/lib/active_genie/logger.rb +16 -28
  35. data/lib/active_genie/providers/anthropic_provider.rb +11 -5
  36. data/lib/active_genie/providers/base_provider.rb +15 -17
  37. data/lib/active_genie/providers/deepseek_provider.rb +17 -9
  38. data/lib/active_genie/providers/google_provider.rb +10 -4
  39. data/lib/active_genie/providers/openai_provider.rb +6 -4
  40. data/lib/active_genie/providers/unified_provider.rb +47 -17
  41. data/lib/active_genie/ranker/elo.rb +41 -36
  42. data/lib/active_genie/ranker/free_for_all.rb +45 -28
  43. data/lib/active_genie/ranker/scoring.rb +20 -11
  44. data/lib/active_genie/ranker/tournament.rb +23 -35
  45. data/lib/active_genie/scorer/jury_bench.rb +15 -29
  46. data/lib/active_genie/utils/base_module.rb +34 -0
  47. data/lib/active_genie/utils/call_wrapper.rb +20 -0
  48. data/lib/active_genie/utils/deep_merge.rb +12 -0
  49. data/lib/active_genie/utils/fiber_by_batch.rb +2 -2
  50. data/lib/active_genie/utils/text_case.rb +18 -0
  51. data/lib/active_genie.rb +16 -18
  52. data/lib/tasks/test.rake +61 -3
  53. metadata +19 -8
  54. data/lib/active_genie/configs/comparator_config.rb +0 -10
  55. data/lib/active_genie/configs/scorer_config.rb +0 -10
@@ -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
@@ -2,9 +2,9 @@
2
2
 
3
3
  module ActiveGenie
4
4
  class ProviderServerError < StandardError
5
- TEXT = <<~TEXT.freeze
6
- Provider server error: #{code}
7
- #{body}
5
+ TEXT = <<~TEXT
6
+ Provider server error: %<code>s
7
+ %<body>s
8
8
 
9
9
  Providers errors are common and can occur for various reasons, such as:
10
10
  - Invalid API key
@@ -16,8 +16,11 @@ module ActiveGenie
16
16
  TEXT
17
17
 
18
18
  def initialize(response)
19
- @response = response
20
- super(format(TEXT, **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
+ ))
21
24
  end
22
25
  end
23
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
- @initial_config = 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,38 +79,16 @@ 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
- )
98
-
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
85
+ first_reasoning_key = provider_response["#{provider_response.keys.first}_explanation"]
110
86
 
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
@@ -122,13 +99,8 @@ module ActiveGenie
122
99
  File.read(File.join(__dir__, 'explanation.prompt.md'))
123
100
  end
124
101
 
125
- def config
126
- @config ||= begin
127
- c = ActiveGenie.configuration.merge(@initial_config)
128
- c.llm.recommended_model = 'deepseek-chat' unless c.llm.recommended_model
129
-
130
- c
131
- end
102
+ def module_config
103
+ { llm: { recommended_model: 'deepseek-chat' } }
132
104
  end
133
105
  end
134
106
  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,13 +27,16 @@ module ActiveGenie
31
27
  def initialize(text, data_to_extract, config: {})
32
28
  @text = text
33
29
  @data_to_extract = data_to_extract
34
- @initial_config = config
30
+ super(config:)
35
31
  end
36
32
 
37
33
  def call
38
34
  response = Explanation.call(@text, extract_with_litote, config:)
39
35
 
40
- response = Explanation.call(response[:litote_rephrased], @data_to_extract, config:) if response[:message_litote]
36
+ if response.data[:message_litote]
37
+ response = Explanation.call(response.data[:litote_rephrased], @data_to_extract,
38
+ config:)
39
+ end
41
40
 
42
41
  response
43
42
  end
@@ -50,13 +49,8 @@ module ActiveGenie
50
49
  @data_to_extract.merge(parameters)
51
50
  end
52
51
 
53
- def config
54
- @config ||= begin
55
- c = ActiveGenie.configuration.merge(@initial_config)
56
- c.llm.recommended_model = 'deepseek-chat' unless c.llm.recommended_model
57
-
58
- c
59
- end
52
+ def module_config
53
+ { llm: { recommended_model: 'deepseek-chat' } }
60
54
  end
61
55
  end
62
56
  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
@@ -8,6 +8,10 @@
8
8
  "type": "string",
9
9
  "description": "The theme for the feud."
10
10
  },
11
+ "why_these_items": {
12
+ "type": "string",
13
+ "description": "Explanation of why these items were chosen."
14
+ },
11
15
  "items": {
12
16
  "type": "array",
13
17
  "description": "The list of items for the feud.",
@@ -16,6 +20,6 @@
16
20
  }
17
21
  }
18
22
  },
19
- "required": ["theme", "items"]
23
+ "required": ["theme", "why_these_items", "items"]
20
24
  }
21
25
  }
@@ -9,17 +9,13 @@ module ActiveGenie
9
9
  # @example Feud usage with two players and criteria
10
10
  # Feud.call("Industries that are most likely to be affected by climate change")
11
11
  #
12
- class Feud
13
- def self.call(...)
14
- new(...).call
15
- end
16
-
12
+ class Feud < ActiveGenie::BaseModule
17
13
  # @param theme [String] The theme for the feud
18
14
  # @param config [Hash] Additional configuration options
19
15
  # @return [Array of strings] List of items
20
16
  def initialize(theme, config: {})
21
17
  @theme = theme
22
- @initial_config = config
18
+ super(config:)
23
19
  end
24
20
 
25
21
  # @return [Array of strings] The list of items
@@ -30,14 +26,17 @@ module ActiveGenie
30
26
  { role: 'user', content: "theme: #{@theme}" }
31
27
  ]
32
28
 
33
- response = ::ActiveGenie::Providers::UnifiedProvider.function_calling(
29
+ provider_response = ::ActiveGenie::Providers::UnifiedProvider.function_calling(
34
30
  messages,
35
31
  FUNCTION,
36
32
  config:
37
33
  )
38
34
 
39
- log_feud(response)
40
- response['items'] || []
35
+ ActiveGenie::Result.new(
36
+ data: provider_response['items'] || [],
37
+ reasoning: provider_response['why_these_items'],
38
+ metadata: provider_response
39
+ )
41
40
  end
42
41
 
43
42
  PROMPT = File.read(File.join(__dir__, 'feud.prompt.md'))
@@ -49,21 +48,8 @@ module ActiveGenie
49
48
  config.lister.number_of_items
50
49
  end
51
50
 
52
- def log_feud(response)
53
- config.logger.call(
54
- code: :feud,
55
- theme: @theme[0..30],
56
- items: response['items'].map { |item| item[0..30] }
57
- )
58
- end
59
-
60
- def config
61
- @config ||= begin
62
- c = ActiveGenie.configuration.merge(@initial_config)
63
- c.llm.recommended_model = 'deepseek-chat' unless c.llm.recommended_model
64
-
65
- c
66
- end
51
+ def module_config
52
+ { llm: { recommended_model: 'claude-haiku-4-5' } }
67
53
  end
68
54
  end
69
55
  end
@@ -16,11 +16,7 @@ module ActiveGenie
16
16
  # "Evaluate technical accuracy and clarity")
17
17
  # # => [ "API Architect", "Technical Writer", "Developer Advocate" ]
18
18
  #
19
- class Juries
20
- def self.call(...)
21
- new(...).call
22
- end
23
-
19
+ class Juries < ActiveGenie::BaseModule
24
20
  # Initializes a new jury recommendation instance
25
21
  #
26
22
  # @param text [String] The text content to analyze for jury recommendations
@@ -29,7 +25,7 @@ module ActiveGenie
29
25
  def initialize(text, criteria, config: {})
30
26
  @text = text
31
27
  @criteria = criteria
32
- @initial_config = config
28
+ super(config:)
33
29
  end
34
30
 
35
31
  def call
@@ -45,7 +41,10 @@ module ActiveGenie
45
41
  parameters: {
46
42
  type: 'object',
47
43
  properties: {
48
- reasoning: { type: 'string' },
44
+ why_these_juries: {
45
+ type: 'string',
46
+ description: 'A brief explanation of why these juries were chosen.'
47
+ },
49
48
  juries: {
50
49
  type: 'array',
51
50
  description: 'The list of best juries',
@@ -58,32 +57,27 @@ module ActiveGenie
58
57
  }
59
58
  }
60
59
 
61
- result = client.function_calling(
60
+ provider_response = ::ActiveGenie::Providers::UnifiedProvider.function_calling(
62
61
  messages,
63
62
  function,
64
63
  config:
65
64
  )
66
65
 
67
- result['juries'] || []
66
+ ActiveGenie::Result.new(
67
+ data: provider_response['juries'] || [],
68
+ reasoning: provider_response['why_these_juries'],
69
+ metadata: provider_response
70
+ )
68
71
  end
69
72
 
70
73
  private
71
74
 
72
- def client
73
- ::ActiveGenie::Providers::UnifiedProvider
74
- end
75
-
76
75
  def prompt
77
76
  @prompt ||= File.read(File.join(__dir__, 'juries.prompt.md'))
78
77
  end
79
78
 
80
- def config
81
- @config ||= begin
82
- c = ActiveGenie.configuration.merge(@initial_config)
83
- c.llm.recommended_model = 'deepseek-chat' unless c.llm.recommended_model
84
-
85
- c
86
- end
79
+ def module_config
80
+ { llm: { recommended_model: 'deepseek-chat' } }
87
81
  end
88
82
  end
89
83
  end