active_genie 0.0.12 → 0.0.19

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 (37) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +65 -22
  3. data/VERSION +1 -1
  4. data/lib/active_genie/battle/README.md +7 -7
  5. data/lib/active_genie/battle/basic.rb +48 -32
  6. data/lib/active_genie/battle.rb +4 -0
  7. data/lib/active_genie/clients/anthropic_client.rb +84 -0
  8. data/lib/active_genie/clients/base_client.rb +241 -0
  9. data/lib/active_genie/clients/google_client.rb +135 -0
  10. data/lib/active_genie/clients/helpers/retry.rb +29 -0
  11. data/lib/active_genie/clients/openai_client.rb +70 -91
  12. data/lib/active_genie/clients/unified_client.rb +4 -4
  13. data/lib/active_genie/concerns/loggable.rb +44 -0
  14. data/lib/active_genie/configuration/log_config.rb +1 -1
  15. data/lib/active_genie/configuration/providers/anthropic_config.rb +54 -0
  16. data/lib/active_genie/configuration/providers/base_config.rb +85 -0
  17. data/lib/active_genie/configuration/providers/deepseek_config.rb +54 -0
  18. data/lib/active_genie/configuration/providers/google_config.rb +56 -0
  19. data/lib/active_genie/configuration/providers/openai_config.rb +54 -0
  20. data/lib/active_genie/configuration/providers_config.rb +7 -4
  21. data/lib/active_genie/configuration/runtime_config.rb +35 -0
  22. data/lib/active_genie/configuration.rb +18 -4
  23. data/lib/active_genie/data_extractor/basic.rb +16 -3
  24. data/lib/active_genie/data_extractor.rb +4 -0
  25. data/lib/active_genie/logger.rb +40 -21
  26. data/lib/active_genie/ranking/elo_round.rb +71 -50
  27. data/lib/active_genie/ranking/free_for_all.rb +31 -14
  28. data/lib/active_genie/ranking/player.rb +11 -16
  29. data/lib/active_genie/ranking/players_collection.rb +4 -4
  30. data/lib/active_genie/ranking/ranking.rb +74 -19
  31. data/lib/active_genie/ranking/ranking_scoring.rb +3 -3
  32. data/lib/active_genie/scoring/basic.rb +44 -25
  33. data/lib/active_genie/scoring/recommended_reviewers.rb +1 -1
  34. data/lib/active_genie/scoring.rb +3 -0
  35. data/lib/tasks/benchmark.rake +27 -0
  36. metadata +92 -70
  37. data/lib/active_genie/configuration/openai_config.rb +0 -56
