ruby-openai 7.4.0 → 8.3.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.
@@ -0,0 +1,40 @@
1
+ module OpenAI
2
+ class Conversations
3
+ def initialize(client:)
4
+ @client = client
5
+ end
6
+
7
+ def retrieve(id:)
8
+ @client.get(path: "/conversations/#{id}")
9
+ end
10
+
11
+ def create(parameters: {})
12
+ @client.json_post(path: "/conversations", parameters: parameters)
13
+ end
14
+
15
+ def modify(id:, parameters: {})
16
+ @client.json_post(path: "/conversations/#{id}", parameters: parameters)
17
+ end
18
+
19
+ def delete(id:)
20
+ @client.delete(path: "/conversations/#{id}")
21
+ end
22
+
23
+ # Item operations within a conversation
24
+ def create_items(conversation_id:, parameters: {})
25
+ @client.json_post(path: "/conversations/#{conversation_id}/items", parameters: parameters)
26
+ end
27
+
28
+ def list_items(conversation_id:, parameters: {})
29
+ @client.get(path: "/conversations/#{conversation_id}/items", parameters: parameters)
30
+ end
31
+
32
+ def get_item(conversation_id:, item_id:)
33
+ @client.get(path: "/conversations/#{conversation_id}/items/#{item_id}")
34
+ end
35
+
36
+ def delete_item(conversation_id:, item_id:)
37
+ @client.delete(path: "/conversations/#{conversation_id}/items/#{item_id}")
38
+ end
39
+ end
40
+ end
data/lib/openai/files.rb CHANGED
@@ -5,6 +5,8 @@ module OpenAI
5
5
  batch
6
6
  fine-tune
7
7
  vision
8
+ user_data
9
+ evals
8
10
  ].freeze
9
11
 
10
12
  def initialize(client:)
@@ -18,9 +20,7 @@ module OpenAI
18
20
  def upload(parameters: {})
19
21
  file_input = parameters[:file]
20
22
  file = prepare_file_input(file_input: file_input)
21
-
22
23
  validate(file: file, purpose: parameters[:purpose], file_input: file_input)
