openai 0.77.1 → 0.79.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 +4 -4
- data/CHANGELOG.md +98 -0
- data/README.md +341 -10
- data/SECURITY.md +6 -22
- data/azure.md +110 -0
- data/bedrock.md +132 -0
- data/examples/mtls_custom_http_client.rb +61 -0
- data/lib/openai/auth/workload_identity.rb +13 -2
- data/lib/openai/auth/workload_identity_auth.rb +2 -1
- data/lib/openai/client.rb +34 -14
- data/lib/openai/errors.rb +11 -1
- data/lib/openai/helpers/streaming/chat_completion_stream.rb +3 -3
- data/lib/openai/helpers/streaming/response_stream.rb +5 -6
- data/lib/openai/helpers/structured_output/base_model.rb +20 -19
- data/lib/openai/helpers/structured_output/json_schema_converter.rb +12 -5
- data/lib/openai/http_client.rb +189 -0
- data/lib/openai/internal/conversation_cursor_page.rb +5 -6
- data/lib/openai/internal/cursor_page.rb +3 -3
- data/lib/openai/internal/logging.rb +419 -0
- data/lib/openai/internal/next_cursor_page.rb +5 -6
- data/lib/openai/internal/page.rb +2 -2
- data/lib/openai/internal/provider.rb +7 -1
- data/lib/openai/internal/read_io_adapter.rb +161 -0
- data/lib/openai/internal/stream.rb +2 -2
- data/lib/openai/internal/transport/base_client.rb +216 -57
- data/lib/openai/internal/type/array_of.rb +6 -4
- data/lib/openai/internal/type/base_model.rb +39 -1
- data/lib/openai/internal/type/base_page.rb +19 -2
- data/lib/openai/internal/type/base_stream.rb +40 -8
- data/lib/openai/internal/type/hash_of.rb +3 -3
- data/lib/openai/internal/util.rb +59 -112
- data/lib/openai/models/admin/organization/audit_log_list_params.rb +3 -1
- data/lib/openai/models/admin/organization/audit_log_list_response.rb +33 -11
- data/lib/openai/models/admin/organization/groups/role_list_response.rb +5 -1
- data/lib/openai/models/admin/organization/groups/role_retrieve_response.rb +5 -1
- data/lib/openai/models/admin/organization/projects/groups/role_list_response.rb +6 -1
- data/lib/openai/models/admin/organization/projects/groups/role_retrieve_response.rb +6 -1
- data/lib/openai/models/admin/organization/projects/service_account_create_response.rb +3 -1
- data/lib/openai/models/admin/organization/projects/users/role_list_response.rb +6 -1
- data/lib/openai/models/admin/organization/projects/users/role_retrieve_response.rb +6 -1
- data/lib/openai/models/admin/organization/users/role_list_response.rb +5 -1
- data/lib/openai/models/admin/organization/users/role_retrieve_response.rb +5 -1
- data/lib/openai/models/all_models.rb +3 -0
- data/lib/openai/models/audio/transcription_create_params.rb +7 -1
- data/lib/openai/models/audio/translation_create_params.rb +7 -1
- data/lib/openai/models/beta/beta_response.rb +9 -0
- data/lib/openai/models/beta/beta_response_stream_event.rb +1 -1
- data/lib/openai/models/beta/beta_responses_client_event.rb +28 -3
- data/lib/openai/models/beta/beta_responses_server_event.rb +1037 -61
- data/lib/openai/models/beta/response_compact_params.rb +9 -0
- data/lib/openai/models/beta/response_create_params.rb +9 -0
- data/lib/openai/models/containers/file_create_params.rb +4 -0
- data/lib/openai/models/content_provenance_check_create_params.rb +4 -0
- data/lib/openai/models/file_create_params.rb +4 -0
- data/lib/openai/models/image_create_variation_params.rb +4 -0
- data/lib/openai/models/image_edit_params.rb +8 -0
- data/lib/openai/models/responses/response_compact_params.rb +9 -0
- data/lib/openai/models/responses/response_stream_event.rb +1 -1
- data/lib/openai/models/responses/responses_client_event.rb +13 -1
- data/lib/openai/models/responses/responses_server_event.rb +1027 -54
- data/lib/openai/models/responses_model.rb +3 -0
- data/lib/openai/models/skill_create_params.rb +4 -0
- data/lib/openai/models/skills/version_create_params.rb +4 -0
- data/lib/openai/models/uploads/part_create_params.rb +4 -0
- data/lib/openai/models/vector_store_search_response.rb +3 -1
- data/lib/openai/models/video_create_character_params.rb +4 -0
- data/lib/openai/models/video_create_params.rb +4 -0
- data/lib/openai/models/video_edit_params.rb +4 -0
- data/lib/openai/models/video_extend_params.rb +4 -0
- data/lib/openai/models.rb +9 -1
- data/lib/openai/net_http_client.rb +295 -0
- data/lib/openai/providers/azure.rb +216 -0
- data/lib/openai/providers/bedrock.rb +3 -1
- data/lib/openai/resources/admin/organization/projects/groups/roles.rb +1 -1
- data/lib/openai/resources/admin/organization/projects/groups.rb +1 -1
- data/lib/openai/resources/admin/organization/projects/users/roles.rb +1 -1
- data/lib/openai/resources/admin/organization/projects.rb +4 -2
- data/lib/openai/resources/audio/transcriptions.rb +8 -0
- data/lib/openai/resources/audio/translations.rb +4 -0
- data/lib/openai/resources/beta/responses.rb +1 -1
- data/lib/openai/resources/beta/threads/runs/steps.rb +2 -2
- data/lib/openai/resources/containers/files.rb +4 -0
- data/lib/openai/resources/content_provenance_checks.rb +4 -0
- data/lib/openai/resources/conversations/items.rb +1 -1
- data/lib/openai/resources/evals/runs/output_items.rb +1 -1
- data/lib/openai/resources/files.rb +4 -0
- data/lib/openai/resources/images.rb +12 -0
- data/lib/openai/resources/responses.rb +3 -2
- data/lib/openai/resources/skills/versions.rb +4 -0
- data/lib/openai/resources/skills.rb +4 -0
- data/lib/openai/resources/uploads/parts.rb +4 -0
- data/lib/openai/resources/vector_stores/file_batches.rb +1 -1
- data/lib/openai/resources/videos.rb +38 -0
- data/lib/openai/resources/webhooks.rb +18 -6
- data/lib/openai/version.rb +1 -1
- data/lib/openai.rb +113 -108
- data/rbi/openai/auth.rbi +4 -4
- data/rbi/openai/client.rbi +15 -3
- data/rbi/openai/errors.rbi +7 -0
- data/rbi/openai/helpers/streaming/events.rbi +12 -0
- data/rbi/openai/http_client.rbi +123 -0
- data/rbi/openai/internal/logging.rbi +192 -0
- data/rbi/openai/internal/provider.rbi +3 -0
- data/rbi/openai/internal/read_io_adapter.rbi +52 -0
- data/rbi/openai/internal/transport/base_client.rbi +80 -10
- data/rbi/openai/internal/type/base_model.rbi +24 -1
- data/rbi/openai/internal/type/base_page.rbi +13 -2
- data/rbi/openai/internal/type/base_stream.rbi +16 -5
- data/rbi/openai/internal/util.rbi +26 -42
- data/rbi/openai/models/admin/organization/audit_log_list_params.rbi +5 -0
- data/rbi/openai/models/admin/organization/audit_log_list_response.rbi +5 -0
- data/rbi/openai/models/all_models.rbi +15 -0
- data/rbi/openai/models/audio/transcription_create_params.rbi +14 -2
- data/rbi/openai/models/audio/translation_create_params.rbi +14 -2
- data/rbi/openai/models/beta/beta_response.rbi +15 -0
- data/rbi/openai/models/beta/beta_response_stream_event.rbi +1 -1
- data/rbi/openai/models/beta/beta_responses_client_event.rbi +37 -1
- data/rbi/openai/models/beta/beta_responses_server_event.rbi +3791 -54
- data/rbi/openai/models/beta/response_compact_params.rbi +15 -0
- data/rbi/openai/models/beta/response_create_params.rbi +15 -0
- data/rbi/openai/models/containers/file_create_params.rbi +8 -0
- data/rbi/openai/models/content_provenance_check_create_params.rbi +8 -0
- data/rbi/openai/models/file_create_params.rbi +8 -0
- data/rbi/openai/models/image_create_variation_params.rbi +8 -0
- data/rbi/openai/models/image_edit_params.rbi +16 -0
- data/rbi/openai/models/responses/response_compact_params.rbi +15 -0
- data/rbi/openai/models/responses/response_stream_event.rbi +1 -1
- data/rbi/openai/models/responses/responses_client_event.rbi +19 -0
- data/rbi/openai/models/responses/responses_server_event.rbi +3282 -53
- data/rbi/openai/models/responses_model.rbi +15 -0
- data/rbi/openai/models/skill_create_params.rbi +8 -0
- data/rbi/openai/models/skills/version_create_params.rbi +8 -0
- data/rbi/openai/models/uploads/part_create_params.rbi +8 -0
- data/rbi/openai/models/video_create_character_params.rbi +8 -0
- data/rbi/openai/models/video_create_params.rbi +8 -0
- data/rbi/openai/models/video_edit_params.rbi +8 -0
- data/rbi/openai/models/video_extend_params.rbi +8 -0
- data/rbi/openai/net_http_client.rbi +64 -0
- data/rbi/openai/providers.rbi +9 -0
- data/rbi/openai/resources/audio/transcriptions.rbi +14 -2
- data/rbi/openai/resources/audio/translations.rbi +7 -1
- data/rbi/openai/resources/containers/files.rbi +4 -0
- data/rbi/openai/resources/content_provenance_checks.rbi +4 -0
- data/rbi/openai/resources/files.rbi +4 -0
- data/rbi/openai/resources/images.rbi +20 -0
- data/rbi/openai/resources/skills/versions.rbi +4 -0
- data/rbi/openai/resources/skills.rbi +4 -0
- data/rbi/openai/resources/uploads/parts.rbi +4 -0
- data/rbi/openai/resources/videos.rbi +16 -0
- data/sig/openai/client.rbs +7 -2
- data/sig/openai/errors.rbs +5 -0
- data/sig/openai/http_client.rbs +76 -0
- data/sig/openai/internal/logging.rbs +88 -0
- data/sig/openai/internal/provider.rbs +3 -1
- data/sig/openai/internal/read_io_adapter.rbs +23 -0
- data/sig/openai/internal/transport/base_client.rbs +41 -6
- data/sig/openai/internal/type/base_model.rbs +13 -0
- data/sig/openai/internal/type/base_page.rbs +8 -1
- data/sig/openai/internal/type/base_stream.rbs +9 -3
- data/sig/openai/internal/util.rbs +12 -16
- data/sig/openai/models/admin/organization/audit_log_list_params.rbs +2 -0
- data/sig/openai/models/admin/organization/audit_log_list_response.rbs +2 -0
- data/sig/openai/models/all_models.rbs +6 -0
- data/sig/openai/models/beta/beta_response.rbs +6 -0
- data/sig/openai/models/beta/beta_responses_client_event.rbs +13 -0
- data/sig/openai/models/beta/beta_responses_server_event.rbs +1946 -54
- data/sig/openai/models/beta/response_compact_params.rbs +6 -0
- data/sig/openai/models/beta/response_create_params.rbs +6 -0
- data/sig/openai/models/responses/response_compact_params.rbs +6 -0
- data/sig/openai/models/responses/responses_client_event.rbs +7 -0
- data/sig/openai/models/responses/responses_server_event.rbs +1786 -53
- data/sig/openai/models/responses_model.rbs +6 -0
- data/sig/openai/net_http_client.rbs +34 -0
- data/sig/openai/providers.rbs +6 -0
- metadata +33 -4
- data/lib/openai/internal/transport/pooled_net_requester.rb +0 -210
- data/rbi/openai/internal/transport/pooled_net_requester.rbi +0 -82
- data/sig/openai/internal/transport/pooled_net_requester.rbs +0 -48
data/lib/openai/internal/page.rb
CHANGED
|
@@ -54,9 +54,9 @@ module OpenAI
|
|
|
54
54
|
#
|
|
55
55
|
# @param client [OpenAI::Internal::Transport::BaseClient]
|
|
56
56
|
# @param req [Hash{Symbol=>Object}]
|
|
57
|
-
# @param
|
|
57
|
+
# @param response_metadata [OpenAI::ResponseMetadata]
|
|
58
58
|
# @param page_data [Array<Object>]
|
|
59
|
-
def initialize(client:, req:,
|
|
59
|
+
def initialize(client:, req:, response_metadata:, page_data:)
|
|
60
60
|
super
|
|
61
61
|
|
|
62
62
|
case page_data
|
|
@@ -7,7 +7,13 @@ module OpenAI
|
|
|
7
7
|
# Provider factories return opaque OpenAI::Provider instances whose public
|
|
8
8
|
# representation does not expose their definition.
|
|
9
9
|
module Provider
|
|
10
|
-
Runtime = Struct.new(
|
|
10
|
+
Runtime = Struct.new(
|
|
11
|
+
:name,
|
|
12
|
+
:base_url,
|
|
13
|
+
:prepare_request,
|
|
14
|
+
:authentication_headers,
|
|
15
|
+
keyword_init: true
|
|
16
|
+
)
|
|
11
17
|
|
|
12
18
|
class << self
|
|
13
19
|
# @api private
|
|
@@ -0,0 +1,161 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module OpenAI
|
|
4
|
+
module Internal
|
|
5
|
+
module Util
|
|
6
|
+
# @api private
|
|
7
|
+
#
|
|
8
|
+
# An adapter that satisfies the IO interface required by `::IO.copy_stream`
|
|
9
|
+
class ReadIOAdapter
|
|
10
|
+
class InterruptibleEnumerator
|
|
11
|
+
# Cancellation must bypass application rescues so a suspended source cannot
|
|
12
|
+
# swallow it and keep the request teardown blocked.
|
|
13
|
+
class Interrupt < Exception # rubocop:disable Lint/InheritException
|
|
14
|
+
end
|
|
15
|
+
private_constant :Interrupt
|
|
16
|
+
|
|
17
|
+
def initialize(source)
|
|
18
|
+
@started = false
|
|
19
|
+
@fiber = Fiber.new do
|
|
20
|
+
source.each { Fiber.yield(_1) }
|
|
21
|
+
rescue Interrupt
|
|
22
|
+
nil
|
|
23
|
+
end
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
def next
|
|
27
|
+
raise StopIteration unless @fiber&.alive?
|
|
28
|
+
|
|
29
|
+
@started = true
|
|
30
|
+
value = @fiber.resume
|
|
31
|
+
raise StopIteration unless @fiber.alive?
|
|
32
|
+
|
|
33
|
+
value
|
|
34
|
+
end
|
|
35
|
+
|
|
36
|
+
def to_a
|
|
37
|
+
values = []
|
|
38
|
+
loop { values << self.next }
|
|
39
|
+
values
|
|
40
|
+
end
|
|
41
|
+
|
|
42
|
+
def close
|
|
43
|
+
@fiber&.raise(Interrupt) if @started && @fiber&.alive?
|
|
44
|
+
rescue Interrupt
|
|
45
|
+
nil
|
|
46
|
+
ensure
|
|
47
|
+
@fiber = nil
|
|
48
|
+
end
|
|
49
|
+
end
|
|
50
|
+
private_constant :InterruptibleEnumerator
|
|
51
|
+
|
|
52
|
+
# @api private
|
|
53
|
+
#
|
|
54
|
+
# @return [Boolean, nil]
|
|
55
|
+
def close? = @closing
|
|
56
|
+
|
|
57
|
+
# @api private
|
|
58
|
+
def close
|
|
59
|
+
case @stream
|
|
60
|
+
in Enumerator
|
|
61
|
+
OpenAI::Internal::Util.close_fused!(@stream)
|
|
62
|
+
in InterruptibleEnumerator | IO if close?
|
|
63
|
+
@stream.close
|
|
64
|
+
else
|
|
65
|
+
end
|
|
66
|
+
end
|
|
67
|
+
|
|
68
|
+
# @api private
|
|
69
|
+
#
|
|
70
|
+
# @param max_len [Integer, nil]
|
|
71
|
+
#
|
|
72
|
+
# @return [String, nil]
|
|
73
|
+
private def read_enum(max_len)
|
|
74
|
+
case max_len
|
|
75
|
+
in nil
|
|
76
|
+
# `loop` rescues StopIteration, but this method handles it below.
|
|
77
|
+
# rubocop:disable Style/InfiniteLoop
|
|
78
|
+
@buf << @stream.next.b while true
|
|
79
|
+
# rubocop:enable Style/InfiniteLoop
|
|
80
|
+
in Integer
|
|
81
|
+
@buf << @stream.next.b while @buf.bytesize < max_len
|
|
82
|
+
read_buffer(max_len)
|
|
83
|
+
end
|
|
84
|
+
rescue StopIteration
|
|
85
|
+
return @buf.slice!(0..) if max_len.nil?
|
|
86
|
+
|
|
87
|
+
@stream = nil
|
|
88
|
+
return nil if @buf.bytesize.zero?
|
|
89
|
+
|
|
90
|
+
read_buffer(max_len)
|
|
91
|
+
end
|
|
92
|
+
|
|
93
|
+
# @api private
|
|
94
|
+
#
|
|
95
|
+
# @param max_len [Integer]
|
|
96
|
+
#
|
|
97
|
+
# @return [String]
|
|
98
|
+
private def read_buffer(max_len)
|
|
99
|
+
read = @buf.byteslice(0, max_len)
|
|
100
|
+
@buf = @buf.byteslice(max_len..) || String.new.b
|
|
101
|
+
read
|
|
102
|
+
end
|
|
103
|
+
|
|
104
|
+
# @api private
|
|
105
|
+
#
|
|
106
|
+
# @param max_len [Integer, nil]
|
|
107
|
+
# @param out_string [String, nil]
|
|
108
|
+
#
|
|
109
|
+
# @return [String, nil]
|
|
110
|
+
def read(max_len = nil, out_string = nil)
|
|
111
|
+
if max_len.is_a?(Integer) && max_len.negative?
|
|
112
|
+
raise ArgumentError, "negative length #{max_len} given"
|
|
113
|
+
end
|
|
114
|
+
|
|
115
|
+
read =
|
|
116
|
+
case @stream
|
|
117
|
+
in nil
|
|
118
|
+
max_len.nil? || max_len.zero? ? +"" : nil
|
|
119
|
+
in IO | StringIO
|
|
120
|
+
return @stream.read(max_len, out_string).tap(&@blk)
|
|
121
|
+
in Enumerator | InterruptibleEnumerator
|
|
122
|
+
read_enum(max_len)
|
|
123
|
+
end
|
|
124
|
+
|
|
125
|
+
case out_string
|
|
126
|
+
in String
|
|
127
|
+
out_string.replace(read || +"")
|
|
128
|
+
read.nil? ? nil : out_string
|
|
129
|
+
in nil
|
|
130
|
+
read
|
|
131
|
+
end
|
|
132
|
+
.tap(&@blk)
|
|
133
|
+
end
|
|
134
|
+
|
|
135
|
+
# @api private
|
|
136
|
+
#
|
|
137
|
+
# @param src [String, Pathname, StringIO, Enumerable<String>]
|
|
138
|
+
# @param blk [Proc]
|
|
139
|
+
#
|
|
140
|
+
# @yieldparam [String]
|
|
141
|
+
def initialize(src, &blk)
|
|
142
|
+
@stream =
|
|
143
|
+
case src
|
|
144
|
+
in String
|
|
145
|
+
StringIO.new(src)
|
|
146
|
+
in Pathname
|
|
147
|
+
@closing = true
|
|
148
|
+
src.open(binmode: true)
|
|
149
|
+
in Enumerator
|
|
150
|
+
@closing = true
|
|
151
|
+
InterruptibleEnumerator.new(src)
|
|
152
|
+
else
|
|
153
|
+
src
|
|
154
|
+
end
|
|
155
|
+
@buf = String.new.b
|
|
156
|
+
@blk = blk
|
|
157
|
+
end
|
|
158
|
+
end
|
|
159
|
+
end
|
|
160
|
+
end
|
|
161
|
+
end
|
|
@@ -1,5 +1,7 @@
|
|
|
1
1
|
# frozen_string_literal: true
|
|
2
2
|
|
|
3
|
+
require_relative "../logging"
|
|
4
|
+
|
|
3
5
|
module OpenAI
|
|
4
6
|
module Internal
|
|
5
7
|
module Transport
|
|
@@ -79,6 +81,28 @@ module OpenAI
|
|
|
79
81
|
end
|
|
80
82
|
end
|
|
81
83
|
|
|
84
|
+
# @api private
|
|
85
|
+
#
|
|
86
|
+
# @param body [Object]
|
|
87
|
+
#
|
|
88
|
+
# @return [Boolean]
|
|
89
|
+
def request_body_replayable?(body)
|
|
90
|
+
case body
|
|
91
|
+
in nil | String | StringIO | Pathname
|
|
92
|
+
true
|
|
93
|
+
in OpenAI::FilePart
|
|
94
|
+
request_body_replayable?(body.content)
|
|
95
|
+
in Hash
|
|
96
|
+
body.each_value.all? { request_body_replayable?(_1) }
|
|
97
|
+
in Array
|
|
98
|
+
body.all? { request_body_replayable?(_1) }
|
|
99
|
+
in IO | Enumerable
|
|
100
|
+
false
|
|
101
|
+
else
|
|
102
|
+
!body.respond_to?(:read)
|
|
103
|
+
end
|
|
104
|
+
end
|
|
105
|
+
|
|
82
106
|
# @api private
|
|
83
107
|
#
|
|
84
108
|
# @param request [Hash{Symbol=>Object}] .
|
|
@@ -143,8 +167,19 @@ module OpenAI
|
|
|
143
167
|
|
|
144
168
|
# from undici
|
|
145
169
|
if OpenAI::Internal::Util.uri_origin(url) != OpenAI::Internal::Util.uri_origin(location)
|
|
146
|
-
|
|
147
|
-
|
|
170
|
+
headers = request.fetch(:headers).reject do |name, _|
|
|
171
|
+
name == "host" || OpenAI::Internal::Logging.credential_header?(name)
|
|
172
|
+
end
|
|
173
|
+
request = {**request, headers: headers}
|
|
174
|
+
end
|
|
175
|
+
|
|
176
|
+
unless request_body_replayable?(request[:body])
|
|
177
|
+
message = "Cannot follow a body-preserving redirect with a non-replayable request body."
|
|
178
|
+
raise OpenAI::Errors::APIConnectionError.new(
|
|
179
|
+
url: location,
|
|
180
|
+
response: response_headers,
|
|
181
|
+
message: message
|
|
182
|
+
)
|
|
148
183
|
end
|
|
149
184
|
|
|
150
185
|
request
|
|
@@ -168,7 +203,7 @@ module OpenAI
|
|
|
168
203
|
# @return [URI::Generic]
|
|
169
204
|
attr_reader :base_url
|
|
170
205
|
|
|
171
|
-
# @return [Float]
|
|
206
|
+
# @return [Float, nil]
|
|
172
207
|
attr_reader :timeout
|
|
173
208
|
|
|
174
209
|
# @return [Integer]
|
|
@@ -186,19 +221,32 @@ module OpenAI
|
|
|
186
221
|
# @return [String, nil]
|
|
187
222
|
attr_reader :idempotency_header
|
|
188
223
|
|
|
224
|
+
# @return [#debug, #info, #warn, #error, nil]
|
|
225
|
+
attr_reader :logger
|
|
226
|
+
|
|
227
|
+
# @return [Symbol]
|
|
228
|
+
attr_reader :log_level
|
|
229
|
+
|
|
230
|
+
# @return [Proc, nil]
|
|
231
|
+
attr_reader :on_retry
|
|
232
|
+
|
|
189
233
|
# @api private
|
|
190
|
-
# @return [
|
|
234
|
+
# @return [#execute]
|
|
191
235
|
attr_reader :requester
|
|
192
236
|
|
|
193
237
|
# @api private
|
|
194
238
|
#
|
|
195
239
|
# @param base_url [String]
|
|
196
|
-
# @param timeout [Float]
|
|
240
|
+
# @param timeout [Float, nil]
|
|
197
241
|
# @param max_retries [Integer]
|
|
198
242
|
# @param initial_retry_delay [Float]
|
|
199
243
|
# @param max_retry_delay [Float]
|
|
200
244
|
# @param headers [Hash{String=>String, Integer, Array<String, Integer, nil>, nil}]
|
|
201
245
|
# @param idempotency_header [String, nil]
|
|
246
|
+
# @param http_client [#execute, nil]
|
|
247
|
+
# @param logger [#debug, #info, #warn, #error, nil]
|
|
248
|
+
# @param log_level [Symbol, String, nil]
|
|
249
|
+
# @param on_retry [Proc, nil]
|
|
202
250
|
def initialize(
|
|
203
251
|
base_url:,
|
|
204
252
|
timeout: 0.0,
|
|
@@ -206,9 +254,28 @@ module OpenAI
|
|
|
206
254
|
initial_retry_delay: 0.0,
|
|
207
255
|
max_retry_delay: 0.0,
|
|
208
256
|
headers: {},
|
|
209
|
-
idempotency_header: nil
|
|
257
|
+
idempotency_header: nil,
|
|
258
|
+
http_client: nil,
|
|
259
|
+
logger: nil,
|
|
260
|
+
log_level: nil,
|
|
261
|
+
on_retry: nil
|
|
210
262
|
)
|
|
211
|
-
|
|
263
|
+
unless http_client.nil? || http_client.respond_to?(:execute)
|
|
264
|
+
raise ArgumentError, "`http_client` must respond to `execute`"
|
|
265
|
+
end
|
|
266
|
+
unless on_retry.nil? || on_retry.respond_to?(:call)
|
|
267
|
+
raise ArgumentError, "`on_retry` must respond to `call`"
|
|
268
|
+
end
|
|
269
|
+
|
|
270
|
+
if log_level.nil?
|
|
271
|
+
log_level = ENV.fetch("OPENAI_LOG", logger.nil? ? :off : :info)
|
|
272
|
+
end
|
|
273
|
+
@log_level = OpenAI::Internal::Logging.normalize_level(log_level)
|
|
274
|
+
OpenAI::Internal::Logging.validate_logger!(logger)
|
|
275
|
+
@logger = logger
|
|
276
|
+
@logger ||= OpenAI::Internal::Logging.default_logger unless @log_level == :off
|
|
277
|
+
@on_retry = on_retry
|
|
278
|
+
@requester = http_client || OpenAI::NetHTTPClient.new
|
|
212
279
|
@headers = OpenAI::Internal::Util.normalized_headers(
|
|
213
280
|
self.class::PLATFORM_HEADERS,
|
|
214
281
|
{
|
|
@@ -318,8 +385,9 @@ module OpenAI
|
|
|
318
385
|
headers["x-stainless-retry-count"] = "0"
|
|
319
386
|
end
|
|
320
387
|
|
|
321
|
-
timeout = opts.fetch(:timeout, @timeout)
|
|
322
|
-
|
|
388
|
+
timeout = opts.fetch(:timeout, @timeout)
|
|
389
|
+
timeout = timeout.to_f.clamp(0..) unless timeout.nil?
|
|
390
|
+
unless headers.key?("x-stainless-timeout") || timeout.nil? || timeout.zero?
|
|
323
391
|
headers["x-stainless-timeout"] = timeout.to_s
|
|
324
392
|
end
|
|
325
393
|
|
|
@@ -342,17 +410,23 @@ module OpenAI
|
|
|
342
410
|
@base_url_components,
|
|
343
411
|
{**req, path: path, query: query}
|
|
344
412
|
)
|
|
345
|
-
|
|
413
|
+
max_retries = opts.fetch(:max_retries, @max_retries)
|
|
414
|
+
max_retries = 0 unless self.class.request_body_replayable?(body)
|
|
346
415
|
{
|
|
347
416
|
method: method,
|
|
348
417
|
url: url,
|
|
349
418
|
headers: headers,
|
|
350
|
-
body:
|
|
351
|
-
max_retries:
|
|
419
|
+
body: body,
|
|
420
|
+
max_retries: max_retries,
|
|
352
421
|
timeout: timeout
|
|
353
422
|
}
|
|
354
423
|
end
|
|
355
424
|
|
|
425
|
+
# @api private
|
|
426
|
+
private def request_replayable?(request)
|
|
427
|
+
self.class.request_body_replayable?(request[:body])
|
|
428
|
+
end
|
|
429
|
+
|
|
356
430
|
# @api private
|
|
357
431
|
#
|
|
358
432
|
# @param headers [Hash{String=>String}]
|
|
@@ -360,23 +434,22 @@ module OpenAI
|
|
|
360
434
|
#
|
|
361
435
|
# @return [Float]
|
|
362
436
|
private def retry_delay(headers, retry_count:)
|
|
363
|
-
# Non-standard extension
|
|
364
|
-
span = Float(headers["retry-after-ms"], exception: false)&.then { _1 / 1000 }
|
|
365
|
-
return span if span
|
|
366
|
-
|
|
367
437
|
retry_header = headers["retry-after"]
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
438
|
+
delays = [
|
|
439
|
+
Float(headers["retry-after-ms"], exception: false)&.then { _1 / 1000 },
|
|
440
|
+
Float(retry_header, exception: false),
|
|
441
|
+
retry_header&.then do
|
|
442
|
+
Time.httpdate(_1) - Time.now
|
|
443
|
+
rescue ArgumentError
|
|
444
|
+
nil
|
|
445
|
+
end
|
|
446
|
+
]
|
|
447
|
+
server_delay = delays.find { _1&.finite? && !_1.negative? }
|
|
448
|
+
return [server_delay, @max_retry_delay].min if server_delay
|
|
376
449
|
|
|
377
|
-
|
|
450
|
+
delay = (@initial_retry_delay * (2**retry_count)).clamp(0, @max_retry_delay)
|
|
378
451
|
jitter = 1 - (0.25 * rand)
|
|
379
|
-
|
|
452
|
+
delay * jitter
|
|
380
453
|
end
|
|
381
454
|
|
|
382
455
|
# @api private
|
|
@@ -411,7 +484,7 @@ module OpenAI
|
|
|
411
484
|
#
|
|
412
485
|
# @option request [Integer] :max_retries
|
|
413
486
|
#
|
|
414
|
-
# @option request [Float] :timeout
|
|
487
|
+
# @option request [Float, nil] :timeout
|
|
415
488
|
#
|
|
416
489
|
# @param redirect_count [Integer]
|
|
417
490
|
#
|
|
@@ -419,32 +492,57 @@ module OpenAI
|
|
|
419
492
|
#
|
|
420
493
|
# @param send_retry_header [Boolean]
|
|
421
494
|
#
|
|
495
|
+
# @yieldreturn [OpenAI::Internal::Logging::Context]
|
|
496
|
+
#
|
|
422
497
|
# @raise [OpenAI::Errors::APIError]
|
|
423
|
-
# @return [
|
|
424
|
-
def send_request(request, redirect_count:, retry_count:, send_retry_header
|
|
498
|
+
# @return [OpenAI::HTTPClient::Response]
|
|
499
|
+
def send_request(request, redirect_count:, retry_count:, send_retry_header:, &context_provider)
|
|
500
|
+
# Generated clients override this hook. A block keeps observability
|
|
501
|
+
# state out of their stable keyword signature and is forwarded by
|
|
502
|
+
# their existing `super` calls.
|
|
503
|
+
log_context = context_provider.call
|
|
425
504
|
if send_retry_header
|
|
426
505
|
request = request.merge(
|
|
427
506
|
headers: request.fetch(:headers).merge("x-stainless-retry-count" => retry_count.to_s)
|
|
428
507
|
)
|
|
429
508
|
end
|
|
430
509
|
|
|
510
|
+
encoded_headers, encoded_body = OpenAI::Internal::Util.encode_content(
|
|
511
|
+
request.fetch(:headers),
|
|
512
|
+
request[:body]
|
|
513
|
+
)
|
|
514
|
+
attempt_request = request.merge(headers: encoded_headers, body: encoded_body)
|
|
431
515
|
prepared_request = prepare_request(
|
|
432
|
-
|
|
516
|
+
attempt_request,
|
|
433
517
|
redirect_count: redirect_count,
|
|
434
518
|
retry_count: retry_count
|
|
435
519
|
)
|
|
436
520
|
url, max_retries, timeout = prepared_request.fetch_values(:url, :max_retries, :timeout)
|
|
437
|
-
input =
|
|
438
|
-
|
|
439
|
-
|
|
440
|
-
|
|
521
|
+
input = OpenAI::HTTPClient::Request.new(
|
|
522
|
+
method: prepared_request.fetch(:method),
|
|
523
|
+
url: url,
|
|
524
|
+
headers: prepared_request.fetch(:headers),
|
|
525
|
+
body: prepared_request[:body],
|
|
526
|
+
timeout: timeout
|
|
527
|
+
)
|
|
528
|
+
log_context.request_started(input, redirect_count: redirect_count)
|
|
441
529
|
|
|
442
530
|
begin
|
|
443
|
-
|
|
531
|
+
http_response = @requester.execute(input)
|
|
532
|
+
unless http_response.is_a?(OpenAI::HTTPClient::Response)
|
|
533
|
+
raise TypeError, "`http_client#execute` must return an OpenAI::HTTPClient::Response"
|
|
534
|
+
end
|
|
535
|
+
|
|
536
|
+
status = http_response.status
|
|
537
|
+
headers = http_response.headers
|
|
538
|
+
http_response = log_context.response_received(http_response)
|
|
539
|
+
stream = http_response.body
|
|
444
540
|
rescue OpenAI::Errors::APIConnectionError => e
|
|
445
541
|
status = e
|
|
542
|
+
stream = nil
|
|
543
|
+
headers = {}
|
|
544
|
+
log_context.attempt_failed(e)
|
|
446
545
|
end
|
|
447
|
-
headers = OpenAI::Internal::Util.normalized_headers(response&.each_header&.to_h)
|
|
448
546
|
|
|
449
547
|
terminal_status =
|
|
450
548
|
case status
|
|
@@ -460,24 +558,25 @@ module OpenAI
|
|
|
460
558
|
url: url,
|
|
461
559
|
status: status,
|
|
462
560
|
headers: headers,
|
|
463
|
-
response:
|
|
561
|
+
response: http_response,
|
|
464
562
|
stream: stream
|
|
465
563
|
)
|
|
466
564
|
end
|
|
467
565
|
|
|
468
566
|
case status
|
|
469
567
|
in ..299
|
|
470
|
-
|
|
568
|
+
http_response
|
|
471
569
|
in 300..399 if redirect_count >= self.class::MAX_REDIRECTS
|
|
472
570
|
self.class.reap_connection!(status, stream: stream)
|
|
473
571
|
|
|
474
572
|
message = "Failed to complete the request within #{self.class::MAX_REDIRECTS} redirects."
|
|
475
|
-
raise OpenAI::Errors::APIConnectionError.new(url: url, response:
|
|
573
|
+
raise OpenAI::Errors::APIConnectionError.new(url: url, response: http_response, message: message)
|
|
476
574
|
in 300..399
|
|
477
575
|
self.class.reap_connection!(status, stream: stream)
|
|
478
576
|
|
|
577
|
+
redirect_source = request.merge(url: prepared_request.fetch(:url))
|
|
479
578
|
redirected_request = self.class.follow_redirect(
|
|
480
|
-
|
|
579
|
+
redirect_source,
|
|
481
580
|
status: status,
|
|
482
581
|
response_headers: headers
|
|
483
582
|
)
|
|
@@ -485,21 +584,30 @@ module OpenAI
|
|
|
485
584
|
redirected_request,
|
|
486
585
|
redirect_count: redirect_count + 1,
|
|
487
586
|
retry_count: retry_count,
|
|
488
|
-
send_retry_header: send_retry_header
|
|
587
|
+
send_retry_header: send_retry_header,
|
|
588
|
+
&context_provider
|
|
489
589
|
)
|
|
490
590
|
in OpenAI::Errors::APIConnectionError if retry_count >= max_retries
|
|
491
591
|
raise status
|
|
492
592
|
in (400..) | OpenAI::Errors::APIConnectionError
|
|
493
593
|
self.class.reap_connection!(status, stream: stream)
|
|
494
594
|
|
|
495
|
-
delay = retry_delay(
|
|
595
|
+
delay = retry_delay(headers, retry_count: retry_count)
|
|
596
|
+
log_context.retry_scheduled(
|
|
597
|
+
status,
|
|
598
|
+
delay: delay,
|
|
599
|
+
response: http_response&.metadata,
|
|
600
|
+
retry_count: retry_count,
|
|
601
|
+
max_retries: max_retries
|
|
602
|
+
)
|
|
496
603
|
sleep(delay)
|
|
497
604
|
|
|
498
605
|
send_request(
|
|
499
606
|
request,
|
|
500
607
|
redirect_count: redirect_count,
|
|
501
608
|
retry_count: retry_count + 1,
|
|
502
|
-
send_retry_header: send_retry_header
|
|
609
|
+
send_retry_header: send_retry_header,
|
|
610
|
+
&context_provider
|
|
503
611
|
)
|
|
504
612
|
end
|
|
505
613
|
end
|
|
@@ -546,41 +654,93 @@ module OpenAI
|
|
|
546
654
|
# @raise [OpenAI::Errors::APIError]
|
|
547
655
|
# @return [Object]
|
|
548
656
|
def request(req)
|
|
657
|
+
url, response, log_context = perform_request(req)
|
|
658
|
+
finish_request(log_context, response) do
|
|
659
|
+
parse_response(req, url: url, response: response)
|
|
660
|
+
end
|
|
661
|
+
end
|
|
662
|
+
|
|
663
|
+
# @api private
|
|
664
|
+
#
|
|
665
|
+
# @param req [Hash{Symbol=>Object}]
|
|
666
|
+
# @return [Array(URI::Generic, OpenAI::HTTPClient::Response, OpenAI::Internal::Logging::Context)]
|
|
667
|
+
private def perform_request(req)
|
|
549
668
|
self.class.validate!(req)
|
|
550
|
-
model = req.fetch(:model) { OpenAI::Internal::Type::Unknown }
|
|
551
669
|
opts = req[:options].to_h
|
|
552
|
-
unwrap = req[:unwrap]
|
|
553
670
|
OpenAI::RequestOptions.validate!(opts)
|
|
554
671
|
request = build_request(req.except(:options), opts)
|
|
555
672
|
url = request.fetch(:url)
|
|
673
|
+
log_context = OpenAI::Internal::Logging::Context.new(
|
|
674
|
+
logger: @logger,
|
|
675
|
+
log_level: @log_level,
|
|
676
|
+
on_retry: @on_retry,
|
|
677
|
+
method: request.fetch(:method),
|
|
678
|
+
url: url
|
|
679
|
+
)
|
|
556
680
|
|
|
557
681
|
# Don't send the current retry count in the headers if the caller modified the header defaults.
|
|
558
682
|
send_retry_header = request.fetch(:headers)["x-stainless-retry-count"] == "0"
|
|
559
|
-
|
|
683
|
+
response = send_request(
|
|
560
684
|
request,
|
|
561
685
|
redirect_count: 0,
|
|
562
686
|
retry_count: 0,
|
|
563
687
|
send_retry_header: send_retry_header
|
|
564
|
-
)
|
|
688
|
+
) { log_context }
|
|
689
|
+
[url, response, log_context]
|
|
690
|
+
rescue StandardError => e
|
|
691
|
+
log_context&.request_failed(e)
|
|
692
|
+
raise
|
|
693
|
+
end
|
|
694
|
+
|
|
695
|
+
# @api private
|
|
696
|
+
#
|
|
697
|
+
# @param log_context [OpenAI::Internal::Logging::Context]
|
|
698
|
+
# @param response [OpenAI::HTTPClient::Response]
|
|
699
|
+
# @return [Object]
|
|
700
|
+
private def finish_request(log_context, response)
|
|
701
|
+
result = yield
|
|
702
|
+
if result.is_a?(OpenAI::Internal::Type::BaseStream)
|
|
703
|
+
return log_context.observe_stream(result, response: response)
|
|
704
|
+
end
|
|
705
|
+
|
|
706
|
+
log_context.completed(response)
|
|
707
|
+
result
|
|
708
|
+
rescue StandardError => e
|
|
709
|
+
log_context.request_failed(e)
|
|
710
|
+
raise
|
|
711
|
+
end
|
|
712
|
+
|
|
713
|
+
# @api private
|
|
714
|
+
#
|
|
715
|
+
# @param req [Hash{Symbol=>Object}]
|
|
716
|
+
# @param url [URI::Generic]
|
|
717
|
+
# @param response [OpenAI::HTTPClient::Response]
|
|
718
|
+
# @return [Object]
|
|
719
|
+
private def parse_response(req, url:, response:)
|
|
720
|
+
model = req.fetch(:model) { OpenAI::Internal::Type::Unknown }
|
|
721
|
+
unwrap = req[:unwrap]
|
|
722
|
+
response_metadata = response.metadata
|
|
565
723
|
|
|
566
|
-
|
|
567
|
-
decoded = OpenAI::Internal::Util.decode_content(headers, stream: stream)
|
|
724
|
+
decoded = OpenAI::Internal::Util.decode_content(response.headers, stream: response.body)
|
|
568
725
|
case req
|
|
569
726
|
in {stream: Class => st}
|
|
570
727
|
st.new(
|
|
571
728
|
model: model,
|
|
572
729
|
url: url,
|
|
573
|
-
|
|
574
|
-
headers: headers,
|
|
730
|
+
response_metadata: response_metadata,
|
|
575
731
|
response: response,
|
|
576
732
|
unwrap: unwrap,
|
|
577
733
|
stream: decoded
|
|
578
734
|
)
|
|
579
735
|
in {page: Class => page}
|
|
580
|
-
page.new(client: self, req: req,
|
|
736
|
+
page.new(client: self, req: req, response_metadata: response_metadata, page_data: decoded)
|
|
581
737
|
else
|
|
582
738
|
unwrapped = OpenAI::Internal::Util.dig(decoded, unwrap)
|
|
583
|
-
OpenAI::Internal::Type::Converter.coerce(model, unwrapped)
|
|
739
|
+
OpenAI::Internal::Type::Converter.coerce(model, unwrapped).tap do |result|
|
|
740
|
+
if result.is_a?(OpenAI::Internal::Type::BaseModel)
|
|
741
|
+
result._set_last_response(response_metadata)
|
|
742
|
+
end
|
|
743
|
+
end
|
|
584
744
|
end
|
|
585
745
|
end
|
|
586
746
|
|
|
@@ -588,9 +748,8 @@ module OpenAI
|
|
|
588
748
|
#
|
|
589
749
|
# @return [String]
|
|
590
750
|
def inspect
|
|
591
|
-
#
|
|
592
|
-
|
|
593
|
-
# rubocop:enable Layout/LineLength
|
|
751
|
+
"#<#{self.class.name}:0x#{object_id.to_s(16)} base_url=#{@base_url} " \
|
|
752
|
+
"max_retries=#{@max_retries} timeout=#{@timeout}>"
|
|
594
753
|
end
|
|
595
754
|
|
|
596
755
|
define_sorbet_constant!(:RequestComponents) do
|
|
@@ -634,7 +793,7 @@ module OpenAI
|
|
|
634
793
|
headers: T::Hash[String, String],
|
|
635
794
|
body: T.anything,
|
|
636
795
|
max_retries: Integer,
|
|
637
|
-
timeout: Float
|
|
796
|
+
timeout: T.nilable(Float)
|
|
638
797
|
}
|
|
639
798
|
end
|
|
640
799
|
end
|
|
@@ -46,9 +46,9 @@ module OpenAI
|
|
|
46
46
|
#
|
|
47
47
|
# @return [Boolean]
|
|
48
48
|
def ==(other)
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
49
|
+
other.is_a?(OpenAI::Internal::Type::ArrayOf) &&
|
|
50
|
+
other.nilable? == nilable? &&
|
|
51
|
+
other.item_type == item_type
|
|
52
52
|
end
|
|
53
53
|
|
|
54
54
|
# @api public
|
|
@@ -120,7 +120,9 @@ module OpenAI
|
|
|
120
120
|
#
|
|
121
121
|
# @return [Object]
|
|
122
122
|
def to_sorbet_type
|
|
123
|
-
|
|
123
|
+
type = OpenAI::Internal::Util::SorbetRuntimeSupport.to_sorbet_type(item_type)
|
|
124
|
+
type = T.nilable(type) if nilable?
|
|
125
|
+
T::Array[type]
|
|
124
126
|
end
|
|
125
127
|
|
|
126
128
|
# @api private
|