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.
Files changed (67) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +2 -2
  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 +15 -0
  41. metadata +51 -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,95 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'player'
4
-
5
- module ActiveGenie
6
- module Ranking
7
- class PlayersCollection
8
- def initialize(param_players)
9
- @players = build(param_players)
10
- end
11
- attr_reader :players
12
-
13
- def coefficient_of_variation
14
- mean = score_mean
15
-
16
- return nil if mean.zero?
17
-
18
- variance = all_scores.map { |num| (num - mean)**2 }.sum / all_scores.size
19
- standard_deviation = Math.sqrt(variance)
20
-
21
- (standard_deviation / mean) * 100
22
- end
23
-
24
- def all_scores
25
- eligible.map(&:score).compact
26
- end
27
-
28
- def score_mean
29
- return 0 if all_scores.empty?
30
-
31
- all_scores.sum.to_f / all_scores.size
32
- end
33
-
34
- def calc_relegation_tier
35
- eligible[(tier_size * -1)..]
36
- end
37
-
38
- def calc_defender_tier
39
- eligible[(tier_size * -2)...(tier_size * -1)]
40
- end
41
-
42
- def eligible
43
- sorted.reject(&:eliminated)
44
- end
45
-
46
- def eligible_size
47
- @players.reject(&:eliminated).size
48
- end
49
-
50
- def elo_eligible?
51
- eligible.size > 15
52
- end
53
-
54
- def sorted
55
- @players.sort_by { |p| -p.sort_value }
56
- end
57
-
58
- def to_json(*_args)
59
- @players.map(&:to_h).to_json
60
- end
61
-
62
- def method_missing(...)
63
- @players.send(...)
64
- end
65
-
66
- def respond_to_missing?(method_name, include_private = false)
67
- @players.respond_to?(method_name, include_private)
68
- end
69
-
70
- private
71
-
72
- def build(param_players)
73
- param_players.map { |p| Player.new(p) }
74
- end
75
-
76
- # Returns the number of players to battle in each round
77
- # based on the eligible size, start fast and go slow until top 10
78
- # Example:
79
- # - 50 eligible, tier_size: 15
80
- # - 35 eligible, tier_size: 11
81
- # - 24 eligible, tier_size: 10
82
- # - 14 eligible, tier_size: 4
83
- # 4 rounds to reach top 10 with 50 players
84
- def tier_size
85
- size = (eligible_size / 3).ceil
86
-
87
- if eligible_size < 10
88
- (eligible_size / 2).ceil
89
- else
90
- size.clamp(10, eligible_size - 10)
91
- end
92
- end
93
- end
94
- end
95
- end
@@ -1,69 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- module ActiveGenie
4
- module Ranking
5
- class RankingScoring
6
- def self.call(...)
7
- new(...).call
8
- end
9
-
10
- def initialize(players, criteria, reviewers: [], config: nil)
11
- @players = players
12
- @criteria = criteria
13
- @config = ActiveGenie.configuration.merge(config)
14
- @reviewers = Array(reviewers).compact.uniq
15
- end
16
-
17
- def call
18
- @config.log.additional_context = { ranking_scoring_id: }
19
- @reviewers = generate_reviewers
20
-
21
- players_without_score.each do |player|
22
- player.score = generate_score(player)
23
- end
24
- end
25
-
26
- private
27
-
28
- def players_without_score
29
- @players_without_score ||= @players.select { |player| player.score.nil? }
30
- end
31
-
32
- def generate_score(player)
33
- score, reasoning = ActiveGenie::Scoring.call(
34
- player.content,
35
- @criteria,
36
- @reviewers,
37
- config: @config
38
- ).values_at('final_score', 'final_reasoning')
39
-
40
- @config.logger.call({ code: :new_score, player_id: player.id, score:, reasoning: })
41
-
42
- score
43
- end
44
-
45
- def generate_reviewers
46
- return @reviewers if @reviewers.size.positive?
47
-
48
- reviewer1, reviewer2, reviewer3 = ActiveGenie::Scoring::RecommendedReviewers.call(
49
- [@players.sample.content, @players.sample.content].join("\n\n"),
50
- @criteria,
51
- config: @config
52
- ).values_at('reviewer1', 'reviewer2', 'reviewer3')
53
-
54
- @config.logger.call({ code: :new_reviewers, reviewers: [reviewer1, reviewer2, reviewer3] })
55
-
56
- [reviewer1, reviewer2, reviewer3]
57
- end
58
-
59
- def ranking_scoring_id
60
- @ranking_scoring_id ||= begin
61
- player_ids = players_without_score.map(&:id).join(',')
62
- ranking_unique_key = [player_ids, @criteria, @config.to_json].join('-')
63
-
64
- "#{Digest::MD5.hexdigest(ranking_unique_key)}-scoring"
65
- end
66
- end
67
- end
68
- end
69
- end
@@ -1,14 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'ranking/ranking'
4
-
5
- module ActiveGenie
6
- # See the [ranking README](lib/active_genie/ranking/README.md) for more information.
7
- module Ranking
8
- module_function
9
-
10
- def call(...)
11
- Ranking.call(...)
12
- end
13
- end
14
- end
@@ -1,9 +0,0 @@
1
- {
2
- "name": "scoring",
3
- "description": "Score the text based on the given criteria.",
4
- "parameters": {
5
- "type": "object",
6
- "properties": {},
7
- "required": {}
8
- }
9
- }
@@ -1,119 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../clients/unified_client'
4
-
5
- module ActiveGenie
6
- module Scoring
7
- # The Generalist class provides a foundation for scoring text content against specified criteria
8
- # using AI-powered evaluation. It supports both single and multiple reviewer scenarios,
9
- # with the ability to automatically recommend reviewers when none are specified.
10
- #
11
- # The scoring process evaluates text based on given criteria and returns detailed feedback
12
- # including individual reviewer scores, reasoning, and a final aggregated score.
13
- #
14
- # @example Generalist usage with a single reviewer
15
- # Generalist.call("Sample text", "Evaluate grammar and clarity", ["Grammar Expert"])
16
- #
17
- # @example Usage with automatic reviewer recommendation
18
- # Generalist.call("Sample text", "Evaluate technical accuracy")
19
- #
20
- class Generalist
21
- # @param text [String] The text content to be evaluated
22
- # @param criteria [String] The evaluation criteria or rubric to assess against
23
- # @param reviewers [Array<String>] Optional list of specific reviewers. If empty,
24
- # reviewers will be automatically recommended based on the content and criteria
25
- # @param config [Hash] Additional configuration config that modify the scoring behavior
26
- # @return [Hash] The evaluation result containing the scores and reasoning
27
- # @return [Number] :final_score The final score of the text based on the criteria and reviewers
28
- # @return [String] :final_reasoning Detailed explanation of why the final score was reached
29
- def self.call(...)
30
- new(...).call
31
- end
32
-
33
- def initialize(text, criteria, reviewers = [], config: {})
34
- @text = text
35
- @criteria = criteria
36
- @param_reviewers = Array(reviewers).compact.uniq
37
- @config = ActiveGenie.configuration.merge(config)
38
- end
39
-
40
- def call
41
- messages = [
42
- { role: 'system', content: PROMPT },
43
- { role: 'user', content: "Scoring criteria: #{@criteria}" },
44
- { role: 'user', content: "Text to score: #{@text}" }
45
- ]
46
-
47
- result = ::ActiveGenie::Clients::UnifiedClient.function_calling(
48
- messages,
49
- build_function,
50
- config: @config
51
- )
52
-
53
- result['final_score'] = 0 if result['final_score'].nil?
54
-
55
- @config.logger.call({
56
- code: :scoring,
57
- text: @text[0..30],
58
- criteria: @criteria[0..30],
59
- reviewers: reviewers,
60
- score: result['final_score'],
61
- reasoning: result['final_reasoning']
62
- })
63
-
64
- result
65
- end
66
-
67
- PROMPT = File.read(File.join(__dir__, 'generalist.prompt.md'))
68
-
69
- private
70
-
71
- def build_function
72
- properties = build_properties
73
-
74
- function = JSON.parse(File.read(File.join(__dir__, 'generalist.json')), symbolize_names: true)
75
- function[:parameters][:properties] = properties
76
- function[:parameters][:required] = properties.keys
77
-
78
- function
79
- end
80
-
81
- def build_properties
82
- properties = {}
83
- reviewers.each do |reviewer|
84
- properties["#{reviewer}_reasoning"] = {
85
- type: 'string',
86
- description: "The reasoning of the scoring process by #{reviewer}."
87
- }
88
- properties["#{reviewer}_score"] = {
89
- type: 'number',
90
- description: "The score given by #{reviewer}.",
91
- min: 0,
92
- max: 100
93
- }
94
- end
95
-
96
- properties[:final_score] = {
97
- type: 'number',
98
- description: 'The final score based on the previous reviewers'
99
- }
100
- properties[:final_reasoning] = {
101
- type: 'string',
102
- description: 'The final reasoning based on the previous reviewers'
103
- }
104
-
105
- properties
106
- end
107
-
108
- def reviewers
109
- @reviewers ||= if @param_reviewers.any?
110
- @param_reviewers
111
- else
112
- result = RecommendedReviewers.call(@text, @criteria, config: @config)
113
-
114
- [result['reviewer1'], result['reviewer2'], result['reviewer3']]
115
- end
116
- end
117
- end
118
- end
119
- end
@@ -1,87 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative '../clients/unified_client'
4
-
5
- module ActiveGenie
6
- module Scoring
7
- # The RecommendedReviewers class intelligently suggests appropriate reviewer roles
8
- # for evaluating text content based on specific criteria. It uses AI to analyze
9
- # the content and criteria to identify the most suitable subject matter experts.
10
- #
11
- # The class ensures a balanced and comprehensive review process by recommending
12
- # three distinct reviewer roles with complementary expertise and perspectives.
13
- #
14
- # @example Getting recommended reviewers for technical content
15
- # RecommendedReviewers.call("Technical documentation about API design",
16
- # "Evaluate technical accuracy and clarity")
17
- # # => { reviewer1: "API Architect", reviewer2: "Technical Writer",
18
- # # reviewer3: "Developer Advocate", reasoning: "..." }
19
- #
20
- class RecommendedReviewers
21
- def self.call(...)
22
- new(...).call
23
- end
24
-
25
- # Initializes a new reviewer recommendation instance
26
- #
27
- # @param text [String] The text content to analyze for reviewer recommendations
28
- # @param criteria [String] The evaluation criteria that will guide reviewer selection
29
- # @param config [Hash] Additional configuration config that modify the recommendation process
30
- def initialize(text, criteria, config: {})
31
- @text = text
32
- @criteria = criteria
33
- @config = ActiveGenie.configuration.merge(config)
34
- end
35
-
36
- def call
37
- messages = [
38
- { role: 'system', content: PROMPT },
39
- { role: 'user', content: "Scoring criteria: #{@criteria}" },
40
- { role: 'user', content: "Text to score: #{@text}" }
41
- ]
42
-
43
- function = {
44
- name: 'identify_reviewers',
45
- description: 'Discover reviewers based on the text and given criteria.',
46
- parameters: {
47
- type: 'object',
48
- properties: {
49
- reasoning: { type: 'string' },
50
- reviewer1: { type: 'string' },
51
- reviewer2: { type: 'string' },
52
- reviewer3: { type: 'string' }
53
- },
54
- required: %w[reasoning reviewer1 reviewer2 reviewer3]
55
- }
56
- }
57
-
58
- client.function_calling(
59
- messages,
60
- function,
61
- config: @config
62
- )
63
- end
64
-
65
- PROMPT = <<~PROMPT
66
- Identify the top 3 suitable reviewer titles or roles based on the provided text and criteria. Selected reviewers must possess subject matter expertise, offer valuable insights, and ensure diverse yet aligned perspectives on the content.
67
-
68
- # Instructions
69
- 1. **Analyze the Text and Criteria**: Examine the content and criteria to identify relevant reviewer titles or roles.
70
- 2. **Determine Subject Matter Expertise**: Select reviewers with substantial knowledge or experience in the subject area.
71
- 3. **Evaluate Insight Contribution**: Prioritize titles or roles capable of providing meaningful and actionable feedback on the content.
72
- 4. **Incorporate Perspective Diversity**: Ensure the selection includes reviewers with varied but complementary viewpoints while maintaining alignment with the criteria.
73
-
74
- # Constraints
75
- - Selected reviewers must align with the content’s subject matter and criteria.
76
- - Include reasoning for how each choice supports a thorough and insightful review.
77
- - Avoid redundant or overly similar titles/roles to maintain diversity.
78
- PROMPT
79
-
80
- private
81
-
82
- def client
83
- ::ActiveGenie::Clients::UnifiedClient
84
- end
85
- end
86
- end
87
- end
@@ -1,23 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require_relative 'scoring/generalist'
4
- require_relative 'scoring/recommended_reviewers'
5
-
6
- module ActiveGenie
7
- # See the [Scoring README](lib/active_genie/scoring/README.md) for more information.
8
- module Scoring
9
- module_function
10
-
11
- def call(...)
12
- Generalist.call(...)
13
- end
14
-
15
- def generalist(...)
16
- Generalist.call(...)
17
- end
18
-
19
- def recommended_reviewers(...)
20
- RecommendedReviewers.call(...)
21
- end
22
- end
23
- end
File without changes
File without changes
File without changes