@@ -0,0 +1,54 @@
1
+ require_relative '../../clients/anthropic_client'
2
+ require_relative './base_config'
3
+
4
+ module ActiveGenie
5
+ module Configuration::Providers
6
+ # Configuration class for the Anthropic API client.
7
+ # Manages API keys, URLs, model selections, and client instantiation.
8
+ class AnthropicConfig < BaseConfig
9
+ NAME = :anthropic
10
+
11
+ # Retrieves the API key.
12
+ # Falls back to the ANTHROPIC_API_KEY environment variable if not set.
13
+ # @return [String, nil] The API key.
14
+ def api_key
15
+ @api_key || ENV['ANTHROPIC_API_KEY']
16
+ end
17
+
18
+ # Retrieves the base API URL for Anthropic API.
19
+ # Defaults to 'https://api.anthropic.com'.
20
+ # @return [String] The API base URL.
21
+ def api_url
22
+ @api_url || 'https://api.anthropic.com'
23
+ end
24
+
25
+ # Lazily initializes and returns an instance of the AnthropicClient.
26
+ # Passes itself (the config object) to the client's constructor.
27
+ # @return [ActiveGenie::Clients::AnthropicClient] The client instance.
28
+ def client
29
+ @client ||= ::ActiveGenie::Clients::AnthropicClient.new(self)
30
+ end
31
+
32
+ # Retrieves the model name designated for the lower tier (e.g., cost-effective, faster).
33
+ # Defaults to 'claude-3-haiku'.
34
+ # @return [String] The lower tier model name.
35
+ def lower_tier_model
36
+ @lower_tier_model || 'claude-3-5-haiku-20241022'
37
+ end
38
+
39
+ # Retrieves the model name designated for the middle tier (e.g., balanced performance).
40
+ # Defaults to 'claude-3-sonnet'.
41
+ # @return [String] The middle tier model name.
42
+ def middle_tier_model
43
+ @middle_tier_model || 'claude-3-7-sonnet-20250219'
44
+ end
45
+
46
+ # Retrieves the model name designated for the upper tier (e.g., most capable).
47
+ # Defaults to 'claude-3-opus'.
48
+ # @return [String] The upper tier model name.
49
+ def upper_tier_model
50
+ @upper_tier_model || 'claude-3-opus-20240229'
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,85 @@
1
+ module ActiveGenie
2
+ module Configuration::Providers
3
+ class BaseConfig
4
+ NAME = :unknown
5
+
6
+ attr_writer :api_key, :organization, :api_url, :client,
7
+ :lower_tier_model, :middle_tier_model, :upper_tier_model
8
+
9
+ # Maps a symbolic tier (:lower_tier, :middle_tier, :upper_tier) to a specific model name.
10
+ # Falls back to the lower_tier_model if the tier is nil or unrecognized.
11
+ # @param tier [Symbol, String, nil] The symbolic tier name.
12
+ # @return [String] The corresponding model name.
13
+ def tier_to_model(tier)
14
+ {
15
+ lower_tier: lower_tier_model,
16
+ middle_tier: middle_tier_model,
17
+ upper_tier: upper_tier_model
18
+ }[tier&.to_sym] || lower_tier_model
19
+ end
20
+
21
+ # Returns a hash representation of the configuration.
22
+ # @param config [Hash] Additional key-value pairs to merge into the hash.
23
+ # @return [Hash] The configuration settings as a hash.
24
+ def to_h(config = {})
25
+ {
26
+ name: NAME,
27
+ api_key:,
28
+ api_url:,
29
+ lower_tier_model:,
30
+ middle_tier_model:,
31
+ upper_tier_model:,
32
+ **config
33
+ }
34
+ end
35
+
36
+ # Validates the configuration.
37
+ # @return [Boolean] True if the configuration is valid, false otherwise.
38
+ def valid?
39
+ api_key && api_url
40
+ end
41
+
42
+ # Retrieves the API key.
43
+ # Falls back to the OPENAI_API_KEY environment variable if not set.
44
+ # @return [String, nil] The API key.
45
+ def api_key
46
+ raise NotImplementedError, "Subclasses must implement this method"
47
+ end
48
+
49
+ # Retrieves the base API URL for OpenAI API.
50
+ # Defaults to 'https://api.openai.com/v1'.
51
+ # @return [String] The API base URL.
52
+ def api_url
53
+ raise NotImplementedError, "Subclasses must implement this method"
54
+ end
55
+
56
+ # Lazily initializes and returns an instance of the OpenaiClient.
57
+ # Passes itself (the config object) to the client's constructor.
58
+ # @return [ActiveGenie::Clients::OpenaiClient] The client instance.
59
+ def client
60
+ raise NotImplementedError, "Subclasses must implement this method"
61
+ end
62
+
63
+ # Retrieves the model name designated for the lower tier (e.g., cost-effective, faster).
64
+ # Defaults to 'gpt-4o-mini'.
65
+ # @return [String] The lower tier model name.
66
+ def lower_tier_model
67
+ raise NotImplementedError, "Subclasses must implement this method"
68
+ end
69
+
70
+ # Retrieves the model name designated for the middle tier (e.g., balanced performance).
71
+ # Defaults to 'gpt-4o'.
72
+ # @return [String] The middle tier model name.
73
+ def middle_tier_model
74
+ raise NotImplementedError, "Subclasses must implement this method"
75
+ end
76
+
77
+ # Retrieves the model name designated for the upper tier (e.g., most capable).
78
+ # Defaults to 'o1-preview'.
79
+ # @return [String] The upper tier model name.
80
+ def upper_tier_model
81
+ raise NotImplementedError, "Subclasses must implement this method"
82
+ end
83
+ end
84
+ end
85
+ end
@@ -0,0 +1,54 @@
1
+ require_relative '../../clients/openai_client'
2
+ require_relative './base_config'
3
+
4
+ module ActiveGenie
5
+ module Configuration::Providers
6
+ # Configuration class for the DeepSeek API client.
7
+ # Manages API keys, organization IDs, URLs, model selections, and client instantiation.
8
+ class DeepseekConfig < BaseConfig
9
+ NAME = :deepseek
10
+
11
+ # Retrieves the API key.
12
+ # Falls back to the DEEPSEEK_API_KEY environment variable if not set.
13
+ # @return [String, nil] The API key.
14
+ def api_key
15
+ @api_key || ENV['DEEPSEEK_API_KEY']
16
+ end
17
+
18
+ # Retrieves the base API URL for DeepSeek API.
19
+ # Defaults to 'https://api.deepseek.com/v1'.
20
+ # @return [String] The API base URL.
21
+ def api_url
22
+ @api_url || 'https://api.deepseek.com/v1'
23
+ end
24
+
25
+ # Lazily initializes and returns an instance of the OpenaiClient.
26
+ # Passes itself (the config object) to the client's constructor.
27
+ # @return [ActiveGenie::Clients::OpenaiClient] The client instance.
28
+ def client
29
+ @client ||= ::ActiveGenie::Clients::OpenaiClient.new(self)
30
+ end
31
+
32
+ # Retrieves the model name designated for the lower tier (e.g., cost-effective, faster).
33
+ # Defaults to 'deepseek-chat'.
34
+ # @return [String] The lower tier model name.
35
+ def lower_tier_model
36
+ @lower_tier_model || 'deepseek-chat'
37
+ end
38
+
39
+ # Retrieves the model name designated for the middle tier (e.g., balanced performance).
40
+ # Defaults to 'deepseek-chat'.
41
+ # @return [String] The middle tier model name.
42
+ def middle_tier_model
43
+ @middle_tier_model || 'deepseek-chat'
44
+ end
45
+
46
+ # Retrieves the model name designated for the upper tier (e.g., most capable).
47
+ # Defaults to 'deepseek-reasoner'.
48
+ # @return [String] The upper tier model name.
49
+ def upper_tier_model
50
+ @upper_tier_model || 'deepseek-reasoner'
51
+ end
52
+ end
53
+ end
54
+ end
@@ -0,0 +1,56 @@
1
+ require_relative '../../clients/google_client'
2
+ require_relative './base_config'
3
+
4
+ module ActiveGenie
5
+ module Configuration::Providers
6
+ # Configuration class for the Google Generative Language API client.
7
+ # Manages API keys, URLs, model selections, and client instantiation.
8
+ class GoogleConfig < BaseConfig
9
+ NAME = :google
10
+
11
+ # Retrieves the API key.
12
+ # Falls back to the GENERATIVE_LANGUAGE_GOOGLE_API_KEY environment variable if not set.
13
+ # @return [String, nil] The API key.
14
+ def api_key
15
+ @api_key || ENV['GENERATIVE_LANGUAGE_GOOGLE_API_KEY'] || ENV['GEMINI_API_KEY']
16
+ end
17
+
18
+ # Retrieves the base API URL for Google Generative Language API.
19
+ # Defaults to 'https://generativelanguage.googleapis.com'.
20
+ # @return [String] The API base URL.
21
+ def api_url
22
+ # Note: Google Generative Language API uses a specific path structure like /v1beta/models/{model}:generateContent
23
+ # The base URL here should be just the domain part.
24
+ @api_url || 'https://generativelanguage.googleapis.com'
25
+ end
26
+
27
+ # Lazily initializes and returns an instance of the GoogleClient.
28
+ # Passes itself (the config object) to the client's constructor.
29
+ # @return [ActiveGenie::Clients::GoogleClient] The client instance.
30
+ def client
31
+ @client ||= ::ActiveGenie::Clients::GoogleClient.new(self)
32
+ end
33
+
34
+ # Retrieves the model name designated for the lower tier (e.g., cost-effective, faster).
35
+ # Defaults to 'gemini-2.0-flash-lite'.
36
+ # @return [String] The lower tier model name.
37
+ def lower_tier_model
38
+ @lower_tier_model || 'gemini-2.0-flash-lite'
39
+ end
40
+
41
+ # Retrieves the model name designated for the middle tier (e.g., balanced performance).
42
+ # Defaults to 'gemini-2.0-flash'.
43
+ # @return [String] The middle tier model name.
44
+ def middle_tier_model
45
+ @middle_tier_model || 'gemini-2.0-flash'
46
+ end
47
+
48
+ # Retrieves the model name designated for the upper tier (e.g., most capable).
49
+ # Defaults to 'gemini-2.5-pro-experimental'.
50
+ # @return [String] The upper tier model name.
51
+ def upper_tier_model
52
+ @upper_tier_model || 'gemini-2.5-pro-experimental'
53
+ end
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,54 @@
1
+ require_relative '../../clients/openai_client'
2
+ require_relative './base_config'
3
+
4
+ module ActiveGenie
5
+ module Configuration::Providers
6
+ # Configuration class for the OpenAI API client.
7
+ # Manages API keys, organization IDs, URLs, model selections, and client instantiation.
8
+ class OpenaiConfig < BaseConfig
9
+ NAME = :openai
10
+
11
+ # Retrieves the API key.
12
+ # Falls back to the OPENAI_API_KEY environment variable if not set.
13
+ # @return [String, nil] The API key.
14
+ def api_key
15
+ @api_key || ENV['OPENAI_API_KEY']
16
+ end
17
+
18
+ # Retrieves the base API URL for OpenAI API.
19
+ # Defaults to 'https://api.openai.com/v1'.
20
+ # @return [String] The API base URL.
21
+ def api_url
22
+ @api_url || 'https://api.openai.com/v1'
23
+ end
24
+
25
+ # Lazily initializes and returns an instance of the OpenaiClient.
26
+ # Passes itself (the config object) to the client's constructor.
27
+ # @return [ActiveGenie::Clients::OpenaiClient] The client instance.
28
+ def client
29
+ @client ||= ::ActiveGenie::Clients::OpenaiClient.new(self)
30
+ end
31
+
32
+ # Retrieves the model name designated for the lower tier (e.g., cost-effective, faster).
33
+ # Defaults to 'gpt-4o-mini'.
34
+ # @return [String] The lower tier model name.
35
+ def lower_tier_model
36
+ @lower_tier_model || 'gpt-4o-mini'
37
+ end
38
+
39
+ # Retrieves the model name designated for the middle tier (e.g., balanced performance).
40
+ # Defaults to 'gpt-4o'.
41
+ # @return [String] The middle tier model name.
42
+ def middle_tier_model
43
+ @middle_tier_model || 'gpt-4o'
44
+ end
45
+
46
+ # Retrieves the model name designated for the upper tier (e.g., most capable).
47
+ # Defaults to 'o1-preview'.
48
+ # @return [String] The upper tier model name.
49
+ def upper_tier_model
50
+ @upper_tier_model || 'o1-preview'
51
+ end
52
+ end
53
+ end
54
+ end
@@ -5,8 +5,9 @@ module ActiveGenie::Configuration
5
5
  @default = nil
