active_genie 0.0.8 → 0.0.10

Sign up to get free protection for your applications and to get access to all the features.
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