active_genie 0.30.1 → 0.30.8
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +4 -4
- data/README.md +7 -5
- data/VERSION +1 -1
- data/lib/active_genie/comparator/debate.rb +13 -31
- data/lib/active_genie/comparator/fight.rb +3 -3
- data/lib/active_genie/comparator.rb +0 -2
- data/lib/active_genie/configs/base_config.rb +25 -0
- data/lib/active_genie/configs/extractor_config.rb +6 -14
- data/lib/active_genie/configs/lister_config.rb +6 -10
- data/lib/active_genie/configs/llm_config.rb +12 -25
- data/lib/active_genie/configs/log_config.rb +19 -16
- data/lib/active_genie/configs/providers/anthropic_config.rb +10 -16
- data/lib/active_genie/configs/providers/deepseek_config.rb +4 -19
- data/lib/active_genie/configs/providers/google_config.rb +4 -19
- data/lib/active_genie/configs/providers/openai_config.rb +4 -19
- data/lib/active_genie/configs/providers/provider_base.rb +21 -67
- data/lib/active_genie/configs/providers_config.rb +39 -20
- data/lib/active_genie/configs/ranker_config.rb +6 -12
- data/lib/active_genie/configuration.rb +23 -60
- data/lib/active_genie/{ranker/entities → entities}/player.rb +4 -0
- data/lib/active_genie/{ranker/entities → entities}/players.rb +1 -1
- data/lib/active_genie/entities/result.rb +29 -0
- data/lib/active_genie/errors/invalid_model_error.rb +42 -0
- data/lib/active_genie/errors/invalid_provider_error.rb +1 -1
- data/lib/active_genie/errors/provider_server_error.rb +26 -0
- data/lib/active_genie/errors/without_available_provider_error.rb +39 -0
- data/lib/active_genie/extractor/data.json +9 -0
- data/lib/active_genie/extractor/data.prompt.md +12 -0
- data/lib/active_genie/extractor/data.rb +71 -0
- data/lib/active_genie/extractor/explanation.rb +22 -41
- data/lib/active_genie/extractor/litote.rb +10 -9
- data/lib/active_genie/extractor.rb +5 -0
- data/lib/active_genie/lister/feud.json +5 -1
- data/lib/active_genie/lister/feud.rb +12 -17
- data/lib/active_genie/lister/juries.rb +18 -16
- data/lib/active_genie/logger.rb +16 -28
- data/lib/active_genie/providers/anthropic_provider.rb +12 -6
- data/lib/active_genie/providers/base_provider.rb +15 -18
- data/lib/active_genie/providers/deepseek_provider.rb +18 -10
- data/lib/active_genie/providers/google_provider.rb +11 -5
- data/lib/active_genie/providers/openai_provider.rb +8 -6
- data/lib/active_genie/providers/unified_provider.rb +58 -3
- data/lib/active_genie/ranker/elo.rb +41 -36
- data/lib/active_genie/ranker/free_for_all.rb +45 -28
- data/lib/active_genie/ranker/scoring.rb +20 -11
- data/lib/active_genie/ranker/tournament.rb +23 -35
- data/lib/active_genie/scorer/jury_bench.rb +18 -23
- data/lib/active_genie/utils/base_module.rb +34 -0
- data/lib/active_genie/utils/call_wrapper.rb +20 -0
- data/lib/active_genie/utils/deep_merge.rb +12 -0
- data/lib/active_genie/utils/fiber_by_batch.rb +2 -2
- data/lib/active_genie/utils/text_case.rb +18 -0
- data/lib/active_genie.rb +16 -18
- data/lib/tasks/benchmark.rake +1 -3
- data/lib/tasks/templates/active_genie.rb +0 -3
- data/lib/tasks/test.rake +62 -1
- metadata +25 -15
- data/lib/active_genie/configs/comparator_config.rb +0 -10
- data/lib/active_genie/configs/scorer_config.rb +0 -10
|
@@ -12,7 +12,7 @@ module ActiveGenie
|
|
|
12
12
|
@higher_tier = players.calc_higher_tier
|
|
13
13
|
@lower_tier = players.calc_lower_tier
|
|
14
14
|
@criteria = criteria
|
|
15
|
-
@
|
|
15
|
+
@initial_config = config
|
|
16
16
|
@tmp_highers = []
|
|
17
17
|
@total_tokens = 0
|
|
18
18
|
@previous_elo = @players.to_h { |player| [player.id, player.elo] }
|
|
@@ -20,16 +20,16 @@ module ActiveGenie
|
|
|
20
20
|
end
|
|
21
21
|
|
|
22
22
|
def call
|
|
23
|
-
|
|
24
|
-
|
|
23
|
+
config.log.add_observer(observers: ->(log) { log_observer(log) })
|
|
24
|
+
config.log.additional_context = { elo_id: }
|
|
25
25
|
|
|
26
|
-
ActiveGenie::FiberByBatch.call(matches, config:
|
|
26
|
+
ActiveGenie::FiberByBatch.call(matches, config:) do |player_a, player_b|
|
|
27
27
|
winner, loser = debate(player_a, player_b)
|
|
28
28
|
|
|
29
29
|
update_players_elo(winner, loser)
|
|
30
30
|
end
|
|
31
31
|
|
|
32
|
-
|
|
32
|
+
elo_result
|
|
33
33
|
end
|
|
34
34
|
|
|
35
35
|
DEBATE_PER_PLAYER = 3
|
|
@@ -38,39 +38,37 @@ module ActiveGenie
|
|
|
38
38
|
private
|
|
39
39
|
|
|
40
40
|
def matches
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
@higher_tier.each_with_object([]) do |attack_player, matches|
|
|
41
|
+
@matches ||= @lower_tier.each_with_object([]) do |lower_player, matches|
|
|
44
42
|
DEBATE_PER_PLAYER.times do
|
|
45
43
|
higher_player = next_higher_player
|
|
46
44
|
|
|
47
|
-
next if
|
|
45
|
+
next if matches.include?([lower_player, higher_player])
|
|
48
46
|
|
|
49
|
-
|
|
50
|
-
matches << [attack_player, higher_player]
|
|
47
|
+
matches << [lower_player, higher_player]
|
|
51
48
|
end
|
|
52
49
|
end
|
|
53
50
|
end
|
|
54
51
|
|
|
55
52
|
def next_higher_player
|
|
56
|
-
@tmp_highers = @higher_tier.
|
|
53
|
+
@tmp_highers = @higher_tier.dup if @tmp_highers.empty?
|
|
57
54
|
|
|
58
|
-
@tmp_highers.pop
|
|
55
|
+
@tmp_highers.count % 2 ? @tmp_highers.shift : @tmp_highers.pop
|
|
59
56
|
end
|
|
60
57
|
|
|
61
58
|
def debate(player_a, player_b)
|
|
59
|
+
debate_config = ActiveGenie::DeepMerge.call(
|
|
60
|
+
config.to_h,
|
|
61
|
+
{ log: { additional_context: { player_a_id: player_a.id, player_b_id: player_b.id } } }
|
|
62
|
+
)
|
|
63
|
+
|
|
62
64
|
result = ActiveGenie::Comparator.by_debate(
|
|
63
65
|
player_a.content,
|
|
64
66
|
player_b.content,
|
|
65
67
|
@criteria,
|
|
66
|
-
config:
|
|
68
|
+
config: ActiveGenie.new_configuration(debate_config)
|
|
67
69
|
)
|
|
68
70
|
|
|
69
|
-
winner, loser =
|
|
70
|
-
when 'player_a' then [player_a, player_b]
|
|
71
|
-
when 'player_b' then [player_b, player_a]
|
|
72
|
-
when 'draw' then [nil, nil]
|
|
73
|
-
end
|
|
71
|
+
winner, loser = result.data == player_a.content ? [player_a, player_b] : [player_b, player_a]
|
|
74
72
|
|
|
75
73
|
[winner, loser]
|
|
76
74
|
end
|
|
@@ -94,27 +92,30 @@ module ActiveGenie
|
|
|
94
92
|
higher_tier_ids = @higher_tier.map(&:id).join(',')
|
|
95
93
|
lower_tier_ids = @lower_tier.map(&:id).join(',')
|
|
96
94
|
|
|
97
|
-
ranker_unique_key = [higher_tier_ids, lower_tier_ids, @criteria
|
|
95
|
+
ranker_unique_key = [higher_tier_ids, lower_tier_ids, @criteria].join('-')
|
|
98
96
|
Digest::MD5.hexdigest(ranker_unique_key)
|
|
99
97
|
end
|
|
100
98
|
end
|
|
101
99
|
|
|
102
|
-
def
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
100
|
+
def elo_result
|
|
101
|
+
result = ActiveGenie::Result.new(
|
|
102
|
+
data: @players.sorted.map(&:content),
|
|
103
|
+
metadata: {
|
|
104
|
+
elo_id:,
|
|
105
|
+
players: @players,
|
|
106
|
+
players_in_round: players_in.map(&:id),
|
|
107
|
+
debates_count: matches.size,
|
|
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
|
+
)
|
|
115
|
+
|
|
116
|
+
ActiveGenie.logger.call({ elo_id:, code: :elo_report, **result.metadata }, config:)
|
|
117
|
+
|
|
118
|
+
result
|
|
118
119
|
end
|
|
119
120
|
|
|
120
121
|
def players_in
|
|
@@ -135,6 +136,10 @@ module ActiveGenie
|
|
|
135
136
|
def log_observer(log)
|
|
136
137
|
@total_tokens += log[:total_tokens] if log[:code] == :llm_usage
|
|
137
138
|
end
|
|
139
|
+
|
|
140
|
+
def config
|
|
141
|
+
@config ||= ActiveGenie.new_configuration(@initial_config)
|
|
142
|
+
end
|
|
138
143
|
end
|
|
139
144
|
end
|
|
140
145
|
end
|
|
@@ -10,22 +10,19 @@ module ActiveGenie
|
|
|
10
10
|
def initialize(players, criteria, config: nil)
|
|
11
11
|
@players = Entities::Players.new(players)
|
|
12
12
|
@criteria = criteria
|
|
13
|
-
@
|
|
13
|
+
@initial_config = config
|
|
14
14
|
@start_time = Time.now
|
|
15
15
|
@total_tokens = 0
|
|
16
16
|
end
|
|
17
17
|
|
|
18
18
|
def call
|
|
19
|
-
|
|
20
|
-
@config.log.additional_context = { free_for_all_id: }
|
|
21
|
-
|
|
22
|
-
ActiveGenie::FiberByBatch.call(matches, config: @config) do |player_a, player_b|
|
|
19
|
+
ActiveGenie::FiberByBatch.call(matches, config:) do |player_a, player_b|
|
|
23
20
|
winner, loser = debate(player_a, player_b)
|
|
24
21
|
|
|
25
22
|
update_players_score(winner, loser)
|
|
26
23
|
end
|
|
27
24
|
|
|
28
|
-
|
|
25
|
+
build_result
|
|
29
26
|
end
|
|
30
27
|
|
|
31
28
|
private
|
|
@@ -37,29 +34,32 @@ module ActiveGenie
|
|
|
37
34
|
end
|
|
38
35
|
|
|
39
36
|
def debate(player_a, player_b)
|
|
40
|
-
|
|
37
|
+
debate_config = ActiveGenie::DeepMerge.call(
|
|
38
|
+
config.to_h,
|
|
39
|
+
{ log: { additional_context: { player_a_id: player_a.id, player_b_id: player_b.id } } }
|
|
40
|
+
)
|
|
41
41
|
|
|
42
42
|
result = ActiveGenie::Comparator.by_debate(
|
|
43
43
|
player_a.content,
|
|
44
44
|
player_b.content,
|
|
45
45
|
@criteria,
|
|
46
|
-
config:
|
|
46
|
+
config: ActiveGenie.new_configuration(debate_config)
|
|
47
47
|
)
|
|
48
48
|
|
|
49
|
-
winner, loser = case result
|
|
50
|
-
when
|
|
51
|
-
|
|
52
|
-
when 'draw' then [nil, nil]
|
|
49
|
+
winner, loser = case result.data
|
|
50
|
+
when player_a.to_s then [player_a, player_b]
|
|
51
|
+
else [player_b, player_a]
|
|
53
52
|
end
|
|
54
53
|
|
|
55
|
-
|
|
54
|
+
ActiveGenie.logger.call(
|
|
56
55
|
{
|
|
57
|
-
|
|
56
|
+
player_a_id: player_a.id,
|
|
57
|
+
player_b_id: player_b.id,
|
|
58
58
|
code: :free_for_all,
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
reasoning: result
|
|
62
|
-
}
|
|
59
|
+
winner: winner.to_s[0..20],
|
|
60
|
+
loser: loser.to_s[0..20],
|
|
61
|
+
reasoning: result.reasoning
|
|
62
|
+
}, config:
|
|
63
63
|
)
|
|
64
64
|
|
|
65
65
|
[winner, loser]
|
|
@@ -80,27 +80,44 @@ module ActiveGenie
|
|
|
80
80
|
def free_for_all_id
|
|
81
81
|
@free_for_all_id ||= begin
|
|
82
82
|
eligible_ids = @players.eligible.map(&:id).join(',')
|
|
83
|
-
ranking_unique_key = [eligible_ids, @criteria
|
|
83
|
+
ranking_unique_key = [eligible_ids, @criteria].join('-')
|
|
84
84
|
Digest::MD5.hexdigest(ranking_unique_key)
|
|
85
85
|
end
|
|
86
86
|
end
|
|
87
87
|
|
|
88
|
-
def
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
88
|
+
def build_result
|
|
89
|
+
result = ActiveGenie::Result.new(
|
|
90
|
+
data: @players.sorted.map(&:content),
|
|
91
|
+
metadata: {
|
|
92
|
+
free_for_all_id:,
|
|
93
|
+
players: @players,
|
|
94
|
+
debates_count: matches.size,
|
|
95
|
+
duration_seconds: Time.now - @start_time,
|
|
96
|
+
total_tokens: @total_tokens
|
|
97
|
+
}
|
|
98
|
+
)
|
|
95
99
|
|
|
96
|
-
|
|
100
|
+
ActiveGenie.logger.call({ code: :free_for_all_report, **result.metadata }, config:)
|
|
97
101
|
|
|
98
|
-
|
|
102
|
+
result
|
|
99
103
|
end
|
|
100
104
|
|
|
101
105
|
def log_observer(log)
|
|
102
106
|
@total_tokens += log[:total_tokens] if log[:code] == :llm_usage
|
|
103
107
|
end
|
|
108
|
+
|
|
109
|
+
def config
|
|
110
|
+
@config ||= begin
|
|
111
|
+
c = ActiveGenie.new_configuration(
|
|
112
|
+
ActiveGenie::DeepMerge.call(
|
|
113
|
+
@initial_config.to_h,
|
|
114
|
+
{ log: { context: { free_for_all_id: } } }
|
|
115
|
+
)
|
|
116
|
+
)
|
|
117
|
+
c.log.add_observer(observers: ->(log) { log_observer(log) })
|
|
118
|
+
c
|
|
119
|
+
end
|
|
120
|
+
end
|
|
104
121
|
end
|
|
105
122
|
end
|
|
106
123
|
end
|
|
@@ -12,14 +12,12 @@ module ActiveGenie
|
|
|
12
12
|
def initialize(players, criteria, juries: [], config: nil)
|
|
13
13
|
@players = Entities::Players.new(players)
|
|
14
14
|
@criteria = criteria
|
|
15
|
-
@
|
|
15
|
+
@initial_config = config
|
|
16
16
|
@juries = Array(juries).compact.uniq
|
|
17
17
|
end
|
|
18
18
|
|
|
19
19
|
def call
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
ActiveGenie::FiberByBatch.call(players_without_score, config: @config) do |player|
|
|
20
|
+
ActiveGenie::FiberByBatch.call(players_without_score, config:) do |player|
|
|
23
21
|
player.score = generate_score(player)
|
|
24
22
|
end
|
|
25
23
|
end
|
|
@@ -31,14 +29,16 @@ module ActiveGenie
|
|
|
31
29
|
end
|
|
32
30
|
|
|
33
31
|
def generate_score(player)
|
|
34
|
-
|
|
32
|
+
result = ActiveGenie::Scorer.by_jury_bench(
|
|
35
33
|
player.content,
|
|
36
34
|
@criteria,
|
|
37
35
|
@juries,
|
|
38
|
-
config:
|
|
39
|
-
)
|
|
36
|
+
config:
|
|
37
|
+
)
|
|
38
|
+
score = result.data.to_i
|
|
39
|
+
reasoning = result.reasoning
|
|
40
40
|
|
|
41
|
-
|
|
41
|
+
ActiveGenie.logger.call({ code: :new_score, player_id: player.id, score:, reasoning: }, config:)
|
|
42
42
|
|
|
43
43
|
score
|
|
44
44
|
end
|
|
@@ -48,9 +48,9 @@ module ActiveGenie
|
|
|
48
48
|
response = ActiveGenie::Lister.juries(
|
|
49
49
|
[@players.sample.content, @players.sample.content].join("\n\n"),
|
|
50
50
|
@criteria,
|
|
51
|
-
config:
|
|
51
|
+
config:
|
|
52
52
|
)
|
|
53
|
-
|
|
53
|
+
ActiveGenie.logger.call({ code: :new_juries, juries: response }, config:)
|
|
54
54
|
response
|
|
55
55
|
end
|
|
56
56
|
end
|
|
@@ -58,11 +58,20 @@ module ActiveGenie
|
|
|
58
58
|
def ranker_scoring_id
|
|
59
59
|
@ranker_scoring_id ||= begin
|
|
60
60
|
player_ids = players_without_score.map(&:id).join(',')
|
|
61
|
-
ranker_unique_key = [player_ids, @criteria
|
|
61
|
+
ranker_unique_key = [player_ids, @criteria].join('-')
|
|
62
62
|
|
|
63
63
|
"#{Digest::MD5.hexdigest(ranker_unique_key)}-scoring"
|
|
64
64
|
end
|
|
65
65
|
end
|
|
66
|
+
|
|
67
|
+
def config
|
|
68
|
+
@config ||= ActiveGenie.new_configuration(
|
|
69
|
+
DeepMerge.call(
|
|
70
|
+
@initial_config,
|
|
71
|
+
{ log: { additional_context: { ranker_scoring_id: } } }
|
|
72
|
+
)
|
|
73
|
+
)
|
|
74
|
+
end
|
|
66
75
|
end
|
|
67
76
|
end
|
|
68
77
|
end
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'entities/players'
|
|
3
|
+
require_relative '../entities/players'
|
|
4
4
|
require_relative 'free_for_all'
|
|
5
5
|
require_relative 'elo'
|
|
6
6
|
require_relative 'scoring'
|
|
@@ -30,33 +30,30 @@ require_relative 'scoring'
|
|
|
30
30
|
# @return [Hash] Final ranked player results
|
|
31
31
|
module ActiveGenie
|
|
32
32
|
module Ranker
|
|
33
|
-
class Tournament
|
|
34
|
-
def self.call(...)
|
|
35
|
-
new(...).call
|
|
36
|
-
end
|
|
37
|
-
|
|
33
|
+
class Tournament < ActiveGenie::BaseModule
|
|
38
34
|
def initialize(players, criteria, juries: [], config: {})
|
|
39
35
|
@players = Entities::Players.new(players)
|
|
40
36
|
@criteria = criteria
|
|
41
37
|
@juries = Array(juries).compact.uniq
|
|
42
|
-
|
|
38
|
+
super(config:)
|
|
43
39
|
end
|
|
44
40
|
|
|
45
41
|
def call
|
|
46
|
-
@config.log.additional_context = { ranker_id: }
|
|
47
|
-
|
|
48
42
|
set_initial_player_scores!
|
|
49
43
|
eliminate_obvious_bad_players!
|
|
50
44
|
|
|
51
45
|
while @players.elo_eligible?
|
|
52
|
-
|
|
46
|
+
elo_result = run_elo_round!
|
|
53
47
|
eliminate_lower_tier_players!
|
|
54
|
-
rebalance_players!(
|
|
48
|
+
rebalance_players!(elo_result)
|
|
55
49
|
end
|
|
56
50
|
|
|
57
51
|
run_free_for_all!
|
|
58
52
|
|
|
59
|
-
|
|
53
|
+
ActiveGenie::Result.new(
|
|
54
|
+
data: sorted_players.map(&:content),
|
|
55
|
+
metadata: @players.map(&:to_h)
|
|
56
|
+
)
|
|
60
57
|
end
|
|
61
58
|
|
|
62
59
|
ELIMINATION_VARIATION = 'variation_too_high'
|
|
@@ -65,12 +62,7 @@ module ActiveGenie
|
|
|
65
62
|
private
|
|
66
63
|
|
|
67
64
|
def set_initial_player_scores!
|
|
68
|
-
Scoring.call(
|
|
69
|
-
@players,
|
|
70
|
-
@criteria,
|
|
71
|
-
juries: @juries,
|
|
72
|
-
config: @config
|
|
73
|
-
)
|
|
65
|
+
Scoring.call(@players, @criteria, juries: @juries, config:)
|
|
74
66
|
end
|
|
75
67
|
|
|
76
68
|
def eliminate_obvious_bad_players!
|
|
@@ -80,50 +72,46 @@ module ActiveGenie
|
|
|
80
72
|
end
|
|
81
73
|
|
|
82
74
|
def run_elo_round!
|
|
83
|
-
Elo.call(
|
|
84
|
-
@players,
|
|
85
|
-
@criteria,
|
|
86
|
-
config: @config
|
|
87
|
-
)
|
|
75
|
+
Elo.call(@players, @criteria, config:)
|
|
88
76
|
end
|
|
89
77
|
|
|
90
78
|
def eliminate_lower_tier_players!
|
|
91
79
|
@players.calc_lower_tier.each { |player| player.eliminated = ELIMINATION_RELEGATION }
|
|
92
80
|
end
|
|
93
81
|
|
|
94
|
-
def rebalance_players!(
|
|
95
|
-
return if
|
|
82
|
+
def rebalance_players!(elo_result)
|
|
83
|
+
return if elo_result.metadata[:highest_elo_diff].negative?
|
|
96
84
|
|
|
97
85
|
@players.eligible.each do |player|
|
|
98
|
-
next if
|
|
86
|
+
next if elo_result.metadata[:players_in_round].include?(player.id)
|
|
99
87
|
|
|
100
|
-
player.elo +=
|
|
88
|
+
player.elo += elo_result.metadata[:highest_elo_diff]
|
|
101
89
|
end
|
|
102
90
|
end
|
|
103
91
|
|
|
104
92
|
def run_free_for_all!
|
|
105
|
-
FreeForAll.call(
|
|
106
|
-
@players,
|
|
107
|
-
@criteria,
|
|
108
|
-
config: @config
|
|
109
|
-
)
|
|
93
|
+
FreeForAll.call(@players, @criteria, config:)
|
|
110
94
|
end
|
|
111
95
|
|
|
112
96
|
def sorted_players
|
|
113
97
|
players = @players.sorted
|
|
114
|
-
|
|
98
|
+
ActiveGenie.logger.call({ ranker_id:, code: :ranker_final, players: players.map(&:to_h) }, config:)
|
|
115
99
|
|
|
116
|
-
players
|
|
100
|
+
players
|
|
117
101
|
end
|
|
118
102
|
|
|
119
103
|
def ranker_id
|
|
120
104
|
@ranker_id ||= begin
|
|
121
105
|
player_ids = @players.map(&:id).join(',')
|
|
122
|
-
ranker_unique_key = [player_ids, @criteria
|
|
106
|
+
ranker_unique_key = [player_ids, @criteria].join('-')
|
|
123
107
|
|
|
124
108
|
Digest::MD5.hexdigest(ranker_unique_key)
|
|
125
109
|
end
|
|
126
110
|
end
|
|
111
|
+
|
|
112
|
+
def module_config
|
|
113
|
+
{ log: { additional_context: { ranker_id: } } }
|
|
114
|
+
end
|
|
127
115
|
end
|
|
128
116
|
end
|
|
129
117
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative '../providers/unified_provider'
|
|
4
|
+
require_relative '../utils/text_case'
|
|
4
5
|
|
|
5
6
|
module ActiveGenie
|
|
6
7
|
module Scorer
|
|
@@ -17,7 +18,7 @@ module ActiveGenie
|
|
|
17
18
|
# @example Usage with automatic jury recommendation
|
|
18
19
|
# JuryBench.call("Sample text", "Evaluate technical accuracy")
|
|
19
20
|
#
|
|
20
|
-
class JuryBench
|
|
21
|
+
class JuryBench < ActiveGenie::BaseModule
|
|
21
22
|
# @param text [String] The text content to be evaluated
|
|
22
23
|
# @param criteria [String] The evaluation criteria or rubric to assess against
|
|
23
24
|
# @param juries [Array<String>] Optional list of specific juries. If empty,
|
|
@@ -26,15 +27,11 @@ module ActiveGenie
|
|
|
26
27
|
# @return [Hash] The evaluation result containing the scores and reasoning
|
|
27
28
|
# @return [Number] :final_score The final score of the text based on the criteria and juries
|
|
28
29
|
# @return [String] :final_reasoning Detailed explanation of why the final score was reached
|
|
29
|
-
def self.call(...)
|
|
30
|
-
new(...).call
|
|
31
|
-
end
|
|
32
|
-
|
|
33
30
|
def initialize(text, criteria, juries = [], config: {})
|
|
34
31
|
@text = text
|
|
35
32
|
@criteria = criteria
|
|
36
33
|
@param_juries = Array(juries).compact.uniq
|
|
37
|
-
|
|
34
|
+
super(config:)
|
|
38
35
|
end
|
|
39
36
|
|
|
40
37
|
def call
|
|
@@ -44,24 +41,17 @@ module ActiveGenie
|
|
|
44
41
|
{ role: 'user', content: "Text to score: #{@text}" }
|
|
45
42
|
]
|
|
46
43
|
|
|
47
|
-
|
|
44
|
+
provider_response = ::ActiveGenie::Providers::UnifiedProvider.function_calling(
|
|
48
45
|
messages,
|
|
49
46
|
build_function,
|
|
50
|
-
config:
|
|
47
|
+
config:
|
|
51
48
|
)
|
|
52
49
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
criteria: @criteria[0..30],
|
|
59
|
-
juries: juries,
|
|
60
|
-
score: result['final_score'],
|
|
61
|
-
reasoning: result['final_reasoning']
|
|
62
|
-
})
|
|
63
|
-
|
|
64
|
-
result
|
|
50
|
+
ActiveGenie::Result.new(
|
|
51
|
+
data: provider_response['final_score'] || 0,
|
|
52
|
+
reasoning: provider_response['final_reasoning'],
|
|
53
|
+
metadata: provider_response
|
|
54
|
+
)
|
|
65
55
|
end
|
|
66
56
|
|
|
67
57
|
PROMPT = File.read(File.join(__dir__, 'jury_bench.prompt.md'))
|
|
@@ -84,11 +74,12 @@ module ActiveGenie
|
|
|
84
74
|
@properties ||= begin
|
|
85
75
|
tmp = {}
|
|
86
76
|
juries.each do |jury|
|
|
87
|
-
|
|
77
|
+
jury_key = ActiveGenie::TextCase.underscore(jury)
|
|
78
|
+
tmp["#{jury_key}_reasoning"] = {
|
|
88
79
|
type: 'string',
|
|
89
80
|
description: "The reasoning of the Scorer process by #{jury}."
|
|
90
81
|
}
|
|
91
|
-
tmp["#{
|
|
82
|
+
tmp["#{jury_key}_score"] = {
|
|
92
83
|
type: 'number',
|
|
93
84
|
description: "The score given by #{jury}.",
|
|
94
85
|
min: 0,
|
|
@@ -113,9 +104,13 @@ module ActiveGenie
|
|
|
113
104
|
@juries ||= if @param_juries.any?
|
|
114
105
|
@param_juries
|
|
115
106
|
else
|
|
116
|
-
::ActiveGenie::Lister::Juries.call(@text, @criteria, config:
|
|
107
|
+
::ActiveGenie::Lister::Juries.call(@text, @criteria, config:).data
|
|
117
108
|
end
|
|
118
109
|
end
|
|
110
|
+
|
|
111
|
+
def module_config
|
|
112
|
+
{ llm: { recommended_model: 'deepseek-chat' } }
|
|
113
|
+
end
|
|
119
114
|
end
|
|
120
115
|
end
|
|
121
116
|
end
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative 'call_wrapper'
|
|
4
|
+
|
|
5
|
+
module ActiveGenie
|
|
6
|
+
class BaseModule
|
|
7
|
+
prepend CallWrapper
|
|
8
|
+
|
|
9
|
+
def self.call(...)
|
|
10
|
+
new(...).call
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def initialize(config: {})
|
|
14
|
+
@initial_config = config || {}
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def call
|
|
18
|
+
raise NotImplementedError, 'Subclasses must implement the `call` method'
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def config
|
|
22
|
+
@config ||= ActiveGenie.new_configuration(
|
|
23
|
+
ActiveGenie::DeepMerge.call(
|
|
24
|
+
@initial_config.to_h,
|
|
25
|
+
module_config
|
|
26
|
+
)
|
|
27
|
+
)
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def module_config
|
|
31
|
+
{}
|
|
32
|
+
end
|
|
33
|
+
end
|
|
34
|
+
end
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveGenie
|
|
4
|
+
module CallWrapper
|
|
5
|
+
def call(*, &)
|
|
6
|
+
response = super # Call the original method
|
|
7
|
+
|
|
8
|
+
if defined?(config)
|
|
9
|
+
ActiveGenie.logger.call(
|
|
10
|
+
{
|
|
11
|
+
code: self.class.name,
|
|
12
|
+
response: response.to_h
|
|
13
|
+
}, config:
|
|
14
|
+
)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
response
|
|
18
|
+
end
|
|
19
|
+
end
|
|
20
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveGenie
|
|
4
|
+
module DeepMerge
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def call(first_hash, second_hash)
|
|
8
|
+
merger = proc { |_key, v1, v2| v1.is_a?(Hash) && v2.is_a?(Hash) ? v1.merge(v2, &merger) : v2 }
|
|
9
|
+
first_hash.to_h.merge(second_hash.to_h, &merger)
|
|
10
|
+
end
|
|
11
|
+
end
|
|
12
|
+
end
|
|
@@ -8,9 +8,9 @@ module ActiveGenie
|
|
|
8
8
|
|
|
9
9
|
def call(items, config:, &block)
|
|
10
10
|
items.each_slice(config.llm.max_fibers).to_a.each do |batch|
|
|
11
|
-
Async do
|
|
11
|
+
Async do |task|
|
|
12
12
|
tasks = batch.map do |item|
|
|
13
|
-
|
|
13
|
+
task.async { block.call(item) }
|
|
14
14
|
end
|
|
15
15
|
|
|
16
16
|
tasks.each(&:wait)
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveGenie
|
|
4
|
+
module TextCase
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def underscore(camel_cased_word)
|
|
8
|
+
return camel_cased_word.to_s.dup unless /[A-Z-]|::/.match?(camel_cased_word)
|
|
9
|
+
|
|
10
|
+
word = camel_cased_word.to_s.gsub('::', '/')
|
|
11
|
+
word.gsub!(/(?<=[A-Z])(?=[A-Z][a-z])|(?<=[a-z\d])(?=[A-Z])/, '_')
|
|
12
|
+
word.tr!('-', '_')
|
|
13
|
+
word.tr!(' ', '_')
|
|
14
|
+
word.downcase!
|
|
15
|
+
word
|
|
16
|
+
end
|
|
17
|
+
end
|
|
18
|
+
end
|