6
6
  end
7
7
 
8
- def register(name, provider_class)
8
+ def register(provider_class)
9
9
  @all ||= {}
10
+ name = provider_class::NAME
10
11
  @all[name] = provider_class.new
11
12
  define_singleton_method(name) do
12
13
  instance_variable_get("@#{name}") || instance_variable_set("@#{name}", @all[name])
@@ -16,11 +17,12 @@ module ActiveGenie::Configuration
16
17
  end
17
18
 
18
19
  def default
19
- @default || @all.values.first
20
+ @default || @all.values.find { |p| p.api_key }.class::NAME
20
21
  end
21
22
 
22
- def all
23
- @all
23
+ def valid
24
+ valid_provider_keys = @all.keys.select { |k| @all[k].valid? }
25
+ @all.slice(*valid_provider_keys)
24
26
  end
25
27
 
26
28
  def to_h(config = {})
@@ -32,6 +34,7 @@ module ActiveGenie::Configuration
32
34
  end
33
35
 
34
36
  private
37
+
35
38
  attr_writer :default
36
39
  end
37
40
  end
@@ -0,0 +1,35 @@
1
+ module ActiveGenie::Configuration
2
+ class RuntimeConfig
3
+ attr_writer :max_tokens, :temperature, :model, :provider, :api_key, :max_retries
4
+
5
+ def max_tokens
6
+ @max_tokens ||= 4096
7
+ end
8
+
9
+ def temperature
10
+ @temperature ||= 0.1
11
+ end
12
+
13
+ def model
14
+ @model
15
+ end
16
+
17
+ def provider
18
+ @provider ||= ActiveGenie.configuration.providers.default
19
+ end
20
+
21
+ def api_key
22
+ @api_key
23
+ end
24
+
25
+ def max_retries
26
+ @max_retries ||= 3
27
+ end
28
+
29
+ def to_h(config = {})
30
+ {
31
+ max_tokens:, temperature:, model:, provider:, api_key:, max_retries:,
32
+ }.merge(config)
33
+ end
34
+ end
35
+ end
@@ -1,6 +1,10 @@
1
1
  require_relative 'configuration/providers_config'
