active_genie 0.30.1 → 0.30.8
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 +4 -4
- data/README.md +7 -5
- data/VERSION +1 -1
- data/lib/active_genie/comparator/debate.rb +13 -31
- data/lib/active_genie/comparator/fight.rb +3 -3
- data/lib/active_genie/comparator.rb +0 -2
- data/lib/active_genie/configs/base_config.rb +25 -0
- data/lib/active_genie/configs/extractor_config.rb +6 -14
- data/lib/active_genie/configs/lister_config.rb +6 -10
- data/lib/active_genie/configs/llm_config.rb +12 -25
- data/lib/active_genie/configs/log_config.rb +19 -16
- data/lib/active_genie/configs/providers/anthropic_config.rb +10 -16
- data/lib/active_genie/configs/providers/deepseek_config.rb +4 -19
- data/lib/active_genie/configs/providers/google_config.rb +4 -19
- data/lib/active_genie/configs/providers/openai_config.rb +4 -19
- data/lib/active_genie/configs/providers/provider_base.rb +21 -67
- data/lib/active_genie/configs/providers_config.rb +39 -20
- data/lib/active_genie/configs/ranker_config.rb +6 -12
- data/lib/active_genie/configuration.rb +23 -60
- data/lib/active_genie/{ranker/entities → entities}/player.rb +4 -0
- data/lib/active_genie/{ranker/entities → entities}/players.rb +1 -1
- data/lib/active_genie/entities/result.rb +29 -0
- data/lib/active_genie/errors/invalid_model_error.rb +42 -0
- data/lib/active_genie/errors/invalid_provider_error.rb +1 -1
- data/lib/active_genie/errors/provider_server_error.rb +26 -0
- data/lib/active_genie/errors/without_available_provider_error.rb +39 -0
- data/lib/active_genie/extractor/data.json +9 -0
- data/lib/active_genie/extractor/data.prompt.md +12 -0
- data/lib/active_genie/extractor/data.rb +71 -0
- data/lib/active_genie/extractor/explanation.rb +22 -41
- data/lib/active_genie/extractor/litote.rb +10 -9
- data/lib/active_genie/extractor.rb +5 -0
- data/lib/active_genie/lister/feud.json +5 -1
- data/lib/active_genie/lister/feud.rb +12 -17
- data/lib/active_genie/lister/juries.rb +18 -16
- data/lib/active_genie/logger.rb +16 -28
- data/lib/active_genie/providers/anthropic_provider.rb +12 -6
- data/lib/active_genie/providers/base_provider.rb +15 -18
- data/lib/active_genie/providers/deepseek_provider.rb +18 -10
- data/lib/active_genie/providers/google_provider.rb +11 -5
- data/lib/active_genie/providers/openai_provider.rb +8 -6
- data/lib/active_genie/providers/unified_provider.rb +58 -3
- data/lib/active_genie/ranker/elo.rb +41 -36
- data/lib/active_genie/ranker/free_for_all.rb +45 -28
- data/lib/active_genie/ranker/scoring.rb +20 -11
- data/lib/active_genie/ranker/tournament.rb +23 -35
- data/lib/active_genie/scorer/jury_bench.rb +18 -23
- data/lib/active_genie/utils/base_module.rb +34 -0
- data/lib/active_genie/utils/call_wrapper.rb +20 -0
- data/lib/active_genie/utils/deep_merge.rb +12 -0
- data/lib/active_genie/utils/fiber_by_batch.rb +2 -2
- data/lib/active_genie/utils/text_case.rb +18 -0
- data/lib/active_genie.rb +16 -18
- data/lib/tasks/benchmark.rake +1 -3
- data/lib/tasks/templates/active_genie.rb +0 -3
- data/lib/tasks/test.rake +62 -1
- metadata +25 -15
- data/lib/active_genie/configs/comparator_config.rb +0 -10
- data/lib/active_genie/configs/scorer_config.rb +0 -10
|
@@ -1,22 +1,34 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'base_config'
|
|
4
|
+
require_relative 'providers/openai_config'
|
|
5
|
+
require_relative 'providers/google_config'
|
|
6
|
+
require_relative 'providers/anthropic_config'
|
|
7
|
+
require_relative 'providers/deepseek_config'
|
|
8
|
+
|
|
3
9
|
module ActiveGenie
|
|
4
10
|
module Config
|
|
5
|
-
class ProvidersConfig
|
|
6
|
-
def initialize
|
|
7
|
-
@all = {
|
|
8
|
-
|
|
11
|
+
class ProvidersConfig < BaseConfig
|
|
12
|
+
def initialize(**args)
|
|
13
|
+
@all = {
|
|
14
|
+
openai: Providers::OpenaiConfig.new(**args.fetch(:openai, {})),
|
|
15
|
+
google: Providers::GoogleConfig.new(**args.fetch(:google, {})),
|
|
16
|
+
anthropic: Providers::AnthropicConfig.new(**args.fetch(:anthropic, {})),
|
|
17
|
+
deepseek: Providers::DeepseekConfig.new(**args.fetch(:deepseek, {}))
|
|
18
|
+
}
|
|
19
|
+
super
|
|
9
20
|
end
|
|
10
21
|
|
|
11
|
-
attr_reader :all
|
|
12
|
-
|
|
13
22
|
def default
|
|
14
|
-
@default
|
|
23
|
+
@default ||= ENV.fetch('PROVIDER_NAME', nil)&.to_s&.downcase&.strip
|
|
15
24
|
end
|
|
16
25
|
|
|
17
26
|
def default=(provider)
|
|
18
|
-
|
|
19
|
-
|
|
27
|
+
@default = provider&.to_s&.downcase&.strip
|
|
28
|
+
end
|
|
29
|
+
|
|
30
|
+
def all
|
|
31
|
+
@all ||= {}
|
|
20
32
|
end
|
|
21
33
|
|
|
22
34
|
def valid
|
|
@@ -24,31 +36,29 @@ module ActiveGenie
|
|
|
24
36
|
@all.slice(*valid_provider_keys)
|
|
25
37
|
end
|
|
26
38
|
|
|
27
|
-
def add(
|
|
39
|
+
def add(name, provider_configs)
|
|
28
40
|
@all ||= {}
|
|
29
|
-
Array(
|
|
30
|
-
name = provider::NAME
|
|
41
|
+
Array(provider_configs).each do |provider_config|
|
|
31
42
|
remove([name]) if @all.key?(name)
|
|
32
43
|
|
|
33
|
-
@all[name] =
|
|
44
|
+
@all[name] = provider_config.new
|
|
34
45
|
end
|
|
35
46
|
|
|
36
47
|
self
|
|
37
48
|
end
|
|
38
49
|
|
|
39
|
-
def remove(
|
|
40
|
-
Array(
|
|
50
|
+
def remove(provider_configs)
|
|
51
|
+
Array(provider_configs).each do |provider|
|
|
41
52
|
@all.delete(provider::NAME)
|
|
42
53
|
end
|
|
43
54
|
|
|
44
55
|
self
|
|
45
56
|
end
|
|
46
57
|
|
|
47
|
-
def
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
end
|
|
58
|
+
def provider_name_by_model(model)
|
|
59
|
+
return nil if model.nil?
|
|
60
|
+
|
|
61
|
+
valid.find { |_, config| config.valid_model?(model) }&.first
|
|
52
62
|
end
|
|
53
63
|
|
|
54
64
|
def method_missing(method_name, *args, &)
|
|
@@ -58,6 +68,15 @@ module ActiveGenie
|
|
|
58
68
|
def respond_to_missing?(method_name, include_private = false)
|
|
59
69
|
@all.key?(method_name) || super
|
|
60
70
|
end
|
|
71
|
+
|
|
72
|
+
def to_h
|
|
73
|
+
h = {}
|
|
74
|
+
@all.each do |key, config|
|
|
75
|
+
h[key] = config.to_h
|
|
76
|
+
end
|
|
77
|
+
|
|
78
|
+
super.merge(h)
|
|
79
|
+
end
|
|
61
80
|
end
|
|
62
81
|
end
|
|
63
82
|
end
|
|
@@ -1,20 +1,14 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative 'base_config'
|
|
4
|
+
|
|
3
5
|
module ActiveGenie
|
|
4
6
|
module Config
|
|
5
|
-
class RankerConfig
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
def initialize
|
|
9
|
-
@score_variation_threshold = 30
|
|
10
|
-
end
|
|
7
|
+
class RankerConfig < BaseConfig
|
|
8
|
+
attr_writer :score_variation_threshold
|
|
11
9
|
|
|
12
|
-
def
|
|
13
|
-
|
|
14
|
-
if config_params[:score_variation_threshold]
|
|
15
|
-
config.score_variation_threshold = config_params[:score_variation_threshold]
|
|
16
|
-
end
|
|
17
|
-
end
|
|
10
|
+
def score_variation_threshold
|
|
11
|
+
@score_variation_threshold ||= 30
|
|
18
12
|
end
|
|
19
13
|
end
|
|
20
14
|
end
|
|
@@ -1,90 +1,53 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
-
require_relative 'configs/comparator_config'
|
|
4
3
|
require_relative 'configs/extractor_config'
|
|
5
4
|
require_relative 'configs/lister_config'
|
|
6
5
|
require_relative 'configs/llm_config'
|
|
7
6
|
require_relative 'configs/log_config'
|
|
8
7
|
require_relative 'configs/providers_config'
|
|
9
8
|
require_relative 'configs/ranker_config'
|
|
10
|
-
require_relative 'configs/scorer_config'
|
|
11
9
|
|
|
12
10
|
module ActiveGenie
|
|
13
11
|
class Configuration
|
|
14
|
-
def
|
|
15
|
-
@
|
|
12
|
+
def initialize(initial_config = nil)
|
|
13
|
+
@initial_config = initial_config || {}
|
|
16
14
|
end
|
|
17
15
|
|
|
18
16
|
def providers
|
|
19
|
-
@providers ||= Config::ProvidersConfig.new
|
|
20
|
-
end
|
|
21
|
-
|
|
22
|
-
def ranker
|
|
23
|
-
@ranker ||= Config::RankerConfig.new
|
|
24
|
-
end
|
|
25
|
-
|
|
26
|
-
def scorer
|
|
27
|
-
@scorer ||= Config::ScorerConfig.new
|
|
17
|
+
@providers ||= Config::ProvidersConfig.new(**@initial_config.fetch(:providers, {}))
|
|
28
18
|
end
|
|
29
19
|
|
|
30
|
-
def
|
|
31
|
-
@
|
|
20
|
+
def llm
|
|
21
|
+
@llm ||= Config::LlmConfig.new(**@initial_config.fetch(:llm, {}))
|
|
32
22
|
end
|
|
33
23
|
|
|
34
|
-
def
|
|
35
|
-
@
|
|
24
|
+
def log
|
|
25
|
+
@log ||= Config::LogConfig.new(**@initial_config.fetch(:log, {}))
|
|
36
26
|
end
|
|
37
27
|
|
|
38
|
-
|
|
39
|
-
@lister ||= Config::ListerConfig.new
|
|
40
|
-
end
|
|
28
|
+
# Modules
|
|
41
29
|
|
|
42
|
-
def
|
|
43
|
-
@
|
|
30
|
+
def ranker
|
|
31
|
+
@ranker ||= Config::RankerConfig.new(**@initial_config.fetch(:ranker, {}))
|
|
44
32
|
end
|
|
45
33
|
|
|
46
|
-
def
|
|
47
|
-
@
|
|
34
|
+
def extractor
|
|
35
|
+
@extractor ||= Config::ExtractorConfig.new(**@initial_config.fetch(:extractor, {}))
|
|
48
36
|
end
|
|
49
37
|
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
def merge(config_params = {})
|
|
53
|
-
return config_params if config_params.is_a?(Configuration)
|
|
54
|
-
|
|
55
|
-
new_configuration = dup
|
|
56
|
-
|
|
57
|
-
SUB_CONFIGS.each do |key|
|
|
58
|
-
config = new_configuration.send(key)
|
|
59
|
-
|
|
60
|
-
next unless config.respond_to?(:merge)
|
|
61
|
-
|
|
62
|
-
new_config = sub_config_merge(config, key, config_params)
|
|
63
|
-
|
|
64
|
-
new_configuration.send("#{key}=", new_config)
|
|
65
|
-
end
|
|
66
|
-
@logger = nil
|
|
67
|
-
|
|
68
|
-
new_configuration
|
|
38
|
+
def lister
|
|
39
|
+
@lister ||= Config::ListerConfig.new(**@initial_config.fetch(:lister, {}))
|
|
69
40
|
end
|
|
70
41
|
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
config.merge(config_params[key.to_s])
|
|
81
|
-
elsif config_params&.key?(key.to_sym)
|
|
82
|
-
config.merge(config_params[key.to_sym])
|
|
83
|
-
else
|
|
84
|
-
config.merge(config_params || {})
|
|
85
|
-
end
|
|
42
|
+
def to_h
|
|
43
|
+
{
|
|
44
|
+
providers: providers.to_h,
|
|
45
|
+
llm: llm.to_h,
|
|
46
|
+
log: log.to_h,
|
|
47
|
+
ranker: ranker.to_h,
|
|
48
|
+
extractor: extractor.to_h,
|
|
49
|
+
lister: lister.to_h
|
|
50
|
+
}
|
|
86
51
|
end
|
|
87
|
-
|
|
88
|
-
attr_writer(*SUB_CONFIGS, :logger)
|
|
89
52
|
end
|
|
90
53
|
end
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveGenie
|
|
4
|
+
class Result
|
|
5
|
+
attr_reader :data, :reasoning, :metadata
|
|
6
|
+
|
|
7
|
+
def initialize(data:, metadata:, reasoning: nil)
|
|
8
|
+
@data = data
|
|
9
|
+
@reasoning = reasoning
|
|
10
|
+
@metadata = metadata
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
def explanation
|
|
14
|
+
@reasoning
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def to_h
|
|
18
|
+
{ data: @data, reasoning: @reasoning, metadata: @metadata }
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def to_s
|
|
22
|
+
to_h.to_s
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
def to_json(...)
|
|
26
|
+
to_h.to_json(...)
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveGenie
|
|
4
|
+
class InvalidModelError < StandardError
|
|
5
|
+
TEXT = <<~TEXT
|
|
6
|
+
Invalid model: %<model>s
|
|
7
|
+
|
|
8
|
+
To configure ActiveGenie, you can either:
|
|
9
|
+
1. Set up global configuration:
|
|
10
|
+
```ruby
|
|
11
|
+
ActiveGenie.configure do |config|
|
|
12
|
+
config.providers.openai.api_key = 'your_api_key'
|
|
13
|
+
config.llm.model = 'gpt-5'
|
|
14
|
+
# ... other configuration options
|
|
15
|
+
end
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
2. Or pass configuration directly to the method call:
|
|
19
|
+
```ruby
|
|
20
|
+
ActiveGenie::Extraction.call(
|
|
21
|
+
arg1,
|
|
22
|
+
arg2,
|
|
23
|
+
config: {
|
|
24
|
+
providers: {
|
|
25
|
+
openai: {
|
|
26
|
+
api_key: 'your_api_key'
|
|
27
|
+
}
|
|
28
|
+
},
|
|
29
|
+
llm: {
|
|
30
|
+
model: 'gpt-5'
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
)
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
TEXT
|
|
37
|
+
|
|
38
|
+
def initialize(model)
|
|
39
|
+
super(format(TEXT, model:))
|
|
40
|
+
end
|
|
41
|
+
end
|
|
42
|
+
end
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveGenie
|
|
4
|
+
class ProviderServerError < StandardError
|
|
5
|
+
TEXT = <<~TEXT
|
|
6
|
+
Provider server error: %<code>s
|
|
7
|
+
%<body>s
|
|
8
|
+
|
|
9
|
+
Providers errors are common and can occur for various reasons, such as:
|
|
10
|
+
- Invalid API key
|
|
11
|
+
- Exceeded usage limits
|
|
12
|
+
- Temporary server issues
|
|
13
|
+
|
|
14
|
+
Be ready to handle these errors gracefully in your application. We recommend implementing retry logic and exponential backoff strategies.
|
|
15
|
+
Usually async workers layer is the ideal place to handle such errors.
|
|
16
|
+
TEXT
|
|
17
|
+
|
|
18
|
+
def initialize(response)
|
|
19
|
+
super(format(
|
|
20
|
+
TEXT,
|
|
21
|
+
code: response&.code.to_s,
|
|
22
|
+
body: response&.body.to_s.strip.empty? ? '(no body)' : response&.body.to_s
|
|
23
|
+
))
|
|
24
|
+
end
|
|
25
|
+
end
|
|
26
|
+
end
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module ActiveGenie
|
|
4
|
+
class WithoutAvailableProviderError < StandardError
|
|
5
|
+
TEXT = <<~TEXT
|
|
6
|
+
Missing at least one credentialed provider to proceed.
|
|
7
|
+
|
|
8
|
+
To configure ActiveGenie, you can either:
|
|
9
|
+
1. Set up global configuration:
|
|
10
|
+
```ruby
|
|
11
|
+
ActiveGenie.configure do |config|
|
|
12
|
+
config.providers.default = 'openai'
|
|
13
|
+
config.providers.openai.api_key = 'your_api_key'
|
|
14
|
+
# ... other configuration options
|
|
15
|
+
end
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
2. Or pass configuration directly to the method call:
|
|
19
|
+
```ruby
|
|
20
|
+
ActiveGenie::Extractor.call(
|
|
21
|
+
arg1,
|
|
22
|
+
arg2,
|
|
23
|
+
config: {
|
|
24
|
+
providers: {
|
|
25
|
+
default: 'openai',
|
|
26
|
+
openai: {
|
|
27
|
+
api_key: 'your_api_key'
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
)
|
|
32
|
+
```
|
|
33
|
+
TEXT
|
|
34
|
+
|
|
35
|
+
def initialize
|
|
36
|
+
super(TEXT)
|
|
37
|
+
end
|
|
38
|
+
end
|
|
39
|
+
end
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
Extract structured and typed data from user messages.
|
|
2
|
+
Identify relevant information within user messages and categorize it into predefined data fields with specific data types.
|
|
3
|
+
|
|
4
|
+
# Steps
|
|
5
|
+
1. **Identify Data Types**: Determine the types of data to collect, such as names, dates, email addresses, phone numbers, etc.
|
|
6
|
+
2. **Extract Information**: Use pattern recognition and language understanding to identify and extract the relevant pieces of data from the user message.
|
|
7
|
+
3. **Categorize Data**: Assign the extracted data to the appropriate predefined fields.
|
|
8
|
+
|
|
9
|
+
# Notes
|
|
10
|
+
- Handle missing or partial information gracefully.
|
|
11
|
+
- Manage multiple occurrences of similar data points by prioritizing the first one unless specified otherwise.
|
|
12
|
+
- Be flexible to handle variations in data format and language clues.
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative '../providers/unified_provider'
|
|
4
|
+
|
|
5
|
+
module ActiveGenie
|
|
6
|
+
module Extractor
|
|
7
|
+
class Data < ActiveGenie::BaseModule
|
|
8
|
+
# Extracts structured data from text based on a predefined schema.
|
|
9
|
+
#
|
|
10
|
+
# @param text [String] The input text to analyze and extract data from
|
|
11
|
+
# @param data_to_extract [Hash] Schema defining the data structure to extract.
|
|
12
|
+
# Each key in the hash represents a field to extract, and its value defines the expected type and constraints.
|
|
13
|
+
# @param config [Hash] Additional config for the extraction process
|
|
14
|
+
#
|
|
15
|
+
# @return [Hash] The extracted data matching the schema structure. Each field will include
|
|
16
|
+
# both the extracted value.
|
|
17
|
+
#
|
|
18
|
+
# @example Extract a person's details
|
|
19
|
+
# schema = {
|
|
20
|
+
# name: { type: 'string', description: 'Full name of the person' },
|
|
21
|
+
# age: { type: 'integer', description: 'Age in years' }
|
|
22
|
+
# }
|
|
23
|
+
# text = "John Doe is 25 years old"
|
|
24
|
+
# Extractor.with_explanation(text, schema)
|
|
25
|
+
# # => { name: "John Doe", age: 25 }
|
|
26
|
+
def initialize(text, data_to_extract, config: {})
|
|
27
|
+
@text = text
|
|
28
|
+
@data_to_extract = data_to_extract
|
|
29
|
+
super(config:)
|
|
30
|
+
end
|
|
31
|
+
|
|
32
|
+
def call
|
|
33
|
+
messages = [
|
|
34
|
+
{ role: 'system', content: prompt },
|
|
35
|
+
{ role: 'user', content: @text }
|
|
36
|
+
]
|
|
37
|
+
|
|
38
|
+
function = JSON.parse(File.read(File.join(__dir__, 'data.json')), symbolize_names: true)
|
|
39
|
+
function[:parameters][:properties] = @data_to_extract
|
|
40
|
+
function[:parameters][:required] = @data_to_extract.keys
|
|
41
|
+
|
|
42
|
+
provider_response = ::ActiveGenie::Providers::UnifiedProvider.function_calling(
|
|
43
|
+
messages,
|
|
44
|
+
function,
|
|
45
|
+
config: config
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
response_formatted(provider_response)
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
private
|
|
52
|
+
|
|
53
|
+
def response_formatted(provider_response)
|
|
54
|
+
data = provider_response.compact
|
|
55
|
+
|
|
56
|
+
ActiveGenie::Result.new(
|
|
57
|
+
data:,
|
|
58
|
+
metadata: provider_response
|
|
59
|
+
)
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
def prompt
|
|
63
|
+
File.read(File.join(__dir__, 'data.prompt.md'))
|
|
64
|
+
end
|
|
65
|
+
|
|
66
|
+
def module_config
|
|
67
|
+
{ llm: { recommended_model: 'deepseek-chat' } }
|
|
68
|
+
end
|
|
69
|
+
end
|
|
70
|
+
end
|
|
71
|
+
end
|
|
@@ -1,14 +1,11 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative '../providers/unified_provider'
|
|
4
|
+
require_relative '../utils/deep_merge'
|
|
4
5
|
|
|
5
6
|
module ActiveGenie
|
|
6
7
|
module Extractor
|
|
7
|
-
class Explanation
|
|
8
|
-
def self.call(...)
|
|
9
|
-
new(...).call
|
|
10
|
-
end
|
|
11
|
-
|
|
8
|
+
class Explanation < ActiveGenie::BaseModule
|
|
12
9
|
# Extracts structured data from text based on a predefined schema.
|
|
13
10
|
#
|
|
14
11
|
# @param text [String] The input text to analyze and extract data from
|
|
@@ -31,7 +28,7 @@ module ActiveGenie
|
|
|
31
28
|
def initialize(text, data_to_extract, config: {})
|
|
32
29
|
@text = text
|
|
33
30
|
@data_to_extract = data_to_extract
|
|
34
|
-
|
|
31
|
+
super(config:)
|
|
35
32
|
end
|
|
36
33
|
|
|
37
34
|
def call
|
|
@@ -46,16 +43,18 @@ module ActiveGenie
|
|
|
46
43
|
function[:parameters][:properties] = properties
|
|
47
44
|
function[:parameters][:required] = properties.keys
|
|
48
45
|
|
|
49
|
-
|
|
46
|
+
provider_response = ::ActiveGenie::Providers::UnifiedProvider.function_calling(
|
|
47
|
+
messages,
|
|
48
|
+
function,
|
|
49
|
+
config: config
|
|
50
|
+
)
|
|
50
51
|
|
|
51
|
-
|
|
52
|
+
response_formatted(provider_response)
|
|
52
53
|
end
|
|
53
54
|
|
|
54
55
|
private
|
|
55
56
|
|
|
56
57
|
def data_to_extract_with_explanation
|
|
57
|
-
return @data_to_extract unless @config.extractor.with_explanation
|
|
58
|
-
|
|
59
58
|
with_explanation = {}
|
|
60
59
|
|
|
61
60
|
@data_to_extract.each do |key, value|
|
|
@@ -80,47 +79,29 @@ module ActiveGenie
|
|
|
80
79
|
with_explanation
|
|
81
80
|
end
|
|
82
81
|
|
|
83
|
-
def
|
|
84
|
-
|
|
85
|
-
messages,
|
|
86
|
-
function,
|
|
87
|
-
config: @config
|
|
88
|
-
)
|
|
82
|
+
def response_formatted(provider_response)
|
|
83
|
+
data = provider_response.slice(*@data_to_extract.keys.map(&:to_s)).transform_keys(&:to_sym)
|
|
89
84
|
|
|
90
|
-
|
|
91
|
-
{
|
|
92
|
-
code: :extractor,
|
|
93
|
-
text: @text[0..30],
|
|
94
|
-
data_to_extract: function[:parameters][:properties],
|
|
95
|
-
extracted_data: response
|
|
96
|
-
}
|
|
97
|
-
)
|
|
85
|
+
first_reasoning_key = provider_response["#{provider_response.keys.first}_explanation"]
|
|
98
86
|
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
simplified_response = {}
|
|
106
|
-
|
|
107
|
-
@data_to_extract.each_key do |key|
|
|
108
|
-
next unless response.key?(key.to_s)
|
|
109
|
-
next if response.key?("#{key}_accuracy") && response["#{key}_accuracy"] < min_accuracy
|
|
110
|
-
|
|
111
|
-
simplified_response[key] = response[key.to_s]
|
|
112
|
-
end
|
|
113
|
-
|
|
114
|
-
simplified_response
|
|
87
|
+
ActiveGenie::Result.new(
|
|
88
|
+
data:,
|
|
89
|
+
reasoning: first_reasoning_key,
|
|
90
|
+
metadata: provider_response
|
|
91
|
+
)
|
|
115
92
|
end
|
|
116
93
|
|
|
117
94
|
def min_accuracy
|
|
118
|
-
|
|
95
|
+
config.extractor.min_accuracy # default 70
|
|
119
96
|
end
|
|
120
97
|
|
|
121
98
|
def prompt
|
|
122
99
|
File.read(File.join(__dir__, 'explanation.prompt.md'))
|
|
123
100
|
end
|
|
101
|
+
|
|
102
|
+
def module_config
|
|
103
|
+
{ llm: { recommended_model: 'deepseek-chat' } }
|
|
104
|
+
end
|
|
124
105
|
end
|
|
125
106
|
end
|
|
126
107
|
end
|
|
@@ -4,11 +4,7 @@ require_relative 'explanation'
|
|
|
4
4
|
|
|
5
5
|
module ActiveGenie
|
|
6
6
|
module Extractor
|
|
7
|
-
class Litote
|
|
8
|
-
def self.call(...)
|
|
9
|
-
new(...).call
|
|
10
|
-
end
|
|
11
|
-
|
|
7
|
+
class Litote < ActiveGenie::BaseModule
|
|
12
8
|
# Extracts data from informal text while also detecting litotes and their meanings.
|
|
13
9
|
# This method extends the basic extraction by analyzing rhetorical devices.
|
|
14
10
|
#
|
|
@@ -31,14 +27,15 @@ module ActiveGenie
|
|
|
31
27
|
def initialize(text, data_to_extract, config: {})
|
|
32
28
|
@text = text
|
|
33
29
|
@data_to_extract = data_to_extract
|
|
34
|
-
|
|
30
|
+
super(config:)
|
|
35
31
|
end
|
|
36
32
|
|
|
37
33
|
def call
|
|
38
|
-
response = Explanation.call(@text, extract_with_litote, config:
|
|
34
|
+
response = Explanation.call(@text, extract_with_litote, config:)
|
|
39
35
|
|
|
40
|
-
if response[:message_litote]
|
|
41
|
-
response = Explanation.call(response[:litote_rephrased], @data_to_extract,
|
|
36
|
+
if response.data[:message_litote]
|
|
37
|
+
response = Explanation.call(response.data[:litote_rephrased], @data_to_extract,
|
|
38
|
+
config:)
|
|
42
39
|
end
|
|
43
40
|
|
|
44
41
|
response
|
|
@@ -51,6 +48,10 @@ module ActiveGenie
|
|
|
51
48
|
|
|
52
49
|
@data_to_extract.merge(parameters)
|
|
53
50
|
end
|
|
51
|
+
|
|
52
|
+
def module_config
|
|
53
|
+
{ llm: { recommended_model: 'deepseek-chat' } }
|
|
54
|
+
end
|
|
54
55
|
end
|
|
55
56
|
end
|
|
56
57
|
end
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require_relative 'extractor/explanation'
|
|
4
|
+
require_relative 'extractor/data'
|
|
4
5
|
require_relative 'extractor/litote'
|
|
5
6
|
|
|
6
7
|
module ActiveGenie
|
|
@@ -15,6 +16,10 @@ module ActiveGenie
|
|
|
15
16
|
Explanation.call(...)
|
|
16
17
|
end
|
|
17
18
|
|
|
19
|
+
def data(...)
|
|
20
|
+
Data.call(...)
|
|
21
|
+
end
|
|
22
|
+
|
|
18
23
|
def with_litote(...)
|
|
19
24
|
Litote.call(...)
|
|
20
25
|
end
|