active_genie 0.0.8 → 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 (39) hide show
  1. checksums.yaml +4 -4
  2. data/README.md +34 -33
  3. data/VERSION +1 -1
  4. data/lib/active_genie/battle/README.md +2 -2
  5. data/lib/active_genie/battle/basic.rb +24 -19
  6. data/lib/active_genie/battle.rb +1 -1
  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 -23
  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 +1 -1
  17. data/lib/active_genie/league/README.md +43 -0
  18. data/lib/active_genie/{leaderboard → league}/elo_ranking.rb +41 -8
  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/{leaderboard → league}/player.rb +17 -10
  22. data/lib/active_genie/league.rb +12 -0
  23. data/lib/active_genie/logger.rb +45 -0
  24. data/lib/active_genie/scoring/README.md +4 -8
  25. data/lib/active_genie/scoring/basic.rb +19 -10
  26. data/lib/active_genie/scoring/recommended_reviews.rb +7 -9
  27. data/lib/active_genie/scoring.rb +1 -1
  28. data/lib/active_genie.rb +9 -17
  29. data/lib/tasks/install.rake +3 -3
  30. data/lib/tasks/templates/active_genie.rb +17 -0
  31. metadata +85 -80
  32. data/lib/active_genie/clients/openai.rb +0 -61
  33. data/lib/active_genie/clients/router.rb +0 -41
  34. data/lib/active_genie/leaderboard/leaderboard.rb +0 -72
  35. data/lib/active_genie/leaderboard/league.rb +0 -48
  36. data/lib/active_genie/leaderboard.rb +0 -11
  37. data/lib/active_genie/utils/math.rb +0 -15
  38. data/lib/tasks/templates/active_genie.yml +0 -7
  39. /data/lib/active_genie/{leaderboard → league}/players_collection.rb +0 -0
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: 834787b505134623a3f6c4995c9dec8a0ce89a1a014600c4d676d8801bf541cd
4
- data.tar.gz: 16ae75ffa3926ecd87052d72ca3f5434be80327b712bb2dba54e55c46fa5ff8d
3
+ metadata.gz: 9d0424a39ba21d821cb2419730387e1b026c35b5e2e5dff9f6d615f3ec54e6a3
4
+ data.tar.gz: 17b460ccd1a689d0f8709af2b84f3cde65aa0075b76104ee1b4bb8b3b0ffc182
5
5
  SHA512:
6
- metadata.gz: 1772fbfa59891e7a3673b50a54bd90914007917854e78fb5f0458db681bf89bd1ec118600d23ab98d6255e1b063b5d582d3b73dc738e66aeadc7f4f0bee75af8
7
- data.tar.gz: bb6a0ea9ef737f7a2ed3ec558dcea5c67bcc2f90a155828e1e25a5a7a3ebd9d0fe6c5422dd9f3d9ff6fb667b63822e3bc86627cdb3e40a3c72c4ef50cee32c68
6
+ metadata.gz: ad98b2d5d063d0d1c4009e1a9f92d6d326ed948cbdee71317c94b6a4a0ee57042609c1f405ca7ce00d4beb321bc1e98ad722646349076929bcdc0a28da8b6b8b
7
+ data.tar.gz: d2cc39b77757619c5235041f12d5182778b4237444f3c2246982ebcf54c0542af0da194783cea57fbe4fbde0985ef635802c648796b4f5c4d7a5d4f42c6519c7
data/README.md CHANGED
@@ -10,9 +10,7 @@ ActiveGenie is a Ruby gem that provides a polished, production-ready interface f
10
10
 
11
11
  - 🎯 **Data Extraction**: Extract structured data from unstructured text with type validation
12
12
  - 📊 **Smart Scoring**: Multi-reviewer evaluation system with automatic expert selection
13
- - 💭 **Sentiment Analysis**: Advanced sentiment analysis with customizable rules
14
- - 🔒 **Safe & Secure**: Built-in validation and sanitization
15
- - 🛠️ **Configurable**: Supports multiple GenAI providers and models
13
+ - 💭 **Leaderboard**: Consistent rank items based on custom criteria, using multiple tecniques of ranking
16
14
 
17
15
  ## Installation
18
16
 
@@ -32,19 +30,13 @@ echo "ActiveGenie.load_tasks" >> Rakefile
32
30
  rails g active_genie:install
