active_genie 0.29.1 → 0.30.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 (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +1 -1
  3. data/VERSION +1 -1
  4. data/lib/active_genie/{battle/generalist.json → comparator/debate.json} +2 -2
  5. data/lib/active_genie/{battle/generalist.prompt.md → comparator/debate.prompt.md} +1 -1
  6. data/lib/active_genie/{battle/generalist.rb → comparator/debate.rb} +20 -21
  7. data/lib/active_genie/{battle → comparator}/fight.rb +7 -7
  8. data/lib/active_genie/comparator.rb +24 -0
  9. data/lib/active_genie/{config/scoring_config.rb → configs/comparator_config.rb} +1 -1
  10. data/lib/active_genie/{config/data_extractor_config.rb → configs/extractor_config.rb} +1 -1
  11. data/lib/active_genie/{config/factory_config.rb → configs/lister_config.rb} +1 -1
  12. data/lib/active_genie/{config → configs}/llm_config.rb +6 -6
  13. data/lib/active_genie/{config/ranking_config.rb → configs/ranker_config.rb} +1 -1
  14. data/lib/active_genie/{config/battle_config.rb → configs/scorer_config.rb} +1 -1
  15. data/lib/active_genie/configuration.rb +19 -19
  16. data/lib/active_genie/errors/invalid_provider_error.rb +1 -1
  17. data/lib/active_genie/{data_extractor/generalist.rb → extractor/explanation.rb} +11 -11
  18. data/lib/active_genie/{data_extractor/from_informal.rb → extractor/litote.rb} +7 -7
  19. data/lib/active_genie/extractor.rb +22 -0
  20. data/lib/active_genie/{factory → lister}/feud.rb +6 -6
  21. data/lib/active_genie/lister/juries.prompt.md +20 -0
  22. data/lib/active_genie/lister/juries.rb +82 -0
  23. data/lib/active_genie/{factory.rb → lister.rb} +7 -6
  24. data/lib/active_genie/{clients/providers/anthropic_client.rb → providers/anthropic_provider.rb} +4 -4
  25. data/lib/active_genie/{clients/providers/base_client.rb → providers/base_provider.rb} +6 -6
  26. data/lib/active_genie/{clients/providers/deepseek_client.rb → providers/deepseek_provider.rb} +3 -3
  27. data/lib/active_genie/{clients/providers/google_client.rb → providers/google_provider.rb} +6 -6
  28. data/lib/active_genie/{clients/providers/openai_client.rb → providers/openai_provider.rb} +3 -3
  29. data/lib/active_genie/providers/unified_provider.rb +44 -0
  30. data/lib/active_genie/{ranking/elo_round.rb → ranker/elo.rb} +35 -35
  31. data/lib/active_genie/ranker/entities/player.rb +124 -0
  32. data/lib/active_genie/ranker/entities/players.rb +102 -0
  33. data/lib/active_genie/{ranking → ranker}/free_for_all.rb +8 -8
  34. data/lib/active_genie/ranker/scoring.rb +66 -0
  35. data/lib/active_genie/{ranking/ranking.rb → ranker/tournament.rb} +17 -25
  36. data/lib/active_genie/ranker.rb +32 -0
  37. data/lib/active_genie/scorer/jury_bench.rb +121 -0
  38. data/lib/active_genie/scorer.rb +17 -0
  39. data/lib/active_genie.rb +9 -9
  40. data/lib/tasks/test.rake +4 -0
  41. metadata +50 -50
  42. data/lib/active_genie/battle.rb +0 -31
  43. data/lib/active_genie/clients/unified_client.rb +0 -50
  44. data/lib/active_genie/data_extractor.rb +0 -23
  45. data/lib/active_genie/ranking/player.rb +0 -122
  46. data/lib/active_genie/ranking/players_collection.rb +0 -95
  47. data/lib/active_genie/ranking/ranking_scoring.rb +0 -69
  48. data/lib/active_genie/ranking.rb +0 -14
  49. data/lib/active_genie/scoring/generalist.json +0 -9
  50. data/lib/active_genie/scoring/generalist.rb +0 -119
  51. data/lib/active_genie/scoring/recommended_reviewers.rb +0 -87
  52. data/lib/active_genie/scoring.rb +0 -23
  53. /data/lib/active_genie/{battle → comparator}/fight.json +0 -0
  54. /data/lib/active_genie/{battle → comparator}/fight.prompt.md +0 -0
  55. /data/lib/active_genie/{config → configs}/log_config.rb +0 -0
  56. /data/lib/active_genie/{config → configs}/providers/anthropic_config.rb +0 -0
  57. /data/lib/active_genie/{config → configs}/providers/deepseek_config.rb +0 -0
  58. /data/lib/active_genie/{config → configs}/providers/google_config.rb +0 -0
  59. /data/lib/active_genie/{config → configs}/providers/openai_config.rb +0 -0
  60. /data/lib/active_genie/{config → configs}/providers/provider_base.rb +0 -0
  61. /data/lib/active_genie/{config → configs}/providers_config.rb +0 -0
  62. /data/lib/active_genie/{data_extractor/generalist.json → extractor/explanation.json} +0 -0
  63. /data/lib/active_genie/{data_extractor/generalist.prompt.md → extractor/explanation.prompt.md} +0 -0
  64. /data/lib/active_genie/{data_extractor/from_informal.json → extractor/litote.json} +0 -0
  65. /data/lib/active_genie/{factory → lister}/feud.json +0 -0
  66. /data/lib/active_genie/{factory → lister}/feud.prompt.md +0 -0
  67. /data/lib/active_genie/{scoring/generalist.prompt.md → scorer/jury_bench.md} +0 -0
@@ -1,21 +1,22 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative 'factory/feud'
3
+ require_relative 'lister/feud'
4
+ require_relative 'lister/juries'
4
5
 
5
6
  module ActiveGenie
6
- module Factory
7
+ module Lister
7
8
  module_function
8
9
 
9
- def feud(...)
10
+ def call(...)
10
11
  Feud.call(...)
11
12
  end
12
13
 
13
- def list(...)
14
+ def with_feud(...)
14
15
  Feud.call(...)
15
16
  end
16
17
 
17
- def call(...)
18
- Feud.call(...)
18
+ def with_juries(...)
19
+ Juries.call(...)
19
20
  end
20
21
  end
21
22
  end
@@ -3,12 +3,12 @@
3
3
  require 'json'
4
4
  require 'net/http'
5
5
  require 'uri'
6
- require_relative 'base_client'
6
+ require_relative 'base_provider'
7
7
 
8
8
  module ActiveGenie
9
- module Clients
10
- # Client for interacting with the Anthropic (Claude) API with json response
11
- class AnthropicClient < BaseClient
9
+ module Providers
10
+ # Provider for interacting with the Anthropic (Claude) API with json response
11
+ class AnthropicProvider < BaseProvider
12
12
  # Requests structured JSON output from the Anthropic Claude model based on a schema.
13
13
  #
14
14
  # @param messages [Array<Hash>] A list of messages representing the conversation history.
@@ -1,9 +1,9 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveGenie
4
- module Clients
5
- class BaseClient
6
- class ClientError < StandardError; end
4
+ module Providers
5
+ class BaseProvider
6
+ class ProviderError < StandardError; end
7
7
 
8
8
  DEFAULT_HEADERS = {
9
9
  'Content-Type': 'application/json',
@@ -81,7 +81,7 @@ module ActiveGenie
81
81
 
82
82
  response = http_request(request, uri)
83
83
 
84
- raise ClientError, "Unexpected response: #{response.code} - #{response.body}" unless response.is_a?(Net::HTTPSuccess)
84
+ raise ProviderError, "Unexpected response: #{response.code} - #{response.body}" unless response.is_a?(Net::HTTPSuccess)
85
85
 
86
86
  parsed_response = parse_response(response)
87
87
 
@@ -143,7 +143,7 @@ module ActiveGenie
143
143
  begin
144
144
  JSON.parse(response.body)
145
145
  rescue JSON::ParserError => e
146
- raise ClientError, "Failed to parse JSON response: #{e.message}"
146
+ raise ProviderError, "Failed to parse JSON response: #{e.message}"
147
147
  end
148
148
  end
149
149
 
@@ -170,7 +170,7 @@ module ActiveGenie
170
170
 
171
171
  begin
172
172
  yield
173
- rescue Net::OpenTimeout, Net::ReadTimeout, ClientError => e
173
+ rescue Net::OpenTimeout, Net::ReadTimeout, ProviderError => e
174
174
  raise if retries > max_retries
175
175
 
176
176
  sleep_time = retry_delay * (2**retries)
@@ -3,11 +3,11 @@
3
3
  require 'json'
4
4
  require 'net/http'
5
5
 
6
- require_relative 'base_client'
6
+ require_relative 'base_provider'
7
7
 
8
8
  module ActiveGenie
9
- module Clients
10
- class DeepseekClient < BaseClient
9
+ module Providers
10
+ class DeepseekProvider < BaseProvider
11
11
  class InvalidResponseError < StandardError; end
12
12
 
13
13
  # Requests structured JSON output from the Deepseek model based on a schema.
@@ -3,12 +3,12 @@
3
3
  require 'json'
4
4
  require 'net/http'
5
5
  require 'uri'
6
- require_relative 'base_client'
6
+ require_relative 'base_provider'
7
7
 
8
8
  module ActiveGenie
9
- module Clients
10
- # Client for interacting with the Google Generative Language API.
11
- class GoogleClient < BaseClient
9
+ module Providers
10
+ # Provider for interacting with the Google Generative Language API.
11
+ class GoogleProvider < BaseProvider
12
12
  # Requests structured JSON output from the Google Generative Language model based on a schema.
13
13
  #
14
14
  # @param messages [Array<Hash>] A list of messages representing the conversation history.
@@ -94,9 +94,9 @@ module ActiveGenie
94
94
  json_instruction = <<~PROMPT
95
95
  Generate a JSON object that strictly adheres to the following JSON schema:
96
96
 
97
- ```json
97
+ <json_schema>
98
98
  #{JSON.pretty_generate(function_schema[:parameters])}
99
- ```
99
+ </json_schema>
100
100
 
101
101
  IMPORTANT: Only output the raw JSON object. Do not include any other text, explanations, or markdown formatting like ```json ... ``` wrappers around the final output.
102
102
  PROMPT
@@ -3,11 +3,11 @@
3
3
  require 'json'
4
4
  require 'net/http'
5
5
 
6
- require_relative 'base_client'
6
+ require_relative 'base_provider'
7
7
 
8
8
  module ActiveGenie
9
- module Clients
10
- class OpenaiClient < BaseClient
9
+ module Providers
10
+ class OpenaiProvider < BaseProvider
11
11
  class InvalidResponseError < StandardError; end
12
12
 
13
13
  # Requests structured JSON output from the OpenAI model based on a schema.
@@ -0,0 +1,44 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'openai_provider'
4
+ require_relative 'anthropic_provider'
5
+ require_relative 'google_provider'
6
+ require_relative 'deepseek_provider'
7
+ require_relative '../errors/invalid_provider_error'
8
+
9
+ module ActiveGenie
10
+ module Providers
11
+ class UnifiedProvider
12
+ class << self
13
+ PROVIDER_NAME_TO_CLIENT = {
14
+ openai: OpenaiProvider,
15
+ anthropic: AnthropicProvider,
16
+ google: GoogleProvider,
17
+ deepseek: DeepseekProvider
18
+ }.freeze
19
+
20
+ def function_calling(messages, function, config: {})
21
+ provider_name = config.llm.provider_name || config.providers.default
22
+ provider = PROVIDER_NAME_TO_CLIENT[provider_name.to_sym]
23
+
24
+ raise ActiveGenie::InvalidProviderError, provider_name if provider.nil?
25
+
26
+ response = provider.new(config).function_calling(messages, function)
27
+
28
+ normalize_response(response)
29
+ end
30
+
31
+ private
32
+
33
+ def normalize_response(response)
34
+ response.each do |key, value|
35
+ response[key] = nil if ['null', 'none', 'undefined', '', 'unknown',
36
+ '<unknown>'].include?(value.to_s.strip.downcase)
37
+ end
38
+
39
+ response
40
+ end
41
+ end
42
+ end
43
+ end
44
+ end
@@ -1,38 +1,38 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveGenie
4
- module Ranking
5
- class EloRound
4
+ module Ranker
5
+ class Elo
6
6
  def self.call(...)
7
7
  new(...).call
8
8
  end
9
9
 
10
10
  def initialize(players, criteria, config: nil)
11
11
  @players = players
12
- @relegation_tier = players.calc_relegation_tier
13
- @defender_tier = players.calc_defender_tier
12
+ @higher_tier = players.calc_higher_tier
13
+ @lower_tier = players.calc_lower_tier
14
14
  @criteria = criteria
15
15
  @config = ActiveGenie.configuration.merge(config)
16
- @tmp_defenders = []
16
+ @tmp_highers = []
17
17
  @total_tokens = 0
18
18
  @previous_elo = @players.to_h { |player| [player.id, player.elo] }
19
- @previous_highest_elo = @defender_tier.max_by(&:elo).elo
19
+ @previous_highest_elo = @higher_tier.max_by(&:elo).elo
20
20
  end
21
21
 
22
22
  def call
23
23
  @config.log.add_observer(observers: ->(log) { log_observer(log) })
24
- @config.log.additional_context = { elo_round_id: }
24
+ @config.log.additional_context = { elo_id: }
25
25
 
26
26
  matches.each do |player_a, player_b|
27
- # TODO: battle can take a while, can be parallelized
28
- winner, loser = battle(player_a, player_b)
27
+ # TODO: debate can take a while, can be parallelized
28
+ winner, loser = debate(player_a, player_b)
29
29
  update_players_elo(winner, loser)
30
30
  end
31
31
 
32
32
  build_report
33
33
  end
34
34
 
35
- BATTLE_PER_PLAYER = 3
35
+ DEBATE_PER_PLAYER = 3
36
36
  K = 32
37
37
 
38
38
  private
@@ -40,26 +40,26 @@ module ActiveGenie
40
40
  def matches
41
41
  match_keys = {}
42
42
 
43
- @relegation_tier.each_with_object([]) do |attack_player, matches|
44
- BATTLE_PER_PLAYER.times do
45
- defense_player = next_defense_player
43
+ @higher_tier.each_with_object([]) do |attack_player, matches|
44
+ DEBATE_PER_PLAYER.times do
45
+ higher_player = next_higher_player
46
46
 
47
- next if match_keys["#{attack_player.id}_#{defense_player.id}"]
47
+ next if match_keys["#{attack_player.id}_#{higher_player.id}"]
48
48
 
49
- match_keys["#{attack_player.id}_#{defense_player.id}"] = true
50
- matches << [attack_player, defense_player]
49
+ match_keys["#{attack_player.id}_#{higher_player.id}"] = true
50
+ matches << [attack_player, higher_player]
51
51
  end
52
52
  end
53
53
  end
54
54
 
55
- def next_defense_player
56
- @tmp_defenders = @defender_tier.shuffle if @tmp_defenders.empty?
55
+ def next_higher_player
56
+ @tmp_highers = @higher_tier.shuffle if @tmp_highers.empty?
57
57
 
58
- @tmp_defenders.pop
58
+ @tmp_highers.pop
59
59
  end
60
60
 
61
- def battle(player_a, player_b)
62
- result = ActiveGenie::Battle.call(
61
+ def debate(player_a, player_b)
62
+ result = ActiveGenie::Comparator.by_debate(
63
63
  player_a.content,
64
64
  player_b.content,
65
65
  @criteria,
@@ -89,21 +89,21 @@ module ActiveGenie
89
89
  player_rating + (K * (score - expected_score)).round
90
90
  end
91
91
 
92
- def elo_round_id
93
- @elo_round_id ||= begin
94
- relegation_tier_ids = @relegation_tier.map(&:id).join(',')
95
- defender_tier_ids = @defender_tier.map(&:id).join(',')
92
+ def elo_id
93
+ @elo_id ||= begin
94
+ higher_tier_ids = @higher_tier.map(&:id).join(',')
95
+ lower_tier_ids = @lower_tier.map(&:id).join(',')
96
96
 
97
- ranking_unique_key = [relegation_tier_ids, defender_tier_ids, @criteria, @config.to_json].join('-')
98
- Digest::MD5.hexdigest(ranking_unique_key)
97
+ ranker_unique_key = [higher_tier_ids, lower_tier_ids, @criteria, @config.to_json].join('-')
98
+ Digest::MD5.hexdigest(ranker_unique_key)
99
99
  end
100
100
  end
101
101
 
102
102
  def build_report
103
103
  report = {
104
- elo_round_id:,
105
- players_in_round: players_in_round.map(&:id),
106
- battles_count: matches.size,
104
+ elo_id:,
105
+ players_in: players_in.map(&:id),
106
+ debates_count: matches.size,
107
107
  total_tokens: @total_tokens,
108
108
  previous_highest_elo: @previous_highest_elo,
109
109
  highest_elo:,
@@ -111,21 +111,21 @@ module ActiveGenie
111
111
  players_elo_diff:
112
112
  }
113
113
 
114
- @config.logger.call({ elo_round_id:, code: :elo_round_report, **report })
114
+ @config.logger.call({ elo_id:, code: :elo_report, **report })
115
115
 
116
116
  report
117
117
  end
118
118
 
119
- def players_in_round
120
- @defender_tier + @relegation_tier
119
+ def players_in
120
+ @lower_tier + @higher_tier
121
121
  end
122
122
 
123
123
  def highest_elo
124
- players_in_round.max_by(&:elo).elo
124
+ players_in.max_by(&:elo).elo
125
125
  end
126
126
 
127
127
  def players_elo_diff
128
- elo_diffs = players_in_round.map do |player|
128
+ elo_diffs = players_in.map do |player|
129
129
  [player.id, player.elo - @previous_elo[player.id]]
130
130
  end
131
131
  elo_diffs.sort_by { |_, diff| -(diff || 0) }.to_h
@@ -0,0 +1,124 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'digest'
4
+
5
+ module ActiveGenie
6
+ module Ranker
7
+ module Entities
8
+ class Player
9
+ def initialize(params)
10
+ @params = params.is_a?(String) ? { content: params } : params.dup
11
+ @params[:content] ||= @params
12
+ end
13
+
14
+ def content
15
+ @content ||= @params[:content]
16
+ end
17
+
18
+ def name
19
+ @name ||= @params[:name] || content[0..10]
20
+ end
21
+
22
+ def id
23
+ @id ||= @params[:id] || Digest::MD5.hexdigest(content.to_s)
24
+ end
25
+
26
+ def score
27
+ @score ||= @params[:score]
28
+ end
29
+
30
+ def elo
31
+ @elo = @elo || @params[:elo] || generate_elo_by_score
32
+ end
33
+
34
+ def ffa_win_count
35
+ @ffa_win_count ||= @params[:ffa_win_count] || 0
36
+ end
37
+
38
+ def ffa_lose_count
39
+ @ffa_lose_count ||= @params[:ffa_lose_count] || 0
40
+ end
41
+
42
+ def ffa_draw_count
43
+ @ffa_draw_count ||= @params[:ffa_draw_count] || 0
44
+ end
45
+
46
+ def eliminated
47
+ @eliminated ||= @params[:eliminated]
48
+ end
49
+
50
+ def score=(value)
51
+ @score = value
52
+ @elo = generate_elo_by_score
53
+ end
54
+
55
+ def elo=(value)
56
+ @elo = value || BASE_ELO
57
+ end
58
+
59
+ attr_writer :eliminated
60
+
61
+ def draw!
62
+ @ffa_draw_count = ffa_draw_count + 1
63
+ end
64
+
65
+ def win!
66
+ @ffa_win_count = ffa_win_count + 1
67
+ end
68
+
69
+ def lose!
70
+ @ffa_lose_count = ffa_lose_count + 1
71
+ end
72
+
73
+ def ffa_score
74
+ (ffa_win_count * 3) + ffa_draw_count
75
+ end
76
+
77
+ def sort_value
78
+ (ffa_score * 1_000_000) + ((elo || 0) * 100) + (score || 0)
79
+ end
80
+
81
+ def to_json(*_args)
82
+ to_h.to_json
83
+ end
84
+
85
+ def to_h
86
+ {
87
+ id:, name:, content:,
88
+
89
+ score:, elo:,
90
+ ffa_win_count:, ffa_lose_count:, ffa_draw_count:,
91
+ eliminated:, ffa_score:, sort_value:
92
+ }
93
+ end
94
+
95
+ def method_missing(method_name, *args, &)
96
+ if method_name == :[] && args.size == 1
97
+ attr_name = args.first.to_sym
98
+
99
+ return send(attr_name) if respond_to?(attr_name)
100
+
101
+ return nil
102
+
103
+ end
104
+
105
+ super
106
+ end
107
+
108
+ def respond_to_missing?(method_name, include_private = false)
109
+ method_name == :[] || super
110
+ end
111
+
112
+ BASE_ELO = 1000
113
+
114
+ private
115
+
116
+ def generate_elo_by_score
117
+ return BASE_ELO if @score.nil?
118
+
119
+ BASE_ELO + (@score - 50)
120
+ end
121
+ end
122
+ end
123
+ end
124
+ end
@@ -0,0 +1,102 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'player'
4
+
5
+ module ActiveGenie
6
+ module Ranker
7
+ module Entities
8
+ class Players
9
+ def initialize(players)
10
+ @players = if players.is_a?(Players)
11
+ players.players
12
+ else
13
+ build(players)
14
+ end
15
+ end
16
+
17
+ attr_reader :players
18
+
19
+ def coefficient_of_variation
20
+ mean = score_mean
21
+
22
+ return 0 if mean.zero?
23
+
24
+ variance = all_scores.map { |num| (num - mean)**2 }.sum / all_scores.size
25
+ standard_deviation = Math.sqrt(variance)
26
+
27
+ (standard_deviation / mean) * 100
28
+ end
29
+
30
+ def all_scores
31
+ eligible.map(&:score).compact
32
+ end
33
+
34
+ def score_mean
35
+ return 0 if all_scores.empty?
36
+
37
+ all_scores.sum.to_f / all_scores.size
38
+ end
39
+
40
+ def calc_higher_tier
41
+ eligible[(tier_size * -1)..]
42
+ end
43
+
44
+ def calc_lower_tier
45
+ eligible[(tier_size * -2)...(tier_size * -1)]
46
+ end
47
+
48
+ def eligible
49
+ sorted.reject(&:eliminated)
50
+ end
51
+
52
+ def eligible_size
53
+ @players.reject(&:eliminated).size
54
+ end
55
+
56
+ def elo_eligible?
57
+ eligible.size > 15
58
+ end
59
+
60
+ def sorted
61
+ @players.sort_by { |p| -p.sort_value }
62
+ end
63
+
64
+ def to_json(*_args)
65
+ @players.map(&:to_h).to_json
66
+ end
67
+
68
+ def method_missing(...)
69
+ @players.send(...)
70
+ end
71
+
72
+ def respond_to_missing?(method_name, include_private = false)
73
+ @players.respond_to?(method_name, include_private)
74
+ end
75
+
76
+ private
77
+
78
+ def build(param_players)
79
+ param_players.map { |p| Player.new(p) }
80
+ end
81
+
82
+ # Returns the number of players to debate in each round
83
+ # based on the eligible size, start fast and go slow until top 10
84
+ # Example:
85
+ # - 50 eligible, tier_size: 15
86
+ # - 35 eligible, tier_size: 11
87
+ # - 24 eligible, tier_size: 10
88
+ # - 14 eligible, tier_size: 4
89
+ # 4 rounds to reach top 10 with 50 players
90
+ def tier_size
91
+ size = (eligible_size / 3).ceil
92
+
93
+ if eligible_size < 10
94
+ (eligible_size / 2).ceil
95
+ else
96
+ size.clamp(10, eligible_size - 10)
97
+ end
98
+ end
99
+ end
100
+ end
101
+ end
102
+ end
@@ -1,14 +1,14 @@
1
1
  # frozen_string_literal: true
2
2
 
3
3
  module ActiveGenie
4
- module Ranking
4
+ module Ranker
5
5
  class FreeForAll
6
6
  def self.call(...)
7
7
  new(...).call
8
8
  end
9
9
 
10
10
  def initialize(players, criteria, config: nil)
11
- @players = players
11
+ @players = Players.new(players)
12
12
  @criteria = criteria
13
13
  @config = config || ActiveGenie.configuration
14
14
  @start_time = Time.now
@@ -20,7 +20,7 @@ module ActiveGenie
20
20
  @config.log.additional_context = { free_for_all_id: }
21
21
 
22
22
  matches.each do |player_a, player_b|
23
- winner, loser = battle(player_a, player_b)
23
+ winner, loser = debate(player_a, player_b)
24
24
 
25
25
  update_players_score(winner, loser)
26
26
  end
@@ -31,15 +31,15 @@ module ActiveGenie
31
31
  private
32
32
 
33
33
  # TODO: reduce the number of matches based on transitivity.
34
- # For example, if A is better than B, and B is better than C, then battle between A and C should be auto win A
34
+ # For example, if A is better than B, and B is better than C, then debate between A and C should be auto win A
35
35
  def matches
36
36
  @players.eligible.combination(2).to_a
37
37
  end
38
38
 
39
- def battle(player_a, player_b)
39
+ def debate(player_a, player_b)
40
40
  log_context = { player_a_id: player_a.id, player_b_id: player_b.id }
41
41
 
42
- result = ActiveGenie::Battle.call(
42
+ result = ActiveGenie::Comparator.debate(
43
43
  player_a.content,
44
44
  player_b.content,
45
45
  @criteria,
@@ -55,7 +55,7 @@ module ActiveGenie
55
55
  @config.logger.call(
56
56
  {
57
57
  **log_context,
58
- code: :free_for_all_battle,
58
+ code: :free_for_all,
59
59
  winner_id: winner&.id,
60
60
  loser_id: loser&.id,
61
61
  reasoning: result['reasoning']
@@ -88,7 +88,7 @@ module ActiveGenie
88
88
  def build_report
89
89
  report = {
90
90
  free_for_all_id:,
91
- battles_count: matches.size,
91
+ debates_count: matches.size,
92
92
  duration_seconds: Time.now - @start_time,
93
93
  total_tokens: @total_tokens
94
94
  }
@@ -0,0 +1,66 @@
1
+ # frozen_string_literal: true
2
+
3
+ module ActiveGenie
4
+ module Ranker
5
+ class Scoring
6
+ def self.call(...)
7
+ new(...).call
8
+ end
9
+
10
+ def initialize(players, criteria, juries: [], config: nil)
11
+ @players = Players.new(players)
12
+ @criteria = criteria
13
+ @config = ActiveGenie.configuration.merge(config)
14
+ @juries = Array(juries).compact.uniq
15
+ end
16
+
17
+ def call
18
+ @config.log.additional_context = { ranker_scoring_id: }
19
+
20
+ players_without_score.each do |player|
21
+ player.score = generate_score(player)
22
+ end
23
+ end
24
+
25
+ private
26
+
27
+ def players_without_score
28
+ @players_without_score ||= @players.select { |player| player.score.nil? }
29
+ end
30
+
31
+ def generate_score(player)
32
+ score, reasoning = ActiveGenie::Scorer.jury_bench(
33
+ player.content,
34
+ @criteria,
35
+ @juries,
36
+ config: @config
37
+ ).values_at('final_score', 'final_reasoning')
38
+
39
+ @config.logger.call({ code: :new_score, player_id: player.id, score:, reasoning: })
40
+
41
+ score
42
+ end
43
+
44
+ def juries
45
+ @juries ||= begin
46
+ response = ActiveGenie::Lister.juries(
47
+ [@players.sample.content, @players.sample.content].join("\n\n"),
48
+ @criteria,
49
+ config: @config
50
+ )
51
+ @config.logger.call({ code: :new_juries, juries: response })
52
+ response
53
+ end
54
+ end
55
+
56
+ def ranker_scoring_id
57
+ @ranker_scoring_id ||= begin
58
+ player_ids = players_without_score.map(&:id).join(',')
59
+ ranker_unique_key = [player_ids, @criteria, @config.to_json].join('-')
60
+
61
+ "#{Digest::MD5.hexdigest(ranker_unique_key)}-scoring"
62
+ end
63
+ end
64
+ end
65
+ end
66
+ end