23
-
24
24
  @client.multipart_post(
25
25
  path: "/files",
26
26
  parameters: parameters.merge(file: file)
@@ -29,12 +29,12 @@ module OpenAI
29
29
  file.close if file.is_a?(File)
30
30
  end
31
31
 
32
- def retrieve(id:)
33
- @client.get(path: "/files/#{id}")
32
+ def retrieve(id:, parameters: {})
33
+ @client.get(path: "/files/#{id}", parameters: parameters)
34
34
  end
35
35
 
36
- def content(id:)
37
- @client.get(path: "/files/#{id}/content")
36
+ def content(id:, parameters: {})
37
+ @client.get(path: "/files/#{id}/content", parameters: parameters)
38
38
  end
39
39
 
40
40
  def delete(id:)
@@ -55,8 +55,12 @@ module OpenAI
55
55
 
56
56
  def validate(file:, purpose:, file_input:)
57
57
  raise ArgumentError, "`file` is required" if file.nil?
58
+
58
59
  unless PURPOSES.include?(purpose)
59
- raise ArgumentError, "`purpose` must be one of `#{PURPOSES.join(',')}`"
60
+ filename = file_input.is_a?(String) ? File.basename(file_input) : "uploaded file"
61
+ message = "The purpose '#{purpose}' for file '#{filename}' is not in the known purpose "
62
+ message += "list: #{PURPOSES.join(', ')}."
63
+ OpenAI.log_message("Warning", message, :warn)
60
64
  end
61
65
 
62
66
  validate_jsonl(file: file) if file_input.is_a?(String) && file_input.end_with?(".jsonl")
data/lib/openai/http.rb CHANGED
@@ -7,68 +7,52 @@ module OpenAI
7
7
  include HTTPHeaders
8
8
 
9
9
  def get(path:, parameters: nil)
10
- parse_jsonl(conn.get(uri(path: path), parameters) do |req|
10
+ parse_json(conn.get(uri(path: path), parameters) do |req|
11
11
  req.headers = headers
12
12
  end&.body)
13
13
  end
14
14
 
15
15
  def post(path:)
16
- parse_jsonl(conn.post(uri(path: path)) do |req|
16
+ parse_json(conn.post(uri(path: path)) do |req|
17
17
  req.headers = headers
18
18
  end&.body)
19
19
  end
20
20
 
21
21
  def json_post(path:, parameters:, query_parameters: {})
22
- conn.post(uri(path: path)) do |req|
22
+ parse_json(conn.post(uri(path: path)) do |req|
23
23
  configure_json_post_request(req, parameters)
24
24
  req.params = req.params.merge(query_parameters)
25
- end&.body
25
+ end&.body)
26
26
  end
27
27
 
28
28
  def multipart_post(path:, parameters: nil)
29
- conn(multipart: true).post(uri(path: path)) do |req|
29
+ parse_json(conn(multipart: true).post(uri(path: path)) do |req|
30
30
  req.headers = headers.merge({ "Content-Type" => "multipart/form-data" })
31
31
  req.body = multipart_parameters(parameters)
32
- end&.body
32
+ end&.body)
33
33
  end
34
34
 
35
35
  def delete(path:)
36
- conn.delete(uri(path: path)) do |req|
36
+ parse_json(conn.delete(uri(path: path)) do |req|
37
37
  req.headers = headers
38
- end&.body
38
+ end&.body)
39
39
  end
40
40
 
41
41
  private
42
42
 
43
- def parse_jsonl(response)
43
+ def parse_json(response)
44
44
  return unless response
45
45
  return response unless response.is_a?(String)
46
46
 
47
- # Convert a multiline string of JSON objects to a JSON array.
48
- response = response.gsub("}\n{", "},{").prepend("[").concat("]")
47
+ original_response = response.dup
48
+ if response.include?("}\n{")
49
+ # Attempt to convert what looks like a multiline string of JSON objects to a JSON array.
50
+ response = response.gsub("}\n{", "},{").prepend("[").concat("]")
51
+ end
49
52
 
50
53
  JSON.parse(response)
51
- end
52
-
53
- # Given a proc, returns an outer proc that can be used to iterate over a JSON stream of chunks.
54
- # For each chunk, the inner user_proc is called giving it the JSON object. The JSON object could
55
- # be a data object or an error object as described in the OpenAI API documentation.
56
- #
57
- # @param user_proc [Proc] The inner proc to call for each JSON object in the chunk.
58
- # @return [Proc] An outer proc that iterates over a raw stream, converting it to JSON.
59
- def to_json_stream(user_proc:)
60
- parser = EventStreamParser::Parser.new
61
-
62
- proc do |chunk, _bytes, env|
63
- if env && env.status != 200
64
- raise_error = Faraday::Response::RaiseError.new
65
- raise_error.on_complete(env.merge(body: try_parse_json(chunk)))
66
- end
67
-
68
- parser.feed(chunk) do |_type, data|
69
- user_proc.call(JSON.parse(data)) unless data == "[DONE]"
70
- end
71
- end
54
+ rescue JSON::ParserError
55
+ original_response
72
56
  end
73
57
 
74
58
  def conn(multipart: false)
@@ -115,7 +99,7 @@ module OpenAI
115
99
  req_parameters = parameters.dup
116
100
 
117
101
  if parameters[:stream].respond_to?(:call)
118
- req.options.on_data = to_json_stream(user_proc: parameters[:stream])
102
+ req.options.on_data = Stream.new(user_proc: parameters[:stream]).to_proc
119
103
  req_parameters[:stream] = true # Necessary to tell OpenAI to stream.
120
104
  elsif parameters[:stream]
121
105
  raise ArgumentError, "The stream parameter must be a Proc or have a #call method"
@@ -124,11 +108,5 @@ module OpenAI
124
108
  req.headers = headers
125
109
  req.body = req_parameters.to_json
126
110
  end
127
-
128
- def try_parse_json(maybe_json)
129
- JSON.parse(maybe_json)
130
- rescue JSON::ParserError
131
- maybe_json
132
- end
133
111
  end
134
112
  end
data/lib/openai/images.rb CHANGED
@@ -19,9 +19,23 @@ module OpenAI
19
19
  private
20
20
 
21
21
  def open_files(parameters)
22
- parameters = parameters.merge(image: File.open(parameters[:image]))
23
- parameters = parameters.merge(mask: File.open(parameters[:mask])) if parameters[:mask]
24
- parameters
22
+ params = parameters.dup
23
+
24
+ if params[:image].is_a?(Array)
25
+ process_image_array(params)
26
+ else
27
+ params[:image] = File.open(params[:image])
28
+ end
29
+
30
+ params[:mask] = File.open(params[:mask]) if params[:mask]
31
+ params
32
+ end
33
+
34
+ def process_image_array(params)
35
+ params[:image].each_with_index do |img_path, index|
36
+ params[:"image[#{index}]"] = File.open(img_path)
37
+ end
38
+ params.delete(:image)
25
39
  end
26
40
  end
27
41
  end
data/lib/openai/models.rb CHANGED
@@ -11,5 +11,9 @@ module OpenAI
11
11
  def retrieve(id:)
12
12
  @client.get(path: "/models/#{id}")
13
13
  end
14
+
15
+ def delete(id:)
16
+ @client.delete(path: "/models/#{id}")
17
+ end
14
18
  end
15
19
  end
@@ -0,0 +1,19 @@
1
+ module OpenAI
2
+ class Realtime
3
+ def initialize(client:)
4
+ @client = client.beta(realtime: "v1")
5
+ end
6
+
7
+ # Create a new real-time session with OpenAI.
8
+ #
9
+ # This method sets up a new session for real-time voice interaction with an OpenAI model.
10
+ # It returns session details that can be used to establish a WebRTC connection.
11
+ #
12
+ # @param parameters [Hash] parameters for the session (see: https://platform.openai.com/docs/api-reference/realtime-sessions/create)
13
+ # @return [Hash] Session details including session ID, ICE servers, and other
14
+ # connection information
15
+ def create(parameters: {})
16
+ @client.json_post(path: "/realtime/sessions", parameters: parameters)
17
+ end
18
+ end
19
+ end
@@ -0,0 +1,23 @@
1
+ module OpenAI
2
+ class Responses
3
+ def initialize(client:)
4
+ @client = client
5
+ end
6
+
7
+ def create(parameters: {})
8
+ @client.json_post(path: "/responses", parameters: parameters)
9
+ end
10
+
11
+ def retrieve(response_id:)
12
+ @client.get(path: "/responses/#{response_id}")
13
+ end
14
+
15
+ def delete(response_id:)
16
+ @client.delete(path: "/responses/#{response_id}")
17
+ end
18
+
19
+ def input_items(response_id:, parameters: {})
20
+ @client.get(path: "/responses/#{response_id}/input_items", parameters: parameters)
21
+ end
22
+ end
23
+ end
@@ -0,0 +1,50 @@
1
+ module OpenAI
2
+ class Stream
3
+ DONE = "[DONE]".freeze
4
+ private_constant :DONE
5
+
6
+ def initialize(user_proc:, parser: EventStreamParser::Parser.new)
7
+ @user_proc = user_proc
8
+ @parser = parser
9
+
10
+ # To be backwards compatible, we need to check how many arguments the user_proc takes.
11
+ @user_proc_arity =
12
+ case user_proc
13
+ when Proc
14
+ user_proc.arity.abs
15
+ else
16
+ user_proc.method(:call).arity.abs
17
+ end
18
+ end
19
+
20
+ def call(chunk, _bytes, env = nil)
21
+ handle_http_error(chunk: chunk, env: env) if env && env.status != 200
22
+
23
+ parser.feed(chunk) do |event, data|
24
+ next if data == DONE
25
+
26
+ args = [JSON.parse(data), event].first(user_proc_arity)
27
+ user_proc.call(*args)
28
+ end
29
+ end
30
+
31
+ def to_proc
32
+ method(:call).to_proc
33
+ end
34
+
35
+ private
36
+
37
+ attr_reader :user_proc, :parser, :user_proc_arity
38
+
39
+ def handle_http_error(chunk:, env:)
40
+ raise_error = Faraday::Response::RaiseError.new
41
+ raise_error.on_complete(env.merge(body: try_parse_json(chunk)))
42
+ end
43
+
44
+ def try_parse_json(maybe_json)
45
+ JSON.parse(maybe_json)
46
+ rescue JSON::ParserError
47
+ maybe_json
48
+ end
49
+ end
50
+ end
@@ -23,5 +23,9 @@ module OpenAI
23
23
  def delete(id:)
24
24
  @client.delete(path: "/vector_stores/#{id}")
25
25
  end
26
+
27
+ def search(id:, parameters: {})
28
+ @client.json_post(path: "/vector_stores/#{id}/search", parameters: parameters)
29
+ end
26
30
  end
27
31
  end
@@ -1,3 +1,3 @@
1
1
  module OpenAI
2
- VERSION = "7.4.0".freeze
2
+ VERSION = "8.3.0".freeze
3
3
  end
data/lib/openai.rb CHANGED
@@ -1,17 +1,19 @@
1
1
  require "faraday"
2
- require "faraday/multipart"
3
-
2
+ require "faraday/multipart" if Gem::Version.new(Faraday::VERSION) >= Gem::Version.new("2.0")
4
3
  require_relative "openai/http"
5
4
  require_relative "openai/client"
6
5
  require_relative "openai/files"
7
6
  require_relative "openai/finetunes"
8
7
  require_relative "openai/images"
9
8
  require_relative "openai/models"
9
+ require_relative "openai/responses"
10
10
  require_relative "openai/assistants"
11
11
  require_relative "openai/threads"
12
12
  require_relative "openai/messages"
13
+ require_relative "openai/realtime"
13
14
  require_relative "openai/runs"
14
15
  require_relative "openai/run_steps"
16
+ require_relative "openai/stream"
15
17
  require_relative "openai/vector_stores"
16
18
  require_relative "openai/vector_store_files"
17
19
  require_relative "openai/vector_store_file_batches"
@@ -19,6 +21,7 @@ require_relative "openai/audio"
19
21
  require_relative "openai/version"
20
22
  require_relative "openai/batches"
21
23
  require_relative "openai/usage"
24
+ require_relative "openai/conversations"
22
25
 
23
26
  module OpenAI
24
27
  class Error < StandardError; end
@@ -31,12 +34,7 @@ module OpenAI
31
34
  rescue Faraday::Error => e
32
35
  raise e unless e.response.is_a?(Hash)
33
36
 
34
- logger = Logger.new($stdout)
35
- logger.formatter = proc do |_severity, _datetime, _progname, msg|
36
- "\033[31mOpenAI HTTP Error (spotted in ruby-openai #{VERSION}): #{msg}\n\033[0m"
37
- end
38
- logger.error(e.response[:body])
39
-
37
+ OpenAI.log_message("OpenAI HTTP Error", e.response[:body], :error)
40
38
  raise e
41
39
  end
42
40
  end
@@ -72,25 +70,38 @@ module OpenAI
72
70
 
73
71
  class << self
74
72
  attr_writer :configuration
75
- end
76
73
 
77
- def self.configuration
78
- @configuration ||= OpenAI::Configuration.new
79
- end
74
+ def configuration
75
+ @configuration ||= OpenAI::Configuration.new
76
+ end
80
77
 
81
- def self.configure
82
- yield(configuration)
83
- end
78
+ def configure
79
+ yield(configuration)
80
+ end
84
81
 
85
- # Estimate the number of tokens in a string, using the rules of thumb from OpenAI:
86
- # https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them
87
- def self.rough_token_count(content = "")
88
- raise ArgumentError, "rough_token_count requires a string" unless content.is_a? String
89
- return 0 if content.empty?
82
+ # Estimate the number of tokens in a string, using the rules of thumb from OpenAI:
83
+ # https://help.openai.com/en/articles/4936856-what-are-tokens-and-how-to-count-them
84
+ def rough_token_count(content = "")
85
+ raise ArgumentError, "rough_token_count requires a string" unless content.is_a? String
86
+ return 0 if content.empty?
87
+
88
+ count_by_chars = content.size / 4.0
89
+ count_by_words = content.split.size * 4.0 / 3
90
+ estimate = ((count_by_chars + count_by_words) / 2.0).round
91
+ [1, estimate].max
92
+ end
90
93
 
91
- count_by_chars = content.size / 4.0
92
- count_by_words = content.split.size * 4.0 / 3
93
- estimate = ((count_by_chars + count_by_words) / 2.0).round
94
- [1, estimate].max
94
+ # Log a message with appropriate formatting
95
+ # @param prefix [String] Prefix to add to the message
96
+ # @param message [String] The message to log
97
+ # @param level [Symbol] The log level (:error, :warn, etc.)
98
+ def log_message(prefix, message, level = :warn)
99
+ color = level == :error ? "\033[31m" : "\033[33m"
100
+ logger = Logger.new($stdout)
101
+ logger.formatter = proc do |_severity, _datetime, _progname, msg|
102
+ "#{color}#{prefix} (spotted in ruby-openai #{VERSION}): #{msg}\n\033[0m"
103
+ end
104
+ logger.send(level, message)
105
+ end
95
106
  end
96
107
  end
data/lib/ruby/openai.rb CHANGED
@@ -1,2 +1 @@
1
1
  require_relative "../openai"
2
- require_relative "../openai/compatibility"
metadata CHANGED
@@ -1,14 +1,13 @@
1
1
  --- !ruby/object:Gem::Specification
2
2
  name: ruby-openai
3
3
  version: !ruby/object:Gem::Version
4
- version: 7.4.0
4
+ version: 8.3.0
5
5
  platform: ruby
6
6
  authors:
7
7
  - Alex
8
- autorequire:
9
8
  bindir: exe
10
9
  cert_chain: []
11
- date: 2025-02-10 00:00:00.000000000 Z
10
+ date: 1980-01-02 00:00:00.000000000 Z
12
11
  dependencies:
13
12
  - !ruby/object:Gem::Dependency
14
13
  name: event_stream_parser
@@ -58,7 +57,6 @@ dependencies:
58
57
  - - ">="
59
58
  - !ruby/object:Gem::Version
60
59
  version: '1'
61
- description:
62
60
  email:
63
61
  - alexrudall@users.noreply.github.com
64
62
  executables: []
@@ -84,6 +82,7 @@ files:
84
82
  - LICENSE.txt
85
83
  - README.md
86
84
  - Rakefile
85
+ - SECURITY.md
87
86
  - bin/console
88
87
  - bin/setup
89
88
  - lib/openai.rb
@@ -91,7 +90,7 @@ files:
91
90
  - lib/openai/audio.rb
92
91
  - lib/openai/batches.rb
93
92
  - lib/openai/client.rb
94
- - lib/openai/compatibility.rb
93
+ - lib/openai/conversations.rb
95
94
  - lib/openai/files.rb
96
95
  - lib/openai/finetunes.rb
97
96
  - lib/openai/http.rb
@@ -99,8 +98,11 @@ files:
99
98
  - lib/openai/images.rb
100
99
  - lib/openai/messages.rb
101
100
  - lib/openai/models.rb
101
+ - lib/openai/realtime.rb
102
+ - lib/openai/responses.rb
102
103
  - lib/openai/run_steps.rb
103
104
  - lib/openai/runs.rb
105
+ - lib/openai/stream.rb
104
106
  - lib/openai/threads.rb
105
107
  - lib/openai/usage.rb
106
108
  - lib/openai/vector_store_file_batches.rb
@@ -119,7 +121,6 @@ metadata:
119
121
  changelog_uri: https://github.com/alexrudall/ruby-openai/blob/main/CHANGELOG.md
120
122
  rubygems_mfa_required: 'true'
121
123
  funding_uri: https://github.com/sponsors/alexrudall
122
- post_install_message:
123
124
  rdoc_options: []
124
125
  require_paths:
125
126
  - lib
@@ -134,8 +135,7 @@ required_rubygems_version: !ruby/object:Gem::Requirement
134
135
  - !ruby/object:Gem::Version
135
136
  version: '0'
136
137
  requirements: []
137
- rubygems_version: 3.5.11
138
- signing_key:
138
+ rubygems_version: 3.6.7
139
139
  specification_version: 4
140
140
  summary: "OpenAI API + Ruby! \U0001F916❤️"
141
141
  test_files: []
@@ -1,11 +0,0 @@
1
- module Ruby
2
- module OpenAI
3
- VERSION = ::OpenAI::VERSION
4
-
5
- Error = ::OpenAI::Error
6
- AuthenticationError = ::OpenAI::AuthenticationError
7
- ConfigurationError = ::OpenAI::ConfigurationError
8
- Configuration = ::OpenAI::Configuration
9
- MiddlewareErrors = ::OpenAI::MiddlewareErrors
10
- end
11
- end