diff --git a/lib/openai/client.rb b/lib/openai/client.rb index 6054af0f..313bfded 100644 --- a/lib/openai/client.rb +++ b/lib/openai/client.rb @@ -21,16 +21,16 @@ def initialize(config = {}, &faraday_middleware) @faraday_middleware = faraday_middleware end - def chat(parameters: {}) - json_post(path: "/chat/completions", parameters: parameters) + def chat(parameters: {}, extra_headers: {}) + json_post(path: "/chat/completions", parameters: parameters, extra_headers: extra_headers) end - def embeddings(parameters: {}) - json_post(path: "/embeddings", parameters: parameters) + def embeddings(parameters: {}, extra_headers: {}) + json_post(path: "/embeddings", parameters: parameters, extra_headers: extra_headers) end - def completions(parameters: {}) - json_post(path: "/completions", parameters: parameters) + def completions(parameters: {}, extra_headers: {}) + json_post(path: "/completions", parameters: parameters, extra_headers: extra_headers) end def audio @@ -97,8 +97,8 @@ def realtime @realtime ||= OpenAI::Realtime.new(client: self) end - def moderations(parameters: {}) - json_post(path: "/moderations", parameters: parameters) + def moderations(parameters: {}, extra_headers: {}) + json_post(path: "/moderations", parameters: parameters, extra_headers: extra_headers) end def usage diff --git a/lib/openai/http.rb b/lib/openai/http.rb index 5459f64c..76107ea8 100644 --- a/lib/openai/http.rb +++ b/lib/openai/http.rb @@ -6,35 +6,35 @@ module OpenAI module HTTP include HTTPHeaders - def get(path:, parameters: nil) + def get(path:, parameters: nil, extra_headers: {}) parse_json(conn.get(uri(path: path), parameters) do |req| - req.headers = headers + req.headers = headers.merge!(extra_headers) end&.body) end - def post(path:) + def post(path:, extra_headers: {}) parse_json(conn.post(uri(path: path)) do |req| - req.headers = headers + req.headers = headers.merge!(extra_headers) end&.body) end - def json_post(path:, parameters:, query_parameters: {}) + def json_post(path:, parameters:, query_parameters: {}, extra_headers: {}) parse_json(conn.post(uri(path: path)) do |req| - configure_json_post_request(req, parameters) + configure_json_post_request(req, parameters, extra_headers: extra_headers) req.params = req.params.merge(query_parameters) end&.body) end - def multipart_post(path:, parameters: nil) + def multipart_post(path:, parameters: nil, extra_headers: {}) parse_json(conn(multipart: true).post(uri(path: path)) do |req| - req.headers = headers.merge({ "Content-Type" => "multipart/form-data" }) + req.headers = headers.merge("Content-Type" => "multipart/form-data").merge!(extra_headers) req.body = multipart_parameters(parameters) end&.body) end - def delete(path:) + def delete(path:, extra_headers: {}) parse_json(conn.delete(uri(path: path)) do |req| - req.headers = headers + req.headers = headers.merge!(extra_headers) end&.body) end @@ -95,7 +95,7 @@ def multipart_parameters(parameters) end end - def configure_json_post_request(req, parameters) + def configure_json_post_request(req, parameters, extra_headers: {}) req_parameters = parameters.dup if parameters[:stream].respond_to?(:call) @@ -105,7 +105,7 @@ def configure_json_post_request(req, parameters) raise ArgumentError, "The stream parameter must be a Proc or have a #call method" end - req.headers = headers + req.headers = headers.merge!(extra_headers) req.body = req_parameters.to_json end end diff --git a/spec/openai/client/http_spec.rb b/spec/openai/client/http_spec.rb index 471289f4..4c5814dc 100644 --- a/spec/openai/client/http_spec.rb +++ b/spec/openai/client/http_spec.rb @@ -1,4 +1,47 @@ RSpec.describe OpenAI::HTTP do + describe "extra_headers" do + include WebMock::API + + let(:client) { OpenAI::Client.new } + let(:custom_headers) { { "X-Custom-Header" => "custom-value", "X-Another" => "another-value" } } + let(:chat_url) { "https://api.openai.com/v1/chat/completions" } + + before do + VCR.turn_off! + WebMock.disable_net_connect! + stub_request(:post, chat_url) + .to_return(status: 200, body: { choices: [] }.to_json, headers: {}) + end + + after do + WebMock.reset! + VCR.turn_on! + end + + it "merges extra_headers into the request" do + client.chat(parameters: { model: "gpt-4", messages: [] }, extra_headers: custom_headers) + + assert_requested(:post, chat_url, headers: custom_headers) + end + + it "allows extra_headers to override default headers" do + client.chat( + parameters: { model: "gpt-4", messages: [] }, + extra_headers: { "Content-Type" => "application/json; charset=utf-8" } + ) + + assert_requested( + :post, chat_url, headers: { "Content-Type" => "application/json; charset=utf-8" } + ) + end + + it "works without extra_headers (backward compatible)" do + client.chat(parameters: { model: "gpt-4", messages: [] }) + + assert_requested(:post, chat_url) + end + end + describe "with an aggressive timeout" do let(:timeout_errors) { [Faraday::ConnectionFailed, Faraday::TimeoutError] } let(:timeout) { 0 }