33
31
  ```
34
32
 
35
- 4. [Optional] Configure your credentials in `config/active_genie.yml`:
36
- ```yaml
37
- GPT-4o-mini:
38
- api_key: <%= ENV['OPENAI_API_KEY'] %>
39
- provider: "openai"
40
-
41
- claude-3-5-sonnet:
42
- api_key: <%= ENV['ANTHROPIC_API_KEY'] %>
43
- provider: "anthropic"
33
+ 4. Configure your credentials in `config/initializers/active_genie.rb`:
34
+ ```ruby
35
+ ActiveGenie.configure do |config|
36
+ config.openai.api_key = ENV['OPENAI_API_KEY']
37
+ end
44
38
  ```
45
39
 
46
- > The first key will be used as default in all modules, in this example `GPT-4o-mini`
47
-
48
40
  ## Quick Start
49
41
 
50
42
  ### Data Extractor
@@ -94,7 +86,7 @@ Text evaluation system that provides detailed scoring and feedback using multipl
94
86
  text = "The code implements a binary search algorithm with O(log n) complexity"
95
87
  criteria = "Evaluate technical accuracy and clarity"
96
88
 
97
- result = ActiveGenie::Scoring::Basic.call(text, criteria)
89
+ result = ActiveGenie::Scoring.basic(text, criteria)
98
90
  # => {
99
91
  # algorithm_expert_score: 95,
100
92
  # algorithm_expert_reasoning: "Accurately describes binary search and its complexity",
@@ -122,7 +114,7 @@ player_a = "Implementation uses dependency injection for better testability"
122
114
  player_b = "Code has high test coverage but tightly coupled components"
123
115
  criteria = "Evaluate code quality and maintainability"
124
116
 
125
- result = ActiveGenie::Battle::Basic.call(player_a, player_b, criteria)
117
+ result = ActiveGenie::Battle.call(player_a, player_b, criteria)
126
118
  # => {
127
119
  # winner_player: "Implementation uses dependency injection for better testability",
128
120
  # reasoning: "Player A's implementation demonstrates better maintainability through dependency injection,
@@ -140,6 +132,29 @@ Features:
140
132
 
141
133
  See the [Battle README](lib/active_genie/battle/README.md) for advanced usage, custom reviewers, and detailed interface documentation.
142
134
 
135
+ ### League
136
+ The League module provides competitive ranking through multi-stage evaluation:
137
+
138
+
139
+ ```ruby
140
+ require 'active_genie'
141
+
142
+ players = ['REST API', 'GraphQL API', 'SOAP API', 'gRPC API', 'Websocket API']
143
+ criteria = "Best one to be used into a high changing environment"
144
+
145
+ result = ActiveGenie::League.call(players, criteria)
146
+ # => {
147
+ # winner_player: "gRPC API",
148
+ # reasoning: "gRPC API is the best one to be used into a high changing environment",
149
+ # }
150
+ ```
151
+
152
+ - **Multi-phase ranking system** combining expert scoring and ELO algorithms
153
+ - **Automatic elimination** of inconsistent performers using statistical analysis
154
+ - **Dynamic ranking adjustments** based on simulated pairwise battles, from bottom to top
155
+
156
+ See the [League README](lib/active_genie/league/README.md) for implementation details, configuration, and advanced ranking strategies.
157
+
143
158
  ### Summarizer (WIP)
144
159
  The summarizer is a tool that can be used to summarize a given text. It uses a set of rules to summarize the text out of the box. Uses the best practices of prompt engineering and engineering to make the summarization as accurate as possible.
145
160
 
@@ -184,22 +199,9 @@ sentiment = ActiveGenie::SentimentAnalyzer.call(text)
184
199
  puts sentiment # => "positive"
185
200
  ```
186
201
 
187
- ### Elo ranking (WIP)
188
- The Elo ranking is a tool that can be used to rank a set of items. It uses a set of rules to rank the items out of the box. Uses the best practices of prompt engineering and engineering to make the ranking as accurate as possible.
189
-
190
- ```ruby
191
- require 'active_genie'
202
+ ## Configuration
192
203
 
