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.
Files changed (35) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +146 -59
  3. data/VERSION +1 -1
  4. data/lib/active_genie/battle/README.md +39 -0
  5. data/lib/active_genie/battle/basic.rb +130 -0
  6. data/lib/active_genie/battle.rb +13 -0
  7. data/lib/active_genie/clients/openai_client.rb +77 -0
  8. data/lib/active_genie/clients/unified_client.rb +19 -0
  9. data/lib/active_genie/configuration/log_config.rb +14 -0
  10. data/lib/active_genie/configuration/openai_config.rb +56 -0
  11. data/lib/active_genie/configuration/providers_config.rb +37 -0
  12. data/lib/active_genie/configuration.rb +18 -22
  13. data/lib/active_genie/data_extractor/README.md +4 -4
  14. data/lib/active_genie/data_extractor/basic.rb +19 -9
  15. data/lib/active_genie/data_extractor/from_informal.rb +18 -7
  16. data/lib/active_genie/data_extractor.rb +5 -5
  17. data/lib/active_genie/league/README.md +43 -0
  18. data/lib/active_genie/league/elo_ranking.rb +121 -0
  19. data/lib/active_genie/league/free_for_all.rb +62 -0
  20. data/lib/active_genie/league/league.rb +120 -0
  21. data/lib/active_genie/league/player.rb +59 -0
  22. data/lib/active_genie/league/players_collection.rb +68 -0
  23. data/lib/active_genie/league.rb +12 -0
  24. data/lib/active_genie/logger.rb +45 -0
  25. data/lib/active_genie/scoring/README.md +4 -8
  26. data/lib/active_genie/scoring/basic.rb +24 -14
  27. data/lib/active_genie/scoring/recommended_reviews.rb +7 -9
  28. data/lib/active_genie/scoring.rb +5 -5
  29. data/lib/active_genie.rb +14 -11
  30. data/lib/tasks/install.rake +3 -3
  31. data/lib/tasks/templates/active_genie.rb +17 -0
  32. metadata +119 -14
  33. data/lib/active_genie/clients/openai.rb +0 -59
  34. data/lib/active_genie/clients/router.rb +0 -41
  35. 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,12 @@
1
+ require_relative 'league/league'
2
+
3
+ module ActiveGenie
4
+ # See the [league README](lib/active_genie/league/README.md) for more information.
5
+ module League
6
+ module_function
7
+
8
+ def call(...)
9
+ league.call(...)
10
+ end
11
+ end
12
+ 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 = [], options: {})`
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
- - `options` [Hash] - Additional configuration options
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, options: {})`
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
- - `options` [Hash] - Additional configuration options
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/router'
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 options [Hash] Additional configuration options that modify the scoring behavior
31
- # @option options [Boolean] :detailed_feedback Request more detailed feedback in the reasoning
32
- # @option options [Hash] :reviewer_weights Custom weights for different reviewers
33
- def initialize(text, criteria, reviewers = [], options: {})
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
- @options = options
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::Router.function_calling(messages, function, options: @options)
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, options: @options)
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/router.rb'
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, options: {})
21
- new(text, criteria, options:).call
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 options [Hash] Additional configuration options that modify the recommendation process
29
- # @option options [Boolean] :prefer_technical Whether to favor technical expertise
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
- @options = options
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::Router.function_calling(messages, function, options:)
56
+ ::ActiveGenie::Clients::UnifiedClient.function_calling(messages, function, config: @config)
59
57
  end
60
58
 
61
59
  private
@@ -2,16 +2,16 @@ require_relative 'scoring/basic'
2
2
  require_relative 'scoring/recommended_reviews'
3
3
 
4
4
  module ActiveGenie
5
- # Text evaluation system that provides detailed scoring and feedback using multiple expert reviewers
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(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
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 :Configuration, File.join(__dir__, 'active_genie/configuration')
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(config) if block_given?
12
+ yield(configuration) if block_given?
13
13
  end
14
-
15
- def [](key)
16
- config.values[key.to_s]
14
+
15
+ def configuration
16
+ @configuration ||= Configuration
17
17
  end
18
18
 
19
- def config_by_model(model)
20
- config.values[model&.to_s&.downcase&.strip] || config.values.values.first || {}
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
@@ -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('lib', 'tasks', 'templates', 'active_genie.yml')
7
- target = File.join('config', 'active_genie.yml')
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 config/active_genie.yml to #{target}"
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.3
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-01-25 00:00:00.000000000 Z
11
+ date: 2025-02-10 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 **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/clients/openai.rb
31
- - lib/active_genie/clients/router.rb
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/active_ai.yml
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: Modules and classes to help you build AI features, like data extraction,
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
@@ -1,7 +0,0 @@
1
- # GPT-4o-mini:
2
- # api_key: <%= ENV['OPENAI_API_KEY'] %>
3
- # provider: 'openai'
4
- #
5
- # claude-3-5-sonnet:
6
- # api_key: <%= ENV['ANTHROPIC_API_KEY'] %>
7
- # provider: 'anthropic'