2
- require_relative 'configuration/openai_config'
2
+ require_relative 'configuration/providers/openai_config'
3
+ require_relative 'configuration/providers/google_config'
4
+ require_relative 'configuration/providers/anthropic_config'
5
+ require_relative 'configuration/providers/deepseek_config'
3
6
  require_relative 'configuration/log_config'
7
+ require_relative 'configuration/runtime_config'
4
8
 
5
9
  module ActiveGenie
6
10
  module Configuration
@@ -9,7 +13,10 @@ module ActiveGenie
9
13
  def providers
10
14
  @providers ||= begin
11
15
  p = ProvidersConfig.new
12
- p.register(:openai, ActiveGenie::Configuration::OpenaiConfig)
16
+ p.register(ActiveGenie::Configuration::Providers::OpenaiConfig)
17
+ p.register(ActiveGenie::Configuration::Providers::GoogleConfig)
18
+ p.register(ActiveGenie::Configuration::Providers::AnthropicConfig)
19
+ p.register(ActiveGenie::Configuration::Providers::DeepseekConfig)
13
20
  p
14
21
  end
15
22
  end
@@ -18,10 +25,17 @@ module ActiveGenie
18
25
  @log ||= LogConfig.new
19
26
  end
20
27
 
28
+ def runtime
29
+ @runtime ||= RuntimeConfig.new
30
+ end
31
+
21
32
  def to_h(configs = {})
