active_genie 0.29.0 → 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.
- checksums.yaml +4 -4
- data/README.md +2 -2
- data/VERSION +1 -1
- data/lib/active_genie/{battle/generalist.json → comparator/debate.json} +2 -2
- data/lib/active_genie/{battle/generalist.prompt.md → comparator/debate.prompt.md} +1 -1
- data/lib/active_genie/{battle/generalist.rb → comparator/debate.rb} +20 -21
- data/lib/active_genie/{battle → comparator}/fight.rb +7 -7
- data/lib/active_genie/comparator.rb +24 -0
- data/lib/active_genie/{config/scoring_config.rb → configs/comparator_config.rb} +1 -1
- data/lib/active_genie/{config/data_extractor_config.rb → configs/extractor_config.rb} +1 -1
- data/lib/active_genie/{config/factory_config.rb → configs/lister_config.rb} +1 -1
- data/lib/active_genie/{config → configs}/llm_config.rb +6 -6
- data/lib/active_genie/{config/ranking_config.rb → configs/ranker_config.rb} +1 -1
- data/lib/active_genie/{config/battle_config.rb → configs/scorer_config.rb} +1 -1
- data/lib/active_genie/configuration.rb +19 -19
- data/lib/active_genie/errors/invalid_provider_error.rb +1 -1
- data/lib/active_genie/{data_extractor/generalist.rb → extractor/explanation.rb} +11 -11
- data/lib/active_genie/{data_extractor/from_informal.rb → extractor/litote.rb} +7 -7
- data/lib/active_genie/extractor.rb +22 -0
- data/lib/active_genie/{factory → lister}/feud.rb +6 -6
- data/lib/active_genie/lister/juries.prompt.md +20 -0
- data/lib/active_genie/lister/juries.rb +82 -0
- data/lib/active_genie/{factory.rb → lister.rb} +7 -6
- data/lib/active_genie/{clients/providers/anthropic_client.rb → providers/anthropic_provider.rb} +4 -4
- data/lib/active_genie/{clients/providers/base_client.rb → providers/base_provider.rb} +6 -6
- data/lib/active_genie/{clients/providers/deepseek_client.rb → providers/deepseek_provider.rb} +3 -3
- data/lib/active_genie/{clients/providers/google_client.rb → providers/google_provider.rb} +6 -6
- data/lib/active_genie/{clients/providers/openai_client.rb → providers/openai_provider.rb} +3 -3
- data/lib/active_genie/providers/unified_provider.rb +44 -0
- data/lib/active_genie/{ranking/elo_round.rb → ranker/elo.rb} +35 -35
- data/lib/active_genie/ranker/entities/player.rb +124 -0
- data/lib/active_genie/ranker/entities/players.rb +102 -0
- data/lib/active_genie/{ranking → ranker}/free_for_all.rb +8 -8
- data/lib/active_genie/ranker/scoring.rb +66 -0
- data/lib/active_genie/{ranking/ranking.rb → ranker/tournament.rb} +17 -25
- data/lib/active_genie/ranker.rb +32 -0
- data/lib/active_genie/scorer/jury_bench.rb +121 -0
- data/lib/active_genie/scorer.rb +17 -0
- data/lib/active_genie.rb +9 -9
- data/lib/tasks/test.rake +15 -0
- metadata +51 -50
- data/lib/active_genie/battle.rb +0 -31
- data/lib/active_genie/clients/unified_client.rb +0 -50
- data/lib/active_genie/data_extractor.rb +0 -23
- data/lib/active_genie/ranking/player.rb +0 -122
- data/lib/active_genie/ranking/players_collection.rb +0 -95
- data/lib/active_genie/ranking/ranking_scoring.rb +0 -69
- data/lib/active_genie/ranking.rb +0 -14
- data/lib/active_genie/scoring/generalist.json +0 -9
- data/lib/active_genie/scoring/generalist.rb +0 -119
- data/lib/active_genie/scoring/recommended_reviewers.rb +0 -87
- data/lib/active_genie/scoring.rb +0 -23
- /data/lib/active_genie/{battle → comparator}/fight.json +0 -0
- /data/lib/active_genie/{battle → comparator}/fight.prompt.md +0 -0
- /data/lib/active_genie/{config → configs}/log_config.rb +0 -0
- /data/lib/active_genie/{config → configs}/providers/anthropic_config.rb +0 -0
- /data/lib/active_genie/{config → configs}/providers/deepseek_config.rb +0 -0
- /data/lib/active_genie/{config → configs}/providers/google_config.rb +0 -0
- /data/lib/active_genie/{config → configs}/providers/openai_config.rb +0 -0
- /data/lib/active_genie/{config → configs}/providers/provider_base.rb +0 -0
- /data/lib/active_genie/{config → configs}/providers_config.rb +0 -0
- /data/lib/active_genie/{data_extractor/generalist.json → extractor/explanation.json} +0 -0
- /data/lib/active_genie/{data_extractor/generalist.prompt.md → extractor/explanation.prompt.md} +0 -0
- /data/lib/active_genie/{data_extractor/from_informal.json → extractor/litote.json} +0 -0
- /data/lib/active_genie/{factory → lister}/feud.json +0 -0
- /data/lib/active_genie/{factory → lister}/feud.prompt.md +0 -0
- /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 '
|
3
|
+
require_relative 'lister/feud'
|
4
|
+
require_relative 'lister/juries'
|
4
5
|
|
5
6
|
module ActiveGenie
|
6
|
-
module
|
7
|
+
module Lister
|
7
8
|
module_function
|
8
9
|
|
9
|
-
def
|
10
|
+
def call(...)
|
10
11
|
Feud.call(...)
|
11
12
|
end
|
12
13
|
|
13
|
-
def
|
14
|
+
def with_feud(...)
|
14
15
|
Feud.call(...)
|
15
16
|
end
|
16
17
|
|
17
|
-
def
|
18
|
-
|
18
|
+
def with_juries(...)
|
19
|
+
Juries.call(...)
|
19
20
|
end
|
20
21
|
end
|
21
22
|
end
|
data/lib/active_genie/{clients/providers/anthropic_client.rb → providers/anthropic_provider.rb}
RENAMED
@@ -3,12 +3,12 @@
|
|
3
3
|
require 'json'
|
4
4
|
require 'net/http'
|
5
5
|
require 'uri'
|
6
|
-
require_relative '
|
6
|
+
require_relative 'base_provider'
|
7
7
|
|
8
8
|
module ActiveGenie
|
9
|
-
module
|
10
|
-
#
|
11
|
-
class
|
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
|
5
|
-
class
|
6
|
-
class
|
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
|
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
|
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,
|
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)
|
data/lib/active_genie/{clients/providers/deepseek_client.rb → providers/deepseek_provider.rb}
RENAMED
@@ -3,11 +3,11 @@
|
|
3
3
|
require 'json'
|
4
4
|
require 'net/http'
|
5
5
|
|
6
|
-
require_relative '
|
6
|
+
require_relative 'base_provider'
|
7
7
|
|
8
8
|
module ActiveGenie
|
9
|
-
module
|
10
|
-
class
|
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 '
|
6
|
+
require_relative 'base_provider'
|
7
7
|
|
8
8
|
module ActiveGenie
|
9
|
-
module
|
10
|
-
#
|
11
|
-
class
|
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
|
-
|
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 '
|
6
|
+
require_relative 'base_provider'
|
7
7
|
|
8
8
|
module ActiveGenie
|
9
|
-
module
|
10
|
-
class
|
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
|
5
|
-
class
|
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
|
-
@
|
13
|
-
@
|
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
|
-
@
|
16
|
+
@tmp_highers = []
|
17
17
|
@total_tokens = 0
|
18
18
|
@previous_elo = @players.to_h { |player| [player.id, player.elo] }
|
19
|
-
@previous_highest_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 = {
|
24
|
+
@config.log.additional_context = { elo_id: }
|
25
25
|
|
26
26
|
matches.each do |player_a, player_b|
|
27
|
-
# TODO:
|
28
|
-
winner, loser =
|
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
|
-
|
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
|
-
@
|
44
|
-
|
45
|
-
|
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}_#{
|
47
|
+
next if match_keys["#{attack_player.id}_#{higher_player.id}"]
|
48
48
|
|
49
|
-
match_keys["#{attack_player.id}_#{
|
50
|
-
matches << [attack_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
|
56
|
-
@
|
55
|
+
def next_higher_player
|
56
|
+
@tmp_highers = @higher_tier.shuffle if @tmp_highers.empty?
|
57
57
|
|
58
|
-
@
|
58
|
+
@tmp_highers.pop
|
59
59
|
end
|
60
60
|
|
61
|
-
def
|
62
|
-
result = ActiveGenie::
|
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
|
93
|
-
@
|
94
|
-
|
95
|
-
|
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
|
-
|
98
|
-
Digest::MD5.hexdigest(
|
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
|
-
|
105
|
-
|
106
|
-
|
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({
|
114
|
+
@config.logger.call({ elo_id:, code: :elo_report, **report })
|
115
115
|
|
116
116
|
report
|
117
117
|
end
|
118
118
|
|
119
|
-
def
|
120
|
-
@
|
119
|
+
def players_in
|
120
|
+
@lower_tier + @higher_tier
|
121
121
|
end
|
122
122
|
|
123
123
|
def highest_elo
|
124
|
-
|
124
|
+
players_in.max_by(&:elo).elo
|
125
125
|
end
|
126
126
|
|
127
127
|
def players_elo_diff
|
128
|
-
elo_diffs =
|
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
|
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 =
|
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
|
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
|
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::
|
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: :
|
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
|
-
|
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
|