active_genie 0.0.12 → 0.0.18
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 +65 -22
- data/VERSION +1 -1
- data/lib/active_genie/battle/README.md +7 -7
- data/lib/active_genie/battle/basic.rb +47 -31
- data/lib/active_genie/battle.rb +4 -0
- data/lib/active_genie/clients/anthropic_client.rb +110 -0
- data/lib/active_genie/clients/google_client.rb +158 -0
- data/lib/active_genie/clients/helpers/retry.rb +29 -0
- data/lib/active_genie/clients/openai_client.rb +61 -83
- data/lib/active_genie/clients/unified_client.rb +4 -4
- data/lib/active_genie/concerns/loggable.rb +44 -0
- data/lib/active_genie/configuration/log_config.rb +1 -1
- data/lib/active_genie/configuration/providers/anthropic_config.rb +54 -0
- data/lib/active_genie/configuration/providers/base_config.rb +85 -0
- data/lib/active_genie/configuration/providers/deepseek_config.rb +54 -0
- data/lib/active_genie/configuration/providers/google_config.rb +56 -0
- data/lib/active_genie/configuration/providers/openai_config.rb +54 -0
- data/lib/active_genie/configuration/providers_config.rb +7 -4
- data/lib/active_genie/configuration/runtime_config.rb +35 -0
- data/lib/active_genie/configuration.rb +18 -4
- data/lib/active_genie/data_extractor/basic.rb +15 -2
- data/lib/active_genie/data_extractor.rb +4 -0
- data/lib/active_genie/logger.rb +40 -21
- data/lib/active_genie/ranking/elo_round.rb +71 -50
- data/lib/active_genie/ranking/free_for_all.rb +31 -14
- data/lib/active_genie/ranking/player.rb +11 -16
- data/lib/active_genie/ranking/players_collection.rb +4 -4
- data/lib/active_genie/ranking/ranking.rb +74 -19
- data/lib/active_genie/ranking/ranking_scoring.rb +3 -3
- data/lib/active_genie/scoring/basic.rb +44 -25
- data/lib/active_genie/scoring.rb +3 -0
- data/lib/tasks/benchmark.rake +27 -0
- metadata +91 -70
- data/lib/active_genie/configuration/openai_config.rb +0 -56
@@ -1,6 +1,10 @@
|
|
1
1
|
require_relative 'configuration/providers_config'
|
2
|
-
require_relative 'configuration/openai_config'
|
2
|
+
require_relative 'configuration/providers/openai_config'
|
3
|
+
require_relative 'configuration/providers/google_config'
|
4
|
+
require_relative 'configuration/providers/anthropic_config'
|
5
|
+
require_relative 'configuration/providers/deepseek_config'
|
3
6
|
require_relative 'configuration/log_config'
|
7
|
+
require_relative 'configuration/runtime_config'
|
4
8
|
|
5
9
|
module ActiveGenie
|
6
10
|
module Configuration
|
@@ -9,7 +13,10 @@ module ActiveGenie
|
|
9
13
|
def providers
|
10
14
|
@providers ||= begin
|
11
15
|
p = ProvidersConfig.new
|
12
|
-
p.register(
|
16
|
+
p.register(ActiveGenie::Configuration::Providers::OpenaiConfig)
|
17
|
+
p.register(ActiveGenie::Configuration::Providers::GoogleConfig)
|
18
|
+
p.register(ActiveGenie::Configuration::Providers::AnthropicConfig)
|
19
|
+
p.register(ActiveGenie::Configuration::Providers::DeepseekConfig)
|
13
20
|
p
|
14
21
|
end
|
15
22
|
end
|
@@ -18,10 +25,17 @@ module ActiveGenie
|
|
18
25
|
@log ||= LogConfig.new
|
19
26
|
end
|
20
27
|
|
28
|
+
def runtime
|
29
|
+
@runtime ||= RuntimeConfig.new
|
30
|
+
end
|
31
|
+
|
21
32
|
def to_h(configs = {})
|
33
|
+
normalized_configs = configs.dig(:runtime) ? configs : { runtime: configs }
|
34
|
+
|
22
35
|
{
|
23
|
-
providers: providers.to_h(
|
24
|
-
log: log.to_h(
|
36
|
+
providers: providers.to_h(normalized_configs.dig(:providers) || {}),
|
37
|
+
log: log.to_h(normalized_configs.dig(:log) || {}),
|
38
|
+
runtime: runtime.to_h(normalized_configs.dig(:runtime) || {})
|
25
39
|
}
|
26
40
|
end
|
27
41
|
end
|
@@ -37,21 +37,34 @@ module ActiveGenie::DataExtractor
|
|
37
37
|
{ role: 'system', content: PROMPT },
|
38
38
|
{ role: 'user', content: @text }
|
39
39
|
]
|
40
|
+
|
41
|
+
properties = data_to_extract_with_explaination
|
42
|
+
|
40
43
|
function = {
|
41
44
|
name: 'data_extractor',
|
42
45
|
description: 'Extract structured and typed data from user messages.',
|
43
46
|
schema: {
|
44
47
|
type: "object",
|
45
|
-
properties
|
48
|
+
properties:,
|
49
|
+
required: properties.keys
|
46
50
|
}
|
47
51
|
}
|
48
52
|
|
49
|
-
::ActiveGenie::Clients::UnifiedClient.function_calling(
|
53
|
+
result = ::ActiveGenie::Clients::UnifiedClient.function_calling(
|
50
54
|
messages,
|
51
55
|
function,
|
52
56
|
model_tier: 'lower_tier',
|
53
57
|
config: @config
|
54
58
|
)
|
59
|
+
|
60
|
+
ActiveGenie::Logger.debug({
|
61
|
+
code: :data_extractor,
|
62
|
+
text: @text[0..30],
|
63
|
+
data_to_extract: @data_to_extract,
|
64
|
+
extracted_data: result
|
65
|
+
})
|
66
|
+
|
67
|
+
result
|
55
68
|
end
|
56
69
|
|
57
70
|
private
|
data/lib/active_genie/logger.rb
CHANGED
@@ -5,56 +5,55 @@ module ActiveGenie
|
|
5
5
|
module Logger
|
6
6
|
module_function
|
7
7
|
|
8
|
-
def with_context(context)
|
8
|
+
def with_context(context, observer: nil)
|
9
9
|
@context ||= {}
|
10
|
+
@observers ||= []
|
10
11
|
begin
|
11
12
|
@context = @context.merge(context)
|
13
|
+
@observers << observer if observer
|
12
14
|
yield if block_given?
|
13
15
|
ensure
|
14
16
|
@context.delete_if { |key, _| context.key?(key) }
|
17
|
+
@observers.delete(observer)
|
15
18
|
end
|
16
19
|
end
|
17
20
|
|
18
21
|
def info(log)
|
19
|
-
|
22
|
+
call(log, level: :info)
|
20
23
|
end
|
21
24
|
|
22
25
|
def error(log)
|
23
|
-
|
26
|
+
call(log, level: :error)
|
24
27
|
end
|
25
28
|
|
26
29
|
def warn(log)
|
27
|
-
|
30
|
+
call(log, level: :warn)
|
28
31
|
end
|
29
32
|
|
30
33
|
def debug(log)
|
31
|
-
|
34
|
+
call(log, level: :debug)
|
32
35
|
end
|
33
36
|
|
34
37
|
def trace(log)
|
35
|
-
|
38
|
+
call(log, level: :trace)
|
36
39
|
end
|
37
40
|
|
38
|
-
def
|
39
|
-
log =
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
41
|
+
def call(data, level: :info)
|
42
|
+
log = {
|
43
|
+
**(@context || {}),
|
44
|
+
**(data || {}),
|
45
|
+
timestamp: Time.now,
|
46
|
+
level: level.to_s.upcase,
|
47
|
+
process_id: Process.pid
|
48
|
+
}
|
44
49
|
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
$stdout.puts log
|
49
|
-
else
|
50
|
-
$stdout.print '.'
|
51
|
-
end
|
50
|
+
append_to_file(log)
|
51
|
+
output(log, level)
|
52
|
+
call_observers(log)
|
52
53
|
|
53
54
|
log
|
54
55
|
end
|
55
56
|
|
56
|
-
private
|
57
|
-
|
58
57
|
# Log Levels
|
59
58
|
#
|
60
59
|
# LOG_LEVELS defines different levels of logging within the application.
|
@@ -68,5 +67,25 @@ module ActiveGenie
|
|
68
67
|
LOG_LEVELS = { info: 0, error: 0, warn: 1, debug: 2, trace: 3 }.freeze
|
69
68
|
|
70
69
|
attr_accessor :context
|
70
|
+
|
71
|
+
def append_to_file(log)
|
72
|
+
FileUtils.mkdir_p('logs')
|
73
|
+
File.write('logs/active_genie.log', "#{JSON.generate(log)}\n", mode: 'a')
|
74
|
+
end
|
75
|
+
|
76
|
+
def output(log, level)
|
77
|
+
config_log_level = LOG_LEVELS[log.dig(:config, :log_level)] || LOG_LEVELS[:info]
|
78
|
+
if config_log_level >= LOG_LEVELS[level]
|
79
|
+
$stdout.puts log
|
80
|
+
else
|
81
|
+
$stdout.print '.'
|
82
|
+
end
|
83
|
+
end
|
84
|
+
|
85
|
+
def call_observers(log)
|
86
|
+
return if @observers.nil? || @observers.size.zero?
|
87
|
+
|
88
|
+
@observers.each { |observer| observer.call(log) }
|
89
|
+
end
|
71
90
|
end
|
72
91
|
end
|
@@ -13,32 +13,39 @@ module ActiveGenie::Ranking
|
|
13
13
|
@criteria = criteria
|
14
14
|
@config = config
|
15
15
|
@tmp_defenders = []
|
16
|
+
@start_time = Time.now
|
17
|
+
@total_tokens = 0
|
18
|
+
@previous_elo = {}
|
19
|
+
@previous_highest_elo = @defender_tier.max_by(&:elo).elo
|
16
20
|
end
|
17
21
|
|
18
22
|
def call
|
19
23
|
ActiveGenie::Logger.with_context(log_context) do
|
20
|
-
|
24
|
+
save_previous_elo
|
25
|
+
matches.each do |player_1, player_2|
|
21
26
|
# TODO: battle can take a while, can be parallelized
|
22
|
-
winner, loser = battle(
|
23
|
-
|
27
|
+
winner, loser = battle(player_1, player_2)
|
24
28
|
next if winner.nil? || loser.nil?
|
25
29
|
|
26
|
-
|
27
|
-
|
28
|
-
winner.elo = new_winner_elo
|
29
|
-
loser.elo = new_loser_elo
|
30
|
+
winner.elo = calculate_new_elo(winner.elo, loser.elo, 1)
|
31
|
+
loser.elo = calculate_new_elo(loser.elo, winner.elo, 0)
|
30
32
|
end
|
31
|
-
|
32
|
-
# TODO: add a round report. Duration, Elo changes, etc.
|
33
33
|
end
|
34
|
+
|
35
|
+
ActiveGenie::Logger.info({ code: :elo_round_report, **report })
|
36
|
+
|
37
|
+
report
|
34
38
|
end
|
35
39
|
|
36
40
|
private
|
37
41
|
|
38
42
|
BATTLE_PER_PLAYER = 3
|
39
|
-
LOSE_PENALTY = 15
|
40
43
|
K = 32
|
41
44
|
|
45
|
+
def save_previous_elo
|
46
|
+
@previous_elo = @players.map { |player| [player.id, player.elo] }.to_h
|
47
|
+
end
|
48
|
+
|
42
49
|
def matches
|
43
50
|
@relegation_tier.reduce([]) do |matches, attack_player|
|
44
51
|
BATTLE_PER_PLAYER.times do
|
@@ -49,53 +56,35 @@ module ActiveGenie::Ranking
|
|
49
56
|
end
|
50
57
|
|
51
58
|
def next_defense_player
|
52
|
-
@tmp_defenders = @defender_tier if @tmp_defenders.size.zero?
|
59
|
+
@tmp_defenders = @defender_tier.shuffle if @tmp_defenders.size.zero?
|
53
60
|
|
54
|
-
@tmp_defenders.
|
61
|
+
@tmp_defenders.pop
|
55
62
|
end
|
56
63
|
|
57
|
-
def battle(
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
ActiveGenie::Logger.debug({
|
72
|
-
step: :elo_round_battle,
|
73
|
-
player_ids: [player_a.id, player_b.id],
|
74
|
-
winner_id: winner&.id,
|
75
|
-
loser_id: loser&.id,
|
76
|
-
reasoning: result['reasoning']
|
77
|
-
})
|
64
|
+
def battle(player_1, player_2)
|
65
|
+
ActiveGenie::Logger.with_context({ player_1_id: player_1.id, player_2_id: player_2.id }) do
|
66
|
+
result = ActiveGenie::Battle.basic(
|
67
|
+
player_1.content,
|
68
|
+
player_2.content,
|
69
|
+
@criteria,
|
70
|
+
config: @config
|
71
|
+
)
|
72
|
+
|
73
|
+
winner, loser = case result['winner']
|
74
|
+
when 'player_1' then [player_1, player_2]
|
75
|
+
when 'player_2' then [player_2, player_1]
|
76
|
+
when 'draw' then [nil, nil]
|
77
|
+
end
|
78
78
|
|
79
|
-
|
79
|
+
[winner, loser]
|
80
|
+
end
|
80
81
|
end
|
81
82
|
|
82
83
|
# INFO: Read more about the Elo rating system on https://en.wikipedia.org/wiki/Elo_rating_system
|
83
|
-
def calculate_new_elo(
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
new_winner_elo = [winner_elo + K * (1 - expected_score_a), max_defense_elo].min
|
88
|
-
new_loser_elo = [loser_elo + K * (1 - expected_score_b) - LOSE_PENALTY, min_relegation_elo].max
|
89
|
-
|
90
|
-
[new_winner_elo, new_loser_elo]
|
91
|
-
end
|
92
|
-
|
93
|
-
def max_defense_elo
|
94
|
-
@defender_tier.max_by(&:elo).elo
|
95
|
-
end
|
96
|
-
|
97
|
-
def min_relegation_elo
|
98
|
-
@relegation_tier.min_by(&:elo).elo
|
84
|
+
def calculate_new_elo(player_rating, opponent_rating, score)
|
85
|
+
expected_score = 1.0 / (1.0 + 10.0 ** ((opponent_rating - player_rating) / 400.0))
|
86
|
+
|
87
|
+
player_rating + (K * (score - expected_score)).round
|
99
88
|
end
|
100
89
|
|
101
90
|
def log_context
|
@@ -109,5 +98,37 @@ module ActiveGenie::Ranking
|
|
109
98
|
ranking_unique_key = [relegation_tier_ids, defender_tier_ids, @criteria, @config.to_json].join('-')
|
110
99
|
Digest::MD5.hexdigest(ranking_unique_key)
|
111
100
|
end
|
101
|
+
|
102
|
+
def report
|
103
|
+
{
|
104
|
+
elo_round_id:,
|
105
|
+
players_in_round: players_in_round.map(&:id),
|
106
|
+
battles_count: matches.size,
|
107
|
+
duration_seconds: Time.now - @start_time,
|
108
|
+
total_tokens: @total_tokens,
|
109
|
+
previous_highest_elo: @previous_highest_elo,
|
110
|
+
highest_elo:,
|
111
|
+
highest_elo_diff: highest_elo - @previous_highest_elo,
|
112
|
+
players_elo_diff:,
|
113
|
+
}
|
114
|
+
end
|
115
|
+
|
116
|
+
def players_in_round
|
117
|
+
@defender_tier + @relegation_tier
|
118
|
+
end
|
119
|
+
|
120
|
+
def highest_elo
|
121
|
+
players_in_round.max_by(&:elo).elo
|
122
|
+
end
|
123
|
+
|
124
|
+
def players_elo_diff
|
125
|
+
players_in_round.map do |player|
|
126
|
+
[player.id, player.elo - @previous_elo[player.id]]
|
127
|
+
end.sort_by { |_, diff| -diff }.to_h
|
128
|
+
end
|
129
|
+
|
130
|
+
def log_observer(log)
|
131
|
+
@total_tokens += log[:total_tokens] if log[:code] == :llm_usage
|
132
|
+
end
|
112
133
|
end
|
113
134
|
end
|
@@ -10,24 +10,28 @@ module ActiveGenie::Ranking
|
|
10
10
|
@players = players
|
11
11
|
@criteria = criteria
|
12
12
|
@config = config
|
13
|
+
@start_time = Time.now
|
14
|
+
@total_tokens = 0
|
13
15
|
end
|
14
16
|
|
15
17
|
def call
|
16
|
-
ActiveGenie::Logger.with_context(log_context) do
|
17
|
-
matches.each do |
|
18
|
-
winner, loser = battle(
|
18
|
+
ActiveGenie::Logger.with_context(log_context, observer: method(:log_observer)) do
|
19
|
+
matches.each do |player_1, player_2|
|
20
|
+
winner, loser = battle(player_1, player_2)
|
19
21
|
|
20
22
|
if winner.nil? || loser.nil?
|
21
|
-
|
22
|
-
|
23
|
+
player_1.draw!
|
24
|
+
player_2.draw!
|
23
25
|
else
|
24
26
|
winner.win!
|
25
27
|
loser.lose!
|
26
28
|
end
|
27
29
|
end
|
28
|
-
|
29
|
-
# TODO: add a freeForAll report. Duration, Elo changes, etc.
|
30
30
|
end
|
31
|
+
|
32
|
+
ActiveGenie::Logger.info({ code: :free_for_all_report, **report })
|
33
|
+
|
34
|
+
report
|
31
35
|
end
|
32
36
|
|
33
37
|
private
|
@@ -38,23 +42,23 @@ module ActiveGenie::Ranking
|
|
38
42
|
@players.eligible.combination(2).to_a
|
39
43
|
end
|
40
44
|
|
41
|
-
def battle(
|
45
|
+
def battle(player_1, player_2)
|
42
46
|
result = ActiveGenie::Battle.basic(
|
43
|
-
|
44
|
-
|
47
|
+
player_1.content,
|
48
|
+
player_2.content,
|
45
49
|
@criteria,
|
46
50
|
config: @config
|
47
51
|
)
|
48
52
|
|
49
53
|
winner, loser = case result['winner']
|
50
|
-
when '
|
51
|
-
when '
|
54
|
+
when 'player_1' then [player_1, player_2, result['reasoning']]
|
55
|
+
when 'player_2' then [player_2, player_1, result['reasoning']]
|
52
56
|
when 'draw' then [nil, nil, result['reasoning']]
|
53
57
|
end
|
54
58
|
|
55
59
|
ActiveGenie::Logger.debug({
|
56
|
-
|
57
|
-
player_ids: [
|
60
|
+
code: :free_for_all_battle,
|
61
|
+
player_ids: [player_1.id, player_2.id],
|
58
62
|
winner_id: winner&.id,
|
59
63
|
loser_id: loser&.id,
|
60
64
|
reasoning: result['reasoning']
|
@@ -72,5 +76,18 @@ module ActiveGenie::Ranking
|
|
72
76
|
ranking_unique_key = [eligible_ids, @criteria, @config.to_json].join('-')
|
73
77
|
Digest::MD5.hexdigest(ranking_unique_key)
|
74
78
|
end
|
79
|
+
|
80
|
+
def report
|
81
|
+
{
|
82
|
+
free_for_all_id:,
|
83
|
+
battles_count: matches.size,
|
84
|
+
duration_seconds: Time.now - @start_time,
|
85
|
+
total_tokens: @total_tokens,
|
86
|
+
}
|
87
|
+
end
|
88
|
+
|
89
|
+
def log_observer(log)
|
90
|
+
@total_tokens += log[:total_tokens] if log[:code] == :llm_usage
|
91
|
+
end
|
75
92
|
end
|
76
93
|
end
|
@@ -21,39 +21,34 @@ module ActiveGenie::Ranking
|
|
21
21
|
attr_accessor :rank
|
22
22
|
|
23
23
|
def score=(value)
|
24
|
+
ActiveGenie::Logger.debug({ code: :new_score, player_id: id, score: value }) if value != @score
|
24
25
|
@score = value
|
25
|
-
|
26
|
-
end
|
27
|
-
|
28
|
-
def elo
|
29
|
-
generate_elo_by_score if @elo.nil?
|
30
|
-
|
31
|
-
@elo
|
26
|
+
@elo = generate_elo_by_score
|
32
27
|
end
|
33
28
|
|
34
29
|
def elo=(value)
|
30
|
+
ActiveGenie::Logger.debug({ code: :new_elo, player_id: id, elo: value }) if value != @elo
|
35
31
|
@elo = value
|
36
|
-
ActiveGenie::Logger.debug({ step: :new_elo, player_id: id, elo: value })
|
37
32
|
end
|
38
33
|
|
39
34
|
def eliminated=(value)
|
35
|
+
ActiveGenie::Logger.debug({ code: :new_eliminated, player_id: id, eliminated: value }) if value != @eliminated
|
40
36
|
@eliminated = value
|
41
|
-
ActiveGenie::Logger.debug({ step: :new_eliminated, player_id: id, eliminated: value })
|
42
37
|
end
|
43
38
|
|
44
39
|
def draw!
|
45
40
|
@ffa_draw_count += 1
|
46
|
-
ActiveGenie::Logger.debug({
|
41
|
+
ActiveGenie::Logger.debug({ code: :new_ffa_score, player_id: id, result: 'draw', ffa_score: })
|
47
42
|
end
|
48
43
|
|
49
44
|
def win!
|
50
45
|
@ffa_win_count += 1
|
51
|
-
ActiveGenie::Logger.debug({
|
46
|
+
ActiveGenie::Logger.debug({ code: :new_ffa_score, player_id: id, result: 'win', ffa_score: })
|
52
47
|
end
|
53
48
|
|
54
49
|
def lose!
|
55
50
|
@ffa_lose_count += 1
|
56
|
-
ActiveGenie::Logger.debug({
|
51
|
+
ActiveGenie::Logger.debug({ code: :new_ffa_score, player_id: id, result: 'lose', ffa_score: })
|
57
52
|
end
|
58
53
|
|
59
54
|
def ffa_score
|
@@ -86,12 +81,12 @@ module ActiveGenie::Ranking
|
|
86
81
|
method_name == :[] || super
|
87
82
|
end
|
88
83
|
|
84
|
+
def generate_elo_by_score
|
85
|
+
BASE_ELO + ((@score || 0) - 50)
|
86
|
+
end
|
87
|
+
|
89
88
|
private
|
90
89
|
|
91
90
|
BASE_ELO = 1000
|
92
|
-
|
93
|
-
def generate_elo_by_score
|
94
|
-
@elo = BASE_ELO + ((@score || 0) - 50)
|
95
|
-
end
|
96
91
|
end
|
97
92
|
end
|
@@ -41,9 +41,9 @@ module ActiveGenie::Ranking
|
|
41
41
|
end
|
42
42
|
|
43
43
|
def sorted
|
44
|
-
@players.sort_by { |p| [-p.ffa_score, -(p.elo || 0), -(p.score || 0)] }
|
45
|
-
|
46
|
-
|
44
|
+
sorted_players = @players.sort_by { |p| [-p.ffa_score, -(p.elo || 0), -(p.score || 0)] }
|
45
|
+
sorted_players.each_with_index { |p, i| p.rank = i + 1 }
|
46
|
+
sorted_players
|
47
47
|
end
|
48
48
|
|
49
49
|
def to_h
|
@@ -57,7 +57,7 @@ module ActiveGenie::Ranking
|
|
57
57
|
private
|
58
58
|
|
59
59
|
def build(param_players)
|
60
|
-
param_players.map { |
|
60
|
+
param_players.map { |p| Player.new(p) }
|
61
61
|
end
|
62
62
|
|
63
63
|
# Returns the number of players to battle in each round
|
@@ -1,3 +1,4 @@
|
|
1
|
+
require_relative '../concerns/loggable'
|
1
2
|
require_relative './players_collection'
|
2
3
|
require_relative './free_for_all'
|
3
4
|
require_relative './elo_round'
|
@@ -28,39 +29,55 @@ require_relative './ranking_scoring'
|
|
28
29
|
# @return [Hash] Final ranked player results
|
29
30
|
module ActiveGenie::Ranking
|
30
31
|
class Ranking
|
32
|
+
include ActiveGenie::Concerns::Loggable
|
33
|
+
|
31
34
|
def self.call(...)
|
32
35
|
new(...).call
|
33
36
|
end
|
34
37
|
|
35
38
|
def initialize(param_players, criteria, reviewers: [], config: {})
|
36
|
-
@param_players = param_players
|
37
39
|
@criteria = criteria
|
38
40
|
@reviewers = Array(reviewers).compact.uniq
|
39
41
|
@config = ActiveGenie::Configuration.to_h(config)
|
40
|
-
@players =
|
42
|
+
@players = PlayersCollection.new(param_players)
|
43
|
+
@elo_rounds_played = 0
|
44
|
+
@elo_round_battle_count = 0
|
45
|
+
@free_for_all_battle_count = 0
|
46
|
+
@total_tokens = 0
|
47
|
+
@start_time = Time.now
|
41
48
|
end
|
42
49
|
|
43
50
|
def call
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
end
|
54
|
-
|
55
|
-
run_free_for_all!
|
51
|
+
initial_log
|
52
|
+
|
53
|
+
set_initial_player_scores!
|
54
|
+
eliminate_obvious_bad_players!
|
55
|
+
|
56
|
+
while @players.elo_eligible?
|
57
|
+
elo_report = run_elo_round!
|
58
|
+
eliminate_relegation_players!
|
59
|
+
rebalance_players!(elo_report)
|
56
60
|
end
|
57
61
|
|
62
|
+
run_free_for_all!
|
63
|
+
final_logs
|
64
|
+
|
58
65
|
@players.sorted
|
59
66
|
end
|
60
67
|
|
61
68
|
private
|
62
69
|
|
63
|
-
SCORE_VARIATION_THRESHOLD =
|
70
|
+
SCORE_VARIATION_THRESHOLD = 15
|
71
|
+
ELIMINATION_VARIATION = 'variation_too_high'
|
72
|
+
ELIMINATION_RELEGATION = 'relegation_tier'
|
73
|
+
|
74
|
+
with_logging_context :log_context, ->(log) {
|
75
|
+
@total_tokens += log[:total_tokens] if log[:code] == :llm_usage
|
76
|
+
}
|
77
|
+
|
78
|
+
def initial_log
|
79
|
+
@players.each { |p| ActiveGenie::Logger.debug({ code: :new_player, player: p.to_h }) }
|
80
|
+
end
|
64
81
|
|
65
82
|
def set_initial_player_scores!
|
66
83
|
RankingScoring.call(@players, @criteria, reviewers: @reviewers, config: @config)
|
@@ -68,20 +85,58 @@ module ActiveGenie::Ranking
|
|
68
85
|
|
69
86
|
def eliminate_obvious_bad_players!
|
70
87
|
while @players.coefficient_of_variation >= SCORE_VARIATION_THRESHOLD
|
71
|
-
@players.eligible.last.eliminated =
|
88
|
+
@players.eligible.last.eliminated = ELIMINATION_VARIATION
|
72
89
|
end
|
73
90
|
end
|
74
91
|
|
75
92
|
def run_elo_round!
|
76
|
-
|
93
|
+
@elo_rounds_played += 1
|
94
|
+
|
95
|
+
elo_report = EloRound.call(@players, @criteria, config: @config)
|
96
|
+
|
97
|
+
@elo_round_battle_count += elo_report[:battles_count]
|
98
|
+
|
99
|
+
elo_report
|
77
100
|
end
|
78
101
|
|
79
102
|
def eliminate_relegation_players!
|
80
|
-
@players.calc_relegation_tier.each { |player| player.eliminated =
|
103
|
+
@players.calc_relegation_tier.each { |player| player.eliminated = ELIMINATION_RELEGATION }
|
104
|
+
end
|
105
|
+
|
106
|
+
def rebalance_players!(elo_report)
|
107
|
+
return if elo_report[:highest_elo_diff].negative?
|
108
|
+
|
109
|
+
@players.eligible.each do |player|
|
110
|
+
next if elo_report[:players_in_round].include?(player.id)
|
111
|
+
|
112
|
+
player.elo += elo_report[:highest_elo_diff]
|
113
|
+
end
|
81
114
|
end
|
82
115
|
|
83
116
|
def run_free_for_all!
|
84
|
-
FreeForAll.call(@players, @criteria, config: @config)
|
117
|
+
ffa_report = FreeForAll.call(@players, @criteria, config: @config)
|
118
|
+
|
119
|
+
@free_for_all_battle_count += ffa_report[:battles_count]
|
120
|
+
end
|
121
|
+
|
122
|
+
def report
|
123
|
+
{
|
124
|
+
ranking_id: ranking_id,
|
125
|
+
players_count: @players.size,
|
126
|
+
variation_too_high: @players.select { |player| player.eliminated == ELIMINATION_VARIATION }.size,
|
127
|
+
elo_rounds_played: @elo_rounds_played,
|
128
|
+
elo_round_battle_count: @elo_round_battle_count,
|
129
|
+
relegation_tier: @players.select { |player| player.eliminated == ELIMINATION_RELEGATION }.size,
|
130
|
+
ffa_round_battle_count: @free_for_all_battle_count,
|
131
|
+
top3: @players.eligible[0..2].map(&:id),
|
132
|
+
total_tokens: @total_tokens,
|
133
|
+
duration_seconds: Time.now - @start_time,
|
134
|
+
}
|
135
|
+
end
|
136
|
+
|
137
|
+
def final_logs
|
138
|
+
ActiveGenie::Logger.debug({ code: :ranking_final, players: @players.sorted.map(&:to_h) })
|
139
|
+
ActiveGenie::Logger.info({ code: :ranking, **report })
|
85
140
|
end
|
86
141
|
|
87
142
|
def log_context
|