active_genie 0.0.24 → 0.0.25
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 +35 -50
- data/VERSION +1 -1
- data/lib/active_genie/battle/README.md +5 -5
- data/lib/active_genie/battle/generalist.rb +132 -0
- data/lib/active_genie/battle.rb +6 -5
- data/lib/active_genie/clients/providers/anthropic_client.rb +77 -0
- data/lib/active_genie/clients/{base_client.rb → providers/base_client.rb} +74 -100
- data/lib/active_genie/clients/providers/deepseek_client.rb +91 -0
- data/lib/active_genie/clients/providers/google_client.rb +132 -0
- data/lib/active_genie/clients/providers/openai_client.rb +96 -0
- data/lib/active_genie/clients/unified_client.rb +42 -12
- data/lib/active_genie/concerns/loggable.rb +11 -23
- data/lib/active_genie/config/battle_config.rb +8 -0
- data/lib/active_genie/config/data_extractor_config.rb +23 -0
- data/lib/active_genie/config/llm_config.rb +36 -0
- data/lib/active_genie/config/log_config.rb +44 -0
- data/lib/active_genie/config/providers/anthropic_config.rb +57 -0
- data/lib/active_genie/config/providers/deepseek_config.rb +50 -0
- data/lib/active_genie/config/providers/google_config.rb +52 -0
- data/lib/active_genie/config/providers/openai_config.rb +50 -0
- data/lib/active_genie/config/providers/provider_base.rb +89 -0
- data/lib/active_genie/config/providers_config.rb +62 -0
- data/lib/active_genie/config/ranking_config.rb +21 -0
- data/lib/active_genie/config/scoring_config.rb +8 -0
- data/lib/active_genie/configuration.rb +51 -28
- data/lib/active_genie/data_extractor/README.md +13 -13
- data/lib/active_genie/data_extractor/from_informal.rb +54 -48
- data/lib/active_genie/data_extractor/generalist.md +12 -0
- data/lib/active_genie/data_extractor/generalist.rb +125 -0
- data/lib/active_genie/data_extractor.rb +7 -5
- data/lib/active_genie/errors/invalid_provider_error.rb +41 -0
- data/lib/active_genie/logger.rb +17 -66
- data/lib/active_genie/ranking/README.md +31 -1
- data/lib/active_genie/ranking/elo_round.rb +107 -104
- data/lib/active_genie/ranking/free_for_all.rb +78 -74
- data/lib/active_genie/ranking/player.rb +79 -71
- data/lib/active_genie/ranking/players_collection.rb +83 -71
- data/lib/active_genie/ranking/ranking.rb +71 -94
- data/lib/active_genie/ranking/ranking_scoring.rb +71 -50
- data/lib/active_genie/ranking.rb +2 -0
- data/lib/active_genie/scoring/README.md +4 -4
- data/lib/active_genie/scoring/generalist.rb +171 -0
- data/lib/active_genie/scoring/recommended_reviewers.rb +70 -71
- data/lib/active_genie/scoring.rb +8 -5
- data/lib/active_genie.rb +23 -1
- data/lib/tasks/benchmark.rake +10 -9
- data/lib/tasks/install.rake +3 -1
- data/lib/tasks/templates/active_genie.rb +11 -6
- metadata +31 -22
- data/lib/active_genie/battle/basic.rb +0 -129
- data/lib/active_genie/clients/anthropic_client.rb +0 -84
- data/lib/active_genie/clients/google_client.rb +0 -135
- data/lib/active_genie/clients/helpers/retry.rb +0 -29
- data/lib/active_genie/clients/openai_client.rb +0 -98
- data/lib/active_genie/configuration/log_config.rb +0 -14
- data/lib/active_genie/configuration/providers/anthropic_config.rb +0 -54
- data/lib/active_genie/configuration/providers/base_config.rb +0 -85
- data/lib/active_genie/configuration/providers/deepseek_config.rb +0 -54
- data/lib/active_genie/configuration/providers/google_config.rb +0 -56
- data/lib/active_genie/configuration/providers/internal_company_api_config.rb +0 -54
- data/lib/active_genie/configuration/providers/openai_config.rb +0 -54
- data/lib/active_genie/configuration/providers_config.rb +0 -40
- data/lib/active_genie/configuration/runtime_config.rb +0 -35
- data/lib/active_genie/data_extractor/basic.rb +0 -101
- data/lib/active_genie/scoring/basic.rb +0 -170
@@ -17,24 +17,24 @@ schema = {
|
|
17
17
|
age: { type: 'integer', description: 'Age in years' }
|
18
18
|
}
|
19
19
|
result = ActiveGenie::DataExtractor.call(text, schema)
|
20
|
-
# => {
|
21
|
-
# name: "John Doe",
|
20
|
+
# => {
|
21
|
+
# name: "John Doe",
|
22
22
|
# name_explanation: "Found directly in text",
|
23
|
-
# age: 25,
|
24
|
-
# age_explanation: "Explicitly stated as 25 years old"
|
23
|
+
# age: 25,
|
24
|
+
# age_explanation: "Explicitly stated as 25 years old"
|
25
25
|
# }
|
26
26
|
|
27
27
|
product = "Nike Air Max 90 - Size 42 - $199.99"
|
28
28
|
schema = {
|
29
|
-
brand: {
|
29
|
+
brand: {
|
30
30
|
type: 'string',
|
31
31
|
enum: ["Nike", "Adidas", "Puma"]
|
32
32
|
},
|
33
|
-
price: {
|
33
|
+
price: {
|
34
34
|
type: 'number',
|
35
35
|
minimum: 0
|
36
36
|
},
|
37
|
-
currency: {
|
37
|
+
currency: {
|
38
38
|
type: 'string',
|
39
39
|
enum: ["USD", "EUR"]
|
40
40
|
},
|
@@ -46,8 +46,8 @@ schema = {
|
|
46
46
|
}
|
47
47
|
|
48
48
|
result = ActiveGenie::DataExtractor.call(product, schema)
|
49
|
-
# => {
|
50
|
-
# brand: "Nike",
|
49
|
+
# => {
|
50
|
+
# brand: "Nike",
|
51
51
|
# brand_explanation: "Brand name found at start of text",
|
52
52
|
# price: 199.99,
|
53
53
|
# price_explanation: "Price found in USD format at end",
|
@@ -70,12 +70,12 @@ The `from_informal` method extends the basic extraction by analyzing rhetorical
|
|
70
70
|
|
71
71
|
```ruby
|
72
72
|
text = "The weather isn't bad today"
|
73
|
-
schema = {
|
74
|
-
mood: { type: 'string', description: 'The mood of the message' }
|
73
|
+
schema = {
|
74
|
+
mood: { type: 'string', description: 'The mood of the message' }
|
75
75
|
}
|
76
76
|
|
77
77
|
result = ActiveGenie::DataExtractor.from_informal(text, schema)
|
78
|
-
# => {
|
78
|
+
# => {
|
79
79
|
# mood: "positive",
|
80
80
|
# mood_explanation: "Speaker views weather favorably",
|
81
81
|
# message_litote: true,
|
@@ -117,7 +117,7 @@ Extracts structured data from text based on a predefined schema.
|
|
117
117
|
- Additional analysis fields when using `from_informal`
|
118
118
|
|
119
119
|
### `.from_informal(text, data_to_extract, config = {})`
|
120
|
-
Extends
|
120
|
+
Extends extraction with rhetorical analysis, particularly for litotes.
|
121
121
|
|
122
122
|
#### Additional Return Fields
|
123
123
|
| Name | Type | Description |
|
@@ -1,58 +1,64 @@
|
|
1
|
-
|
2
|
-
class FromInformal
|
3
|
-
def self.call(...)
|
4
|
-
new(...).call()
|
5
|
-
end
|
1
|
+
# frozen_string_literal: true
|
6
2
|
|
7
|
-
|
8
|
-
# This method extends the basic extraction by analyzing rhetorical devices.
|
9
|
-
#
|
10
|
-
# @param text [String] The informal text to analyze
|
11
|
-
# @param data_to_extract [Hash] Schema defining the data structure to extract
|
12
|
-
# @param config [Hash] Additional config for the extraction process
|
13
|
-
#
|
14
|
-
# @return [Hash] The extracted data including litote analysis. In addition to the
|
15
|
-
# schema-defined fields, includes:
|
16
|
-
# - message_litote: Whether the text contains a litote
|
17
|
-
# - litote_rephrased: The positive rephrasing of any detected litote
|
18
|
-
#
|
19
|
-
# @example Analyze text with litote
|
20
|
-
# text = "The weather isn't bad today"
|
21
|
-
# schema = { mood: { type: 'string', description: 'The mood of the message' } }
|
22
|
-
# DataExtractor.from_informal(text, schema)
|
23
|
-
# # => { mood: "positive", mood_explanation: "Speaker views weather favorably",
|
24
|
-
# # message_litote: true,
|
25
|
-
# # litote_rephrased: "The weather is good today" }
|
26
|
-
def initialize(text, data_to_extract, config: {})
|
27
|
-
@text = text
|
28
|
-
@data_to_extract = data_to_extract
|
29
|
-
@config = ActiveGenie::Configuration.to_h(config)
|
30
|
-
end
|
3
|
+
require_relative 'generalist'
|
31
4
|
|
32
|
-
|
33
|
-
|
5
|
+
module ActiveGenie
|
6
|
+
module DataExtractor
|
7
|
+
class FromInformal
|
8
|
+
def self.call(...)
|
9
|
+
new(...).call
|
10
|
+
end
|
34
11
|
|
35
|
-
|
36
|
-
|
12
|
+
# Extracts data from informal text while also detecting litotes and their meanings.
|
13
|
+
# This method extends the basic extraction by analyzing rhetorical devices.
|
14
|
+
#
|
15
|
+
# @param text [String] The informal text to analyze
|
16
|
+
# @param data_to_extract [Hash] Schema defining the data structure to extract
|
17
|
+
# @param config [Hash] Additional config for the extraction process
|
18
|
+
#
|
19
|
+
# @return [Hash] The extracted data including litote analysis. In addition to the
|
20
|
+
# schema-defined fields, includes:
|
21
|
+
# - message_litote: Whether the text contains a litote
|
22
|
+
# - litote_rephrased: The positive rephrasing of any detected litote
|
23
|
+
#
|
24
|
+
# @example Analyze text with litote
|
25
|
+
# text = "The weather isn't bad today"
|
26
|
+
# schema = { mood: { type: 'string', description: 'The mood of the message' } }
|
27
|
+
# DataExtractor.from_informal(text, schema)
|
28
|
+
# # => { mood: "positive", mood_explanation: "Speaker views weather favorably",
|
29
|
+
# # message_litote: true,
|
30
|
+
# # litote_rephrased: "The weather is good today" }
|
31
|
+
def initialize(text, data_to_extract, config: {})
|
32
|
+
@text = text
|
33
|
+
@data_to_extract = data_to_extract
|
34
|
+
@config = ActiveGenie.configuration.merge(config)
|
37
35
|
end
|
38
36
|
|
39
|
-
|
40
|
-
|
37
|
+
def call
|
38
|
+
response = Generalist.call(@text, data_to_extract_with_litote, config: @config)
|
39
|
+
|
40
|
+
if response['message_litote']
|
41
|
+
response = Generalist.call(response['litote_rephrased'], @data_to_extract, config: @config)
|
42
|
+
end
|
41
43
|
|
42
|
-
|
43
|
-
|
44
|
-
|
45
|
-
|
46
|
-
|
47
|
-
|
48
|
-
|
49
|
-
|
50
|
-
|
51
|
-
|
52
|
-
|
53
|
-
|
44
|
+
response
|
45
|
+
end
|
46
|
+
|
47
|
+
private
|
48
|
+
|
49
|
+
def data_to_extract_with_litote
|
50
|
+
{
|
51
|
+
**@data_to_extract,
|
52
|
+
message_litote: {
|
53
|
+
type: 'boolean',
|
54
|
+
description: 'Return true if the message is a litote. A litote is a figure of speech that uses understatement to emphasize a point by stating a negative to further affirm a positive, often incorporating double negatives for effect.'
|
55
|
+
},
|
56
|
+
litote_rephrased: {
|
57
|
+
type: 'string',
|
58
|
+
description: 'The true meaning of the litote. Rephrase the message to a positive and active statement.'
|
59
|
+
}
|
54
60
|
}
|
55
|
-
|
61
|
+
end
|
56
62
|
end
|
57
63
|
end
|
58
64
|
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,125 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative '../clients/unified_client'
|
4
|
+
|
5
|
+
module ActiveGenie
|
6
|
+
module DataExtractor
|
7
|
+
class Generalist
|
8
|
+
def self.call(...)
|
9
|
+
new(...).call
|
10
|
+
end
|
11
|
+
|
12
|
+
# Extracts structured data from text based on a predefined schema.
|
13
|
+
#
|
14
|
+
# @param text [String] The input text to analyze and extract data from
|
15
|
+
# @param data_to_extract [Hash] Schema defining the data structure to extract.
|
16
|
+
# Each key in the hash represents a field to extract, and its value defines the expected type and constraints.
|
17
|
+
# @param config [Hash] Additional config for the extraction process
|
18
|
+
#
|
19
|
+
# @return [Hash] The extracted data matching the schema structure. Each field will include
|
20
|
+
# both the extracted value and an explanation of how it was derived.
|
21
|
+
#
|
22
|
+
# @example Extract a person's details
|
23
|
+
# schema = {
|
24
|
+
# name: { type: 'string', description: 'Full name of the person' },
|
25
|
+
# age: { type: 'integer', description: 'Age in years' }
|
26
|
+
# }
|
27
|
+
# text = "John Doe is 25 years old"
|
28
|
+
# DataExtractor.call(text, schema)
|
29
|
+
# # => { name: "John Doe", name_explanation: "Found directly in text",
|
30
|
+
# # age: 25, age_explanation: "Explicitly stated as 25 years old" }
|
31
|
+
def initialize(text, data_to_extract, config: {})
|
32
|
+
@text = text
|
33
|
+
@data_to_extract = data_to_extract
|
34
|
+
@config = ActiveGenie.configuration.merge(config)
|
35
|
+
end
|
36
|
+
|
37
|
+
def call
|
38
|
+
messages = [
|
39
|
+
{ role: 'system', content: prompt },
|
40
|
+
{ role: 'user', content: @text }
|
41
|
+
]
|
42
|
+
|
43
|
+
properties = data_to_extract_with_explanation
|
44
|
+
|
45
|
+
function = {
|
46
|
+
name: 'data_extractor',
|
47
|
+
description: 'Extract structured and typed data from text',
|
48
|
+
parameters: {
|
49
|
+
type: 'object',
|
50
|
+
properties:,
|
51
|
+
required: properties.keys
|
52
|
+
}
|
53
|
+
}
|
54
|
+
|
55
|
+
response = function_calling(messages, function)
|
56
|
+
|
57
|
+
simplify_response(response)
|
58
|
+
end
|
59
|
+
|
60
|
+
private
|
61
|
+
|
62
|
+
def data_to_extract_with_explanation
|
63
|
+
return @data_to_extract unless @config.data_extractor.with_explanation
|
64
|
+
|
65
|
+
with_explanation = {}
|
66
|
+
|
67
|
+
@data_to_extract.each do |key, value|
|
68
|
+
with_explanation[key] = value
|
69
|
+
with_explanation["#{key}_explanation"] = {
|
70
|
+
type: 'string',
|
71
|
+
description: "The chain of thought that led to the conclusion about: #{key}. Can be blank if the user didn't provide any context"
|
72
|
+
}
|
73
|
+
with_explanation["#{key}_accuracy"] = {
|
74
|
+
type: 'integer',
|
75
|
+
description: 'The accuracy of the extracted data, what is the percentage of confidence? When 100 it means the data is explicitly stated in the text. When 0 it means is no way to discover the data from the text'
|
76
|
+
}
|
77
|
+
end
|
78
|
+
|
79
|
+
with_explanation
|
80
|
+
end
|
81
|
+
|
82
|
+
def function_calling(messages, function)
|
83
|
+
response = ::ActiveGenie::Clients::UnifiedClient.function_calling(
|
84
|
+
messages,
|
85
|
+
function,
|
86
|
+
config: @config
|
87
|
+
)
|
88
|
+
|
89
|
+
ActiveGenie::Logger.call(
|
90
|
+
{
|
91
|
+
code: :data_extractor,
|
92
|
+
text: @text[0..30],
|
93
|
+
data_to_extract: function[:parameters][:properties],
|
94
|
+
extracted_data: response
|
95
|
+
}
|
96
|
+
)
|
97
|
+
|
98
|
+
response
|
99
|
+
end
|
100
|
+
|
101
|
+
def simplify_response(response)
|
102
|
+
return response if @config.data_extractor.verbose
|
103
|
+
|
104
|
+
simplified_response = {}
|
105
|
+
|
106
|
+
@data_to_extract.each_key do |key|
|
107
|
+
next if !response.key?(key)
|
108
|
+
next if response.key?("#{key}_accuracy") && response["#{key}_accuracy"] < min_accuracy
|
109
|
+
|
110
|
+
simplified_response[key] = response[key]
|
111
|
+
end
|
112
|
+
|
113
|
+
simplified_response
|
114
|
+
end
|
115
|
+
|
116
|
+
def min_accuracy
|
117
|
+
@config.data_extractor.min_accuracy # default 70
|
118
|
+
end
|
119
|
+
|
120
|
+
def prompt
|
121
|
+
File.read(File.join(__dir__, 'generalist.md'))
|
122
|
+
end
|
123
|
+
end
|
124
|
+
end
|
125
|
+
end
|
@@ -1,4 +1,6 @@
|
|
1
|
-
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
require_relative 'data_extractor/generalist'
|
2
4
|
require_relative 'data_extractor/from_informal'
|
3
5
|
|
4
6
|
module ActiveGenie
|
@@ -6,12 +8,12 @@ module ActiveGenie
|
|
6
8
|
module DataExtractor
|
7
9
|
module_function
|
8
10
|
|
9
|
-
def
|
10
|
-
|
11
|
+
def call(...)
|
12
|
+
Generalist.call(...)
|
11
13
|
end
|
12
14
|
|
13
|
-
def
|
14
|
-
|
15
|
+
def generalist(...)
|
16
|
+
Generalist.call(...)
|
15
17
|
end
|
16
18
|
|
17
19
|
def from_informal(...)
|
@@ -0,0 +1,41 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
3
|
+
module ActiveGenie
|
4
|
+
class InvalidProviderError < StandardError
|
5
|
+
TEXT = <<~TEXT
|
6
|
+
Invalid provider: %<provider>s
|
7
|
+
|
8
|
+
To configure ActiveGenie, you can either:
|
9
|
+
1. Set up global configuration:
|
10
|
+
```ruby
|
11
|
+
ActiveGenie.configure do |config|
|
12
|
+
config.provider = 'your_provider'
|
13
|
+
config.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::DataExtraction.call(
|
21
|
+
arg1,
|
22
|
+
arg2,
|
23
|
+
config: {
|
24
|
+
provider: 'your_provider',
|
25
|
+
api_key: 'your_api_key'
|
26
|
+
}
|
27
|
+
)
|
28
|
+
```
|
29
|
+
|
30
|
+
Available providers: %<available_providers>s
|
31
|
+
TEXT
|
32
|
+
|
33
|
+
def initialize(provider)
|
34
|
+
super(format(TEXT, provider:, available_providers:))
|
35
|
+
end
|
36
|
+
|
37
|
+
def available_providers
|
38
|
+
ActiveGenie.configuration.providers.all.keys.join(', ')
|
39
|
+
end
|
40
|
+
end
|
41
|
+
end
|
data/lib/active_genie/logger.rb
CHANGED
@@ -1,3 +1,5 @@
|
|
1
|
+
# frozen_string_literal: true
|
2
|
+
|
1
3
|
require 'json'
|
2
4
|
require 'fileutils'
|
3
5
|
|
@@ -5,87 +7,36 @@ module ActiveGenie
|
|
5
7
|
module Logger
|
6
8
|
module_function
|
7
9
|
|
8
|
-
def
|
9
|
-
@context ||= {}
|
10
|
-
@observers ||= []
|
11
|
-
begin
|
12
|
-
@context = @context.merge(context)
|
13
|
-
@observers << observer if observer
|
14
|
-
yield if block_given?
|
15
|
-
ensure
|
16
|
-
@context.delete_if { |key, _| context.key?(key) }
|
17
|
-
@observers.delete(observer)
|
18
|
-
end
|
19
|
-
end
|
20
|
-
|
21
|
-
def info(log)
|
22
|
-
call(log, level: :info)
|
23
|
-
end
|
24
|
-
|
25
|
-
def error(log)
|
26
|
-
call(log, level: :error)
|
27
|
-
end
|
28
|
-
|
29
|
-
def warn(log)
|
30
|
-
call(log, level: :warn)
|
31
|
-
end
|
32
|
-
|
33
|
-
def debug(log)
|
34
|
-
call(log, level: :debug)
|
35
|
-
end
|
36
|
-
|
37
|
-
def trace(log)
|
38
|
-
call(log, level: :trace)
|
39
|
-
end
|
40
|
-
|
41
|
-
def call(data, level: :info)
|
10
|
+
def call(data)
|
42
11
|
log = {
|
43
12
|
**(@context || {}),
|
44
13
|
**(data || {}),
|
45
14
|
timestamp: Time.now,
|
46
|
-
level: level.to_s.upcase,
|
47
15
|
process_id: Process.pid
|
48
16
|
}
|
49
17
|
|
50
|
-
|
51
|
-
|
52
|
-
call_observers(log)
|
18
|
+
persist!(log)
|
19
|
+
$stdout.puts log
|
20
|
+
ActiveGenie.configuration.log.call_observers(log)
|
53
21
|
|
54
22
|
log
|
55
23
|
end
|
56
24
|
|
57
|
-
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
# - :error -> Represents critical errors that prevent the application from functioning correctly.
|
65
|
-
# - :debug -> Provides detailed logs for debugging, offering the necessary context for audits but with slightly less detail than trace logs.
|
66
|
-
# - :trace -> Logs every external call with the highest level of detail, primarily for auditing or state-saving purposes. These logs do not provide context regarding triggers or reasons.
|
67
|
-
LOG_LEVELS = { info: 0, error: 0, warn: 1, debug: 2, trace: 3 }.freeze
|
68
|
-
|
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 '.'
|
25
|
+
def with_context(context)
|
26
|
+
@context ||= {}
|
27
|
+
begin
|
28
|
+
@context = @context.merge(context)
|
29
|
+
yield if block_given?
|
30
|
+
ensure
|
31
|
+
@context.delete_if { |key, _| context.key?(key) }
|
82
32
|
end
|
83
33
|
end
|
84
34
|
|
85
|
-
|
86
|
-
return if @observers.nil? || @observers.size.zero?
|
35
|
+
attr_accessor :context
|
87
36
|
|
88
|
-
|
37
|
+
def persist!(log)
|
38
|
+
FileUtils.mkdir_p('log')
|
39
|
+
File.write('log/active_genie.log', "#{JSON.generate(log)}\n", mode: 'a')
|
89
40
|
end
|
90
41
|
end
|
91
42
|
end
|
@@ -40,4 +40,34 @@ The method processes the players through scoring, elimination, and ranking phase
|
|
40
40
|
- Adjust initial criteria to ensure consistency
|
41
41
|
- Adjust each player's content to ensure consistency
|
42
42
|
- Support players with images or audio
|
43
|
-
- Parallelize processing battles and scoring
|
43
|
+
- Parallelize processing battles and scoring
|
44
|
+
|
45
|
+
## Ranking Configuration
|
46
|
+
|
47
|
+
| Config | Description | Default |
|
48
|
+
|--------|-------------|---------|
|
49
|
+
| `score_variation_threshold` | Threshold for eliminating players with inconsistent scores | `30` |
|
50
|
+
|
51
|
+
## Ranking Callbacks
|
52
|
+
Callbacks are optional and can be used to watch any changes in players, battles, or scoring.
|
53
|
+
|
54
|
+
| Callback | Description |
|
55
|
+
|--------|-------------|
|
56
|
+
| `watch_players` | Callback to watch any changes in players |
|
57
|
+
| `watch_battles` | Callback to watch any changes in battles |
|
58
|
+
| `watch_scoring` | Callback to watch any changes in scoring |
|
59
|
+
|
60
|
+
Example of callback usage:
|
61
|
+
|
62
|
+
```ruby
|
63
|
+
result = ActiveGenie::Ranking.call(
|
64
|
+
players,
|
65
|
+
criteria,
|
66
|
+
config: {
|
67
|
+
watch_players: ->(player) { puts player },
|
68
|
+
watch_battles: ->(battle) { puts battle },
|
69
|
+
watch_scoring: ->(scoring) { puts scoring }
|
70
|
+
}
|
71
|
+
)
|
72
|
+
```
|
73
|
+
|