193
- items = ['Square', 'Circle', 'Triangle']
194
- criterias = 'items that look rounded'
195
- ranked_items = ActiveGenie::EloRanking.call(items, criterias, rounds: 10)
196
- puts ranked_items # => [{ name: "Circle", score: 1500 }, { name: "Square", score: 800 }, { name: "Triangle", score: 800 }]
197
- ```
198
-
199
-
200
- ## Configuration Options
201
-
202
- | Option | Description | Default |
204
+ | Config | Description | Default |
203
205
  |--------|-------------|---------|
204
206
  | `provider` | LLM provider (openai, anthropic, etc) | `nil` |
205
207
  | `model` | Model to use | `nil` |
@@ -207,7 +209,7 @@ puts ranked_items # => [{ name: "Circle", score: 1500 }, { name: "Square", score
207
209
  | `timeout` | Request timeout in seconds | `5` |
208
210
  | `max_retries` | Maximum retry attempts | `3` |
209
211
 
210
- > **Note:** Each module can append its own set of configuration options, see the individual module documentation for details.
212
+ > **Note:** Each module can append its own set of configuration, see the individual module documentation for details.
211
213
 
212
214
  ## Contributing
213
215
 
@@ -219,4 +221,3 @@ puts ranked_items # => [{ name: "Circle", score: 1500 }, { name: "Square", score
219
221
  ## License
220
222
 
221
223
  This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.
222
-
data/VERSION CHANGED
@@ -1 +1 @@
1
- 0.0.8
1
+ 0.0.10
@@ -27,11 +27,11 @@ result = ActiveGenie::Battle::Basic.call(player_a, player_b, criteria)
27
27
  ```
28
28
 
29
29
  ## Interface
30
- ### Basic.call(player_a, player_b, criteria, options: {})
30
+ ### Basic.call(player_a, player_b, criteria, config: {})
31
31
  - `player_a` [String, Hash] - The content or submission from the first player
32
32
  - `player_b` [String, Hash] - The content or submission from the second player
33
33
  - `criteria` [String] - The evaluation criteria or rules to assess against
34
- - `options` [Hash] - Additional configuration options that modify the battle evaluation behavior
34
+ - `config` [Hash] - Additional configuration config that modify the battle evaluation behavior
35
35
 
36
36
  Returns a Hash containing:
37
37
  - `winner_player` [String, Hash] - The winning player's content (either player_a or player_b)
@@ -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::Battle
6
6
  # The Basic class provides a foundation for evaluating battles between two players
@@ -14,23 +14,23 @@ module ActiveGenie::Battle
14
14
  # Basic.call("Player A content", "Player B content", "Evaluate keyword usage and pattern matching")
15
15
  #
16
16
  class Basic
17
- def self.call(player_a, player_b, criteria, options: {})
18
- new(player_a, player_b, criteria, options:).call
17
+ def self.call(player_a, player_b, criteria, config: {})
18
+ new(player_a, player_b, criteria, config:).call
19
19
  end
20
20
 
21
21
  # @param player_a [String] The content or submission from the first player
22
22
  # @param player_b [String] The content or submission from the second player
23
23
  # @param criteria [String] The evaluation criteria or rules to assess against
24
- # @param options [Hash] Additional configuration options that modify the battle evaluation behavior
24
+ # @param config [Hash] Additional configuration config that modify the battle evaluation behavior
25
25
  # @return [Hash] The evaluation result containing the winner and reasoning
26
26
  # @return [String] :winner The @param player_a or player_b
27
27
  # @return [String] :reasoning Detailed explanation of why the winner was chosen
28
28
  # @return [String] :what_could_be_changed_to_avoid_draw A suggestion on how to avoid a draw
29
- def initialize(player_a, player_b, criteria, options: {})
29
+ def initialize(player_a, player_b, criteria, config: {})
30
30
  @player_a = player_a
31
31
  @player_b = player_b
32
32
  @criteria = criteria
33
- @options = options
33
+ @config = config
34
34
  @response = nil
35
35
  end
36
36
 
@@ -42,7 +42,7 @@ module ActiveGenie::Battle
42
42
  { role: 'user', content: "player_b: #{player_content(@player_b)}" },
43
43
  ]
44
44
 
45
- @response = ::ActiveGenie::Clients::Router.function_calling(messages, FUNCTION, options: @options)
45
+ @response = ::ActiveGenie::Clients::UnifiedClient.function_calling(messages, FUNCTION, config:)
46
46
 
