diff --git a/lib/openai/http.rb b/lib/openai/http.rb index 644e692d..56631f4a 100644 --- a/lib/openai/http.rb +++ b/lib/openai/http.rb @@ -63,19 +63,28 @@ def parse_json(response) # @return [Proc] An outer proc that iterates over a raw stream, converting it to JSON. def to_json_stream(user_proc:) parser = EventStreamParser::Parser.new + accumulated_error = "" proc do |chunk, _bytes, env| if env && env.status != 200 - raise_error = Faraday::Response::RaiseError.new - raise_error.on_complete(env.merge(body: try_parse_json(chunk))) - end - - parser.feed(chunk) do |_type, data| - user_proc.call(JSON.parse(data)) unless data == "[DONE]" + accumulated_error += chunk + raise_error_when_ready(env, accumulated_error) + else + parser.feed(chunk) do |_type, data| + user_proc.call(JSON.parse(data)) unless data == "[DONE]" + end end end end + def raise_error_when_ready(env, accumulated_error) + parsed_error = try_parse_json(accumulated_error) + return if parsed_error.is_a?(String) + + raise_error = Faraday::Response::RaiseError.new + raise_error.on_complete(env.merge(body: parsed_error)) + end + def conn(multipart: false) connection = Faraday.new do |f| f.options[:timeout] = @request_timeout diff --git a/spec/openai/client/http_spec.rb b/spec/openai/client/http_spec.rb index 39261518..e31f5150 100644 --- a/spec/openai/client/http_spec.rb +++ b/spec/openai/client/http_spec.rb @@ -177,6 +177,43 @@ end end + context "with a HTTP error response with body containing JSON split across chunks" do + it "raise an error" do + env = Faraday::Env.from( + method: :post, + url: URI("http://example.com"), + status: 400, + request: {}, + response: Faraday::Response.new + ) + + expected_body = { + "error" => { + "message" => "Test error", + "type" => "test_error", + "param" => nil, + "code" => "test" + } + } + + json = expected_body.to_json + # Split the JSON into two chunks in the middle + chunks = [json[0..(json.length / 2)], json[((json.length / 2) + 1)..]] + + begin + chunks.each do |chunk| + stream.call(chunk, 0, env) + end + rescue Faraday::BadRequestError => e + expect(e.response).to include(status: 400) + expect(e.response[:body]).to eq(expected_body) + else + raise "Expected to raise Faraday::BadRequestError" + end + end + end + + context "when called with JSON split across chunks" do it "calls the user proc with the data parsed as JSON" do expect(user_proc).to receive(:call).with(JSON.parse('{ "foo": "bar" }'))