jevrb 0.1.0
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 +7 -0
- data/LICENSE +21 -0
- data/README.md +175 -0
- data/lib/jevrb/answer.rb +214 -0
- data/lib/jevrb/choice.rb +41 -0
- data/lib/jevrb/client.rb +185 -0
- data/lib/jevrb/constants.rb +11 -0
- data/lib/jevrb/errors.rb +76 -0
- data/lib/jevrb/noul.rb +36 -0
- data/lib/jevrb/question.rb +22 -0
- data/lib/jevrb/result.rb +59 -0
- data/lib/jevrb/retry_policy.rb +130 -0
- data/lib/jevrb/score.rb +29 -0
- data/lib/jevrb/support.rb +57 -0
- data/lib/jevrb/transport.rb +45 -0
- data/lib/jevrb/usage.rb +27 -0
- data/lib/jevrb/version.rb +5 -0
- data/lib/jevrb.rb +22 -0
- data/sig/jevrb.rbs +203 -0
- metadata +61 -0
data/lib/jevrb/errors.rb
ADDED
|
@@ -0,0 +1,76 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jevrb
|
|
4
|
+
class Error < StandardError; end
|
|
5
|
+
|
|
6
|
+
class ConfigurationError < Error; end
|
|
7
|
+
|
|
8
|
+
class APIError < Error
|
|
9
|
+
attr_reader :status, :body, :headers, :endpoint
|
|
10
|
+
|
|
11
|
+
def initialize(status:, body:, headers:, endpoint:, message: nil)
|
|
12
|
+
@status = status
|
|
13
|
+
@body = body
|
|
14
|
+
@headers = headers.freeze
|
|
15
|
+
@endpoint = endpoint
|
|
16
|
+
|
|
17
|
+
super(message || "TypeSafe API request failed with status #{status}")
|
|
18
|
+
end
|
|
19
|
+
|
|
20
|
+
def request_id
|
|
21
|
+
headers["x-typesafe-request-id"]
|
|
22
|
+
end
|
|
23
|
+
end
|
|
24
|
+
|
|
25
|
+
class BadRequestError < APIError; end
|
|
26
|
+
class AuthenticationError < APIError; end
|
|
27
|
+
class PermissionDeniedError < APIError; end
|
|
28
|
+
class NotFoundError < APIError; end
|
|
29
|
+
class UnprocessableEntityError < APIError; end
|
|
30
|
+
|
|
31
|
+
class RateLimitError < APIError
|
|
32
|
+
def retry_after_ms
|
|
33
|
+
value = headers["retry-after-ms"]
|
|
34
|
+
return if value.nil?
|
|
35
|
+
|
|
36
|
+
Float(value)
|
|
37
|
+
rescue ArgumentError, TypeError
|
|
38
|
+
nil
|
|
39
|
+
end
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
class InternalServerError < APIError; end
|
|
43
|
+
|
|
44
|
+
class ConnectionError < Error
|
|
45
|
+
attr_reader :endpoint
|
|
46
|
+
|
|
47
|
+
def initialize(endpoint:, message: nil)
|
|
48
|
+
@endpoint = endpoint
|
|
49
|
+
super(message || "TypeSafe API connection failed")
|
|
50
|
+
end
|
|
51
|
+
end
|
|
52
|
+
|
|
53
|
+
class TimeoutError < ConnectionError
|
|
54
|
+
attr_reader :timeout
|
|
55
|
+
|
|
56
|
+
def initialize(endpoint:, timeout:, message: nil)
|
|
57
|
+
@timeout = timeout
|
|
58
|
+
super(endpoint: endpoint, message: message || "TypeSafe API request exceeded #{timeout} seconds")
|
|
59
|
+
end
|
|
60
|
+
end
|
|
61
|
+
|
|
62
|
+
class ResponseValidationError < APIError
|
|
63
|
+
attr_reader :field_path
|
|
64
|
+
|
|
65
|
+
def initialize(status:, body:, headers:, field_path:, endpoint:, message: nil)
|
|
66
|
+
@field_path = field_path
|
|
67
|
+
super(
|
|
68
|
+
status: status,
|
|
69
|
+
body: body,
|
|
70
|
+
headers: headers,
|
|
71
|
+
endpoint: endpoint,
|
|
72
|
+
message: message || "TypeSafe API response is invalid at #{field_path}"
|
|
73
|
+
)
|
|
74
|
+
end
|
|
75
|
+
end
|
|
76
|
+
end
|
data/lib/jevrb/noul.rb
ADDED
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jevrb
|
|
4
|
+
class Noul < Question
|
|
5
|
+
CRITERIA_KEYS = %w[true false].freeze
|
|
6
|
+
|
|
7
|
+
def self.build(instructions:, criteria: nil)
|
|
8
|
+
new(instructions: instructions, criteria: criteria)
|
|
9
|
+
end
|
|
10
|
+
|
|
11
|
+
private
|
|
12
|
+
|
|
13
|
+
def initialize(instructions:, criteria:)
|
|
14
|
+
super(type: "noul", instructions: instructions, criteria: normalize_criteria(criteria))
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def normalize_criteria(criteria)
|
|
18
|
+
return if criteria.nil?
|
|
19
|
+
raise ArgumentError, "criteria must be a hash" unless criteria.is_a?(Hash)
|
|
20
|
+
|
|
21
|
+
normalized = criteria.each_with_object({}) do |(key, value), result|
|
|
22
|
+
normalized_key = key.to_s
|
|
23
|
+
raise ArgumentError, "criteria accepts only true and false keys" unless CRITERIA_KEYS.include?(normalized_key)
|
|
24
|
+
raise ArgumentError, "criteria contains duplicate key #{normalized_key.inspect}" if result.key?(normalized_key)
|
|
25
|
+
|
|
26
|
+
result[normalized_key.freeze] = Support.normalize_json_content(
|
|
27
|
+
value,
|
|
28
|
+
path: "criteria.#{normalized_key}",
|
|
29
|
+
allow_nil: true
|
|
30
|
+
)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
normalized.freeze
|
|
34
|
+
end
|
|
35
|
+
end
|
|
36
|
+
end
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jevrb
|
|
4
|
+
class Question
|
|
5
|
+
attr_reader :type, :instructions, :criteria
|
|
6
|
+
|
|
7
|
+
def to_h
|
|
8
|
+
hash = { type: type, instructions: instructions }
|
|
9
|
+
hash[:criteria] = criteria unless criteria.nil?
|
|
10
|
+
hash
|
|
11
|
+
end
|
|
12
|
+
|
|
13
|
+
private
|
|
14
|
+
|
|
15
|
+
def initialize(type:, instructions:, criteria:)
|
|
16
|
+
@type = type.freeze
|
|
17
|
+
@instructions = Support.normalize_json_content(instructions, path: "instructions")
|
|
18
|
+
@criteria = criteria
|
|
19
|
+
freeze
|
|
20
|
+
end
|
|
21
|
+
end
|
|
22
|
+
end
|
data/lib/jevrb/result.rb
ADDED
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jevrb
|
|
4
|
+
class Result
|
|
5
|
+
ANSWER_CLASSES = {
|
|
6
|
+
"noul" => NoulAnswer,
|
|
7
|
+
"choice" => ChoiceAnswer,
|
|
8
|
+
"score" => ScoreAnswer
|
|
9
|
+
}.freeze
|
|
10
|
+
|
|
11
|
+
attr_reader :model, :answers, :usage, :request_id, :nouls, :choices, :scores
|
|
12
|
+
|
|
13
|
+
def self.from_hash(value, key_map: {}, request_id: nil)
|
|
14
|
+
value = ResponseSupport.hash(value, "response")
|
|
15
|
+
model = ResponseSupport.string(ResponseSupport.required(value, "model", "response"), "model")
|
|
16
|
+
usage = Usage.from_hash(ResponseSupport.required(value, "usage", "response"))
|
|
17
|
+
answers = parse_answers(ResponseSupport.required(value, "answers", "response"), key_map)
|
|
18
|
+
|
|
19
|
+
new(model: model, answers: answers, usage: usage, request_id: request_id)
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
def self.parse_answers(value, key_map)
|
|
23
|
+
ResponseSupport.hash(value, "answers").each_with_object({}) do |(key, answer), result|
|
|
24
|
+
key = ResponseSupport.string(key, "answers key")
|
|
25
|
+
answer = ResponseSupport.hash(answer, "answers.#{key}")
|
|
26
|
+
type = ResponseSupport.string(
|
|
27
|
+
ResponseSupport.required(answer, "type", "answers.#{key}"),
|
|
28
|
+
"answers.#{key}.type"
|
|
29
|
+
)
|
|
30
|
+
answer_class = ANSWER_CLASSES[type]
|
|
31
|
+
raise ResponseDataError.new("answers.#{key}.type", "unknown answer type #{type.inspect}") unless answer_class
|
|
32
|
+
|
|
33
|
+
result[key_map.fetch(key, key)] = answer_class.from_hash(answer, path: "answers.#{key}")
|
|
34
|
+
end.freeze
|
|
35
|
+
end
|
|
36
|
+
private_class_method :parse_answers
|
|
37
|
+
|
|
38
|
+
def to_h
|
|
39
|
+
{
|
|
40
|
+
model: model,
|
|
41
|
+
answers: answers.transform_values(&:to_h),
|
|
42
|
+
usage: usage.to_h
|
|
43
|
+
}
|
|
44
|
+
end
|
|
45
|
+
|
|
46
|
+
private
|
|
47
|
+
|
|
48
|
+
def initialize(model:, answers:, usage:, request_id:)
|
|
49
|
+
@model = model
|
|
50
|
+
@answers = answers
|
|
51
|
+
@usage = usage
|
|
52
|
+
@request_id = request_id&.dup&.freeze
|
|
53
|
+
@nouls = answers.select { |_key, answer| answer.is_a?(NoulAnswer) }.freeze
|
|
54
|
+
@choices = answers.select { |_key, answer| answer.is_a?(ChoiceAnswer) }.freeze
|
|
55
|
+
@scores = answers.select { |_key, answer| answer.is_a?(ScoreAnswer) }.freeze
|
|
56
|
+
freeze
|
|
57
|
+
end
|
|
58
|
+
end
|
|
59
|
+
end
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "time"
|
|
4
|
+
|
|
5
|
+
module Jevrb
|
|
6
|
+
class RetryPolicy
|
|
7
|
+
DEFAULT_HTTP_STATUSES = [408, 429, *(500..599)].freeze
|
|
8
|
+
|
|
9
|
+
attr_reader :max_retries, :backoff_initial, :backoff_max, :backoff_jitter,
|
|
10
|
+
:http_statuses, :respect_retry_after, :retry_connection_errors,
|
|
11
|
+
:retry_timeout_errors, :timeout
|
|
12
|
+
|
|
13
|
+
def initialize(
|
|
14
|
+
max_retries: 2,
|
|
15
|
+
backoff_initial: 0.5,
|
|
16
|
+
backoff_max: 5.0,
|
|
17
|
+
backoff_jitter: 0.25,
|
|
18
|
+
http_statuses: DEFAULT_HTTP_STATUSES,
|
|
19
|
+
respect_retry_after: true,
|
|
20
|
+
retry_connection_errors: true,
|
|
21
|
+
retry_timeout_errors: true,
|
|
22
|
+
timeout: 30.0,
|
|
23
|
+
sleeper: nil,
|
|
24
|
+
random: nil,
|
|
25
|
+
clock: nil
|
|
26
|
+
)
|
|
27
|
+
@max_retries = nonnegative_integer(max_retries, "max_retries")
|
|
28
|
+
@backoff_initial = nonnegative_number(backoff_initial, "backoff_initial")
|
|
29
|
+
@backoff_max = nonnegative_number(backoff_max, "backoff_max")
|
|
30
|
+
@backoff_jitter = fraction(backoff_jitter, "backoff_jitter")
|
|
31
|
+
@http_statuses = http_statuses.to_a.uniq.freeze
|
|
32
|
+
@respect_retry_after = boolean(respect_retry_after, "respect_retry_after")
|
|
33
|
+
@retry_connection_errors = boolean(retry_connection_errors, "retry_connection_errors")
|
|
34
|
+
@retry_timeout_errors = boolean(retry_timeout_errors, "retry_timeout_errors")
|
|
35
|
+
@timeout = timeout.nil? ? nil : nonnegative_number(timeout, "timeout")
|
|
36
|
+
@sleeper = sleeper || ->(seconds) { sleep(seconds) }
|
|
37
|
+
@random = random || Random.new
|
|
38
|
+
@clock = clock || -> { Process.clock_gettime(Process::CLOCK_MONOTONIC) }
|
|
39
|
+
freeze
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def execute
|
|
43
|
+
retries = 0
|
|
44
|
+
started_at = @clock.call
|
|
45
|
+
|
|
46
|
+
begin
|
|
47
|
+
yield
|
|
48
|
+
rescue Error => e
|
|
49
|
+
raise unless retries < max_retries && retryable?(e)
|
|
50
|
+
|
|
51
|
+
delay = retry_delay(e, retries)
|
|
52
|
+
elapsed = @clock.call - started_at
|
|
53
|
+
raise if timeout && elapsed + delay >= timeout
|
|
54
|
+
|
|
55
|
+
@sleeper.call(delay)
|
|
56
|
+
retries += 1
|
|
57
|
+
retry
|
|
58
|
+
end
|
|
59
|
+
end
|
|
60
|
+
|
|
61
|
+
def retryable?(error)
|
|
62
|
+
return retry_timeout_errors if error.is_a?(TimeoutError)
|
|
63
|
+
return retry_connection_errors if error.is_a?(ConnectionError)
|
|
64
|
+
return http_statuses.include?(error.status) if error.is_a?(APIError)
|
|
65
|
+
|
|
66
|
+
false
|
|
67
|
+
end
|
|
68
|
+
|
|
69
|
+
def retry_delay(error, retry_number)
|
|
70
|
+
header_delay = retry_after_delay(error)
|
|
71
|
+
return header_delay unless header_delay.nil?
|
|
72
|
+
|
|
73
|
+
base = [backoff_initial * (2**retry_number), backoff_max].min
|
|
74
|
+
base * (1.0 - (@random.rand * backoff_jitter))
|
|
75
|
+
end
|
|
76
|
+
|
|
77
|
+
private
|
|
78
|
+
|
|
79
|
+
def retry_after_delay(error)
|
|
80
|
+
return unless respect_retry_after && error.is_a?(APIError)
|
|
81
|
+
|
|
82
|
+
milliseconds = parse_nonnegative_number(error.headers["retry-after-ms"])
|
|
83
|
+
return milliseconds / 1000.0 unless milliseconds.nil?
|
|
84
|
+
|
|
85
|
+
value = error.headers["retry-after"]
|
|
86
|
+
return if value.nil?
|
|
87
|
+
|
|
88
|
+
seconds = parse_nonnegative_number(value)
|
|
89
|
+
return seconds unless seconds.nil?
|
|
90
|
+
|
|
91
|
+
[Time.httpdate(value) - Time.now, 0.0].max
|
|
92
|
+
rescue ArgumentError
|
|
93
|
+
nil
|
|
94
|
+
end
|
|
95
|
+
|
|
96
|
+
def parse_nonnegative_number(value)
|
|
97
|
+
return if value.nil?
|
|
98
|
+
|
|
99
|
+
number = Float(value)
|
|
100
|
+
number if number.finite? && number >= 0
|
|
101
|
+
rescue ArgumentError, TypeError
|
|
102
|
+
nil
|
|
103
|
+
end
|
|
104
|
+
|
|
105
|
+
def nonnegative_integer(value, name)
|
|
106
|
+
return value if value.is_a?(Integer) && value >= 0
|
|
107
|
+
|
|
108
|
+
raise ArgumentError, "#{name} must be a nonnegative integer"
|
|
109
|
+
end
|
|
110
|
+
|
|
111
|
+
def nonnegative_number(value, name)
|
|
112
|
+
return value.to_f if (value.is_a?(Integer) || value.is_a?(Float)) && value.finite? && value >= 0
|
|
113
|
+
|
|
114
|
+
raise ArgumentError, "#{name} must be a nonnegative number"
|
|
115
|
+
end
|
|
116
|
+
|
|
117
|
+
def fraction(value, name)
|
|
118
|
+
number = nonnegative_number(value, name)
|
|
119
|
+
return number if number <= 1.0
|
|
120
|
+
|
|
121
|
+
raise ArgumentError, "#{name} must be between 0 and 1"
|
|
122
|
+
end
|
|
123
|
+
|
|
124
|
+
def boolean(value, name)
|
|
125
|
+
return value if [true, false].include?(value)
|
|
126
|
+
|
|
127
|
+
raise ArgumentError, "#{name} must be true or false"
|
|
128
|
+
end
|
|
129
|
+
end
|
|
130
|
+
end
|
data/lib/jevrb/score.rb
ADDED
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jevrb
|
|
4
|
+
class Score < Question
|
|
5
|
+
MIN_LEVELS = 2
|
|
6
|
+
MAX_LEVELS = 10
|
|
7
|
+
|
|
8
|
+
def self.build(instructions:, criteria:)
|
|
9
|
+
new(instructions: instructions, criteria: criteria)
|
|
10
|
+
end
|
|
11
|
+
|
|
12
|
+
private
|
|
13
|
+
|
|
14
|
+
def initialize(instructions:, criteria:)
|
|
15
|
+
super(type: "score", instructions: instructions, criteria: normalize_criteria(criteria))
|
|
16
|
+
end
|
|
17
|
+
|
|
18
|
+
def normalize_criteria(criteria)
|
|
19
|
+
raise ArgumentError, "criteria must be an array" unless criteria.is_a?(Array)
|
|
20
|
+
unless (MIN_LEVELS..MAX_LEVELS).cover?(criteria.length)
|
|
21
|
+
raise ArgumentError, "criteria must contain #{MIN_LEVELS} through #{MAX_LEVELS} levels"
|
|
22
|
+
end
|
|
23
|
+
|
|
24
|
+
criteria.each_with_index.map do |value, index|
|
|
25
|
+
Support.normalize_json_content(value, path: "criteria[#{index}]")
|
|
26
|
+
end.freeze
|
|
27
|
+
end
|
|
28
|
+
end
|
|
29
|
+
end
|
|
@@ -0,0 +1,57 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jevrb
|
|
4
|
+
module Support
|
|
5
|
+
module_function
|
|
6
|
+
|
|
7
|
+
def normalize_json_content(value, path:, allow_nil: false)
|
|
8
|
+
return nil if value.nil? && allow_nil
|
|
9
|
+
|
|
10
|
+
unless value.is_a?(String) || value.is_a?(Hash) || value.is_a?(Array)
|
|
11
|
+
raise ArgumentError, "#{path} must be a string, hash, or array"
|
|
12
|
+
end
|
|
13
|
+
|
|
14
|
+
normalize_json_value(value, path: path)
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def normalize_json_value(value, path:)
|
|
18
|
+
case value
|
|
19
|
+
when String
|
|
20
|
+
value.dup.freeze
|
|
21
|
+
when Integer, TrueClass, FalseClass, NilClass
|
|
22
|
+
value
|
|
23
|
+
when Float
|
|
24
|
+
raise ArgumentError, "#{path} must contain only finite numbers" unless value.finite?
|
|
25
|
+
|
|
26
|
+
value
|
|
27
|
+
when Array
|
|
28
|
+
value.each_with_index.map do |entry, index|
|
|
29
|
+
normalize_json_value(entry, path: "#{path}[#{index}]")
|
|
30
|
+
end.freeze
|
|
31
|
+
when Hash
|
|
32
|
+
normalize_json_hash(value, path: path)
|
|
33
|
+
else
|
|
34
|
+
raise ArgumentError, "#{path} contains a non-JSON value: #{value.class}"
|
|
35
|
+
end
|
|
36
|
+
end
|
|
37
|
+
|
|
38
|
+
def normalize_json_hash(value, path:)
|
|
39
|
+
value.each_with_object({}) do |(key, entry), result|
|
|
40
|
+
unless key.is_a?(String) || key.is_a?(Symbol)
|
|
41
|
+
raise ArgumentError, "#{path} contains a non-string key: #{key.inspect}"
|
|
42
|
+
end
|
|
43
|
+
|
|
44
|
+
normalized_key = key.to_s
|
|
45
|
+
raise ArgumentError, "#{path} contains duplicate key #{normalized_key.inspect}" if result.key?(normalized_key)
|
|
46
|
+
|
|
47
|
+
result[normalized_key.freeze] = normalize_json_value(entry, path: "#{path}.#{normalized_key}")
|
|
48
|
+
end.freeze
|
|
49
|
+
end
|
|
50
|
+
|
|
51
|
+
def required_hash_value(hash, key, path:)
|
|
52
|
+
return hash[key] if hash.key?(key)
|
|
53
|
+
|
|
54
|
+
raise KeyError, "missing required field #{path}.#{key}"
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require "net/http"
|
|
4
|
+
require "openssl"
|
|
5
|
+
require "uri"
|
|
6
|
+
|
|
7
|
+
module Jevrb
|
|
8
|
+
class Transport
|
|
9
|
+
Response = Struct.new(:status, :body, :headers, keyword_init: true)
|
|
10
|
+
private_constant :Response
|
|
11
|
+
|
|
12
|
+
def initialize(base_url:, http_factory: Net::HTTP.method(:new))
|
|
13
|
+
@base_url = base_url.chomp("/")
|
|
14
|
+
@http_factory = http_factory
|
|
15
|
+
end
|
|
16
|
+
|
|
17
|
+
def endpoint(path)
|
|
18
|
+
"#{@base_url}/#{path.sub(%r{\A/+}, "")}"
|
|
19
|
+
end
|
|
20
|
+
|
|
21
|
+
def post(path, body:, headers:, timeout:)
|
|
22
|
+
url = endpoint(path)
|
|
23
|
+
uri = URI.parse(url)
|
|
24
|
+
http = @http_factory.call(uri.host, uri.port)
|
|
25
|
+
http.use_ssl = uri.scheme == "https"
|
|
26
|
+
http.open_timeout = timeout
|
|
27
|
+
http.read_timeout = timeout
|
|
28
|
+
http.write_timeout = timeout if http.respond_to?(:write_timeout=)
|
|
29
|
+
|
|
30
|
+
request = Net::HTTP::Post.new(uri.request_uri, headers)
|
|
31
|
+
request.body = body
|
|
32
|
+
response = http.request(request)
|
|
33
|
+
|
|
34
|
+
Response.new(
|
|
35
|
+
status: response.code.to_i,
|
|
36
|
+
body: response.body,
|
|
37
|
+
headers: response.each_header.to_h.freeze
|
|
38
|
+
).freeze
|
|
39
|
+
rescue Timeout::Error => e
|
|
40
|
+
raise TimeoutError.new(endpoint: "POST #{url}", timeout: timeout, message: e.message)
|
|
41
|
+
rescue IOError, SocketError, SystemCallError, OpenSSL::SSL::SSLError, Net::ProtocolError => e
|
|
42
|
+
raise ConnectionError.new(endpoint: "POST #{url}", message: e.message)
|
|
43
|
+
end
|
|
44
|
+
end
|
|
45
|
+
end
|
data/lib/jevrb/usage.rb
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Jevrb
|
|
4
|
+
class Usage
|
|
5
|
+
attr_reader :input_tokens, :output_tokens
|
|
6
|
+
|
|
7
|
+
def self.from_hash(value, path: "usage")
|
|
8
|
+
value = ResponseSupport.hash(value, path)
|
|
9
|
+
new(
|
|
10
|
+
input_tokens: ResponseSupport.integer_or_nil(value["input_tokens"], "#{path}.input_tokens"),
|
|
11
|
+
output_tokens: ResponseSupport.integer_or_nil(value["output_tokens"], "#{path}.output_tokens")
|
|
12
|
+
)
|
|
13
|
+
end
|
|
14
|
+
|
|
15
|
+
def to_h
|
|
16
|
+
{ input_tokens: input_tokens, output_tokens: output_tokens }
|
|
17
|
+
end
|
|
18
|
+
|
|
19
|
+
private
|
|
20
|
+
|
|
21
|
+
def initialize(input_tokens:, output_tokens:)
|
|
22
|
+
@input_tokens = input_tokens
|
|
23
|
+
@output_tokens = output_tokens
|
|
24
|
+
freeze
|
|
25
|
+
end
|
|
26
|
+
end
|
|
27
|
+
end
|
data/lib/jevrb.rb
ADDED
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
require_relative "jevrb/version"
|
|
4
|
+
require_relative "jevrb/constants"
|
|
5
|
+
require_relative "jevrb/errors"
|
|
6
|
+
require_relative "jevrb/support"
|
|
7
|
+
require_relative "jevrb/question"
|
|
8
|
+
require_relative "jevrb/noul"
|
|
9
|
+
require_relative "jevrb/choice"
|
|
10
|
+
require_relative "jevrb/score"
|
|
11
|
+
require_relative "jevrb/answer"
|
|
12
|
+
require_relative "jevrb/usage"
|
|
13
|
+
require_relative "jevrb/result"
|
|
14
|
+
require_relative "jevrb/retry_policy"
|
|
15
|
+
require_relative "jevrb/transport"
|
|
16
|
+
require_relative "jevrb/client"
|
|
17
|
+
|
|
18
|
+
module Jevrb
|
|
19
|
+
private_constant :Support, :Transport
|
|
20
|
+
end
|
|
21
|
+
|
|
22
|
+
Jev = Jevrb
|