active_genie 0.0.3 → 0.0.8

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e7f834cd4da9f695c2a3f69b1d7d5516831afaa2d6830f89527b46e7e19bc6f9
4
- data.tar.gz: 8e14a0011c2551b975105d2120506461fb5f0e9e33206a6960a9de544148016b
3
+ metadata.gz: 834787b505134623a3f6c4995c9dec8a0ce89a1a014600c4d676d8801bf541cd
4
+ data.tar.gz: 16ae75ffa3926ecd87052d72ca3f5434be80327b712bb2dba54e55c46fa5ff8d
5
5
  SHA512:
6
- metadata.gz: ed3662a287028dacf2f60bda458d0de38ad16d4666421e3b836780d9f2dbb469be935e0b6c3d1fff851f82282c4c4564bebc725354f2f213d0cf21e30ce5ce93
7
- data.tar.gz: 50b0d666a061361322ec2bfb38ef0e49110d6b31be3c3b55e3873f6ca5389003d739f209c7a9e1b695e3901de703effe02344863fed80495c104e886b1d1fa3f
6
+ metadata.gz: 1772fbfa59891e7a3673b50a54bd90914007917854e78fb5f0458db681bf89bd1ec118600d23ab98d6255e1b063b5d582d3b73dc738e66aeadc7f4f0bee75af8
7
+ data.tar.gz: bb6a0ea9ef737f7a2ed3ec558dcea5c67bcc2f90a155828e1e25a5a7a3ebd9d0fe6c5422dd9f3d9ff6fb667b63822e3bc86627cdb3e40a3c72c4ef50cee32c68
data/README.md CHANGED
@@ -1,86 +1,154 @@
1
- # ActiveGenie
2
- Ruby gem for out of the box AI features. LLMs are just a raw tools that need to be polished and refined to be useful. This gem is a collection of tools that can be used to make LLMs more useful and easier to use.
3
- Think this gem is like ActiveStorage but for LLMs.
1
+ # ActiveGenie 🧞‍♂️
2
+ > Transform your Ruby application with powerful, production-ready GenAI features
4
3
 
