active_genie 0.30.0 → 0.30.1

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 CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: a3798199139490fc67b7bd1e154c74ff7c53411bd035cd9e2fd1d9b46e676a90
4
- data.tar.gz: 5d9be13a369c0a6116257822f75365241ea7f628e0bdc11af085ecb267cef806
3
+ metadata.gz: 5673a34f1cd8c2b2a9a884a73ded1bf12e28f4d29ab5f351ecb3374bc9ca0617
4
+ data.tar.gz: 7ca0bc8d8bdc8b6d3618bd5f22bd13f80a9ac4399fa0282e6b11a461beb1cc1d
5
5
  SHA512:
6
- metadata.gz: 1929da5851c827ef6b693aa4f66b6118a2cda72c1b7996e602e0b0abd0537d3ac8de4fcd58d55c34fcb30ac798b237e0dea2f42e1f2431f6b61594e74807a2fd
7
- data.tar.gz: 4d8a653613a1782f8542a531abf23d0171e5e30d36d42bee1d756963d645113b9ee151206ae81ba34a053b408df945d4f98e1ef35e23fc2fc47023776961dee8
6
+ metadata.gz: f174076b72df558ef82d3834043ae3299ab1aa41d235d6b547cbee45d21804295b87729a92dda51910d37a076f954fb8b4e2460443c1814f73221b5663255d96
7
+ data.tar.gz: 503840469d6a67206d67d3f5b27f73803cb736cd4c826e7ea4b4575f1f702b7875fdeb979a875c29e88d5b06d9d18b49c94ae4f957f13563289428bdc1d83cd0
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.30.0
1
+ 0.30.1
@@ -4,7 +4,7 @@ module ActiveGenie
4
4
  module Config
5
5
  class LlmConfig
6
6
  attr_accessor :model, :temperature, :max_tokens, :max_retries, :retry_delay,
7
- :model_tier, :read_timeout, :open_timeout, :provider
7
+ :model_tier, :read_timeout, :open_timeout, :provider, :max_fibers
8
8
  attr_reader :provider_name
9
9
 
10
10
  def initialize
@@ -18,6 +18,7 @@ module ActiveGenie
18
18
  @model_tier = 'lower_tier'
19
19
  @read_timeout = nil
20
20
  @open_timeout = nil
21
+ @max_fibers = 10
21
22
  end
22
23
 
23
24
  def provider_name=(provider_name)
@@ -11,7 +11,7 @@ module ActiveGenie
11
11
  attr_reader :all
12
12
 
13
13
  def default
14
- @default || valid.keys.first
14
+ @default || ENV.fetch('PROVIDER_NAME', nil) || valid.keys.first
15
15
  end
16
16
 
17
17
  def default=(provider)
@@ -35,10 +35,10 @@ module ActiveGenie
35
35
  end
36
36
 
37
37
  def call
38
- response = Generalist.call(@text, extract_with_litote, config: @config)
38
+ response = Explanation.call(@text, extract_with_litote, config: @config)
39
39
 
40
40
  if response[:message_litote]
41
- response = Generalist.call(response[:litote_rephrased], @data_to_extract, config: @config)
41
+ response = Explanation.call(response[:litote_rephrased], @data_to_extract, config: @config)
42
42
  end
43
43
 
44
44
  response
@@ -47,7 +47,7 @@ module ActiveGenie
47
47
  private
48
48
 
49
49
  def extract_with_litote
50
- parameters = JSON.parse(File.read(File.join(__dir__, 'with_litote.json')), symbolize_names: true)
50
+ parameters = JSON.parse(File.read(File.join(__dir__, 'litote.json')), symbolize_names: true)
51
51
 
52
52
  @data_to_extract.merge(parameters)
53
53
  end
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../clients/unified_client'
3
+ require_relative '../providers/unified_provider'
4
4
 
5
5
  module ActiveGenie
6
6
  module Lister
@@ -30,7 +30,7 @@ module ActiveGenie
30
30
  { role: 'user', content: "theme: #{@theme}" }
31
31
  ]
32
32
 
