active_genie 0.0.3 → 0.0.10
Sign up to get free protection for your applications and to get access to all the features.
- 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
|