33
+ normalized_configs = configs.dig(:runtime) ? configs : { runtime: configs }
34
+
22
35
  {
23
- providers: providers.to_h(configs.dig(:providers) || {}),
24
- log: log.to_h(configs.dig(:log) || {})
36
+ providers: providers.to_h(normalized_configs.dig(:providers) || {}),
37
+ log: log.to_h(normalized_configs.dig(:log) || {}),
38
+ runtime: runtime.to_h(normalized_configs.dig(:runtime) || {})
25
39
  }
26
40
  end
27
41
  end
@@ -37,21 +37,34 @@ module ActiveGenie::DataExtractor
37
37
  { role: 'system', content: PROMPT },
38
38
  { role: 'user', content: @text }
39
39
  ]
40
+
41
+ properties = data_to_extract_with_explaination
42
+
40
43
  function = {
41
44
  name: 'data_extractor',
42
45
  description: 'Extract structured and typed data from user messages.',
43
- schema: {
46
+ parameters: {
44
47
  type: "object",
45
- properties: data_to_extract_with_explaination
48
+ properties:,
49
+ required: properties.keys
46
50
  }
47
51
  }
48
52
 
49
- ::ActiveGenie::Clients::UnifiedClient.function_calling(
53
+ result = ::ActiveGenie::Clients::UnifiedClient.function_calling(
50
54
  messages,
51
55
  function,
52
56
  model_tier: 'lower_tier',
53
57
  config: @config
54
58
  )
59
+
60
+ ActiveGenie::Logger.debug({
61
+ code: :data_extractor,
62
+ text: @text[0..30],
63
+ data_to_extract: @data_to_extract,
64
+ extracted_data: result
65
+ })
66
+
67
+ result
55
68
  end
56
69
 
57
70
  private
@@ -10,6 +10,10 @@ module ActiveGenie
10
10
  Basic.call(...)
11
11
  end
12
12
 
13
+ def call(...)
14
+ Basic.call(...)
15
+ end
16
+
13
17
  def from_informal(...)
14
18
  FromInformal.call(...)
15
19
  end
@@ -5,56 +5,55 @@ module ActiveGenie
5
5
  module Logger
6
6
  module_function
7
7
 
8
- def with_context(context)
8
+ def with_context(context, observer: nil)
9
9
  @context ||= {}
10
+ @observers ||= []
10
11
  begin
11
12
  @context = @context.merge(context)
13
+ @observers << observer if observer
12
14
  yield if block_given?
13
15
  ensure
14
16
  @context.delete_if { |key, _| context.key?(key) }
17
+ @observers.delete(observer)
15
18
  end
16
19
  end
17
20
 
18
21
  def info(log)
19
- save(log, level: :info)
22
+ call(log, level: :info)
20
23
  end
21
24
 
22
25
  def error(log)
23
- save(log, level: :error)
26
+ call(log, level: :error)
24
27
  end
25
28
 
26
29
  def warn(log)
27
- save(log, level: :warn)
30
+ call(log, level: :warn)
28
31
  end
29
32
 
30
33
  def debug(log)
31
- save(log, level: :debug)
34
+ call(log, level: :debug)
32
35
  end
33
36
 
34
37
  def trace(log)
35
- save(log, level: :trace)
38
+ call(log, level: :trace)
36
39
  end
37
40
 
38
- def save(data, level: :info)
39
- log = @context.merge(data || {})
40
- log[:timestamp] = Time.now
41
- log[:level] = level.to_s.upcase
42
- log[:process_id] = Process.pid
43
- config_log_level = LOG_LEVELS[log.dig(:config, :log_level)] || LOG_LEVELS[:info]
41
+ def call(data, level: :info)
42
+ log = {
43
+ **(@context || {}),
44
+ **(data || {}),
45
+ timestamp: Time.now,
46
+ level: level.to_s.upcase,
47
+ process_id: Process.pid
48
+ }
44
49
 
45
- FileUtils.mkdir_p('logs')
46
- File.write('logs/active_genie.log', "#{JSON.generate(log)}\n", mode: 'a')
47
- if config_log_level >= LOG_LEVELS[level]
48
- $stdout.puts log
49
- else
50
- $stdout.print '.'
51
- end
50
+ append_to_file(log)
51
+ output(log, level)
52
+ call_observers(log)
52
53
 
53
54
  log
54
55
  end
55
56
 
56
- private
57
-
58
57
  # Log Levels
59
58
  #
60
59
  # LOG_LEVELS defines different levels of logging within the application.
@@ -68,5 +67,25 @@ module ActiveGenie
68
67
  LOG_LEVELS = { info: 0, error: 0, warn: 1, debug: 2, trace: 3 }.freeze
69
68
 
70
69
  attr_accessor :context
70
+
71
+ def append_to_file(log)
72
+ FileUtils.mkdir_p('logs')
73
+ File.write('logs/active_genie.log', "#{JSON.generate(log)}\n", mode: 'a')
74
+ end
75
+
76
+ def output(log, level)
77
+ config_log_level = LOG_LEVELS[log.dig(:config, :log_level)] || LOG_LEVELS[:info]
78
+ if config_log_level >= LOG_LEVELS[level]
79
+ $stdout.puts log
80
+ else
81
+ $stdout.print '.'
82
+ end
83
+ end
84
+
85
+ def call_observers(log)
86
+ return if @observers.nil? || @observers.size.zero?
87
+
88
+ @observers.each { |observer| observer.call(log) }
89
+ end
71
90
  end
72
91
  end