47
47
  response_formatted
48
48
  end
@@ -56,18 +56,12 @@ module ActiveGenie::Battle
56
56
  end
57
57
 
58
58
  def response_formatted
59
- if @response['winner'] == 'player_a'
60
- @response['winner'] = @player_a
61
- @response['loser'] = @player_b
62
- elsif @response['winner'] == 'player_b'
63
- @response['winner'] = @player_b
64
- @response['loser'] = @player_a
65
- else
66
- @response['winner'] = nil
67
- @response['loser'] = nil
68
- end
69
-
70
- @response
59
+ winner = case @response['winner']
60
+ when 'player_a' then @player_a
61
+ when 'player_b' then @player_b
62
+ end
63
+
64
+ @response.merge!('winner' => winner, 'loser' => winner ? (winner == @player_a ? @player_b : @player_a) : nil)
71
65
  end
72
66
 
73
67
  PROMPT = <<~PROMPT
@@ -121,5 +115,16 @@ module ActiveGenie::Battle
121
115
  }
122
116
  }
123
117
  }
118
+
119
+ def config
120
+ {
121
+ all_providers: { model_tier: 'lower_tier' },
122
+ log: {
123
+ **(@config.dig(:log) || {}),
124
+ trace: self.class.name,
125
+ },
126
+ **@config
127
+ }
128
+ end
124
129
  end
125
130
  end
@@ -2,7 +2,7 @@
2
2
  require_relative 'battle/basic'
3
3
 
4
4
  module ActiveGenie
5
- # Battle module
5
+ # See the [Battle README](lib/active_genie/battle/README.md) for more information.
6
6
  module Battle
7
7
  module_function
8
8
 
