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.
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'