active_genie 0.0.3 → 0.0.10
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 +146 -59
- data/VERSION +1 -1
- data/lib/active_genie/battle/README.md +39 -0
- data/lib/active_genie/battle/basic.rb +130 -0
- data/lib/active_genie/battle.rb +13 -0
- data/lib/active_genie/clients/openai_client.rb +77 -0
- data/lib/active_genie/clients/unified_client.rb +19 -0
- data/lib/active_genie/configuration/log_config.rb +14 -0
- data/lib/active_genie/configuration/openai_config.rb +56 -0
- data/lib/active_genie/configuration/providers_config.rb +37 -0
- data/lib/active_genie/configuration.rb +18 -22
- data/lib/active_genie/data_extractor/README.md +4 -4
- data/lib/active_genie/data_extractor/basic.rb +19 -9
- data/lib/active_genie/data_extractor/from_informal.rb +18 -7
- data/lib/active_genie/data_extractor.rb +5 -5
- data/lib/active_genie/league/README.md +43 -0
- data/lib/active_genie/league/elo_ranking.rb +121 -0
- data/lib/active_genie/league/free_for_all.rb +62 -0
- data/lib/active_genie/league/league.rb +120 -0
- data/lib/active_genie/league/player.rb +59 -0
- data/lib/active_genie/league/players_collection.rb +68 -0
- data/lib/active_genie/league.rb +12 -0
- data/lib/active_genie/logger.rb +45 -0
- data/lib/active_genie/scoring/README.md +4 -8
- data/lib/active_genie/scoring/basic.rb +24 -14
- data/lib/active_genie/scoring/recommended_reviews.rb +7 -9
- data/lib/active_genie/scoring.rb +5 -5
- data/lib/active_genie.rb +14 -11
- data/lib/tasks/install.rake +3 -3
- data/lib/tasks/templates/active_genie.rb +17 -0
- metadata +119 -14
- data/lib/active_genie/clients/openai.rb +0 -59
- data/lib/active_genie/clients/router.rb +0 -41
- data/lib/tasks/templates/active_ai.yml +0 -7
@@ -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,45 @@
|
|
1
|
+
require 'json'
|
2
|
+
require 'fileutils'
|
3
|
+
|
4
|
+
module ActiveGenie
|
5
|
+
module Logger
|
6
|
+
module_function
|
7
|
+
|
8
|
+
def info(log)
|
9
|
+
save(log, level: :info)
|
10
|
+
end
|
11
|
+
|
12
|
+
def error(log)
|
13
|
+
save(log, level: :error)
|
14
|
+
end
|
15
|
+
|
16
|
+
def warn(log)
|
17
|
+
save(log, level: :warn)
|
18
|
+
end
|
19
|
+
|
20
|
+
def debug(log)
|
21
|
+
save(log, level: :debug)
|
22
|
+
end
|
23
|
+
|
24
|
+
def trace(log)
|
25
|
+
save(log, level: :trace)
|
26
|
+
end
|
27
|
+
|
28
|
+
LOG_LEVELS = { info: 0, error: 1, warn: 2, debug: 3, trace: 4 }.freeze
|
29
|
+
|
30
|
+
def save(log, level: :info)
|
31
|
+
return if LOG_LEVELS[log.dig(:log, :log_level)] || -1 < LOG_LEVELS[level]
|
32
|
+
|
33
|
+
log[:trace] = log.dig(:trace)&.to_s&.gsub('ActiveGenie::', '')
|
34
|
+
log[:timestamp] = Time.now
|
35
|
+
log[:level] = level.to_s.upcase
|
36
|
+
log[:process_id] = Process.pid
|
37
|
+
|
38
|
+
FileUtils.mkdir_p('logs')
|
39
|
+
File.write('logs/active_genie.log', "#{JSON.generate(log)}\n", mode: 'a')
|
40
|
+
puts log
|
41
|
+
|
42
|
+
log
|
43
|
+
end
|
44
|
+
end
|
45
|
+
end
|
@@ -49,26 +49,22 @@ result = ActiveGenie::Scoring::Basic.call(text, criteria)
|
|
49
49
|
|
50
50
|
## Interface
|
51
51
|
|
52
|
-
### `Basic.call(text, criteria, reviewers = [],
|
52
|
+
### `Basic.call(text, criteria, reviewers = [], config: {})`
|
53
53
|
Main interface for scoring text content.
|
54
54
|
|
55
55
|
#### Parameters
|
56
56
|
- `text` [String] - The text content to be evaluated
|
57
57
|
- `criteria` [String] - The evaluation criteria or rubric to assess against
|
58
58
|
- `reviewers` [Array<String>] - Optional list of specific reviewers
|
59
|
-
- `
|
60
|
-
- `:detailed_feedback` [Boolean] - Request more detailed feedback (WIP)
|
61
|
-
- `:reviewer_weights` [Hash] - Custom weights for different reviewers (WIP)
|
59
|
+
- `config` [Hash] - Additional configuration config
|
62
60
|
|
63
|
-
### `RecommendedReviews.call(text, criteria,
|
61
|
+
### `RecommendedReviews.call(text, criteria, config: {})`
|
64
62
|
Recommends appropriate reviewers based on content and criteria.
|
65
63
|
|
66
64
|
#### Parameters
|
67
65
|
- `text` [String] - The text content to analyze
|
68
66
|
- `criteria` [String] - The evaluation criteria
|
69
|
-
- `
|
70
|
-
- `:prefer_technical` [Boolean] - Favor technical expertise (WIP)
|
71
|
-
- `:prefer_domain` [Boolean] - Favor domain expertise (WIP)
|
67
|
+
- `config` [Hash] - Additional configuration config
|
72
68
|
|
73
69
|
### Usage Notes
|
74
70
|
- Best suited for objective evaluation of text content
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '../clients/
|
3
|
+
require_relative '../clients/unified_client'
|
4
4
|
|
5
5
|
module ActiveGenie::Scoring
|
6
6
|
# The Basic class provides a foundation for scoring text content against specified criteria
|
@@ -17,24 +17,23 @@ 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,
|
29
23
|
# reviewers will be automatically recommended based on the content and criteria
|
30
|
-
# @param
|
31
|
-
# @
|
32
|
-
#
|
33
|
-
|
24
|
+
# @param config [Hash] Additional configuration config that modify the scoring behavior
|
25
|
+
# @return [Hash] The evaluation result containing the scores and reasoning
|
26
|
+
# @return [Number] :final_score The final score of the text based on the criteria and reviewers
|
27
|
+
# @return [String] :final_reasoning Detailed explanation of why the final score was reached
|
28
|
+
def self.call(text, criteria, reviewers = [], config: {})
|
29
|
+
new(text, criteria, reviewers, config:).call
|
30
|
+
end
|
31
|
+
|
32
|
+
def initialize(text, criteria, reviewers = [], config: {})
|
34
33
|
@text = text
|
35
34
|
@criteria = criteria
|
36
35
|
@reviewers = Array(reviewers).compact.uniq
|
37
|
-
@
|
36
|
+
@config = config
|
38
37
|
end
|
39
38
|
|
40
39
|
def call
|
@@ -77,7 +76,7 @@ module ActiveGenie::Scoring
|
|
77
76
|
}
|
78
77
|
}
|
79
78
|
|
80
|
-
::ActiveGenie::Clients::
|
79
|
+
::ActiveGenie::Clients::UnifiedClient.function_calling(messages, function, config:)
|
81
80
|
end
|
82
81
|
|
83
82
|
private
|
@@ -86,7 +85,7 @@ module ActiveGenie::Scoring
|
|
86
85
|
@get_or_recommend_reviewers ||= if @reviewers.count > 0
|
87
86
|
@reviewers
|
88
87
|
else
|
89
|
-
recommended_reviews = RecommendedReviews.call(@text, @criteria,
|
88
|
+
recommended_reviews = RecommendedReviews.call(@text, @criteria, config:)
|
90
89
|
|
91
90
|
[recommended_reviews[:reviewer1], recommended_reviews[:reviewer2], recommended_reviews[:reviewer3]]
|
92
91
|
end
|
@@ -112,5 +111,16 @@ module ActiveGenie::Scoring
|
|
112
111
|
- Deconstruct each criterion into actionable components for a systematic evaluation.
|
113
112
|
- If the text lacks information, apply reasonable judgment to assign a score while clearly explaining the rationale.
|
114
113
|
PROMPT
|
114
|
+
|
115
|
+
def config
|
116
|
+
{
|
117
|
+
all_providers: { model_tier: 'lower_tier' },
|
118
|
+
log: {
|
119
|
+
**(@config.dig(:log) || {}),
|
120
|
+
trace: self.class.name,
|
121
|
+
},
|
122
|
+
**@config
|
123
|
+
}
|
124
|
+
end
|
115
125
|
end
|
116
126
|
end
|
@@ -1,6 +1,6 @@
|
|
1
1
|
# frozen_string_literal: true
|
2
2
|
|
3
|
-
require_relative '../clients/
|
3
|
+
require_relative '../clients/unified_client'
|
4
4
|
|
5
5
|
module ActiveGenie::Scoring
|
6
6
|
# The RecommendedReviews class intelligently suggests appropriate reviewer roles
|
@@ -17,21 +17,19 @@ module ActiveGenie::Scoring
|
|
17
17
|
# # reviewer3: "Developer Advocate", reasoning: "..." }
|
18
18
|
#
|
19
19
|
class RecommendedReviews
|
20
|
-
def self.call(text, criteria,
|
21
|
-
new(text, criteria,
|
20
|
+
def self.call(text, criteria, config: {})
|
21
|
+
new(text, criteria, config:).call
|
22
22
|
end
|
23
23
|
|
24
24
|
# Initializes a new reviewer recommendation instance
|
25
25
|
#
|
26
26
|
# @param text [String] The text content to analyze for reviewer recommendations
|
27
27
|
# @param criteria [String] The evaluation criteria that will guide reviewer selection
|
28
|
-
# @param
|
29
|
-
|
30
|
-
# @option options [Boolean] :prefer_domain Whether to favor domain expertise
|
31
|
-
def initialize(text, criteria, options: {})
|
28
|
+
# @param config [Hash] Additional configuration config that modify the recommendation process
|
29
|
+
def initialize(text, criteria, config: {})
|
32
30
|
@text = text
|
33
31
|
@criteria = criteria
|
34
|
-
@
|
32
|
+
@config = config
|
35
33
|
end
|
36
34
|
|
37
35
|
def call
|
@@ -55,7 +53,7 @@ module ActiveGenie::Scoring
|
|
55
53
|
}
|
56
54
|
}
|
57
55
|
|
58
|
-
::ActiveGenie::Clients::
|
56
|
+
::ActiveGenie::Clients::UnifiedClient.function_calling(messages, function, config: @config)
|
59
57
|
end
|
60
58
|
|
61
59
|
private
|
data/lib/active_genie/scoring.rb
CHANGED
@@ -2,16 +2,16 @@ require_relative 'scoring/basic'
|
|
2
2
|
require_relative 'scoring/recommended_reviews'
|
3
3
|
|
4
4
|
module ActiveGenie
|
5
|
-
#
|
5
|
+
# See the [Scoring README](lib/active_genie/scoring/README.md) for more information.
|
6
6
|
module Scoring
|
7
7
|
module_function
|
8
8
|
|
9
|
-
def basic(
|
10
|
-
Basic.call(
|
9
|
+
def basic(...)
|
10
|
+
Basic.call(...)
|
11
11
|
end
|
12
12
|
|
13
|
-
def recommended_reviews(
|
14
|
-
RecommendedReviews.call(
|
13
|
+
def recommended_reviews(...)
|
14
|
+
RecommendedReviews.call(...)
|
15
15
|
end
|
16
16
|
end
|
17
17
|
end
|
data/lib/active_genie.rb
CHANGED
@@ -1,23 +1,26 @@
|
|
1
|
+
require_relative 'active_genie/logger'
|
2
|
+
require_relative 'active_genie/configuration'
|
3
|
+
|
1
4
|
module ActiveGenie
|
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 :
|
8
|
+
autoload :Leaderboard, File.join(__dir__, 'active_genie/leaderboard')
|
5
9
|
|
6
10
|
class << self
|
7
|
-
def config
|
8
|
-
@config ||= Configuration.new
|
9
|
-
end
|
10
|
-
|
11
11
|
def configure
|
12
|
-
yield(
|
12
|
+
yield(configuration) if block_given?
|
13
13
|
end
|
14
|
-
|
15
|
-
def
|
16
|
-
|
14
|
+
|
15
|
+
def configuration
|
16
|
+
@configuration ||= Configuration
|
17
17
|
end
|
18
18
|
|
19
|
-
def
|
20
|
-
|
19
|
+
def load_tasks
|
20
|
+
return unless defined?(Rake)
|
21
|
+
|
22
|
+
Rake::Task.define_task(:environment)
|
23
|
+
Dir.glob(File.join(__dir__, 'tasks', '*.rake')).each { |r| load r }
|
21
24
|
end
|
22
25
|
end
|
23
26
|
end
|
data/lib/tasks/install.rake
CHANGED
@@ -3,10 +3,10 @@ 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(
|
7
|
-
target = File.join('config', 'active_genie.
|
6
|
+
source = File.join(__dir__, 'templates', 'active_genie.rb')
|
7
|
+
target = File.join('config', 'initializers', 'active_genie.rb')
|
8
8
|
|
9
9
|
FileUtils.cp(source, target)
|
10
|
-
puts "Successfully installed
|
10
|
+
puts "Successfully installed active_genie!"
|
11
11
|
end
|
12
12
|
end
|
@@ -0,0 +1,17 @@
|
|
1
|
+
|
2
|
+
ActiveGenie.configure do |config|
|
3
|
+
# example with openai and the current default for each config
|
4
|
+
# config.providers.openai.api_key = ENV['OPENAI_API_KEY']
|
5
|
+
# config.providers.openai.organization = ENV['OPENAI_ORGANIZATION']
|
6
|
+
# config.providers.openai.api_url = 'https://api.openai.com/v1'
|
7
|
+
# config.providers.openai.lower_tier_model = 'gpt-4o-mini'
|
8
|
+
# config.providers.openai.middle_tier_model = 'gpt-4o'
|
9
|
+
# config.providers.openai.upper_tier_model = 'o1-preview'
|
10
|
+
# config.providers.openai.client = ActiveGenie::Providers::Openai::Client.new(config)
|
11
|
+
|
12
|
+
# example how add a new provider
|
13
|
+
# config.providers.register(:internal_company_api, InternalCompanyApi::Configuration)
|
14
|
+
|
15
|
+
# Logs configuration
|
16
|
+
# config.log_level = :debug # default is :info
|
17
|
+
end
|
metadata
CHANGED
@@ -1,22 +1,114 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: active_genie
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 0.0.
|
4
|
+
version: 0.0.10
|
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-
|
11
|
+
date: 2025-02-10 00:00:00.000000000 Z
|
12
12
|
dependencies: []
|
13
|
-
description:
|
14
|
-
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
13
|
+
description: "# ActiveGenie \U0001F9DE♂️\n> Transform your Ruby application with
|
14
|
+
powerful, production-ready GenAI features\n\n[](https://badge.fury.io/rb/active_genie)\n[](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 **Leaderboard**:
|
21
|
+
Consistent rank items based on custom criteria, using multiple tecniques of ranking\n\n##
|
22
|
+
Installation\n\n1. Add to your Gemfile:\n```ruby\ngem 'active_genie'\n```\n\n2.
|
23
|
+
Install the gem:\n```shell\nbundle install\n```\n\n3. Generate the configuration:\n```shell\necho
|
24
|
+
\"ActiveGenie.load_tasks\" >> Rakefile\nrails g active_genie:install\n```\n\n4.
|
25
|
+
Configure your credentials in `config/initializers/active_genie.rb`:\n```ruby\nActiveGenie.configure
|
26
|
+
do |config|\n config.openai.api_key = ENV['OPENAI_API_KEY']\nend\n```\n\n## Quick
|
27
|
+
Start\n\n### Data Extractor\nExtract structured data from text using AI-powered
|
28
|
+
analysis, handling informal language and complex expressions.\n\n```ruby\ntext =
|
29
|
+
\"Nike Air Max 90 - Size 42 - $199.99\"\nschema = {\n brand: { \n type: 'string',\n
|
30
|
+
\ enum: [\"Nike\", \"Adidas\", \"Puma\"]\n },\n price: { \n type: 'number',\n
|
31
|
+
\ minimum: 0\n },\n size: {\n type: 'integer',\n minimum: 35,\n maximum:
|
32
|
+
46\n }\n}\n\nresult = ActiveGenie::DataExtractor.call(text, schema)\n# => { \n#
|
33
|
+
\ brand: \"Nike\", \n# brand_explanation: \"Brand name found at start of
|
34
|
+
text\",\n# price: 199.99,\n# price_explanation: \"Price found in USD format
|
35
|
+
at end\",\n# size: 42,\n# size_explanation: \"Size explicitly stated in
|
36
|
+
the middle\"\n# }\n```\n\nFeatures:\n- Structured data extraction with type validation\n-
|
37
|
+
Schema-based extraction with custom constraints\n- Informal text analysis (litotes,
|
38
|
+
hedging)\n- Detailed explanations for extracted values\n\nSee the [Data Extractor
|
39
|
+
README](lib/active_genie/data_extractor/README.md) for informal text processing,
|
40
|
+
advanced schemas, and detailed interface documentation.\n\n### Scoring\nText evaluation
|
41
|
+
system that provides detailed scoring and feedback using multiple expert reviewers.
|
42
|
+
Get balanced scoring through AI-powered expert reviewers that automatically adapt
|
43
|
+
to your content.\n\n```ruby\ntext = \"The code implements a binary search algorithm
|
44
|
+
with O(log n) complexity\"\ncriteria = \"Evaluate technical accuracy and clarity\"\n\nresult
|
45
|
+
= ActiveGenie::Scoring.basic(text, criteria)\n# => {\n# algorithm_expert_score:
|
46
|
+
95,\n# algorithm_expert_reasoning: \"Accurately describes binary search and
|
47
|
+
its complexity\",\n# technical_writer_score: 90,\n# technical_writer_reasoning:
|
48
|
+
\"Clear and concise explanation of the algorithm\",\n# final_score: 92.5\n#
|
49
|
+
\ }\n```\n\nFeatures:\n- Multi-reviewer evaluation with automatic expert selection\n-
|
50
|
+
Detailed feedback with scoring reasoning\n- Customizable reviewer weights\n- Flexible
|
51
|
+
evaluation criteria\n\nSee the [Scoring README](lib/active_genie/scoring/README.md)
|
52
|
+
for advanced usage, custom reviewers, and detailed interface documentation.\n\n###
|
53
|
+
Battle\nAI-powered battle evaluation system that determines winners between two
|
54
|
+
players based on specified criteria.\n\n```ruby\nrequire 'active_genie'\n\nplayer_a
|
55
|
+
= \"Implementation uses dependency injection for better testability\"\nplayer_b
|
56
|
+
= \"Code has high test coverage but tightly coupled components\"\ncriteria = \"Evaluate
|
57
|
+
code quality and maintainability\"\n\nresult = ActiveGenie::Battle.call(player_a,
|
58
|
+
player_b, criteria)\n# => {\n# winner_player: \"Implementation uses dependency
|
59
|
+
injection for better testability\",\n# reasoning: \"Player A's implementation
|
60
|
+
demonstrates better maintainability through dependency injection, \n# which
|
61
|
+
allows for easier testing and component replacement. While Player B has good test
|
62
|
+
coverage, \n# the tight coupling makes the code harder to maintain
|
63
|
+
and modify.\",\n# what_could_be_changed_to_avoid_draw: \"Focus on specific
|
64
|
+
architectural patterns and design principles\"\n# }\n```\n\nFeatures:\n- Multi-reviewer
|
65
|
+
evaluation with automatic expert selection\n- Detailed feedback with scoring reasoning\n-
|
66
|
+
Customizable reviewer weights\n- Flexible evaluation criteria\n\nSee the [Battle
|
67
|
+
README](lib/active_genie/battle/README.md) for advanced usage, custom reviewers,
|
68
|
+
and detailed interface documentation.\n\n### League\nThe League module provides
|
69
|
+
competitive ranking through multi-stage evaluation:\n\n\n```ruby\nrequire 'active_genie'\n\nplayers
|
70
|
+
= ['REST API', 'GraphQL API', 'SOAP API', 'gRPC API', 'Websocket API']\ncriteria
|
71
|
+
= \"Best one to be used into a high changing environment\"\n\nresult = ActiveGenie::League.call(players,
|
72
|
+
criteria)\n# => {\n# winner_player: \"gRPC API\",\n# reasoning: \"gRPC
|
73
|
+
API is the best one to be used into a high changing environment\",\n# }\n```\n\n-
|
74
|
+
**Multi-phase ranking system** combining expert scoring and ELO algorithms\n- **Automatic
|
75
|
+
elimination** of inconsistent performers using statistical analysis\n- **Dynamic
|
76
|
+
ranking adjustments** based on simulated pairwise battles, from bottom to top\n\nSee
|
77
|
+
the [League README](lib/active_genie/league/README.md) for implementation details,
|
78
|
+
configuration, and advanced ranking strategies.\n\n### Summarizer (WIP)\nThe summarizer
|
79
|
+
is a tool that can be used to summarize a given text. It uses a set of rules to
|
80
|
+
summarize the text out of the box. Uses the best practices of prompt engineering
|
81
|
+
and engineering to make the summarization as accurate as possible.\n\n```ruby\nrequire
|
82
|
+
'active_genie'\n\ntext = \"Example text to be summarized. The fox jumps over the
|
83
|
+
dog\"\nsummarized_text = ActiveGenie::Summarizer.call(text)\nputs summarized_text
|
84
|
+
# => \"The fox jumps over the dog\"\n```\n\n### Language detector (WIP)\nThe language
|
85
|
+
detector is a tool that can be used to detect the language of a given text. It uses
|
86
|
+
a set of rules to detect the language of the text out of the box. Uses the best
|
87
|
+
practices of prompt engineering and engineering to make the language detection as
|
88
|
+
accurate as possible.\n\n```ruby\nrequire 'active_genie'\n\ntext = \"Example text
|
89
|
+
to be detected\"\nlanguage = ActiveGenie::LanguageDetector.call(text)\nputs language
|
90
|
+
# => \"en\"\n```\n\n### Translator (WIP)\nThe translator is a tool that can be used
|
91
|
+
to translate a given text. It uses a set of rules to translate the text out of the
|
92
|
+
box. Uses the best practices of prompt engineering and engineering to make the translation
|
93
|
+
as accurate as possible.\n\n```ruby\nrequire 'active_genie'\n\ntext = \"Example
|
94
|
+
text to be translated\"\ntranslated_text = ActiveGenie::Translator.call(text, from:
|
95
|
+
'en', to: 'pt')\nputs translated_text # => \"Exemplo de texto a ser traduzido\"\n```\n\n###
|
96
|
+
Sentiment analyzer (WIP)\nThe sentiment analyzer is a tool that can be used to analyze
|
97
|
+
the sentiment of a given text. It uses a set of rules to analyze the sentiment of
|
98
|
+
the text out of the box. Uses the best practices of prompt engineering and engineering
|
99
|
+
to make the sentiment analysis as accurate as possible.\n\n```ruby\nrequire 'active_genie'\n\ntext
|
100
|
+
= \"Example text to be analyzed\"\nsentiment = ActiveGenie::SentimentAnalyzer.call(text)\nputs
|
101
|
+
sentiment # => \"positive\"\n```\n\n## Configuration\n\n| Config | Description |
|
102
|
+
Default |\n|--------|-------------|---------|\n| `provider` | LLM provider (openai,
|
103
|
+
anthropic, etc) | `nil` |\n| `model` | Model to use | `nil` |\n| `api_key` | Provider
|
104
|
+
API key | `nil` |\n| `timeout` | Request timeout in seconds | `5` |\n| `max_retries`
|
105
|
+
| Maximum retry attempts | `3` |\n\n> **Note:** Each module can append its own set
|
106
|
+
of configuration, see the individual module documentation for details.\n\n## Contributing\n\n1.
|
107
|
+
Fork the repository\n2. Create your feature branch (`git checkout -b feature/amazing-feature`)\n3.
|
108
|
+
Commit your changes (`git commit -m 'Add amazing feature'`)\n4. Push to the branch
|
109
|
+
(`git push origin feature/amazing-feature`)\n5. Open a Pull Request\n## License\n\nThis
|
110
|
+
project is licensed under the MIT License - see the [LICENSE](LICENSE) file for
|
111
|
+
details.\n"
|
20
112
|
email:
|
21
113
|
- radames@roriz.dev
|
22
114
|
executables: []
|
@@ -27,19 +119,33 @@ files:
|
|
27
119
|
- README.md
|
28
120
|
- VERSION
|
29
121
|
- lib/active_genie.rb
|
30
|
-
- lib/active_genie/
|
31
|
-
- lib/active_genie/
|
122
|
+
- lib/active_genie/battle.rb
|
123
|
+
- lib/active_genie/battle/README.md
|
124
|
+
- lib/active_genie/battle/basic.rb
|
125
|
+
- lib/active_genie/clients/openai_client.rb
|
126
|
+
- lib/active_genie/clients/unified_client.rb
|
32
127
|
- lib/active_genie/configuration.rb
|
128
|
+
- lib/active_genie/configuration/log_config.rb
|
129
|
+
- lib/active_genie/configuration/openai_config.rb
|
130
|
+
- lib/active_genie/configuration/providers_config.rb
|
33
131
|
- lib/active_genie/data_extractor.rb
|
34
132
|
- lib/active_genie/data_extractor/README.md
|
35
133
|
- lib/active_genie/data_extractor/basic.rb
|
36
134
|
- lib/active_genie/data_extractor/from_informal.rb
|
135
|
+
- lib/active_genie/league.rb
|
136
|
+
- lib/active_genie/league/README.md
|
137
|
+
- lib/active_genie/league/elo_ranking.rb
|
138
|
+
- lib/active_genie/league/free_for_all.rb
|
139
|
+
- lib/active_genie/league/league.rb
|
140
|
+
- lib/active_genie/league/player.rb
|
141
|
+
- lib/active_genie/league/players_collection.rb
|
142
|
+
- lib/active_genie/logger.rb
|
37
143
|
- lib/active_genie/scoring.rb
|
38
144
|
- lib/active_genie/scoring/README.md
|
39
145
|
- lib/active_genie/scoring/basic.rb
|
40
146
|
- lib/active_genie/scoring/recommended_reviews.rb
|
41
147
|
- lib/tasks/install.rake
|
42
|
-
- lib/tasks/templates/
|
148
|
+
- lib/tasks/templates/active_genie.rb
|
43
149
|
homepage: https://github.com/Roriz/active_genie
|
44
150
|
licenses:
|
45
151
|
- Apache-2.0
|
@@ -67,6 +173,5 @@ requirements: []
|
|
67
173
|
rubygems_version: 3.5.3
|
68
174
|
signing_key:
|
69
175
|
specification_version: 4
|
70
|
-
summary:
|
71
|
-
summarization, scoring, and ranking.
|
176
|
+
summary: Transform your Ruby application with powerful, production-ready GenAI features
|
72
177
|
test_files: []
|
@@ -1,59 +0,0 @@
|
|
1
|
-
require 'json'
|
2
|
-
require 'net/http'
|
3
|
-
|
4
|
-
module ActiveGenie::Clients
|
5
|
-
class Openai
|
6
|
-
class << self
|
7
|
-
def function_calling(messages, function, options: {})
|
8
|
-
app_config = ActiveGenie.config_by_model(options[:model])
|
9
|
-
|
10
|
-
model = options[:model] || app_config[:model]
|
11
|
-
|
12
|
-
raise "Model can't be blank" if model.nil?
|
13
|
-
|
14
|
-
payload = {
|
15
|
-
messages:,
|
16
|
-
response_format: {
|
17
|
-
type: 'json_schema',
|
18
|
-
json_schema: function
|
19
|
-
},
|
20
|
-
model:,
|
21
|
-
}
|
22
|
-
|
23
|
-
api_key = options[:api_key] || app_config[:api_key]
|
24
|
-
|
25
|
-
headers = DEFAULT_HEADERS.merge(
|
26
|
-
'Authorization': "Bearer #{api_key}"
|
27
|
-
).compact
|
28
|
-
|
29
|
-
response = request(payload, headers)
|
30
|
-
|
31
|
-
JSON.parse(response.dig('choices', 0, 'message', 'content'))
|
32
|
-
rescue JSON::ParserError
|
33
|
-
nil
|
34
|
-
end
|
35
|
-
|
36
|
-
def request(payload, headers)
|
37
|
-
response = Net::HTTP.post(
|
38
|
-
URI(API_URL),
|
39
|
-
payload.to_json,
|
40
|
-
headers
|
41
|
-
)
|
42
|
-
|
43
|
-
raise OpenaiError, response.body unless response.is_a?(Net::HTTPSuccess)
|
44
|
-
return nil if response.body.empty?
|
45
|
-
|
46
|
-
JSON.parse(response.body)
|
47
|
-
end
|
48
|
-
|
49
|
-
end
|
50
|
-
|
51
|
-
API_URL = 'https://api.openai.com/v1/chat/completions'.freeze
|
52
|
-
DEFAULT_HEADERS = {
|
53
|
-
'Content-Type': 'application/json',
|
54
|
-
}
|
55
|
-
|
56
|
-
# TODO: add some more rich error handling
|
57
|
-
class OpenaiError < StandardError; end
|
58
|
-
end
|
59
|
-
end
|
@@ -1,41 +0,0 @@
|
|
1
|
-
require_relative './openai'
|
2
|
-
|
3
|
-
module ActiveGenie::Clients
|
4
|
-
class Router
|
5
|
-
class << self
|
6
|
-
def function_calling(messages, function, options: {})
|
7
|
-
app_config = ActiveGenie.config_by_model(options[:model])
|
8
|
-
|
9
|
-
provider = options[:provider] || app_config[:provider]
|
10
|
-
client = PROVIDER_TO_CLIENT[provider&.downcase&.strip&.to_sym]
|
11
|
-
raise "Provider \"#{provider}\" not supported" unless client
|
12
|
-
|
13
|
-
response = client.function_calling(messages, function, options:)
|
14
|
-
|
15
|
-
clear_invalid_values(response)
|
16
|
-
end
|
17
|
-
|
18
|
-
private
|
19
|
-
|
20
|
-
PROVIDER_TO_CLIENT = {
|
21
|
-
openai: Openai,
|
22
|
-
}
|
23
|
-
|
24
|
-
INVALID_VALUES = [
|
25
|
-
'not sure',
|
26
|
-
'not clear',
|
27
|
-
'not specified',
|
28
|
-
'none',
|
29
|
-
'null',
|
30
|
-
'undefined',
|
31
|
-
].freeze
|
32
|
-
|
33
|
-
def clear_invalid_values(data)
|
34
|
-
data.reduce({}) do |acc, (field, value)|
|
35
|
-
acc[field] = value unless INVALID_VALUES.include?(value)
|
36
|
-
acc
|
37
|
-
end
|
38
|
-
end
|
39
|
-
end
|
40
|
-
end
|
41
|
-
end
|