@@ -0,0 +1,77 @@
1
+ require 'json'
2
+ require 'net/http'
3
+
4
+ module ActiveGenie::Clients
5
+ class OpenaiClient
6
+ def initialize(config)
7
+ @app_config = config
8
+ end
9
+
10
+ def function_calling(messages, function, config: {})
11
+ model = config[:model]
12
+ model = @app_config.tier_to_model(config.dig(:all_providers, :model_tier)) if model.nil? && config.dig(:all_providers, :model_tier)
13
+ model = @app_config.lower_tier_model if model.nil?
14
+
15
+ payload = {
16
+ messages:,
17
+ response_format: {
18
+ type: 'json_schema',
19
+ json_schema: function
20
+ },
21
+ model:,
22
+ }
23
+
24
+ api_key = config[:api_key] || @app_config.api_key
25
+ headers = DEFAULT_HEADERS.merge(
26
+ 'Authorization': "Bearer #{api_key}"
27
+ ).compact
28
+
29
+ response = request(payload, headers, config:)
30
+
31
+ parsed_response = JSON.parse(response.dig('choices', 0, 'message', 'content'))
32
+ parsed_response.dig('properties') || parsed_response
33
+ rescue JSON::ParserError
34
+ nil
35
+ end
36
+
37
+ private
38
+
39
+ def request(payload, headers, config:)
40
+ start_time = Time.now
41
+ response = Net::HTTP.post(
42
+ URI("#{@app_config.api_url}/chat/completions"),
43
+ payload.to_json,
44
+ headers
45
+ )
46
+
47
+ raise OpenaiError, response.body unless response.is_a?(Net::HTTPSuccess)
48
+ return nil if response.body.empty?
49
+
50
+ parsed_body = JSON.parse(response.body)
51
+ log_response(start_time, parsed_body, config:)
52
+
53
+ parsed_body
54
+ end
55
+
56
+ DEFAULT_HEADERS = {
57
+ 'Content-Type': 'application/json',
58
+ }
59
+
60
+ def log_response(start_time, response, config:)
61
+ ActiveGenie::Logger.trace(
62
+ {
63
+ **config.dig(:log),
64
+ category: :llm,
65
+ trace: "#{config.dig(:log, :trace)}/#{self.class.name}",
66
+ total_tokens: response.dig('usage', 'total_tokens'),
67
+ model: response.dig('model'),
68
+ request_duration: Time.now - start_time,
69
+ openai: response
70
+ }
71
+ )
72
+ end
73
+
74
+ # TODO: add some more rich error handling
75
+ class OpenaiError < StandardError; end
76
+ end
77
+ end
@@ -0,0 +1,19 @@
1
+ module ActiveGenie::Clients
2
+ class UnifiedClient
3
+ class << self
4
+ def function_calling(messages, function, config: {})
5
+ provider_name = config[:provider]&.downcase&.strip&.to_sym
6
+ provider = ActiveGenie.configuration.providers.all[provider_name] || ActiveGenie.configuration.providers.default
7
+
8
+ raise InvalidProviderError if provider.nil? || provider.client.nil?
9
+
10
+ provider.client.function_calling(messages, function, config:)
11
+ end
12
+
13
+ private
14
+
15
+ # TODO: improve error message
16
+ class InvalidProviderError < StandardError; end
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,14 @@
1
+
2
+ module ActiveGenie::Configuration
3
+ class LogConfig
4
+ attr_writer :log_level
5
+
6
+ def log_level
7
+ @log_level ||= :info
8
+ end
9
+
10
+ def to_h(config = {})
11
+ { log_level:, **config }
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,56 @@
1
+ require_relative '../clients/openai_client'
2
+
3
+ module ActiveGenie::Configuration
4
+ class OpenaiConfig
5
+ attr_writer :api_key, :organization, :api_url, :client,
6
+ :lower_tier_model, :middle_tier_model, :upper_tier_model
7
+
8
+ def api_key
9
+ @api_key || ENV['OPENAI_API_KEY']
10
+ end
11
+
12
+ def organization
13
+ @organization || ENV['OPENAI_ORGANIZATION']
14
+ end
15
+
16
+ def lower_tier_model
17
+ @lower_tier_model || 'gpt-4o-mini'
18
+ end
19
+
20
+ def middle_tier_model
21
+ @middle_tier_model || 'gpt-4o'
22
+ end
23
+
24
+ def upper_tier_model
25
+ @upper_tier_model || 'o1-preview'
26
+ end
27
+
28
+ def tier_to_model(tier)
29
+ {
30
+ lower_tier: lower_tier_model,
31
+ middle_tier: middle_tier_model,
32
+ upper_tier: upper_tier_model
33
+ }[tier&.to_sym]
34
+ end
35
+
36
+ def api_url
37
+ @api_url || 'https://api.openai.com/v1'
38
+ end
39
+
40
+ def client
41
+ @client ||= ::ActiveGenie::Clients::OpenaiClient.new(self)
42
+ end
43
+
44
+ def to_h(config = {})
45
+ {
46
+ api_key:,
47
+ organization:,
48
+ api_url:,
49
+ lower_tier_model:,
50
+ middle_tier_model:,
51
+ upper_tier_model:,
52
+ **config
53
+ }
54
+ end
55
+ end
56
+ end
@@ -0,0 +1,37 @@
1
+ module ActiveGenie::Configuration
2
+ class ProvidersConfig
3
+ def initialize
4
+ @all = {}
5
+ @default = nil
6
+ end
7
+
8
+ def register(name, provider_class)
9
+ @all ||= {}
10
+ @all[name] = provider_class.new
11
+ define_singleton_method(name) do
12
+ instance_variable_get("@#{name}") || instance_variable_set("@#{name}", @all[name])
13
+ end
14
+
15
+ self
16
+ end
17
+
18
+ def default
19
+ @default || @all.values.first
20
+ end
21
+
22
+ def all
23
+ @all
24
+ end
25
+
26
+ def to_h(config = {})
27
+ hash_all = {}
28
+ @all.each do |name, provider|
29
+ hash_all[name] = provider.to_h(config.dig(name) || {})
30
+ end
31
+ hash_all
32
+ end
33
+
34
+ private
35
+ attr_writer :default
36
+ end
37
+ end
@@ -1,33 +1,28 @@
1
- require 'yaml'
1
+ require_relative 'configuration/providers_config'
2
+ require_relative 'configuration/openai_config'
3
+ require_relative 'configuration/log_config'
2
4
 
3
5
  module ActiveGenie
4
- class Configuration
5
- attr_accessor :path_to_config
6
+ module Configuration
7
+ module_function
6
8
 