33
- response = ::ActiveGenie::Clients::UnifiedClient.function_calling(
33
+ response = ::ActiveGenie::Providers::UnifiedProvider.function_calling(
34
34
  messages,
35
35
  FUNCTION,
36
36
  config: @config
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../clients/unified_client'
3
+ require_relative '../providers/unified_provider'
4
4
 
5
5
  module ActiveGenie
6
6
  module Lister
@@ -71,7 +71,7 @@ module ActiveGenie
71
71
  private
72
72
 
73
73
  def client
74
- ::ActiveGenie::Clients::UnifiedClient
74
+ ::ActiveGenie::Providers::UnifiedProvider
75
75
  end
76
76
 
77
77
  def prompt
@@ -1,9 +1,12 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require 'net/http'
4
+
3
5
  module ActiveGenie
4
6
  module Providers
5
7
  class BaseProvider
6
- class ProviderError < StandardError; end
8
+ class ProviderUnknownError < StandardError; end
9
+ class ProviderServerError < StandardError; end
7
10
 
8
11
  DEFAULT_HEADERS = {
9
12
  'Content-Type': 'application/json',
@@ -81,7 +84,7 @@ module ActiveGenie
81
84
 
82
85
  response = http_request(request, uri)
83
86
 
84
- raise ProviderError, "Unexpected response: #{response.code} - #{response.body}" unless response.is_a?(Net::HTTPSuccess)
87
+ raise ProviderUnknownError, "Unexpected response: #{response.code} - #{response.body}" unless response.is_a?(Net::HTTPSuccess)
85
88
 
86
89
  parsed_response = parse_response(response)
87
90
 
@@ -143,7 +146,7 @@ module ActiveGenie
143
146
  begin
144
147
  JSON.parse(response.body)
145
148
  rescue JSON::ParserError => e
146
- raise ProviderError, "Failed to parse JSON response: #{e.message}"
149
+ raise ProviderUnknownError, "Failed to parse JSON response: #{e.message}"
147
150
  end
148
151
  end
149
152
 
@@ -169,8 +172,12 @@ module ActiveGenie
169
172
  retries = 0
170
173
 
171
174
  begin
172
- yield
173
- rescue Net::OpenTimeout, Net::ReadTimeout, ProviderError => e
175
+ response = yield
176
+
177
+ raise ProviderServerError, "Provider server error: #{response.code} - #{response.body}" if !response.is_a?(Net::HTTPSuccess) && response.code.to_i >= 500
178
+
179
+ response
180
+ rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED, ProviderServerError => e
174
181
  raise if retries > max_retries
175
182
 
176
183
  sleep_time = retry_delay * (2**retries)
@@ -10,7 +10,7 @@ module ActiveGenie
10
10
  module Providers
11
11
  class UnifiedProvider
12
12
  class << self
13
- PROVIDER_NAME_TO_CLIENT = {
13
+ PROVIDER_NAME_TO_PROVIDER = {
14
14
  openai: OpenaiProvider,
15
15
  anthropic: AnthropicProvider,
16
16
  google: GoogleProvider,
@@ -19,7 +19,7 @@ module ActiveGenie
19
19
 
20
20
  def function_calling(messages, function, config: {})
21
21
  provider_name = config.llm.provider_name || config.providers.default
22
- provider = PROVIDER_NAME_TO_CLIENT[provider_name.to_sym]
22
+ provider = PROVIDER_NAME_TO_PROVIDER[provider_name.to_sym]
23
23
 
24
24
  raise ActiveGenie::InvalidProviderError, provider_name if provider.nil?
25
25
 
@@ -23,9 +23,9 @@ module ActiveGenie
23
23
  @config.log.add_observer(observers: ->(log) { log_observer(log) })
24
24
  @config.log.additional_context = { elo_id: }
25
25
 
26
- matches.each do |player_a, player_b|
27
- # TODO: debate can take a while, can be parallelized
26
+ ActiveGenie::FiberByBatch.call(matches, config: @config) do |player_a, player_b|
28
27
  winner, loser = debate(player_a, player_b)
28
+
29
29
  update_players_elo(winner, loser)
30
30
  end
31
31
 
@@ -105,6 +105,7 @@ module ActiveGenie
105
105
  players_in: players_in.map(&:id),
106
106
  debates_count: matches.size,
107
107
  total_tokens: @total_tokens,
108
+ players_in_round: players_in.map(&:id),
108
109
  previous_highest_elo: @previous_highest_elo,
109
110
  highest_elo:,
110
111
  highest_elo_diff: highest_elo - @previous_highest_elo,
@@ -8,7 +8,7 @@ module ActiveGenie
8
8
  end
9
9
 
10
10
  def initialize(players, criteria, config: nil)
11
- @players = Players.new(players)
11
+ @players = Entities::Players.new(players)
12
12
  @criteria = criteria
13
13
  @config = config || ActiveGenie.configuration
14
14
  @start_time = Time.now
@@ -19,7 +19,7 @@ module ActiveGenie
19
19
  @config.log.add_observer(observers: ->(log) { log_observer(log) })
20
20
  @config.log.additional_context = { free_for_all_id: }
21
21
 
22
- matches.each do |player_a, player_b|
22
+ ActiveGenie::FiberByBatch.call(matches, config: @config) do |player_a, player_b|
23
23
  winner, loser = debate(player_a, player_b)
24
24
 
25
25
  update_players_score(winner, loser)
@@ -39,7 +39,7 @@ module ActiveGenie
39
39
  def debate(player_a, player_b)
40
40
  log_context = { player_a_id: player_a.id, player_b_id: player_b.id }
41
41
 
42
- result = ActiveGenie::Comparator.debate(
42
+ result = ActiveGenie::Comparator.by_debate(
43
43
  player_a.content,
44
44
  player_b.content,
45
45
  @criteria,
@@ -1,5 +1,7 @@
1
1
  # frozen_string_literal: true
2
2
 
3
+ require_relative '../utils/fiber_by_batch'
4
+
3
5
  module ActiveGenie
4
6
  module Ranker
5
7
  class Scoring
@@ -8,7 +10,7 @@ module ActiveGenie
8
10
  end
9
11
 
10
12
  def initialize(players, criteria, juries: [], config: nil)
11
- @players = Players.new(players)
13
+ @players = Entities::Players.new(players)
12
14
  @criteria = criteria
13
15
  @config = ActiveGenie.configuration.merge(config)
14
16
  @juries = Array(juries).compact.uniq
@@ -17,7 +19,7 @@ module ActiveGenie
17
19
  def call
18
20
  @config.log.additional_context = { ranker_scoring_id: }
19
21
 
20
- players_without_score.each do |player|
22
+ ActiveGenie::FiberByBatch.call(players_without_score, config: @config) do |player|
21
23
  player.score = generate_score(player)
22
24
  end
23
25
  end
@@ -29,7 +31,7 @@ module ActiveGenie
29
31
  end
30
32
 
31
33
  def generate_score(player)
32
- score, reasoning = ActiveGenie::Scorer.jury_bench(
34
+ score, reasoning = ActiveGenie::Scorer.by_jury_bench(
33
35
  player.content,
34
36
  @criteria,
35
37
  @juries,
@@ -35,12 +35,11 @@ module ActiveGenie
35
35
  new(...).call
36
36
  end
37
37
 
38
- def initialize(players, criteria, reviewers: [], config: {})
39
- @players = Players.new(players)
38
+ def initialize(players, criteria, juries: [], config: {})
39
+ @players = Entities::Players.new(players)
40
40
  @criteria = criteria
41
- @reviewers = Array(reviewers).compact.uniq
41
+ @juries = Array(juries).compact.uniq
42
42
  @config = ActiveGenie.configuration.merge(config)
43
- @players = nil
44
43
  end
45
44
 
46
45
  def call
@@ -51,7 +50,7 @@ module ActiveGenie
51
50
 
52
51
  while @players.elo_eligible?
53
52
  elo_report = run_elo_round!
54
- eliminate_relegation_players!
53
+ eliminate_lower_tier_players!
55
54
  rebalance_players!(elo_report)
56
55
  end
57
56
 
@@ -69,7 +68,7 @@ module ActiveGenie
69
68
  Scoring.call(
70
69
  @players,
71
70
  @criteria,
72
- reviewers: @reviewers,
71
+ juries: @juries,
73
72
  config: @config
74
73
  )
75
74
  end
@@ -88,8 +87,8 @@ module ActiveGenie
88
87
  )
89
88
  end
90
89
 
91
- def eliminate_relegation_players!
92
- @players.calc_relegation_tier.each { |player| player.eliminated = ELIMINATION_RELEGATION }
90
+ def eliminate_lower_tier_players!
91
+ @players.calc_lower_tier.each { |player| player.eliminated = ELIMINATION_RELEGATION }
93
92
  end
94
93
 
95
94
  def rebalance_players!(elo_report)
@@ -1,6 +1,6 @@
1
1
  # frozen_string_literal: true
2
2
 
3
- require_relative '../clients/unified_client'
3
+ require_relative '../providers/unified_provider'
4
4
 
5
5
  module ActiveGenie
6
6
  module Scorer
@@ -44,7 +44,7 @@ module ActiveGenie
44
44
  { role: 'user', content: "Text to score: #{@text}" }
45
45
  ]
46
46
 
47
- result = ::ActiveGenie::Clients::UnifiedClient.function_calling(
47
+ result = ::ActiveGenie::Providers::UnifiedProvider.function_calling(
48
48
  messages,
49
49
  build_function,
50
50
  config: @config
@@ -0,0 +1,21 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'async'
4
+
5
+ module ActiveGenie
6
+ module FiberByBatch
7
+ module_function
8
+
9
+ def call(items, config:, &block)
10
+ items.each_slice(config.llm.max_fibers).to_a.each do |batch|
11
+ Async do
12
+ tasks = batch.map do |item|
13
+ Async { block.call(item) }
14
+ end
15
+
16
+ tasks.each(&:wait)
17
+ end.wait
18
+ end
19
+ end
20
+ end
21
+ end
metadata CHANGED
@@ -1,7 +1,7 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: active_genie
3
3
  version: !ruby/object:Gem::Version
4
- version: 0.30.0
4
+ version: 0.30.1
5
5
  platform: ruby
6
6
  authors:
7
7
  - Radamés Roriz
@@ -9,9 +9,23 @@ autorequire:
9
9
  bindir: bin
10
10
  cert_chain: []
11
11
  date: 2025-08-15 00:00:00.000000000 Z
12
- dependencies: []
12
+ dependencies:
13
+ - !ruby/object:Gem::Dependency
14
+ name: async
15
+ requirement: !ruby/object:Gem::Requirement
16
+ requirements:
17
+ - - ">="
18
+ - !ruby/object:Gem::Version
19
+ version: '2.0'
20
+ type: :runtime
21
+ prerelease: false
22
+ version_requirements: !ruby/object:Gem::Requirement
23
+ requirements:
24
+ - - ">="
25
+ - !ruby/object:Gem::Version
26
+ version: '2.0'
13
27
  description: |
14
- ActiveGenie is a Ruby gem that helps developers build reliable, future-proof GenAI features without worrying about changing models, prompts, or providers. Like Lodash for GenAI, it offers simple, reusable modules for tasks like data extraction, scoring, and ranking, so you can focus on your app’s logic, not the shifting AI landscape.
28
+ ActiveGenie is a Ruby gem that helps developers build reliable, future-proof GenAI features without worrying about changing models, prompts, or providers. Like Lodash for GenAI, it offers simple, reusable modules for tasks like extractor, comparator, scorer, and ranker, so you can focus on your app’s logic, not the shifting AI landscape.
15
29
  Behind the scenes, a custom benchmarking system keeps everything consistent across LLM vendors and versions, release after release.
16
30
  email:
17
31
  - radames@roriz.dev
@@ -73,8 +87,9 @@ files:
73
87
  - lib/active_genie/ranker/scoring.rb
74
88
  - lib/active_genie/ranker/tournament.rb
75
89
  - lib/active_genie/scorer.rb
76
- - lib/active_genie/scorer/jury_bench.md
90
+ - lib/active_genie/scorer/jury_bench.prompt.md
77
91
  - lib/active_genie/scorer/jury_bench.rb
92
+ - lib/active_genie/utils/fiber_by_batch.rb
78
93
  - lib/tasks/benchmark.rake
79
94
  - lib/tasks/install.rake
80
95
  - lib/tasks/templates/active_genie.rb