5
4
  [![Gem Version](https://badge.fury.io/rb/active_genie.svg?icon=si%3Arubygems)](https://badge.fury.io/rb/active_genie)
5
+ [![Ruby](https://github.com/roriz/active_genie/actions/workflows/ruby.yml/badge.svg)](https://github.com/roriz/active_genie/actions/workflows/ruby.yml)
6
+
7
+ ActiveGenie is a Ruby gem that provides a polished, production-ready interface for working with Generative AI (GenAI) models. Just like ActiveStorage simplifies file handling in Rails, ActiveGenie makes it effortless to integrate GenAI capabilities into your Ruby applications.
8
+
9
+ ## Features
10
+
11
+ - 🎯 **Data Extraction**: Extract structured data from unstructured text with type validation
12
+ - 📊 **Smart Scoring**: Multi-reviewer evaluation system with automatic expert selection
13
+ - 💭 **Sentiment Analysis**: Advanced sentiment analysis with customizable rules
14
+ - 🔒 **Safe & Secure**: Built-in validation and sanitization
15
+ - 🛠️ **Configurable**: Supports multiple GenAI providers and models
6
16
 
7
17
  ## Installation
8
- Add this line to your application's Gemfile:
9
18
 
10
- 1. Add the gem to your Gemfile
19
+ 1. Add to your Gemfile:
11
20
  ```ruby
12
21
  gem 'active_genie'
13
22
  ```
14
- 2. install the gem
23
+
24
+ 2. Install the gem:
15
25
  ```shell
16
26
  bundle install
17
27
  ```
18
- 3. call the initializer to create default configurations
28
+
29
+ 3. Generate the configuration:
19
30
  ```shell
31
+ echo "ActiveGenie.load_tasks" >> Rakefile
20
32
  rails g active_genie:install
21
33
  ```
22
- 4. Add the right credentials to the `config/active_genie.yml` file
34
+
35
+ 4. [Optional] Configure your credentials in `config/active_genie.yml`:
23
36
  ```yaml
24
37
  GPT-4o-mini:
25
38
  api_key: <%= ENV['OPENAI_API_KEY'] %>
26
39
  provider: "openai"
40
+
41
+ claude-3-5-sonnet:
42
+ api_key: <%= ENV['ANTHROPIC_API_KEY'] %>
43
+ provider: "anthropic"
27
44
  ```
28
45
 
29
- ## Quick Example
30
- ```ruby
31
- require 'active_genie'
46
+ > The first key will be used as default in all modules, in this example `GPT-4o-mini`
32
47
 
33
- puts ActiveGenie::DataExtractor.call(
34
- "Hello, my name is Radamés Roriz",
35
- { full_name: { type: 'string' } },
36
- options: { model: 'gpt-4o-mini', api_key: 'your-api-key', provider: 'openai' } # Optional if has config/active_genie.yml
37
- ) # => { full_name: "Radamés Roriz" }
38
- ```
48
+ ## Quick Start
39
49
 
40
50
  ### Data Extractor
41
- Extract structured data from text using LLM-powered analysis, handling informal language and complex expressions.
51
+ Extract structured data from text using AI-powered analysis, handling informal language and complex expressions.
42
52
 
43
53
  ```ruby
44
- require 'active_genie'
45
-
46
- text = "iPhone 14 Pro Max"
54
+ text = "Nike Air Max 90 - Size 42 - $199.99"
47
55
  schema = {
48
- brand: { type: 'string' },
49
- model: { type: 'string' }
56
+ brand: {
57
+ type: 'string',
58
+ enum: ["Nike", "Adidas", "Puma"]
59
+ },
60
+ price: {
61
+ type: 'number',
62
+ minimum: 0
63
+ },
64
+ size: {
65
+ type: 'integer',
66
+ minimum: 35,
67
+ maximum: 46
68
+ }
50
69
  }
51
- result = ActiveGenie::DataExtractor.call(
52
- text,
53
- schema,
54
- options: { model: 'GPT-4o-mini', api_key: 'your-api-key', provider: 'openai' } # Optional if
55
- )
56
- # => { brand: "iPhone", model: "14 Pro Max" }
70
+
71
+ result = ActiveGenie::DataExtractor.call(text, schema)
72
+ # => {
73
+ # brand: "Nike",
74
+ # brand_explanation: "Brand name found at start of text",
75
+ # price: 199.99,
76
+ # price_explanation: "Price found in USD format at end",
77
+ # size: 42,
78
+ # size_explanation: "Size explicitly stated in the middle"
79
+ # }
57
80
  ```
58
81
 
59
- - More examples in the [Data Extractor README](lib/data_extractor/README.md)
60
- - Extract from ambiguous [from_informal](lib/data_extractor/README.md#extract-from-informal-text)
61
- - Interface details in the [Interface](lib/data_extractor/README.md#interface)
82
+ Features:
83
+ - Structured data extraction with type validation
84
+ - Schema-based extraction with custom constraints
85
+ - Informal text analysis (litotes, hedging)
86
+ - Detailed explanations for extracted values
62
87
 
63
- ### Summarizer (WIP)
64
- The summarizer is a tool that can be used to summarize a given text. It uses a set of rules to summarize the text out of the box. Uses the best practices of prompt engineering and engineering to make the summarization as accurate as possible.
88
+ See the [Data Extractor README](lib/active_genie/data_extractor/README.md) for informal text processing, advanced schemas, and detailed interface documentation.
89
+
90
+ ### Scoring
91
+ Text evaluation system that provides detailed scoring and feedback using multiple expert reviewers. Get balanced scoring through AI-powered expert reviewers that automatically adapt to your content.
92
+
93
+ ```ruby
94
+ text = "The code implements a binary search algorithm with O(log n) complexity"
95
+ criteria = "Evaluate technical accuracy and clarity"
96
+
97
+ result = ActiveGenie::Scoring::Basic.call(text, criteria)
98
+ # => {
99
+ # algorithm_expert_score: 95,
100
+ # algorithm_expert_reasoning: "Accurately describes binary search and its complexity",
101
+ # technical_writer_score: 90,
102
+ # technical_writer_reasoning: "Clear and concise explanation of the algorithm",
103
+ # final_score: 92.5
104
+ # }
105
+ ```
106
+
107
+ Features:
108
+ - Multi-reviewer evaluation with automatic expert selection
109
+ - Detailed feedback with scoring reasoning
110
+ - Customizable reviewer weights
111
+ - Flexible evaluation criteria
112
+
113
+ See the [Scoring README](lib/active_genie/scoring/README.md) for advanced usage, custom reviewers, and detailed interface documentation.
114
+
115
+ ### Battle
116
+ AI-powered battle evaluation system that determines winners between two players based on specified criteria.
65
117
 
66
118
  ```ruby
67
119
  require 'active_genie'
68
120
 
69
- text = "Example text to be summarized. The fox jumps over the dog"
70
- summarized_text = ActiveGenie::Summarizer.call(text)
71
- puts summarized_text # => "The fox jumps over the dog"
121
+ player_a = "Implementation uses dependency injection for better testability"
122
+ player_b = "Code has high test coverage but tightly coupled components"
123
+ criteria = "Evaluate code quality and maintainability"
124
+
125
+ result = ActiveGenie::Battle::Basic.call(player_a, player_b, criteria)
126
+ # => {
127
+ # winner_player: "Implementation uses dependency injection for better testability",
128
+ # reasoning: "Player A's implementation demonstrates better maintainability through dependency injection,
129
+ # which allows for easier testing and component replacement. While Player B has good test coverage,
130
+ # the tight coupling makes the code harder to maintain and modify.",
131
+ # what_could_be_changed_to_avoid_draw: "Focus on specific architectural patterns and design principles"
132
+ # }
72
133
  ```
73
134
 
74
- ### Scorer (WIP)
75
- The scorer is a tool that can be used to score a given text. It uses a set of rules to score the text out of the box. Uses the best practices of prompt engineering and engineering to make the scoring as accurate as possible.
135
+ Features:
136
+ - Multi-reviewer evaluation with automatic expert selection
137
+ - Detailed feedback with scoring reasoning
138
+ - Customizable reviewer weights
139
+ - Flexible evaluation criteria
140
+
141
+ See the [Battle README](lib/active_genie/battle/README.md) for advanced usage, custom reviewers, and detailed interface documentation.
142
+
143
+ ### Summarizer (WIP)
144
+ The summarizer is a tool that can be used to summarize a given text. It uses a set of rules to summarize the text out of the box. Uses the best practices of prompt engineering and engineering to make the summarization as accurate as possible.
76
145
 
77
146
  ```ruby
78
147
  require 'active_genie'
79
148
 
80
- text = "Example text to be scored. The fox jumps over the dog"
81
- criterias = 'Grammar, Relevance'
82
- score = ActiveGenie::Scorer.call(text, criterias)
83
- puts score # => { 'Grammar' => 0.8, 'Relevance' => 1.0, total: 0.9 }
149
+ text = "Example text to be summarized. The fox jumps over the dog"
150
+ summarized_text = ActiveGenie::Summarizer.call(text)
151
+ puts summarized_text # => "The fox jumps over the dog"
84
152
  ```
85
153
 
86
154
  ### Language detector (WIP)
@@ -128,9 +196,27 @@ ranked_items = ActiveGenie::EloRanking.call(items, criterias, rounds: 10)
128
196
  puts ranked_items # => [{ name: "Circle", score: 1500 }, { name: "Square", score: 800 }, { name: "Triangle", score: 800 }]
129
197
  ```
130
198
 
131
- ## Good practices
132
- - LLMs can take a long time to respond, so avoid putting them in the main thread
133
- - Do not use the LLMs to extract sensitive or personal data
134
199
 
200
+ ## Configuration Options
201
+
202
+ | Option | Description | Default |
203
+ |--------|-------------|---------|
204
+ | `provider` | LLM provider (openai, anthropic, etc) | `nil` |
205
+ | `model` | Model to use | `nil` |
206
+ | `api_key` | Provider API key | `nil` |
207
+ | `timeout` | Request timeout in seconds | `5` |
208
+ | `max_retries` | Maximum retry attempts | `3` |
209
+
210
+ > **Note:** Each module can append its own set of configuration options, see the individual module documentation for details.
211
+
212
+ ## Contributing
213
+
214
+ 1. Fork the repository
215
+ 2. Create your feature branch (`git checkout -b feature/amazing-feature`)
216
+ 3. Commit your changes (`git commit -m 'Add amazing feature'`)
217
+ 4. Push to the branch (`git push origin feature/amazing-feature`)
218
+ 5. Open a Pull Request
135
219
  ## License
136
- See the [LICENSE](LICENSE)
220
+
221
+ This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
222
+
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.3
1
+ 0.0.8
@@ -0,0 +1,39 @@
1
+ # Battle
2
+ AI-powered battle evaluation system that determines winners between two players based on specified criteria.
3
+
4
+ ## Features
5
+ - Content comparison - Evaluate submissions from two players against defined criteria
6
+ - Objective analysis - AI-powered assessment of how well each player meets requirements
7
+ - Detailed reasoning - Comprehensive explanation of why a winner was chosen
8
+ - Draw avoidance - Suggestions on how to modify content to avoid draws
9
+ - Flexible input - Support for both string content and structured data with content field
10
+
11
+ ## Basic Usage
12
+ Evaluate a battle between two players with simple text content:
13
+
14
+ ```ruby
15
+ player_a = "Implementation uses dependency injection for better testability"
16
+ player_b = "Code has high test coverage but tightly coupled components"
17
+ criteria = "Evaluate code quality and maintainability"
18
+
19
+ result = ActiveGenie::Battle::Basic.call(player_a, player_b, criteria)
20
+ # => {
21
+ # winner_player: "Implementation uses dependency injection for better testability",
22
+ # reasoning: "Player A's implementation demonstrates better maintainability through dependency injection,
23
+ # which allows for easier testing and component replacement. While Player B has good test coverage,
24
+ # the tight coupling makes the code harder to maintain and modify.",
25
+ # what_could_be_changed_to_avoid_draw: "Focus on specific architectural patterns and design principles"
26
+ # }
27
+ ```
28
+
29
+ ## Interface
30
+ ### Basic.call(player_a, player_b, criteria, options: {})
31
+ - `player_a` [String, Hash] - The content or submission from the first player
32
+ - `player_b` [String, Hash] - The content or submission from the second player
33
+ - `criteria` [String] - The evaluation criteria or rules to assess against
34
+ - `options` [Hash] - Additional configuration options that modify the battle evaluation behavior
35
+
36
+ Returns a Hash containing:
37
+ - `winner_player` [String, Hash] - The winning player's content (either player_a or player_b)
38
+ - `reasoning` [String] - Detailed explanation of why the winner was chosen
39
+ - `what_could_be_changed_to_avoid_draw` [String] - A suggestion on how to avoid a draw
@@ -0,0 +1,125 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative '../clients/router'
4
+
5
+ module ActiveGenie::Battle
6
+ # The Basic class provides a foundation for evaluating battles between two players
7
+ # using AI-powered evaluation. It determines a winner based on specified criteria,
8
+ # analyzing how well each player meets the requirements.
9
+ #
10
+ # The battle evaluation process compares two players' content against given criteria
11
+ # and returns detailed feedback including the winner and reasoning for the decision.
12
+ #
13
+ # @example Basic usage with two players and criteria
14
+ # Basic.call("Player A content", "Player B content", "Evaluate keyword usage and pattern matching")
15
+ #
16
+ class Basic
17
+ def self.call(player_a, player_b, criteria, options: {})
18
+ new(player_a, player_b, criteria, options:).call
19
+ end
20
+
21
+ # @param player_a [String] The content or submission from the first player
22
+ # @param player_b [String] The content or submission from the second player
23
+ # @param criteria [String] The evaluation criteria or rules to assess against
24
+ # @param options [Hash] Additional configuration options that modify the battle evaluation behavior
25
+ # @return [Hash] The evaluation result containing the winner and reasoning
26
+ # @return [String] :winner The @param player_a or player_b
27
+ # @return [String] :reasoning Detailed explanation of why the winner was chosen
28
+ # @return [String] :what_could_be_changed_to_avoid_draw A suggestion on how to avoid a draw
29
+ def initialize(player_a, player_b, criteria, options: {})
30
+ @player_a = player_a
31
+ @player_b = player_b
32
+ @criteria = criteria
33
+ @options = options
34
+ @response = nil
35
+ end
36
+
37
+ def call
38
+ messages = [
39
+ { role: 'system', content: PROMPT },
40
+ { role: 'user', content: "criteria: #{@criteria}" },
41
+ { role: 'user', content: "player_a: #{player_content(@player_a)}" },
42
+ { role: 'user', content: "player_b: #{player_content(@player_b)}" },
43
+ ]
44
+
45
+ @response = ::ActiveGenie::Clients::Router.function_calling(messages, FUNCTION, options: @options)
46
+
47
+ response_formatted
48
+ end
49
+
50
+ private
51
+
52
+ def player_content(player)
53
+ return player.dig('content') if player.is_a?(Hash)
54
+
55
+ player
56
+ end
57
+
58
+ def response_formatted
59
+ if @response['winner'] == 'player_a'
60
+ @response['winner'] = @player_a
61
+ @response['loser'] = @player_b
62
+ elsif @response['winner'] == 'player_b'
63
+ @response['winner'] = @player_b
64
+ @response['loser'] = @player_a
65
+ else
66
+ @response['winner'] = nil
67
+ @response['loser'] = nil
68
+ end
69
+
70
+ @response
71
+ end
72
+
73
+ PROMPT = <<~PROMPT
74
+ Evaluate a battle between player_a and player_b using predefined criteria and identify the winner.
75
+
76
+ Consider rules, keywords, and patterns as the criteria for evaluation. Analyze the content from both players objectively, focusing on who meets the criteria most effectively. Explain your decision clearly, with specific reasoning on how the chosen player fulfilled the criteria better than the other. Avoid selecting a draw unless absolutely necessary.
77
+
78
+ # Steps
79
+ 1. **Review Predefined Criteria**: Understand the specific rules, keywords, and patterns that serve as the basis for evaluation.
80
+ 2. **Analyze Content**: Examine the contributions of both player_a and player_b. Look for how each player meets or fails to meet the criteria.
81
+ 3. **Comparison**: Compare both players against each criterion to determine who aligns better with the standards set.
82
+ 4. **Decision-Making**: Based on the analysis, determine the player who meets the most or all criteria effectively.
83
+ 5. **Provide Justification**: Offer a clear and concise reason for your choice detailing how the winner outperformed the other.
84
+
85
+ # Examples
86
+ - **Example 1**:
87
+ - Input: Player A uses keyword X, follows rule Y, Player B uses keyword Z, breaks rule Y.
88
+ - Output: winner: player_a
89
+ - Justification: Player A successfully used keyword X and followed rule Y, whereas Player B broke rule Y.
90
+
91
+ - **Example 2**:
92
+ - Input: Player A matches pattern P, Player B matches pattern P, uses keyword Q.
93
+ - Output: winner: player_b
94
+ - Justification: Both matched pattern P, but Player B also used keyword Q, meeting more criteria.
95
+
96
+ # Notes
97
+ - Avoid drawing if a clear winner can be discerned.
98
+ - Critically assess each player's adherence to the criteria.
99
+ - Clearly communicate the reasoning behind your decision.
100
+ PROMPT
101
+
102
+ FUNCTION = {
103
+ name: 'battle_evaluation',
104
+ description: 'Evaluate a battle between player_a and player_b using predefined criteria and identify the winner.',
105
+ schema: {
106
+ type: "object",
107
+ properties: {
108
+ winner: {
109
+ type: 'string',
110
+ description: 'The player who won the battle based on the criteria.',
111
+ enum: ['player_a', 'player_b', 'draw']
112
+ },
113
+ reasoning_of_winner: {
114
+ type: 'string',
115
+ description: 'The detailed reasoning about why the winner won based on the criteria.',
116
+ },
117
+ what_could_be_changed_to_avoid_draw: {
118
+ type: 'string',
119
+ description: 'Suggestions on how to avoid a draw based on the criteria. Be as objective and short as possible. Can be empty.',
120
+ }
121
+ }
122
+ }
123
+ }
124
+ end
125
+ end
@@ -0,0 +1,13 @@
1
+
2
+ require_relative 'battle/basic'
3
+
4
+ module ActiveGenie
5
+ # Battle module
6
+ module Battle
7
+ module_function
8
+
9
+ def basic(...)
10
+ Basic.call(...)
11
+ end
12
+ end
13
+ end
@@ -28,7 +28,9 @@ module ActiveGenie::Clients
28
28
 
29
29
  response = request(payload, headers)
30
30
 
31
- JSON.parse(response.dig('choices', 0, 'message', 'content'))
31
+ parsed_response = JSON.parse(response.dig('choices', 0, 'message', 'content'))
32
+
33
+ parsed_response.dig('properties') || parsed_response
32
34
  rescue JSON::ParserError
33
35
  nil
34
36
  end
@@ -5,7 +5,7 @@ module ActiveGenie
5
5
  attr_accessor :path_to_config
6
6
 
7
7
  def initialize
8
- @path_to_config = File.join(__dir__, 'config', 'gen_ai.yml')
8
+ @path_to_config = File.join('config', 'active_genie.yml')
9
9
  end
10
10
 
11
11
  def values
@@ -23,7 +23,8 @@ module ActiveGenie
23
23
  def load_values
24
24
  return {} unless File.exist?(@path_to_config)
25
25
 
26
- YAML.load_file(@path_to_config) || {}
26
+ yaml_content = ERB.new(File.read(@path_to_config)).result
27
+ YAML.safe_load(yaml_content, aliases: true) || {}
27
28
  rescue Psych::SyntaxError => e
28
29
  warn "ActiveGenie.warning: Config file '#{@path_to_config}' is not a valid YAML file (#{e.message}), using default configuration"
29
30
  {}
@@ -6,12 +6,12 @@ module ActiveGenie
6
6
  module DataExtractor
7
7
  module_function
8
8
 
9
- def basic(text, data_to_extract, options: {})
10
- Basic.call(text, data_to_extract, options:)
9
+ def basic(...)
10
+ Basic.call(...)
11
11
  end
12
12
 
13
- def from_informal(text, data_to_extract, options: {})
14
- FromInformal.call(text, data_to_extract, options:)
13
+ def from_informal(...)
14
+ FromInformal.call(...)
15
15
  end
16
16
  end
17
17
  end
@@ -0,0 +1,88 @@
1
+ require_relative '../battle/basic'
2
+ require_relative '../utils/math'
3
+
4
+ module ActiveGenie::Leaderboard
5
+ class EloRanking
6
+ def self.call(players, criteria, options: {})
7
+ new(players, criteria, options:).call
8
+ end
9
+
10
+ def initialize(players, criteria, options: {})
11
+ @players = players
12
+ @criteria = criteria
13
+ @options = options
14
+ end
15
+
16
+ def call
17
+ @players.each(&:generate_elo_by_score)
18
+
19
+ while @players.eligible_size > MINIMAL_PLAYERS_TO_BATTLE
20
+ round = create_round(@players.tier_relegation, @players.tier_defense)
21
+
22
+ round.each do |player_a, player_b|
23
+ winner, loser = battle(player_a, player_b) # This can take a while, can be parallelized
24
+ update_elo(winner, loser)
25
+ end
26
+
27
+ @players.tier_relegation.each { |player| player.eliminated = "relegation/#{@players.eligible_size}" }
28
+ end
29
+
30
+ @players
31
+ end
32
+
33
+ private
34
+
35
+ MATCHS_PER_PLAYER = 3
36
+ LOSE_PENALTY = 15
37
+ MINIMAL_PLAYERS_TO_BATTLE = 10
38
+
39
+ # Create a round of matches
40
+ # each round is exactly 1 regation player vs 3 defense players for all regation players
41
+ # each match is unique (player vs player)
42
+ # each defense player is battle exactly 3 times
43
+ def create_round(relegation_players, defense_players)
44
+ matches = []
45
+
46
+ relegation_players.each do |player_a|
47
+ player_enemies = []
48
+ MATCHS_PER_PLAYER.times do
49
+ defender = nil
50
+ while defender.nil? || player_enemies.include?(defender.id)
51
+ defender = defense_players.sample
52
+ end
53
+
54
+ matches << [player_a, defender].shuffle
55
+ player_enemies << defender.id
56
+ end
57
+ end
58
+
59
+ matches
60
+ end
61
+
62
+ def battle(player_a, player_b)
63
+ ActiveGenie::Battle.basic(
64
+ player_a,
65
+ player_b,
66
+ @criteria,
67
+ options: @options
68
+ ).values_at('winner', 'loser')
69
+ end
70
+
71
+ def update_elo(winner, loser)
72
+ return if winner.nil? || loser.nil?
73
+
74
+ new_winner_elo, new_loser_elo = ActiveGenie::Utils::Math.calculate_new_elo(winner.elo, loser.elo)
75
+
76
+ winner.elo = [new_winner_elo, max_defense_elo].min
77
+ loser.elo = [new_loser_elo - LOSE_PENALTY, min_relegation_elo].max
78
+ end
79
+
80
+ def max_defense_elo
81
+ @players.tier_defense.max_by(&:elo).elo
82
+ end
83
+
84
+ def min_relegation_elo
85
+ @players.tier_relegation.min_by(&:elo).elo
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,72 @@
1
+ require_relative './players_collection'
2
+ require_relative './league'
3
+ require_relative './elo_ranking'
4
+ require_relative '../scoring/recommended_reviews'
5
+
6
+ module ActiveGenie::Leaderboard
7
+ class Leaderboard
8
+ def self.call(param_players, criteria, options: {})
9
+ new(param_players, criteria, options:).call
10
+ end
11
+
12
+ def initialize(param_players, criteria, options: {})
13
+ @param_players = param_players
14
+ @criteria = criteria
15
+ @options = options
16
+ end
17
+
18
+ def call
19
+ set_initial_score_players
20
+ eliminate_obvious_bad_players
21
+ run_elo_ranking if players.eligible_size > 10
22
+ run_league
23
+
24
+ players.to_h
25
+ end
26
+
27
+ private
28
+
29
+ SCORE_VARIATION_THRESHOLD = 10
30
+ MATCHS_PER_PLAYER = 3
31
+
32
+ def set_initial_score_players
33
+ players.each do |player|
34
+ player.score = generate_score(player.content) # This can take a while, can be parallelized
35
+ end
36
+ end
37
+
38
+ def generate_score(content)
39
+ ActiveGenie::Scoring::Basic.call(content, @criteria, reviewers, options: @options)['final_score']
40
+ end
41
+
42
+ def eliminate_obvious_bad_players
43
+ while players.coefficient_of_variation >= SCORE_VARIATION_THRESHOLD
44
+ players.eligible.last.eliminated = 'too_low_score'
45
+ end
46
+ end
47
+
48
+ def run_elo_ranking
49
+ EloRanking.call(players, @criteria, options: @options)
50
+ end
51
+
52
+ def run_league
53
+ League.call(players, @criteria, options: @options)
54
+ end
55
+
56
+ def reviewers
57
+ [recommended_reviews['reviewer1'], recommended_reviews['reviewer2'], recommended_reviews['reviewer3']]
58
+ end
59
+
60
+ def recommended_reviews
61
+ @recommended_reviews ||= ActiveGenie::Scoring::RecommendedReviews.call(
62
+ players.sample,
63
+ @criteria,
64
+ options: @options
65
+ )
66
+ end
67
+
68
+ def players
69
+ @players ||= PlayersCollection.new(@param_players)
70
+ end
71
+ end
72
+ end
@@ -0,0 +1,48 @@
1
+ require_relative '../battle/basic'
2
+
3
+ module ActiveGenie::Leaderboard
4
+ class League
5
+ def self.call(players, criteria, options: {})
6
+ new(players, criteria, options:).call
7
+ end
8
+
9
+ def initialize(players, criteria, options: {})
10
+ @players = players
11
+ @criteria = criteria
12
+ @options = options
13
+ end
14
+
15
+ def call
16
+ matches.each do |player_a, player_b|
17
+ winner, loser = battle(player_a, player_b)
18
+
19
+ if winner.nil? || loser.nil?
20
+ player_a.league[:draw] += 1
21
+ player_b.league[:draw] += 1
22
+ else
23
+ winner.league[:win] += 1
24
+ loser.league[:lose] += 1
25
+ end
26
+ end
27
+
28
+ @players
29
+ end
30
+
31
+ private
32
+
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 A should clearly be better than C
35
+ def matches
36
+ @players.eligible.combination(2).to_a
37
+ end
38
+
39
+ def battle(player_a, player_b)
40
+ ActiveGenie::Battle.basic(
41
+ player_a,
42
+ player_b,
43
+ @criteria,
44
+ options: @options
45
+ ).values_at('winner', 'loser')
46
+ end
47
+ end
48
+ end
@@ -0,0 +1,52 @@
1
+ require 'securerandom'
2
+
3
+ module ActiveGenie::Leaderboard
4
+ class Player
5
+ def initialize(params)
6
+ params = { content: params } if params.is_a?(String)
7
+
8
+ @id = params.dig(:id) || SecureRandom.uuid
9
+ @content = params.dig(:content) || params
10
+ @score = params.dig(:score) || nil
11
+ @elo = params.dig(:elo) || nil
12
+ @league = {
13
+ win: params.dig(:league, :win) || 0,
14
+ lose: params.dig(:league, :lose) || 0,
15
+ draw: params.dig(:league, :draw) || 0
16
+ }
17
+ @eliminated = params.dig(:eliminated) || nil
18
+ end
19
+
20
+ attr_reader :id, :content, :score, :elo, :league, :eliminated
21
+
22
+ def generate_elo_by_score
23
+ return if !@elo.nil? || @score.nil?
24
+
25
+ @elo = BASE_ELO + (@score - 50)
26
+ end
27
+
28
+ def score=(value)
29
+ @score = value
30
+ end
31
+
32
+ def elo=(value)
33
+ @elo = value
34
+ end
35
+
36
+ def eliminated=(value)
37
+ @eliminated = value
38
+ end
39
+
40
+ def league_score
41
+ @league[:win] * 3 + @league[:draw]
42
+ end
43
+
44
+ def to_h
45
+ { id:, content:, score:, elo:, eliminated:, league: }
46
+ end
47
+
48
+ private
49
+
50
+ BASE_ELO = 1000
51
+ end
52
+ end
@@ -0,0 +1,68 @@
1
+ require_relative '../utils/math'
2
+ require_relative './player'
3
+
4
+ module ActiveGenie::Leaderboard
5
+ class PlayersCollection
6
+ def initialize(param_players)
7
+ @players = build(param_players)
8
+ end
9
+ attr_reader :players
10
+
11
+ def coefficient_of_variation
12
+ score_list = eligible.map(&:score)
13
+ mean = score_list.sum.to_f / score_list.size
14
+ return nil if mean == 0 # To avoid division by zero
15
+
16
+ variance = score_list.map { |num| (num - mean) ** 2 }.sum / score_list.size
17
+ standard_deviation = Math.sqrt(variance)
18
+
19
+ (standard_deviation / mean) * 100
20
+ end
21
+
22
+ def tier_relegation
23
+ eligible[(tier_size*-1)..-1]
24
+ end
25
+
26
+ def tier_defense
27
+ eligible[(tier_size*-2)...(tier_size*-1)]
28
+ end
29
+
30
+ def eligible
31
+ sorted.reject(&:eliminated)
32
+ end
33
+
34
+ def eligible_size
35
+ @players.reject(&:eliminated).size
36
+ end
37
+
38
+ def to_h
39
+ sorted.map(&:to_h)
40
+ end
41
+
42
+ def method_missing(...)
43
+ @players.send(...)
44
+ end
45
+
46
+ def sorted
47
+ @players.sort_by { |p| [-p.league_score, -(p.elo || 0), -p.score] }
48
+ end
49
+
50
+ private
51
+
52
+ def build(param_players)
53
+ param_players.map { |player| Player.new(player) }
54
+ end
55
+
56
+ # Returns the number of players to battle in each round
57
+ # based on the eligible size, start fast and go slow until top 10
58
+ # Example:
59
+ # - 50 eligible, tier_size: 15
60
+ # - 35 eligible, tier_size: 11
61
+ # - 24 eligible, tier_size: 10
62
+ # - 14 eligible, tier_size: 4
63
+ # 4 rounds to reach top 10 with 50 players
64
+ def tier_size
65
+ [[(eligible_size / 3).ceil, 10].max, eligible_size - 10].min
66
+ end
67
+ end
68
+ end
@@ -0,0 +1,11 @@
1
+ require_relative 'leaderboard/leaderboard'
2
+
3
+ module ActiveGenie
4
+ module Leaderboard
5
+ module_function
6
+
7
+ def call(...)
8
+ Leaderboard.call(...)
9
+ end
10
+ end
11
+ end
@@ -17,12 +17,6 @@ module ActiveGenie::Scoring
17
17
  # Basic.call("Sample text", "Evaluate technical accuracy")
18
18
  #
19
19
  class Basic
20
- def self.call(text, criteria, reviewers = [], options: {})
21
- new(text, criteria, reviewers, options:).call
22
- end
23
-
24
- # Initializes a new scoring evaluation instance
25
- #
26
20
  # @param text [String] The text content to be evaluated
27
21
  # @param criteria [String] The evaluation criteria or rubric to assess against
28
22
  # @param reviewers [Array<String>] Optional list of specific reviewers. If empty,
@@ -30,6 +24,13 @@ module ActiveGenie::Scoring
30
24
  # @param options [Hash] Additional configuration options that modify the scoring behavior
31
25
  # @option options [Boolean] :detailed_feedback Request more detailed feedback in the reasoning
32
26
  # @option options [Hash] :reviewer_weights Custom weights for different reviewers
27
+ # @return [Hash] The evaluation result containing the scores and reasoning
28
+ # @return [Number] :final_score The final score of the text based on the criteria and reviewers
29
+ # @return [String] :final_reasoning Detailed explanation of why the final score was reached
30
+ def self.call(text, criteria, reviewers = [], options: {})
31
+ new(text, criteria, reviewers, options:).call
32
+ end
33
+
33
34
  def initialize(text, criteria, reviewers = [], options: {})
34
35
  @text = text
35
36
  @criteria = criteria
@@ -55,7 +55,7 @@ module ActiveGenie::Scoring
55
55
  }
56
56
  }
57
57
 
58
- ::ActiveGenie::Clients::Router.function_calling(messages, function, options:)
58
+ ::ActiveGenie::Clients::Router.function_calling(messages, function, options: @options)
59
59
  end
60
60
 
61
61
  private
@@ -6,12 +6,12 @@ module ActiveGenie
6
6
  module Scoring
7
7
  module_function
8
8
 
9
- def basic(text, criteria, reviewers = [], options: {})
10
- Basic.call(text, criteria, reviewers, options:)
9
+ def basic(...)
10
+ Basic.call(...)
11
11
  end
12
12
 
13
- def recommended_reviews(text, criteria, options: {})
14
- RecommendedReviews.call(text, criteria, options:)
13
+ def recommended_reviews(...)
14
+ RecommendedReviews.call(...)
15
15
  end
16
16
  end
17
17
  end
@@ -0,0 +1,15 @@
1
+ module ActiveGenie::Utils
2
+ module Math
3
+ module_function
4
+
5
+ def self.calculate_new_elo(winner, loser, k: 32)
6
+ expected_score_a = 1 / (1 + 10**((loser - winner) / 400))
7
+ expected_score_b = 1 - expected_score_a
8
+
9
+ new_elo_winner = winner + k * (1 - expected_score_a)
10
+ new_elo_loser = loser + k * (1 - expected_score_b)
11
+
12
+ [new_elo_winner, new_elo_loser]
13
+ end
14
+ end
15
+ end
data/lib/active_genie.rb CHANGED
@@ -1,22 +1,33 @@
1
1
  module ActiveGenie
2
+ autoload :Configuration, File.join(__dir__, 'active_genie/configuration')
3
+
4
+ # Modules
2
5
  autoload :DataExtractor, File.join(__dir__, 'active_genie/data_extractor')
6
+ autoload :Battle, File.join(__dir__, 'active_genie/battle')
3
7
  autoload :Scoring, File.join(__dir__, 'active_genie/scoring')
4
- autoload :Configuration, File.join(__dir__, 'active_genie/configuration')
8
+ autoload :Leaderboard, File.join(__dir__, 'active_genie/leaderboard')
9
+
10
+ class << self
11
+ def configure
12
+ yield(config) if block_given?
13
+ end
14
+
15
+ def load_tasks
16
+ return unless defined?(Rake)
17
+
18
+ Rake::Task.define_task(:environment)
19
+ Dir.glob(File.join(__dir__, 'tasks', '*.rake')).each { |r| load r }
20
+ end
5
21
 
6
- class << self
7
22
  def config
8
23
  @config ||= Configuration.new
9
24
  end
10
25
 
11
- def configure
12
- yield(config) if block_given?
13
- end
14
-
15
26
  def [](key)
16
27
  config.values[key.to_s]
17
28
  end
18
29
 
19
- def config_by_model(model)
30
+ def config_by_model(model = nil)
20
31
  config.values[model&.to_s&.downcase&.strip] || config.values.values.first || {}
21
32
  end
22
33
  end
@@ -3,7 +3,7 @@ require 'fileutils'
3
3
  namespace :active_genie do
4
4
  desc 'Install active_genie configuration file'
5
5
  task :install do
6
- source = File.join('lib', 'tasks', 'templates', 'active_genie.yml')
6
+ source = File.join(__dir__, 'templates', 'active_genie.yml')
7
7
  target = File.join('config', 'active_genie.yml')
8
8
 
9
9
  FileUtils.cp(source, target)
@@ -1,4 +1,4 @@
1
- # GPT-4o-mini:
1
+ # gpt-4o-mini:
2
2
  # api_key: <%= ENV['OPENAI_API_KEY'] %>
3
3
  # provider: 'openai'
4
4
  #
metadata CHANGED
@@ -1,22 +1,113 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_genie
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.0.3
4
+ version: 0.0.8
5
5
  platform: ruby
6
6
  authors:
7
7
  - Radamés Roriz
8
8
  autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
- date: 2025-01-25 00:00:00.000000000 Z
11
+ date: 2025-02-06 00:00:00.000000000 Z
12
12
  dependencies: []
13
- description: |2
14
- ActiveGenie provides a robust toolkit for integrating AI capabilities into Ruby applications.
15
- Features include:
16
- * Structured data extraction from text
17
- * Smart text summarization
18
- * Content scoring and ranking
19
- * AI-powered classification
13
+ description: "# ActiveGenie \U0001F9DE‍♂️\n> Transform your Ruby application with
14
+ powerful, production-ready GenAI features\n\n[![Gem Version](https://badge.fury.io/rb/active_genie.svg?icon=si%3Arubygems)](https://badge.fury.io/rb/active_genie)\n[![Ruby](https://github.com/roriz/active_genie/actions/workflows/ruby.yml/badge.svg)](https://github.com/roriz/active_genie/actions/workflows/ruby.yml)\n\nActiveGenie
15
+ is a Ruby gem that provides a polished, production-ready interface for working with
16
+ Generative AI (GenAI) models. Just like ActiveStorage simplifies file handling in
17
+ Rails, ActiveGenie makes it effortless to integrate GenAI capabilities into your
18
+ Ruby applications.\n\n## Features\n\n- \U0001F3AF **Data Extraction**: Extract structured
19
+ data from unstructured text with type validation\n- \U0001F4CA **Smart Scoring**:
20
+ Multi-reviewer evaluation system with automatic expert selection\n- \U0001F4AD **Sentiment
21
+ Analysis**: Advanced sentiment analysis with customizable rules\n- \U0001F512 **Safe
22
+ & Secure**: Built-in validation and sanitization\n- \U0001F6E0️ **Configurable**:
23
+ Supports multiple GenAI providers and models\n\n## Installation\n\n1. Add to your
24
+ Gemfile:\n```ruby\ngem 'active_genie'\n```\n\n2. Install the gem:\n```shell\nbundle
25
+ install\n```\n\n3. Generate the configuration:\n```shell\necho \"ActiveGenie.load_tasks\"
26
+ >> Rakefile\nrails g active_genie:install\n```\n\n4. [Optional] Configure your credentials
27
+ in `config/active_genie.yml`:\n```yaml\nGPT-4o-mini:\n api_key: <%= ENV['OPENAI_API_KEY']
28
+ %>\n provider: \"openai\"\n\nclaude-3-5-sonnet:\n api_key: <%= ENV['ANTHROPIC_API_KEY']
29
+ %>\n provider: \"anthropic\"\n```\n\n> The first key will be used as default in
30
+ all modules, in this example `GPT-4o-mini`\n\n## Quick Start\n\n### Data Extractor\nExtract
31
+ structured data from text using AI-powered analysis, handling informal language
32
+ and complex expressions.\n\n```ruby\ntext = \"Nike Air Max 90 - Size 42 - $199.99\"\nschema
33
+ = {\n brand: { \n type: 'string',\n enum: [\"Nike\", \"Adidas\", \"Puma\"]\n
34
+ \ },\n price: { \n type: 'number',\n minimum: 0\n },\n size: {\n type:
35
+ 'integer',\n minimum: 35,\n maximum: 46\n }\n}\n\nresult = ActiveGenie::DataExtractor.call(text,
36
+ schema)\n# => { \n# brand: \"Nike\", \n# brand_explanation: \"Brand name
37
+ found at start of text\",\n# price: 199.99,\n# price_explanation: \"Price
38
+ found in USD format at end\",\n# size: 42,\n# size_explanation: \"Size
39
+ explicitly stated in the middle\"\n# }\n```\n\nFeatures:\n- Structured data extraction
40
+ with type validation\n- Schema-based extraction with custom constraints\n- Informal
41
+ text analysis (litotes, hedging)\n- Detailed explanations for extracted values\n\nSee
42
+ the [Data Extractor README](lib/active_genie/data_extractor/README.md) for informal
43
+ text processing, advanced schemas, and detailed interface documentation.\n\n###
44
+ Scoring\nText evaluation system that provides detailed scoring and feedback using
45
+ multiple expert reviewers. Get balanced scoring through AI-powered expert reviewers
46
+ that automatically adapt to your content.\n\n```ruby\ntext = \"The code implements
47
+ a binary search algorithm with O(log n) complexity\"\ncriteria = \"Evaluate technical
48
+ accuracy and clarity\"\n\nresult = ActiveGenie::Scoring::Basic.call(text, criteria)\n#
49
+ => {\n# algorithm_expert_score: 95,\n# algorithm_expert_reasoning: \"Accurately
50
+ describes binary search and its complexity\",\n# technical_writer_score: 90,\n#
51
+ \ technical_writer_reasoning: \"Clear and concise explanation of the algorithm\",\n#
52
+ \ final_score: 92.5\n# }\n```\n\nFeatures:\n- Multi-reviewer evaluation with
53
+ automatic expert selection\n- Detailed feedback with scoring reasoning\n- Customizable
54
+ reviewer weights\n- Flexible evaluation criteria\n\nSee the [Scoring README](lib/active_genie/scoring/README.md)
55
+ for advanced usage, custom reviewers, and detailed interface documentation.\n\n###
56
+ Battle\nAI-powered battle evaluation system that determines winners between two
57
+ players based on specified criteria.\n\n```ruby\nrequire 'active_genie'\n\nplayer_a
58
+ = \"Implementation uses dependency injection for better testability\"\nplayer_b
59
+ = \"Code has high test coverage but tightly coupled components\"\ncriteria = \"Evaluate
60
+ code quality and maintainability\"\n\nresult = ActiveGenie::Battle::Basic.call(player_a,
61
+ player_b, criteria)\n# => {\n# winner_player: \"Implementation uses dependency
62
+ injection for better testability\",\n# reasoning: \"Player A's implementation
63
+ demonstrates better maintainability through dependency injection, \n# which
64
+ allows for easier testing and component replacement. While Player B has good test
65
+ coverage, \n# the tight coupling makes the code harder to maintain
66
+ and modify.\",\n# what_could_be_changed_to_avoid_draw: \"Focus on specific
67
+ architectural patterns and design principles\"\n# }\n```\n\nFeatures:\n- Multi-reviewer
68
+ evaluation with automatic expert selection\n- Detailed feedback with scoring reasoning\n-
69
+ Customizable reviewer weights\n- Flexible evaluation criteria\n\nSee the [Battle
70
+ README](lib/active_genie/battle/README.md) for advanced usage, custom reviewers,
71
+ and detailed interface documentation.\n\n### Summarizer (WIP)\nThe summarizer is
72
+ a tool that can be used to summarize a given text. It uses a set of rules to summarize
73
+ the text out of the box. Uses the best practices of prompt engineering and engineering
74
+ to make the summarization as accurate as possible.\n\n```ruby\nrequire 'active_genie'\n\ntext
75
+ = \"Example text to be summarized. The fox jumps over the dog\"\nsummarized_text
76
+ = ActiveGenie::Summarizer.call(text)\nputs summarized_text # => \"The fox jumps
77
+ over the dog\"\n```\n\n### Language detector (WIP)\nThe language detector is a tool
78
+ that can be used to detect the language of a given text. It uses a set of rules
79
+ to detect the language of the text out of the box. Uses the best practices of prompt
80
+ engineering and engineering to make the language detection as accurate as possible.\n\n```ruby\nrequire
81
+ 'active_genie'\n\ntext = \"Example text to be detected\"\nlanguage = ActiveGenie::LanguageDetector.call(text)\nputs
82
+ language # => \"en\"\n```\n\n### Translator (WIP)\nThe translator is a tool that
83
+ can be used to translate a given text. It uses a set of rules to translate the text
84
+ out of the box. Uses the best practices of prompt engineering and engineering to
85
+ make the translation as accurate as possible.\n\n```ruby\nrequire 'active_genie'\n\ntext
86
+ = \"Example text to be translated\"\ntranslated_text = ActiveGenie::Translator.call(text,
87
+ from: 'en', to: 'pt')\nputs translated_text # => \"Exemplo de texto a ser traduzido\"\n```\n\n###
88
+ Sentiment analyzer (WIP)\nThe sentiment analyzer is a tool that can be used to analyze
89
+ the sentiment of a given text. It uses a set of rules to analyze the sentiment of
90
+ the text out of the box. Uses the best practices of prompt engineering and engineering
91
+ to make the sentiment analysis as accurate as possible.\n\n```ruby\nrequire 'active_genie'\n\ntext
92
+ = \"Example text to be analyzed\"\nsentiment = ActiveGenie::SentimentAnalyzer.call(text)\nputs
93
+ sentiment # => \"positive\"\n```\n\n### Elo ranking (WIP)\nThe Elo ranking is a
94
+ tool that can be used to rank a set of items. It uses a set of rules to rank the
95
+ items out of the box. Uses the best practices of prompt engineering and engineering
96
+ to make the ranking as accurate as possible.\n\n```ruby\nrequire 'active_genie'\n\nitems
97
+ = ['Square', 'Circle', 'Triangle']\ncriterias = 'items that look rounded'\nranked_items
98
+ = ActiveGenie::EloRanking.call(items, criterias, rounds: 10)\nputs ranked_items
99
+ # => [{ name: \"Circle\", score: 1500 }, { name: \"Square\", score: 800 }, { name:
100
+ \"Triangle\", score: 800 }]\n```\n\n\n## Configuration Options\n\n| Option | Description
101
+ | Default |\n|--------|-------------|---------|\n| `provider` | LLM provider (openai,
102
+ anthropic, etc) | `nil` |\n| `model` | Model to use | `nil` |\n| `api_key` | Provider
103
+ API key | `nil` |\n| `timeout` | Request timeout in seconds | `5` |\n| `max_retries`
104
+ | Maximum retry attempts | `3` |\n\n> **Note:** Each module can append its own set
105
+ of configuration options, see the individual module documentation for details.\n\n##
106
+ Contributing\n\n1. Fork the repository\n2. Create your feature branch (`git checkout
107
+ -b feature/amazing-feature`)\n3. Commit your changes (`git commit -m 'Add amazing
108
+ feature'`)\n4. Push to the branch (`git push origin feature/amazing-feature`)\n5.
109
+ Open a Pull Request\n## License\n\nThis project is licensed under the MIT License
110
+ - see the [LICENSE](LICENSE) file for details.\n\n"
20
111
  email:
21
112
  - radames@roriz.dev
22
113
  executables: []
@@ -27,6 +118,9 @@ files:
27
118
  - README.md
28
119
  - VERSION
29
120
  - lib/active_genie.rb
121
+ - lib/active_genie/battle.rb
122
+ - lib/active_genie/battle/README.md
123
+ - lib/active_genie/battle/basic.rb
30
124
  - lib/active_genie/clients/openai.rb
31
125
  - lib/active_genie/clients/router.rb
32
126
  - lib/active_genie/configuration.rb
@@ -34,12 +128,19 @@ files:
34
128
  - lib/active_genie/data_extractor/README.md
35
129
  - lib/active_genie/data_extractor/basic.rb
36
130
  - lib/active_genie/data_extractor/from_informal.rb
131
+ - lib/active_genie/leaderboard.rb
132
+ - lib/active_genie/leaderboard/elo_ranking.rb
133
+ - lib/active_genie/leaderboard/leaderboard.rb
134
+ - lib/active_genie/leaderboard/league.rb
135
+ - lib/active_genie/leaderboard/player.rb
136
+ - lib/active_genie/leaderboard/players_collection.rb
37
137
  - lib/active_genie/scoring.rb
38
138
  - lib/active_genie/scoring/README.md
39
139
  - lib/active_genie/scoring/basic.rb
40
140
  - lib/active_genie/scoring/recommended_reviews.rb
141
+ - lib/active_genie/utils/math.rb
41
142
  - lib/tasks/install.rake
42
- - lib/tasks/templates/active_ai.yml
143
+ - lib/tasks/templates/active_genie.yml
43
144
  homepage: https://github.com/Roriz/active_genie
44
145
  licenses:
45
146
  - Apache-2.0
@@ -67,6 +168,5 @@ requirements: []
67
168
  rubygems_version: 3.5.3
68
169
  signing_key:
69
170
  specification_version: 4
70
- summary: Modules and classes to help you build AI features, like data extraction,
71
- summarization, scoring, and ranking.
171
+ summary: Transform your Ruby application with powerful, production-ready GenAI features
72
172
  test_files: []