7
- def initialize
8
- @path_to_config = File.join('config', 'active_genie.yml')
9
- end
10
-
11
- def values
12
- return @values if @values
13
-
14
- @values = load_values.transform_keys(&:to_sym)
15
- @values.each do |key, _value|
16
- @values[key][:model] = key
17
- @values[key] = @values[key].transform_keys(&:to_sym)
9
+ def providers
10
+ @providers ||= begin
11
+ p = ProvidersConfig.new
12
+ p.register(:openai, ActiveGenie::Configuration::OpenaiConfig)
13
+ p
18
14
  end
19
15
  end
20
16
 
21
- private
22
-
23
- def load_values
24
- return {} unless File.exist?(@path_to_config)
17
+ def log
18
+ @log ||= LogConfig.new
19
+ end
25
20
 
26
- yaml_content = ERB.new(File.read(@path_to_config)).result
27
- YAML.safe_load(yaml_content, aliases: true) || {}
28
- rescue Psych::SyntaxError => e
29
- warn "ActiveGenie.warning: Config file '#{@path_to_config}' is not a valid YAML file (#{e.message}), using default configuration"
30
- {}
21
+ def to_h(configs = {})
22
+ {
23
+ providers: providers.to_h(configs.dig(:providers) || {}),
24
+ log: log.to_h(configs.dig(:log) || {})
25
+ }
31
26
  end
32
27
  end
33
28
  end
@@ -95,7 +95,7 @@ result = ActiveGenie::DataExtractor.from_informal(text, schema)
95
95
 
96
96
  ## Interface
97
97
 
98
- ### `.call(text, data_to_extract, options = {})`
98
+ ### `.call(text, data_to_extract, config = {})`
99
99
  Extracts structured data from text based on a predefined schema.
100
100
 
101
101
  #### Parameters
@@ -103,9 +103,9 @@ Extracts structured data from text based on a predefined schema.
103
103
  | --- | --- | --- | --- | --- |
104
104
  | `text` | `String` | The text to analyze and extract data from | Yes | "John Doe is 25 years old" |
105
105
  | `data_to_extract` | `Hash` | Schema defining the data structure to extract | Yes | `{ name: { type: 'string' } }` |
106
- | `options` | `Hash` | Additional extraction configuration | No | `{ model: "gpt-4" }` |
106
+ | `config` | `Hash` | Additional extraction configuration | No | `{ model: "gpt-4" }` |
107
107
 
108
- #### Options
108
+ #### config
109
109
  | Name | Type | Description |
110
110
  | --- | --- | --- |
111
111
  | `model` | `String` | The model to use for the extraction |
@@ -117,7 +117,7 @@ Extracts structured data from text based on a predefined schema.
117
117
  - Explanation field for each extracted value
118
118
  - Additional analysis fields when using `from_informal`
119
119
 
120
- ### `.from_informal(text, data_to_extract, options = {})`
120
+ ### `.from_informal(text, data_to_extract, config = {})`
121
121
  Extends basic extraction with rhetorical analysis, particularly for litotes.
122
122
 
123
123
  #### Additional Return Fields
@@ -1,9 +1,10 @@
1
- require_relative '../clients/router.rb'
1
+
2
+ require_relative '../clients/unified_client'
2
3
 
3
4
  module ActiveGenie::DataExtractor
4
5
  class Basic
5
- def self.call(text, data_to_extract, options: {})
6
- new(text, data_to_extract, options:).call
6
+ def self.call(text, data_to_extract, config: {})
7
+ new(text, data_to_extract, config:).call
7
8
  end
8
9
 
9
10
  # Extracts structured data from text based on a predefined schema.
@@ -11,9 +12,7 @@ module ActiveGenie::DataExtractor
11
12
  # @param text [String] The input text to analyze and extract data from
12
13
  # @param data_to_extract [Hash] Schema defining the data structure to extract.
13
14
  # Each key in the hash represents a field to extract, and its value defines the expected type and constraints.
14
- # @param options [Hash] Additional options for the extraction process
15
- # @option options [String] :model The model to use for the extraction
16
- # @option options [String] :api_key The API key to use for the extraction
15
+ # @param config [Hash] Additional config for the extraction process
17
16
  #
18
17
  # @return [Hash] The extracted data matching the schema structure. Each field will include
19
18
  # both the extracted value and an explanation of how it was derived.
