From e9ddb80bed4b1085196b2304ee1c136c2662f2a7 Mon Sep 17 00:00:00 2001 From: stevenou Date: Fri, 9 May 2025 11:15:27 -0700 Subject: [PATCH 1/3] handle parsing for error when chunked --- lib/openai/http.rb | 9 ++++++-- spec/openai/client/http_spec.rb | 37 +++++++++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 2 deletions(-) diff --git a/lib/openai/http.rb b/lib/openai/http.rb index 644e692d..224d4fde 100644 --- a/lib/openai/http.rb +++ b/lib/openai/http.rb @@ -63,11 +63,16 @@ 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))) + accumulated_error += chunk + parsed_error = try_parse_json(accumulated_error) + if parsed_error.is_a?(Hash) + raise_error = Faraday::Response::RaiseError.new + raise_error.on_complete(env.merge(body: parsed_error)) + end end parser.feed(chunk) do |_type, data| 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" }')) From 5e53170ec638d129e2e7c0cfb67fa048ef1fa403 Mon Sep 17 00:00:00 2001 From: stevenou Date: Fri, 9 May 2025 11:18:19 -0700 Subject: [PATCH 2/3] don't even call user_proc when there is an error --- lib/openai/http.rb | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/openai/http.rb b/lib/openai/http.rb index 224d4fde..436f3723 100644 --- a/lib/openai/http.rb +++ b/lib/openai/http.rb @@ -73,10 +73,10 @@ def to_json_stream(user_proc:) raise_error = Faraday::Response::RaiseError.new raise_error.on_complete(env.merge(body: parsed_error)) end - end - - parser.feed(chunk) do |_type, data| - user_proc.call(JSON.parse(data)) unless data == "[DONE]" + else + parser.feed(chunk) do |_type, data| + user_proc.call(JSON.parse(data)) unless data == "[DONE]" + end end end end From 1c75e37f048ed155ef0d8305d7a50cbe0357256d Mon Sep 17 00:00:00 2001 From: stevenou Date: Fri, 9 May 2025 11:29:32 -0700 Subject: [PATCH 3/3] rubocop --- lib/openai/http.rb | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/lib/openai/http.rb b/lib/openai/http.rb index 436f3723..56631f4a 100644 --- a/lib/openai/http.rb +++ b/lib/openai/http.rb @@ -63,16 +63,12 @@ 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 = '' + accumulated_error = "" proc do |chunk, _bytes, env| if env && env.status != 200 accumulated_error += chunk - parsed_error = try_parse_json(accumulated_error) - if parsed_error.is_a?(Hash) - raise_error = Faraday::Response::RaiseError.new - raise_error.on_complete(env.merge(body: parsed_error)) - end + raise_error_when_ready(env, accumulated_error) else parser.feed(chunk) do |_type, data| user_proc.call(JSON.parse(data)) unless data == "[DONE]" @@ -81,6 +77,14 @@ def to_json_stream(user_proc:) 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