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
|
@@ -8,6 +8,10 @@
|
|
|
8
8
|
"type": "string",
|
|
9
9
|
"description": "The theme for the feud."
|
|
10
10
|
},
|
|
11
|
+
"why_these_items": {
|
|
12
|
+
"type": "string",
|
|
13
|
+
"description": "Explanation of why these items were chosen."
|
|
14
|
+
},
|
|
11
15
|
"items": {
|
|
12
16
|
"type": "array",
|
|
13
17
|
"description": "The list of items for the feud.",
|
|
@@ -16,6 +20,6 @@
|
|
|
16
20
|
}
|
|
17
21
|
}
|
|
18
22
|
},
|
|
19
|
-
"required": ["theme", "items"]
|
|
23
|
+
"required": ["theme", "why_these_items", "items"]
|
|
20
24
|
}
|
|
21
25
|
}
|
|
@@ -9,17 +9,13 @@ module ActiveGenie
|
|
|
9
9
|
# @example Feud usage with two players and criteria
|
|
10
10
|
# Feud.call("Industries that are most likely to be affected by climate change")
|
|
11
11
|
#
|
|
12
|
-
class Feud
|
|
13
|
-
def self.call(...)
|
|
14
|
-
new(...).call
|
|
15
|
-
end
|
|
16
|
-
|
|
12
|
+
class Feud < ActiveGenie::BaseModule
|
|
17
13
|
# @param theme [String] The theme for the feud
|
|
18
14
|
# @param config [Hash] Additional configuration options
|
|
19
15
|
# @return [Array of strings] List of items
|
|
20
16
|
def initialize(theme, config: {})
|
|
21
17
|
@theme = theme
|
|
22
|
-
|
|
18
|
+
super(config:)
|
|
23
19
|
end
|
|
24
20
|
|
|
25
21
|
# @return [Array of strings] The list of items
|
|
@@ -30,14 +26,17 @@ module ActiveGenie
|
|
|
30
26
|
{ role: 'user', content: "theme: #{@theme}" }
|
|
31
27
|
]
|
|
32
28
|
|
|
33
|
-
|
|
29
|
+
provider_response = ::ActiveGenie::Providers::UnifiedProvider.function_calling(
|
|
34
30
|
messages,
|
|
35
31
|
FUNCTION,
|
|
36
|
-
config:
|
|
32
|
+
config:
|
|
37
33
|
)
|
|
38
34
|
|
|
39
|
-
|
|
40
|
-
|
|
35
|
+
ActiveGenie::Result.new(
|
|
36
|
+
data: provider_response['items'] || [],
|
|
37
|
+
reasoning: provider_response['why_these_items'],
|
|
38
|
+
metadata: provider_response
|
|
39
|
+
)
|
|
41
40
|
end
|
|
42
41
|
|
|
43
42
|
PROMPT = File.read(File.join(__dir__, 'feud.prompt.md'))
|
|
@@ -46,15 +45,11 @@ module ActiveGenie
|
|
|
46
45
|
private
|
|
47
46
|
|
|
48
47
|
def number_of_items
|
|
49
|
-
|
|
48
|
+
config.lister.number_of_items
|
|
50
49
|
end
|
|
51
50
|
|
|
52
|
-
def
|
|
53
|
-
|
|
54
|
-
code: :feud,
|
|
55
|
-
theme: @theme[0..30],
|
|
56
|
-
items: response['items'].map { |item| item[0..30] }
|
|
57
|
-
)
|
|
51
|
+
def module_config
|
|
52
|
+
{ llm: { recommended_model: 'claude-haiku-4-5' } }
|
|
58
53
|
end
|
|
59
54
|
end
|
|
60
55
|
end
|
|
@@ -14,14 +14,9 @@ module ActiveGenie
|
|
|
14
14
|
# @example Getting jury for technical content
|
|
15
15
|
# Juries.call("Technical documentation about API design",
|
|
16
16
|
# "Evaluate technical accuracy and clarity")
|
|
17
|
-
# # =>
|
|
18
|
-
# # jury3: "Developer Advocate", reasoning: "..." }
|
|
17
|
+
# # => [ "API Architect", "Technical Writer", "Developer Advocate" ]
|
|
19
18
|
#
|
|
20
|
-
class Juries
|
|
21
|
-
def self.call(...)
|
|
22
|
-
new(...).call
|
|
23
|
-
end
|
|
24
|
-
|
|
19
|
+
class Juries < ActiveGenie::BaseModule
|
|
25
20
|
# Initializes a new jury recommendation instance
|
|
26
21
|
#
|
|
27
22
|
# @param text [String] The text content to analyze for jury recommendations
|
|
@@ -30,7 +25,7 @@ module ActiveGenie
|
|
|
30
25
|
def initialize(text, criteria, config: {})
|
|
31
26
|
@text = text
|
|
32
27
|
@criteria = criteria
|
|
33
|
-
|
|
28
|
+
super(config:)
|
|
34
29
|
end
|
|
35
30
|
|
|
36
31
|
def call
|
|
@@ -46,7 +41,10 @@ module ActiveGenie
|
|
|
46
41
|
parameters: {
|
|
47
42
|
type: 'object',
|
|
48
43
|
properties: {
|
|
49
|
-
|
|
44
|
+
why_these_juries: {
|
|
45
|
+
type: 'string',
|
|
46
|
+
description: 'A brief explanation of why these juries were chosen.'
|
|
47
|
+
},
|
|
50
48
|
juries: {
|
|
51
49
|
type: 'array',
|
|
52
50
|
description: 'The list of best juries',
|
|
@@ -59,24 +57,28 @@ module ActiveGenie
|
|
|
59
57
|
}
|
|
60
58
|
}
|
|
61
59
|
|
|
62
|
-
|
|
60
|
+
provider_response = ::ActiveGenie::Providers::UnifiedProvider.function_calling(
|
|
63
61
|
messages,
|
|
64
62
|
function,
|
|
65
|
-
config:
|
|
63
|
+
config:
|
|
66
64
|
)
|
|
67
65
|
|
|
68
|
-
|
|
66
|
+
ActiveGenie::Result.new(
|
|
67
|
+
data: provider_response['juries'] || [],
|
|
68
|
+
reasoning: provider_response['why_these_juries'],
|
|
69
|
+
metadata: provider_response
|
|
70
|
+
)
|
|
69
71
|
end
|
|
70
72
|
|
|
71
73
|
private
|
|
72
74
|
|
|
73
|
-
def client
|
|
74
|
-
::ActiveGenie::Providers::UnifiedProvider
|
|
75
|
-
end
|
|
76
|
-
|
|
77
75
|
def prompt
|
|
78
76
|
@prompt ||= File.read(File.join(__dir__, 'juries.prompt.md'))
|
|
79
77
|
end
|
|
78
|
+
|
|
79
|
+
def module_config
|
|
80
|
+
{ llm: { recommended_model: 'deepseek-chat' } }
|
|
81
|
+
end
|
|
80
82
|
end
|
|
81
83
|
end
|
|
82
84
|
end
|
data/lib/active_genie/logger.rb
CHANGED
|
@@ -5,32 +5,24 @@ require 'fileutils'
|
|
|
5
5
|
|
|
6
6
|
module ActiveGenie
|
|
7
7
|
class Logger
|
|
8
|
-
def
|
|
9
|
-
|
|
10
|
-
end
|
|
11
|
-
|
|
12
|
-
def call(data)
|
|
13
|
-
log = data.merge(@log_config.additional_context)
|
|
8
|
+
def call(data, config:)
|
|
9
|
+
log = data.merge(config.log.additional_context || {})
|
|
14
10
|
.merge(
|
|
15
11
|
timestamp: Time.now,
|
|
16
12
|
process_id: Process.pid
|
|
17
13
|
)
|
|
18
14
|
|
|
19
|
-
persist!(log)
|
|
20
|
-
output_call(log)
|
|
21
|
-
call_observers(log)
|
|
15
|
+
persist!(log, config:)
|
|
16
|
+
output_call(log, config:)
|
|
17
|
+
call_observers(log, config:)
|
|
22
18
|
|
|
23
19
|
log
|
|
24
20
|
end
|
|
25
21
|
|
|
26
|
-
def merge(log_config = nil)
|
|
27
|
-
new(log_config:)
|
|
28
|
-
end
|
|
29
|
-
|
|
30
22
|
private
|
|
31
23
|
|
|
32
|
-
def call_observers(log)
|
|
33
|
-
Array(
|
|
24
|
+
def call_observers(log, config:)
|
|
25
|
+
Array(config.log.observers).each do |observer|
|
|
34
26
|
next unless observer[:scope].all? { |key, value| log[key.to_sym] == value }
|
|
35
27
|
|
|
36
28
|
observer[:observer]&.call(log)
|
|
@@ -39,9 +31,9 @@ module ActiveGenie
|
|
|
39
31
|
end
|
|
40
32
|
end
|
|
41
33
|
|
|
42
|
-
def output_call(log)
|
|
43
|
-
if
|
|
44
|
-
|
|
34
|
+
def output_call(log, config:)
|
|
35
|
+
if config.log.output
|
|
36
|
+
config.log.output&.call(log)
|
|
45
37
|
else
|
|
46
38
|
$stdout.puts log
|
|
47
39
|
end
|
|
@@ -49,20 +41,16 @@ module ActiveGenie
|
|
|
49
41
|
call(code: :output_error, error: e.message)
|
|
50
42
|
end
|
|
51
43
|
|
|
52
|
-
def persist!(log)
|
|
53
|
-
file_path =
|
|
44
|
+
def persist!(log, config:)
|
|
45
|
+
file_path = if log.key?(:fine_tune) && log[:fine_tune]
|
|
46
|
+
config.log.fine_tune_file_path
|
|
47
|
+
else
|
|
48
|
+
config.log.file_path
|
|
49
|
+
end
|
|
54
50
|
folder_path = File.dirname(file_path)
|
|
55
51
|
|
|
56
52
|
FileUtils.mkdir_p(folder_path)
|
|
57
53
|
File.write(file_path, "#{JSON.generate(log)}\n", mode: 'a')
|
|
58
54
|
end
|
|
59
|
-
|
|
60
|
-
def log_to_file_path(log)
|
|
61
|
-
if log.key?(:fine_tune) && log[:fine_tune]
|
|
62
|
-
@log_config.fine_tune_file_path
|
|
63
|
-
else
|
|
64
|
-
@log_config.file_path
|
|
65
|
-
end
|
|
66
|
-
end
|
|
67
55
|
end
|
|
68
56
|
end
|
|
@@ -30,7 +30,11 @@ module ActiveGenie
|
|
|
30
30
|
temperature: @config.llm.temperature || 0
|
|
31
31
|
}
|
|
32
32
|
|
|
33
|
-
|
|
33
|
+
response = retry_with_backoff do
|
|
34
|
+
request(payload)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
response.dig('content', 0, 'input')
|
|
34
38
|
end
|
|
35
39
|
|
|
36
40
|
ANTHROPIC_ENDPOINT = '/v1/messages'
|
|
@@ -55,7 +59,7 @@ module ActiveGenie
|
|
|
55
59
|
def request(payload)
|
|
56
60
|
response = post(url, payload, headers:)
|
|
57
61
|
|
|
58
|
-
|
|
62
|
+
ActiveGenie.logger.call(
|
|
59
63
|
{
|
|
60
64
|
code: :llm_usage,
|
|
61
65
|
input_tokens: response.dig('usage', 'input_tokens'),
|
|
@@ -65,15 +69,17 @@ module ActiveGenie
|
|
|
65
69
|
'output_tokens'),
|
|
66
70
|
model: payload[:model],
|
|
67
71
|
usage: response['usage']
|
|
68
|
-
}
|
|
72
|
+
},
|
|
73
|
+
config: @config
|
|
69
74
|
)
|
|
70
|
-
|
|
75
|
+
ActiveGenie.logger.call(
|
|
71
76
|
{
|
|
72
77
|
code: :function_calling,
|
|
73
78
|
fine_tune: true,
|
|
74
79
|
payload:,
|
|
75
80
|
parsed_response: response.dig('content', 0, 'input')
|
|
76
|
-
}
|
|
81
|
+
},
|
|
82
|
+
config: @config
|
|
77
83
|
)
|
|
78
84
|
|
|
79
85
|
response
|
|
@@ -84,7 +90,7 @@ module ActiveGenie
|
|
|
84
90
|
end
|
|
85
91
|
|
|
86
92
|
def model
|
|
87
|
-
@config.llm.model
|
|
93
|
+
@config.llm.model
|
|
88
94
|
end
|
|
89
95
|
|
|
90
96
|
def headers
|
|
@@ -1,12 +1,12 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
3
|
require 'net/http'
|
|
4
|
+
require_relative '../errors/provider_server_error'
|
|
4
5
|
|
|
5
6
|
module ActiveGenie
|
|
6
7
|
module Providers
|
|
7
8
|
class BaseProvider
|
|
8
9
|
class ProviderUnknownError < StandardError; end
|
|
9
|
-
class ProviderServerError < StandardError; end
|
|
10
10
|
|
|
11
11
|
DEFAULT_HEADERS = {
|
|
12
12
|
'Content-Type': 'application/json',
|
|
@@ -104,9 +104,7 @@ module ActiveGenie
|
|
|
104
104
|
http.read_timeout = @config.llm.read_timeout || DEFAULT_TIMEOUT
|
|
105
105
|
http.open_timeout = @config.llm.open_timeout || DEFAULT_OPEN_TIMEOUT
|
|
106
106
|
|
|
107
|
-
|
|
108
|
-
http.request(request)
|
|
109
|
-
end
|
|
107
|
+
http.request(request)
|
|
110
108
|
end
|
|
111
109
|
|
|
112
110
|
# Apply headers to the request
|
|
@@ -154,7 +152,7 @@ module ActiveGenie
|
|
|
154
152
|
#
|
|
155
153
|
# @param details [Hash] Request and response details
|
|
156
154
|
def log_request_details(uri:, request:, response:, start_time:, parsed_response:)
|
|
157
|
-
|
|
155
|
+
ActiveGenie.logger.call(
|
|
158
156
|
{
|
|
159
157
|
code: :http_request,
|
|
160
158
|
uri: uri.to_s,
|
|
@@ -162,7 +160,7 @@ module ActiveGenie
|
|
|
162
160
|
status: response.code,
|
|
163
161
|
duration: Time.now - start_time,
|
|
164
162
|
response_size: parsed_response.to_s.bytesize
|
|
165
|
-
}
|
|
163
|
+
}, config: @config
|
|
166
164
|
)
|
|
167
165
|
end
|
|
168
166
|
|
|
@@ -172,23 +170,22 @@ module ActiveGenie
|
|
|
172
170
|
retries = 0
|
|
173
171
|
|
|
174
172
|
begin
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
response
|
|
180
|
-
rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED, ProviderServerError => e
|
|
173
|
+
yield
|
|
174
|
+
rescue Net::OpenTimeout, Net::ReadTimeout, Errno::ECONNREFUSED, ActiveGenie::ProviderServerError,
|
|
175
|
+
JSON::ParserError => e
|
|
181
176
|
raise if retries > max_retries
|
|
182
177
|
|
|
183
178
|
sleep_time = retry_delay * (2**retries)
|
|
184
179
|
retries += 1
|
|
185
180
|
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
181
|
+
ActiveGenie.logger.call(
|
|
182
|
+
{
|
|
183
|
+
code: :retry_attempt,
|
|
184
|
+
attempt: retries,
|
|
185
|
+
max_retries:,
|
|
186
|
+
next_retry_in_seconds: sleep_time,
|
|
187
|
+
error: e.message
|
|
188
|
+
}, config: @config
|
|
192
189
|
)
|
|
193
190
|
|
|
194
191
|
sleep(sleep_time)
|
|
@@ -25,14 +25,14 @@ module ActiveGenie
|
|
|
25
25
|
model:
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
response =
|
|
29
|
-
|
|
30
|
-
if response.nil? || response.keys.empty?
|
|
31
|
-
raise InvalidResponseError,
|
|
32
|
-
"Invalid response: #{response}"
|
|
28
|
+
response = retry_with_backoff do
|
|
29
|
+
request(payload)
|
|
33
30
|
end
|
|
34
31
|
|
|
35
|
-
|
|
32
|
+
raise InvalidResponseError, "Invalid response: #{response}" if response.keys.empty?
|
|
33
|
+
raise InvalidResponseError, 'Invalid response: empty' if response.nil?
|
|
34
|
+
|
|
35
|
+
ActiveGenie.logger.call({ code: :function_calling, fine_tune: true, payload:, response: }, config: @config)
|
|
36
36
|
|
|
37
37
|
response
|
|
38
38
|
end
|
|
@@ -44,7 +44,7 @@ module ActiveGenie
|
|
|
44
44
|
|
|
45
45
|
return nil if response.nil?
|
|
46
46
|
|
|
47
|
-
|
|
47
|
+
ActiveGenie.logger.call(
|
|
48
48
|
{
|
|
49
49
|
code: :llm_usage,
|
|
50
50
|
input_tokens: response.dig('usage', 'prompt_tokens'),
|
|
@@ -52,11 +52,19 @@ module ActiveGenie
|
|
|
52
52
|
total_tokens: response.dig('usage', 'total_tokens'),
|
|
53
53
|
model:,
|
|
54
54
|
usage: response['usage']
|
|
55
|
-
}
|
|
55
|
+
}, config: @config
|
|
56
56
|
)
|
|
57
57
|
|
|
58
|
-
parsed_response = JSON.parse(response
|
|
58
|
+
parsed_response = JSON.parse(get_response_body(response))
|
|
59
59
|
parsed_response['message'] || parsed_response
|
|
60
|
+
rescue JSON::ParserError
|
|
61
|
+
raise InvalidResponseError, "Invalid response: #{get_response_body(response)}"
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def get_response_body(response)
|
|
65
|
+
response.dig('choices', 0, 'message', 'tool_calls', 0, 'function', 'arguments')
|
|
66
|
+
.gsub(', " "', '')
|
|
67
|
+
.strip
|
|
60
68
|
end
|
|
61
69
|
|
|
62
70
|
def function_to_tool(function)
|
|
@@ -74,7 +82,7 @@ module ActiveGenie
|
|
|
74
82
|
end
|
|
75
83
|
|
|
76
84
|
def model
|
|
77
|
-
@config.llm.model
|
|
85
|
+
@config.llm.model
|
|
78
86
|
end
|
|
79
87
|
|
|
80
88
|
def url
|
|
@@ -29,12 +29,17 @@ module ActiveGenie
|
|
|
29
29
|
}
|
|
30
30
|
params = { key: provider_config.api_key }
|
|
31
31
|
|
|
32
|
-
response =
|
|
32
|
+
response = retry_with_backoff do
|
|
33
|
+
request(payload, params)
|
|
34
|
+
end
|
|
33
35
|
|
|
34
36
|
json_string = response&.dig('candidates', 0, 'content', 'parts', 0, 'text')
|
|
35
37
|
return nil if json_string.nil? || json_string.empty?
|
|
36
38
|
|
|
37
|
-
|
|
39
|
+
ActiveGenie.logger.call(
|
|
40
|
+
{ code: :function_calling, fine_tune: true, payload:, parsed_response: json_string },
|
|
41
|
+
config: @config
|
|
42
|
+
)
|
|
38
43
|
|
|
39
44
|
normalize_response(json_string)
|
|
40
45
|
end
|
|
@@ -50,7 +55,7 @@ module ActiveGenie
|
|
|
50
55
|
def request(payload, params)
|
|
51
56
|
response = post(url, payload, headers: DEFAULT_HEADERS, params:)
|
|
52
57
|
|
|
53
|
-
|
|
58
|
+
ActiveGenie.logger.call(
|
|
54
59
|
{
|
|
55
60
|
code: :llm_usage,
|
|
56
61
|
input_tokens: response['usageMetadata']['promptTokenCount'] || 0,
|
|
@@ -58,7 +63,8 @@ module ActiveGenie
|
|
|
58
63
|
total_tokens: response['usageMetadata']['totalTokenCount'] || (prompt_tokens + candidates_tokens),
|
|
59
64
|
model:,
|
|
60
65
|
usage: response['usageMetadata'] || {}
|
|
61
|
-
}
|
|
66
|
+
},
|
|
67
|
+
config: @config
|
|
62
68
|
)
|
|
63
69
|
|
|
64
70
|
response
|
|
@@ -108,7 +114,7 @@ module ActiveGenie
|
|
|
108
114
|
end
|
|
109
115
|
|
|
110
116
|
def model
|
|
111
|
-
@config.llm.model
|
|
117
|
+
@config.llm.model
|
|
112
118
|
end
|
|
113
119
|
|
|
114
120
|
def url
|
|
@@ -25,11 +25,13 @@ module ActiveGenie
|
|
|
25
25
|
model:
|
|
26
26
|
}
|
|
27
27
|
|
|
28
|
-
response =
|
|
28
|
+
response = retry_with_backoff do
|
|
29
|
+
request(payload)
|
|
30
|
+
end
|
|
29
31
|
|
|
30
32
|
raise InvalidResponseError, "Invalid response: #{response}" if response.nil? || response.keys.empty?
|
|
31
33
|
|
|
32
|
-
|
|
34
|
+
ActiveGenie.logger.call({ code: :function_calling, fine_tune: true, payload:, response: }, config: @config)
|
|
33
35
|
|
|
34
36
|
response
|
|
35
37
|
end
|
|
@@ -37,11 +39,11 @@ module ActiveGenie
|
|
|
37
39
|
private
|
|
38
40
|
|
|
39
41
|
def request(payload)
|
|
40
|
-
response = post(url, payload, headers:
|
|
42
|
+
response = post(url, payload, headers:)
|
|
41
43
|
|
|
42
44
|
return nil if response.nil?
|
|
43
45
|
|
|
44
|
-
|
|
46
|
+
ActiveGenie.logger.call(
|
|
45
47
|
{
|
|
46
48
|
code: :llm_usage,
|
|
47
49
|
input_tokens: response.dig('usage', 'prompt_tokens'),
|
|
@@ -49,7 +51,7 @@ module ActiveGenie
|
|
|
49
51
|
total_tokens: response.dig('usage', 'total_tokens'),
|
|
50
52
|
model:,
|
|
51
53
|
usage: response['usage']
|
|
52
|
-
}
|
|
54
|
+
}, config: @config
|
|
53
55
|
)
|
|
54
56
|
|
|
55
57
|
parsed_response = JSON.parse(response.dig('choices', 0, 'message', 'tool_calls', 0, 'function', 'arguments'))
|
|
@@ -71,7 +73,7 @@ module ActiveGenie
|
|
|
71
73
|
end
|
|
72
74
|
|
|
73
75
|
def model
|
|
74
|
-
@config.llm.model
|
|
76
|
+
@config.llm.model
|
|
75
77
|
end
|
|
76
78
|
|
|
77
79
|
def url
|
|
@@ -5,6 +5,8 @@ require_relative 'anthropic_provider'
|
|
|
5
5
|
require_relative 'google_provider'
|
|
6
6
|
require_relative 'deepseek_provider'
|
|
7
7
|
require_relative '../errors/invalid_provider_error'
|
|
8
|
+
require_relative '../errors/invalid_model_error'
|
|
9
|
+
require_relative '../errors/without_available_provider_error'
|
|
8
10
|
|
|
9
11
|
module ActiveGenie
|
|
10
12
|
module Providers
|
|
@@ -18,10 +20,13 @@ module ActiveGenie
|
|
|
18
20
|
}.freeze
|
|
19
21
|
|
|
20
22
|
def function_calling(messages, function, config: {})
|
|
21
|
-
provider_name = config
|
|
22
|
-
provider = PROVIDER_NAME_TO_PROVIDER[provider_name.to_sym]
|
|
23
|
+
model, provider_name = model_and_provider_by(config)
|
|
23
24
|
|
|
24
|
-
|
|
25
|
+
provider = PROVIDER_NAME_TO_PROVIDER[provider_name&.to_sym]
|
|
26
|
+
|
|
27
|
+
raise ActiveGenie::WithoutAvailableProviderError if provider.nil?
|
|
28
|
+
|
|
29
|
+
config.llm.model = model
|
|
25
30
|
|
|
26
31
|
response = provider.new(config).function_calling(messages, function)
|
|
27
32
|
|
|
@@ -30,6 +35,56 @@ module ActiveGenie
|
|
|
30
35
|
|
|
31
36
|
private
|
|
32
37
|
|
|
38
|
+
# rubocop:disable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
|
39
|
+
def model_and_provider_by(config)
|
|
40
|
+
model, provider_name = explicit_choice(config)
|
|
41
|
+
model, provider_name = global_default(config) if model.nil? && provider_name.nil?
|
|
42
|
+
model, provider_name = module_recommendation(config) if model.nil? && provider_name.nil?
|
|
43
|
+
|
|
44
|
+
model, provider_name = infer_from_partial(config, model, provider_name) if model.nil? || provider_name.nil?
|
|
45
|
+
model, provider_name = any_available(config) if model.nil? || provider_name.nil?
|
|
46
|
+
|
|
47
|
+
[model, provider_name]
|
|
48
|
+
end
|
|
49
|
+
# rubocop:enable Metrics/AbcSize, Metrics/PerceivedComplexity, Metrics/CyclomaticComplexity
|
|
50
|
+
|
|
51
|
+
def explicit_choice(config)
|
|
52
|
+
model = config.llm.model
|
|
53
|
+
provider_name = config.providers.default
|
|
54
|
+
|
|
55
|
+
[model, provider_name]
|
|
56
|
+
end
|
|
57
|
+
|
|
58
|
+
def global_default(config)
|
|
59
|
+
provider_name = config.providers.default
|
|
60
|
+
|
|
61
|
+
[nil, provider_name]
|
|
62
|
+
end
|
|
63
|
+
|
|
64
|
+
def module_recommendation(config)
|
|
65
|
+
model = config.llm.recommended_model
|
|
66
|
+
provider_name = config.providers.provider_name_by_model(model) if model
|
|
67
|
+
|
|
68
|
+
return nil if model.nil? || provider_name.nil?
|
|
69
|
+
|
|
70
|
+
[model, provider_name]
|
|
71
|
+
end
|
|
72
|
+
|
|
73
|
+
def infer_from_partial(config, model, provider_name)
|
|
74
|
+
provider_name ||= config.providers.provider_name_by_model(model) if model
|
|
75
|
+
model ||= config.providers.valid[provider_name.to_sym]&.default_model if provider_name
|
|
76
|
+
|
|
77
|
+
[model, provider_name]
|
|
78
|
+
end
|
|
79
|
+
|
|
80
|
+
def any_available(config)
|
|
81
|
+
provider = config.providers.valid.first
|
|
82
|
+
provider_name = provider&.first
|
|
83
|
+
model = provider&.last&.default_model
|
|
84
|
+
|
|
85
|
+
[model, provider_name]
|
|
86
|
+
end
|
|
87
|
+
|
|
33
88
|
def normalize_response(response)
|
|
34
89
|
response.each do |key, value|
|
|
35
90
|
response[key] = nil if ['null', 'none', 'undefined', '', 'unknown',
|