@@ -27,10 +26,10 @@ module ActiveGenie::DataExtractor
27
26
  # DataExtractor.call(text, schema)
28
27
  # # => { name: "John Doe", name_explanation: "Found directly in text",
29
28
  # # age: 25, age_explanation: "Explicitly stated as 25 years old" }
30
- def initialize(text, data_to_extract, options: {})
29
+ def initialize(text, data_to_extract, config: {})
31
30
  @text = text
32
31
  @data_to_extract = data_to_extract
33
- @options = options
32
+ @config = ActiveGenie::Configuration.to_h(config)
34
33
  end
35
34
 
36
35
  def call
@@ -47,7 +46,7 @@ module ActiveGenie::DataExtractor
47
46
  }
48
47
  }
49
48
 
50
- ::ActiveGenie::Clients::Router.function_calling(messages, function, options: @options)
49
+ ::ActiveGenie::Clients::UnifiedClient.function_calling(messages, function, config:)
51
50
  end
52
51
 
53
52
  private
@@ -84,5 +83,16 @@ module ActiveGenie::DataExtractor
84
83
 
85
84
  with_explaination
86
85
  end
86
+
87
+ def config
88
+ {
89
+ all_providers: { model_tier: 'lower_tier' },
90
+ log: {
91
+ **(@config.dig(:log) || {}),
92
+ trace: self.class.name,
93
+ },
94
+ **@config
95
+ }
96
+ end
87
97
  end
88
98
  end
@@ -1,7 +1,7 @@
1
1
  module ActiveGenie::DataExtractor
2
2
  class FromInformal
3
- def self.call(text, data_to_extract, options: {})
4
- new(text, data_to_extract, options:).call()
3
+ def self.call(text, data_to_extract, config: {})
4
+ new(text, data_to_extract, config:).call()
5
5
  end
6
6
 
7
7
  # Extracts data from informal text while also detecting litotes and their meanings.
@@ -9,7 +9,7 @@ module ActiveGenie::DataExtractor
9
9
  #
10
10
  # @param text [String] The informal text to analyze
11
11
  # @param data_to_extract [Hash] Schema defining the data structure to extract
12
- # @param options [Hash] Additional options for the extraction process
12
+ # @param config [Hash] Additional config for the extraction process
13
13
  #
14
14
  # @return [Hash] The extracted data including litote analysis. In addition to the
15
15
  # schema-defined fields, includes:
@@ -23,17 +23,17 @@ module ActiveGenie::DataExtractor
23
23
  # # => { mood: "positive", mood_explanation: "Speaker views weather favorably",
24
24
  # # message_litote: true,
25
25
  # # litote_rephrased: "The weather is good today" }
26
- def initialize(text, data_to_extract, options: {})
26
+ def initialize(text, data_to_extract, config: {})
27
27
  @text = text
28
28
  @data_to_extract = data_to_extract
29
- @options = options
29
+ @config = ActiveGenie::Configuration.to_h(config)
30
30
  end
31
31
 
32
32
  def call
33
- response = Basic.call(@text, data_to_extract_with_litote, options: @options)
33
+ response = Basic.call(@text, data_to_extract_with_litote, config:)
34
34
 
35
35
  if response['message_litote']
36
- response = Basic.call(response['litote_rephrased'], @data_to_extract, options: @options)
36
+ response = Basic.call(response['litote_rephrased'], @data_to_extract, config:)
37
37
  end
38
38
 
39
39
  response
@@ -54,5 +54,16 @@ module ActiveGenie::DataExtractor
54
54
  }
55
55
  }
56
56
  end
57
+
58
+ def config
59
+ {
60
+ all_providers: { model_tier: 'lower_tier' },
61
+ **@config,
62
+ log: {
63
+ **@config.dig(:log),
64
+ trace: self.class.name,
65
+ },
66
+ }
67
+ end
57
68
  end
58
69
  end
@@ -2,7 +2,7 @@ require_relative 'data_extractor/basic'
2
2
  require_relative 'data_extractor/from_informal'
3
3
 
4
4
  module ActiveGenie
5
- # Extract structured data from text using AI-powered analysis, handling informal language and complex expressions.
5
+ # See the [DataExtractor README](lib/active_genie/data_extractor/README.md) for more information.
6
6
  module DataExtractor
7
7
  module_function
8
8