fix(openai): support streaming image relay and image edit for images API - #4608
Conversation
|
Note Reviews pausedIt looks like this branch is under active development. To avoid overwhelming you with review comments due to an influx of new commits, CodeRabbit has automatically paused this review. You can configure this behavior by changing the Use the following commands to manage reviews:
Use the checkboxes below for quick actions:
WalkthroughImageRequest now honors JSON/multipart ChangesOpenAI Images Streaming Support
Sequence Diagram(s)sequenceDiagram
participant Client
participant NewAPI as "NewAPI Relay"
participant Upstream as "OpenAI Upstream"
Client->>NewAPI: POST /v1/images (stream=true / multipart)
NewAPI->>Upstream: proxy request
Upstream-->>NewAPI: text/event-stream (event/data SSE lines)
NewAPI->>Client: forward SSE lines (event/data), flush each
NewAPI->>NewAPI: parse data -> dto.SimpleResponse -> normalize usage
Upstream-->>NewAPI: data: [DONE] / EOF
NewAPI->>NewAPI: applyUsagePostProcessing(lastData) -> record usage
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes Possibly related PRs
Suggested reviewers
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✏️ Tip: You can configure your own custom pre-merge checks in the settings. ✨ Finishing Touches🧪 Generate unit tests (beta)
Warning Review ran into problems🔥 ProblemsGit: Failed to clone repository. Please run the Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@relay/channel/openai/image_stream_test.go`:
- Around line 16-21: The test TestOpenaiImageStreamHandlerForwardsSSEAndUsage
sets global Gin mode via gin.SetMode(gin.TestMode) but doesn't restore the
previous mode; update the test to capture the current mode (e.g., prevMode :=
gin.Mode()), call gin.SetMode(gin.TestMode) as done now, and add a t.Cleanup
closure that restores gin.SetMode(prevMode) alongside the existing
constant.StreamingTimeout restore so the global Gin mode is reverted after the
test.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 3b13492d-e5c4-4394-8191-5886936a74d5
📒 Files selected for processing (9)
dto/openai_image.godto/openai_image_test.gorelay/channel/openai/adaptor.gorelay/channel/openai/image_edit_test.gorelay/channel/openai/image_stream_test.gorelay/channel/openai/relay-openai.gorelay/helper/openai_image_request_test.gorelay/helper/stream_scanner.gorelay/helper/valid_request.go
2d27fba to
c98a5b4
Compare
|
本次修复重点考虑了计费逻辑,应该能较完满解决问题。 |
c98a5b4 to
55f3767
Compare
There was a problem hiding this comment.
Actionable comments posted: 1
🧹 Nitpick comments (1)
relay/helper/openai_image_request_test.go (1)
18-52: ⚡ Quick winLGTM — the happy-path multipart stream test is well-structured
The body-reusability assertion at lines 44–46 correctly verifies that
ParseMultipartFormReusablerestoresc.Request.Bodyafter first read. The subsequentParseMultipartFormReusable(c)call at line 48 correctly relies onc.Request.MultipartFormhaving been pre-populated byvalid_request.go(line 155), which prevents re-reading the already-drained body.Optional: The error branch at
valid_request.golines 164–166 (malformedstreamvalue → return error) has no test coverage. Consider adding:func TestGetAndValidOpenAIImageRequestMultipartStreamInvalidValue(t *testing.T) { gin.SetMode(gin.TestMode) var body bytes.Buffer writer := multipart.NewWriter(&body) require.NoError(t, writer.WriteField("model", "gpt-image-1")) require.NoError(t, writer.WriteField("stream", "notabool")) require.NoError(t, writer.Close()) recorder := httptest.NewRecorder() c, _ := gin.CreateTestContext(recorder) c.Request = httptest.NewRequest(http.MethodPost, "/v1/images/edits", &body) c.Request.Header.Set("Content-Type", writer.FormDataContentType()) _, err := GetAndValidOpenAIImageRequest(c, relayconstant.RelayModeImagesEdits) require.Error(t, err) require.Contains(t, err.Error(), "invalid stream value") }🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@relay/helper/openai_image_request_test.go` around lines 18 - 52, Add a negative test that covers the malformed "stream" value path in GetAndValidOpenAIImageRequest: create a multipart request with model "gpt-image-1" and stream set to "notabool", set Content-Type to the writer's FormDataContentType, call GetAndValidOpenAIImageRequest(c, relayconstant.RelayModeImagesEdits) and assert it returns an error containing "invalid stream value" (e.g., name the test TestGetAndValidOpenAIImageRequestMultipartStreamInvalidValue and mirror the existing multipart setup/recorder/gin context used in TestGetAndValidOpenAIImageRequestMultipartStream).
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@relay/channel/openai/relay-openai.go`:
- Around line 604-620: OpenaiImageStreamHandler currently forces SSE headers for
every response; instead guard by checking resp.StatusCode and
resp.Header["Content-Type"] (or resp.Header.Get("Content-Type")) for an SSE
content type and a successful status before calling
helper.SetEventStreamHeaders; if the response is non-200 or not SSE (e.g.,
OpenAI JSON error), do NOT set SSE headers—read the body, return the original
status and a types.NewOpenAIError (including the response body/error text) so
the client gets the real HTTP error, and keep defer
service.CloseResponseBodyGracefully(resp) and existing handling of
info.StreamStatus unchanged.
---
Nitpick comments:
In `@relay/helper/openai_image_request_test.go`:
- Around line 18-52: Add a negative test that covers the malformed "stream"
value path in GetAndValidOpenAIImageRequest: create a multipart request with
model "gpt-image-1" and stream set to "notabool", set Content-Type to the
writer's FormDataContentType, call GetAndValidOpenAIImageRequest(c,
relayconstant.RelayModeImagesEdits) and assert it returns an error containing
"invalid stream value" (e.g., name the test
TestGetAndValidOpenAIImageRequestMultipartStreamInvalidValue and mirror the
existing multipart setup/recorder/gin context used in
TestGetAndValidOpenAIImageRequestMultipartStream).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 8859bf41-d1cd-493f-921d-35f7672b79b3
📒 Files selected for processing (6)
relay/channel/openai/image_edit_test.gorelay/channel/openai/image_stream_test.gorelay/channel/openai/relay-openai.gorelay/helper/openai_image_request_test.gorelay/helper/stream_scanner.gorelay/helper/valid_request.go
✅ Files skipped from review due to trivial changes (1)
- relay/channel/openai/image_edit_test.go
🚧 Files skipped from review as they are similar to previous changes (1)
- relay/helper/stream_scanner.go
There was a problem hiding this comment.
♻️ Duplicate comments (2)
relay/channel/openai/image_stream_test.go (1)
19-23:⚠️ Potential issue | 🟡 Minor | ⚡ Quick winRestore Gin mode in test cleanup to avoid global-state leakage.
At Line 19, the test mutates global Gin mode but only restores
constant.StreamingTimeout. Please also restore the previous Gin mode int.Cleanup.Suggested fix
func TestOpenaiImageStreamHandlerForwardsSSEAndUsage(t *testing.T) { - gin.SetMode(gin.TestMode) + oldMode := gin.Mode() + gin.SetMode(gin.TestMode) + t.Cleanup(func() { gin.SetMode(oldMode) }) oldTimeout := constant.StreamingTimeout constant.StreamingTimeout = 30 t.Cleanup(func() { constant.StreamingTimeout = oldTimeout })#!/bin/bash # Verify gin mode mutation is paired with cleanup restore in this test file. rg -n -C2 'gin\.SetMode\(gin\.TestMode\)|gin\.Mode\(\)|t\.Cleanup\(func\(\)\s*\{\s*gin\.SetMode' relay/channel/openai/image_stream_test.go🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@relay/channel/openai/image_stream_test.go` around lines 19 - 23, The test sets global Gin mode via gin.SetMode(gin.TestMode) but only restores constant.StreamingTimeout in t.Cleanup, causing global-state leakage; update the cleanup to capture the original Gin mode with gin.Mode() before calling gin.SetMode and restore it inside the existing t.Cleanup alongside restoring constant.StreamingTimeout so both constant.StreamingTimeout and Gin's mode (captured via gin.Mode()) are reset after the test.relay/channel/openai/relay-openai.go (1)
616-617:⚠️ Potential issue | 🟠 Major | ⚡ Quick winPreserve upstream non-SSE/error responses before forcing SSE output.
At Line 616,
helper.SetEventStreamHeaders(c)is unconditional. If upstream returns non-2xx or JSON error, this path can rewrite the response contract and mask real error semantics.Suggested fix
func OpenaiImageStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Response) (*dto.Usage, *types.NewAPIError) { if resp == nil || resp.Body == nil { logger.LogError(c, "invalid image stream response") return nil, types.NewOpenAIError(fmt.Errorf("invalid response"), types.ErrorCodeBadResponse, http.StatusInternalServerError) } - defer service.CloseResponseBodyGracefully(resp) - - usage := &dto.Usage{} - var lastStreamData []byte - - helper.SetEventStreamHeaders(c) + contentType := strings.ToLower(resp.Header.Get("Content-Type")) + if resp.StatusCode < http.StatusOK || resp.StatusCode >= http.StatusMultipleChoices || + !strings.Contains(contentType, "text/event-stream") { + // keep non-stream/error behavior intact + return OpenaiHandlerWithUsage(c, info, resp) + } + defer service.CloseResponseBodyGracefully(resp) + + usage := &dto.Usage{} + var lastStreamData []byte + + helper.SetEventStreamHeaders(c)🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@relay/channel/openai/relay-openai.go` around lines 616 - 617, helper.SetEventStreamHeaders(c) is being called unconditionally which forces SSE headers even when the upstream response is non-2xx or contains a JSON error; change the flow so that before calling helper.SetEventStreamHeaders(c) you check the upstream status/response (use info.StreamStatus and any upstream response body/status) and only set SSE headers when the upstream returns a successful stream (e.g., 2xx and not an error JSON). In practice, move or gate the helper.SetEventStreamHeaders(c) call behind a conditional that verifies info.StreamStatus indicates a streaming success and avoid mutating headers when upstream returned an error status or non-SSE payload.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Duplicate comments:
In `@relay/channel/openai/image_stream_test.go`:
- Around line 19-23: The test sets global Gin mode via gin.SetMode(gin.TestMode)
but only restores constant.StreamingTimeout in t.Cleanup, causing global-state
leakage; update the cleanup to capture the original Gin mode with gin.Mode()
before calling gin.SetMode and restore it inside the existing t.Cleanup
alongside restoring constant.StreamingTimeout so both constant.StreamingTimeout
and Gin's mode (captured via gin.Mode()) are reset after the test.
In `@relay/channel/openai/relay-openai.go`:
- Around line 616-617: helper.SetEventStreamHeaders(c) is being called
unconditionally which forces SSE headers even when the upstream response is
non-2xx or contains a JSON error; change the flow so that before calling
helper.SetEventStreamHeaders(c) you check the upstream status/response (use
info.StreamStatus and any upstream response body/status) and only set SSE
headers when the upstream returns a successful stream (e.g., 2xx and not an
error JSON). In practice, move or gate the helper.SetEventStreamHeaders(c) call
behind a conditional that verifies info.StreamStatus indicates a streaming
success and avoid mutating headers when upstream returned an error status or
non-SSE payload.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: fcf2b9f7-9e88-48f3-9a4d-0169ef31bc39
📒 Files selected for processing (7)
dto/openai_image_test.gorelay/channel/openai/image_edit_test.gorelay/channel/openai/image_stream_test.gorelay/channel/openai/relay-openai.gorelay/helper/openai_image_request_test.gorelay/helper/stream_scanner.gorelay/helper/valid_request.go
✅ Files skipped from review due to trivial changes (3)
- dto/openai_image_test.go
- relay/channel/openai/image_edit_test.go
- relay/helper/valid_request.go
🚧 Files skipped from review as they are similar to previous changes (2)
- relay/helper/openai_image_request_test.go
- relay/helper/stream_scanner.go
cfc9a34 to
f706730
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
relay/channel/openai/image_stream_test.go (1)
51-61: ⚡ Quick win
data: [DONE]forwarding is not asserted.The test verifies that SSE event lines and the usage payload are forwarded, but it never checks that the stream-termination sentinel (
data: [DONE]) is present in the recorded body. The handler forwards all lines unconditionally, so this is a coverage gap rather than a production defect — but a targeted regression could silently remove[DONE]forwarding without tripping any existing assertion.✅ Proposed addition
require.Contains(t, recorder.Body.String(), `data: {"usage":{"input_tokens":3,"output_tokens":4,"total_tokens":7,"input_tokens_details":{"image_tokens":2,"text_tokens":1}}}`) +require.Contains(t, recorder.Body.String(), `data: [DONE]`) require.Equal(t, "text/event-stream", recorder.Header().Get("Content-Type"))🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed. In `@relay/channel/openai/image_stream_test.go` around lines 51 - 61, The test for OpenaiImageStreamHandler misses asserting that the SSE stream terminator "data: [DONE]" is forwarded; update the test in relay/channel/openai/image_stream_test.go to also assert that recorder.Body.String() (or equivalent recorded output) contains the literal `data: [DONE]`, ensuring the handler's forwarding of the stream-termination sentinel is covered along with the existing checks for partial_image events and usage payloads.
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Nitpick comments:
In `@relay/channel/openai/image_stream_test.go`:
- Around line 51-61: The test for OpenaiImageStreamHandler misses asserting that
the SSE stream terminator "data: [DONE]" is forwarded; update the test in
relay/channel/openai/image_stream_test.go to also assert that
recorder.Body.String() (or equivalent recorded output) contains the literal
`data: [DONE]`, ensuring the handler's forwarding of the stream-termination
sentinel is covered along with the existing checks for partial_image events and
usage payloads.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: d47a1817-cb81-45ea-86f1-80384ff32ed3
📒 Files selected for processing (7)
dto/openai_image_test.gorelay/channel/openai/image_edit_test.gorelay/channel/openai/image_stream_test.gorelay/channel/openai/relay-openai.gorelay/helper/openai_image_request_test.gorelay/helper/stream_scanner.gorelay/helper/valid_request.go
✅ Files skipped from review due to trivial changes (2)
- relay/channel/openai/image_edit_test.go
- relay/helper/stream_scanner.go
🚧 Files skipped from review as they are similar to previous changes (4)
- relay/helper/openai_image_request_test.go
- dto/openai_image_test.go
- relay/helper/valid_request.go
- relay/channel/openai/relay-openai.go
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@relay/channel/openai/relay-openai.go`:
- Around line 594-599: The normalization block copies several token subfields
from usage.InputTokensDetails to usage.PromptTokensDetails but omits
CachedCreationTokens, so add copying of that field as well: when
usage.InputTokensDetails != nil assign
usage.PromptTokensDetails.CachedCreationTokens =
usage.InputTokensDetails.CachedCreationTokens alongside the existing assignments
(i.e., update the block that references usage.InputTokensDetails and
usage.PromptTokensDetails to include CachedCreationTokens).
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: dc2ddfb7-4a53-4df2-b3fc-fde827b24322
📒 Files selected for processing (8)
dto/openai_image_test.gorelay/channel/openai/adaptor.gorelay/channel/openai/image_edit_test.gorelay/channel/openai/image_stream_test.gorelay/channel/openai/relay-openai.gorelay/helper/openai_image_request_test.gorelay/helper/stream_scanner.gorelay/helper/valid_request.go
✅ Files skipped from review due to trivial changes (3)
- relay/channel/openai/image_stream_test.go
- relay/helper/valid_request.go
- dto/openai_image_test.go
🚧 Files skipped from review as they are similar to previous changes (3)
- relay/channel/openai/image_edit_test.go
- relay/helper/stream_scanner.go
- relay/helper/openai_image_request_test.go
|
@coderabbitai resume |
✅ Actions performedReviews resumed. |
There was a problem hiding this comment.
Actionable comments posted: 1
🤖 Prompt for all review comments with AI agents
Verify each finding against the current code and only fix it if needed.
Inline comments:
In `@relay/channel/openai/image_edit_test.go`:
- Around line 75-118: The test
TestConvertImageEditRequestParsesReusableMultipartWhenFormIsMissing needs to
exercise the "stream" path as well: include the "stream" field in the multipart
body (e.g., add writer.WriteField("stream", "true")) and set the
dto.ImageRequest.Stream flag (e.g., request.Stream = true) before calling
(&Adaptor{}).ConvertImageRequest so the fallback reusable-multipart parsing
covers the stream branch.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 41d4fe8d-d518-4a7c-99e8-6a600afa91c6
📒 Files selected for processing (8)
dto/openai_image_test.gorelay/channel/openai/adaptor.gorelay/channel/openai/image_edit_test.gorelay/channel/openai/image_stream_test.gorelay/channel/openai/relay-openai.gorelay/helper/openai_image_request_test.gorelay/helper/stream_scanner.gorelay/helper/valid_request.go
✅ Files skipped from review due to trivial changes (2)
- dto/openai_image_test.go
- relay/helper/openai_image_request_test.go
🚧 Files skipped from review as they are similar to previous changes (4)
- relay/channel/openai/adaptor.go
- relay/channel/openai/image_stream_test.go
- relay/channel/openai/relay-openai.go
- relay/helper/valid_request.go
|
需要考虑一下增加stream的版本和没有增加stream之间的套娃,也就是当前端按流处理但是返回的端实际上是按非流处理的情况。 |
f706730 to
71a66dc
Compare
There was a problem hiding this comment.
🧹 Nitpick comments (1)
relay/channel/openai/image_stream_test.go (1)
18-63: 💤 Low value
OpenaiImageStreamHandlertest looks good overall — one minor coverage gap.The SSE body at line 31 sends usage as a bare
data:line (no precedingevent:line). If the actual OpenAI streaming API also delivers the usage event with an explicitevent: response.completedor similar prefix, the test would not exercise that code path. Consider adding a second sub-case (or a seconddata:event with anevent:prefix on the usage payload) to confirm the handler correctly extracts usage regardless of whether anevent:line precedes the usagedata:line.🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the rest with a brief reason, keep changes minimal, and validate. In `@relay/channel/openai/image_stream_test.go` around lines 18 - 63, The test TestOpenaiImageStreamHandlerForwardsSSEAndUsage only covers a usage payload sent as a bare `data:` line; add a second sub-case (or extend the test body) that sends the usage payload with a preceding `event:` line (e.g., `event: response.completed` followed by the `data: {...usage...}`) so OpenaiImageStreamHandler is exercised for the code path that parses usage when an `event:` prefix exists; update assertions to assert identical usage extraction and SSE forwarding behavior for this variant as well.
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Nitpick comments:
In `@relay/channel/openai/image_stream_test.go`:
- Around line 18-63: The test TestOpenaiImageStreamHandlerForwardsSSEAndUsage
only covers a usage payload sent as a bare `data:` line; add a second sub-case
(or extend the test body) that sends the usage payload with a preceding `event:`
line (e.g., `event: response.completed` followed by the `data: {...usage...}`)
so OpenaiImageStreamHandler is exercised for the code path that parses usage
when an `event:` prefix exists; update assertions to assert identical usage
extraction and SSE forwarding behavior for this variant as well.
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: 99a50d2e-ead7-4e65-9be3-54b151979831
📒 Files selected for processing (9)
dto/openai_image.godto/openai_image_test.gorelay/channel/openai/adaptor.gorelay/channel/openai/image_edit_test.gorelay/channel/openai/image_stream_test.gorelay/channel/openai/relay-openai.gorelay/helper/openai_image_request_test.gorelay/helper/stream_scanner.gorelay/helper/valid_request.go
✅ Files skipped from review due to trivial changes (1)
- relay/helper/openai_image_request_test.go
🚧 Files skipped from review as they are similar to previous changes (6)
- dto/openai_image_test.go
- relay/helper/valid_request.go
- relay/channel/openai/adaptor.go
- relay/channel/openai/relay-openai.go
- dto/openai_image.go
- relay/helper/stream_scanner.go
已补充处理“客户端按流式请求,但上游/下游旧版本实际返回非流 JSON”的套娃场景。 这次调整点:
可以避免前面已经按 SSE/ping 提交响应头后,后面又返回裸 JSON,导致客户端语义不一致或解析异常的问题。 新增测试:
已验证: go test ./relay/channel/openai -run 'TestOpenaiImageStreamHandler|TestNormalizeOpenAIUsage' |
There was a problem hiding this comment.
Actionable comments posted: 2
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.
Inline comments:
In `@relay/channel/openai/relay-openai.go`:
- Around line 707-744: The first-response time is being recorded after emitting
all image events and the final DONE in this OpenAI image streaming path; move
the call to info.SetFirstResponseTime() so it executes before emitting the first
event (i.e., place it immediately before the for _, image := range
imageResp.Data loop), and keep the rest of the info updates
(info.ReceivedResponseCount, StreamStatus) after the loop as-is; reference the
existing symbols info.SetFirstResponseTime(), info.ReceivedResponseCount, and
the for _, image := range imageResp.Data loop when making the change.
- Around line 629-668: Replace the bufio.Scanner usage that reads resp.Body with
a bufio.Reader and use ReadBytes('\n') or ReadString('\n') to stream lines so
you don't hit the fixed-size Scanner buffer limit; locate the loop that
currently uses scanner := bufio.NewScanner(resp.Body) and scanner.Scan(), change
it to create a reader (bufio.NewReader(resp.Body)) and read each line with
reader.ReadBytes('\n') (or ReadString), then keep the existing handling logic
for lines (checking strings.HasPrefix("data:"), updating
info.SetFirstResponseTime(), lastStreamData handling, usage
unmarshalling/normalizeOpenAIUsage, writing to c.Writer, helper.FlushWriter and
calling info.StreamStatus.SetEndReason(...) on errors) so behavior remains the
same but without the 64MB truncation risk.
🪄 Autofix (Beta)
Fix all unresolved CodeRabbit comments on this PR:
- Push a commit to this branch (recommended)
- Create a new PR with the fixes
ℹ️ Review info
⚙️ Run configuration
Configuration used: Organization UI
Review profile: CHILL
Plan: Pro
Run ID: f016d749-fddc-427c-8405-f778b782d87a
📒 Files selected for processing (2)
relay/channel/openai/image_stream_test.gorelay/channel/openai/relay-openai.go
🚧 Files skipped from review as they are similar to previous changes (1)
- relay/channel/openai/image_stream_test.go
|
改动内容:
|
| return &usageResp.Usage, nil | ||
| } | ||
|
|
||
| func openAIErrorStatusCode(oaiError *types.OpenAIError, fallback int) int { |
There was a problem hiding this comment.
加这个openAIErrorStatusCode代码是为了什么呢?
There was a problem hiding this comment.
不好意思佬,下午在忙,这里是基于我实际观察选择补充的代码。这里是为了处理上游 HTTP 200 但 body.error 非空的情况。图片链路的传输,容易遇到CF的120s超时,除了用流和partial_image作为解决方案,我观察到很多地方采用一种先发status 200,然后在body中持续传输空字节来规避超时的方法,但是如果后续出错status已经无法更改。我发现newapi的现有代码也有这种,status为200但body里面放了error的情况并不少见,因此引入了这种做法。例如:relay/channel/jimeng/image.go relay/channel/zhipu_4v/image.go 具体到这个函数就是在status为200的情况下从body里的error里面pick出对应的errorcode
There was a problem hiding this comment.
必须严格使用上游状态码,这部分代码是不能接受的,状态码为200的情况下,虽然body error status为错误状态,但是无法确认上游是否已经扣费,这种情况下进行重试,极有可能导致大额亏损
|
这个 PR 是个很好的功能,感谢贡献。我稍后整理确认后会合并。 有一点建议:这个 PR 里修改了较多与主题关联不强的内容,例如 responses 相关代码、stream scanner 相关调整等,整体涉及文件偏多,会增加维护者的审核负担。这可能也是 PR 一直搁置、没有维护者及时跟进的原因之一。 后续提交类似功能时,建议尽量把变更范围控制在当前功能本身:核心功能、必要的兼容处理、对应测试放在一个 PR;额外重构或无关修复可以拆成单独 PR。这样更容易 review,也更容易尽快合并 还有一点小建议:当前提交记录里不少 commit author 是 Codex codex@local,这会让提交历史看起来不太正式,也不利于后续追踪贡献者和排查问题。建议之后使用自己的 GitHub 用户名和邮箱提交,AI 可以辅助写代码,但最终提交身份最好保持为贡献者本人。 |
|
完了,又被遗忘了 @seefs001 @Calcium-Ion @t0ng7u @songquanpeng |
* fix(openai): support streaming image relay and image edit for images API (QuantumNous#4608) * fix(openai): support streaming image relay * fix(openai): keep image edit multipart body reusable * test(openai): cover image stream usage details * test(openai): cover image edit fallback stream field * fix(openai): wrap image json fallback as stream * fix(relay): support OpenAI image streaming * fix(openai): record image stream upstream error events * fix(openai): harden image stream relay * fix(openai): return image JSON errors * fix(relay): reset stream status per scanner run * fix(relay): drop upstream credit passthrough * fix(openai): keep image errors minimal * fix(openai): keep image error status from response --------- Co-authored-by: CaIon <i@caion.me> * fix: 适配上游合并测试文件 import 路径为本 fork module(newapi) 上游 QuantumNous#4608 新增的 openai 图片相关测试文件使用 github.com/QuantumNous/new-api 路径, 本 fork module 名为 newapi,统一替换为 newapi/ 前缀以恢复编译。 Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com> --------- Co-authored-by: gaoren002 <83566620+gaoren002@users.noreply.github.com> Co-authored-by: CaIon <i@caion.me> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
PR#3 以 squash 方式合并了上游 QuantumNous#4608(OpenAI 图片流式中继与编辑修复), 实际代码已在 main 中,但 squash 切断了到上游提交 d2576dd 的祖先链路, 导致比较工具按 SHA 仍认为该上游提交未合并。 此处用 -s ours 仅补记父链(不改动任何文件内容),恢复正常的上游合并记录。
…codes * origin/main: (45 commits) fix(openai): support streaming image relay and image edit for images API (QuantumNous#4608) perf(web): improve dialog sizing and footer layout feat(web): add shared dialog wrapper perf(web): simplify public page hero copy perf(model-pricing): move pricing tabs into page title feat(json-editor): add reusable JSON code editor perf(model-pricing): improve JSON pricing editor layout perf(model-pricing): reduce duplicate model name display fix: support six-decimal steps in model pricing editor fix: respect theme for multiselect combobox popover fix: reuse stream scanner buffer in channel handlers (QuantumNous#5225) fix: 收窄 OpenAI o 系列模型适配范围 (QuantumNous#5293) fix(i18n): clarify thinking adapter copy (QuantumNous#5242) fix: limit anonymous request body (QuantumNous#5244) fix(relay): fix Anthropic-compatible compatibility for GLM (avoid chunked encoding) (QuantumNous#5307) feat: 支持配置渠道被禁用后是否清空渠道粘性 (QuantumNous#5306) fix: add relay idle connection timeout config (QuantumNous#5309) feat(web): show user id on profile page perf(model-pricing): refine visual editor actions refactor(model-pricing): split visual pricing editor modules ... # Conflicts: # router/api-router.go # web/default/src/features/usage-logs/components/usage-logs-mobile-card.tsx
…API (QuantumNous#4608) * fix(openai): support streaming image relay * fix(openai): keep image edit multipart body reusable * test(openai): cover image stream usage details * test(openai): cover image edit fallback stream field * fix(openai): wrap image json fallback as stream * fix(relay): support OpenAI image streaming * fix(openai): record image stream upstream error events * fix(openai): harden image stream relay * fix(openai): return image JSON errors * fix(relay): reset stream status per scanner run * fix(relay): drop upstream credit passthrough * fix(openai): keep image errors minimal * fix(openai): keep image error status from response --------- Co-authored-by: CaIon <i@caion.me> (cherry picked from commit d2576dd)
…API (QuantumNous#4608) * fix(openai): support streaming image relay * fix(openai): keep image edit multipart body reusable * test(openai): cover image stream usage details * test(openai): cover image edit fallback stream field * fix(openai): wrap image json fallback as stream * fix(relay): support OpenAI image streaming * fix(openai): record image stream upstream error events * fix(openai): harden image stream relay * fix(openai): return image JSON errors * fix(relay): reset stream status per scanner run * fix(relay): drop upstream credit passthrough * fix(openai): keep image errors minimal * fix(openai): keep image error status from response --------- Co-authored-by: CaIon <i@caion.me>
…API (QuantumNous#4608) * fix(openai): support streaming image relay * fix(openai): keep image edit multipart body reusable * test(openai): cover image stream usage details * test(openai): cover image edit fallback stream field * fix(openai): wrap image json fallback as stream * fix(relay): support OpenAI image streaming * fix(openai): record image stream upstream error events * fix(openai): harden image stream relay * fix(openai): return image JSON errors * fix(relay): reset stream status per scanner run * fix(relay): drop upstream credit passthrough * fix(openai): keep image errors minimal * fix(openai): keep image error status from response --------- Co-authored-by: CaIon <i@caion.me>
Upstream changes (50+ commits, v1.0.0-rc.11): - data-table perf: row selection memo, column pinning, badge display - Fixes: channel test dialog (QuantumNous#5517), CC Switch model selector (QuantumNous#5515), API key form options (QuantumNous#5512), cell overflow (QuantumNous#5510), kimi k2.6 temp (QuantumNous#5390), Anthropic-compatible GLM chunked encoding (QuantumNous#5307), streaming image relay (QuantumNous#4608) - Feat: audit auth method tracking (QuantumNous#5462), channel affinity clear toggle (QuantumNous#5306), relay idle timeout config (QuantumNous#5309), 6-decimal pricing precision (QuantumNous#5332) - Classic frontend: Rsbuild support, Semi React 19 adapter - Shared dialog wrapper, JSON code editor, debounce channel search Conflict resolved: web/bun.lock (accepted upstream, will regenerate) Co-Authored-By: Claude <noreply@anthropic.com>
Merge upstream v1.0.0-rc.11 (50+ commits): - data-table perf: row selection memo, column pinning, badge display - Fixes: channel test dialog (QuantumNous#5517), CC Switch (QuantumNous#5515), API key (QuantumNous#5512), kimi k2.6 temp (QuantumNous#5390), GLM chunked encoding (QuantumNous#5307), streaming image (QuantumNous#4608) - Feat: audit auth tracking (QuantumNous#5462), channel affinity toggle (QuantumNous#5306), relay idle timeout (QuantumNous#5309), 6-decimal pricing (QuantumNous#5332) - Shared dialog wrapper, JSON code editor, classic Rsbuild support SEO optimization: - robots.txt: 10 AI crawler blocks + 22 path disallows + crawl-delay - sitemap.xml: 7 public URLs with 6-language hreflang annotations - index.html: hreflang tags, og:locale:alternate, og:image, canonical, 5 structured data types (Organization, SoftwareApplication, FAQPage, WebSite, SearchAction), expanded keywords (gateway, agent router, aggregation, orchestration) - i18n/config.ts: sync <html lang> with active language for SEO Co-Authored-By: Claude <noreply@anthropic.com>
…API (QuantumNous#4608) * fix(openai): support streaming image relay * fix(openai): keep image edit multipart body reusable * test(openai): cover image stream usage details * test(openai): cover image edit fallback stream field * fix(openai): wrap image json fallback as stream * fix(relay): support OpenAI image streaming * fix(openai): record image stream upstream error events * fix(openai): harden image stream relay * fix(openai): return image JSON errors * fix(relay): reset stream status per scanner run * fix(relay): drop upstream credit passthrough * fix(openai): keep image errors minimal * fix(openai): keep image error status from response --------- Co-authored-by: CaIon <i@caion.me>
…API (QuantumNous#4608) * fix(openai): support streaming image relay * fix(openai): keep image edit multipart body reusable * test(openai): cover image stream usage details * test(openai): cover image edit fallback stream field * fix(openai): wrap image json fallback as stream * fix(relay): support OpenAI image streaming * fix(openai): record image stream upstream error events * fix(openai): harden image stream relay * fix(openai): return image JSON errors * fix(relay): reset stream status per scanner run * fix(relay): drop upstream credit passthrough * fix(openai): keep image errors minimal * fix(openai): keep image error status from response --------- Co-authored-by: CaIon <i@caion.me>
…API (QuantumNous#4608) * fix(openai): support streaming image relay * fix(openai): keep image edit multipart body reusable * test(openai): cover image stream usage details * test(openai): cover image edit fallback stream field * fix(openai): wrap image json fallback as stream * fix(relay): support OpenAI image streaming * fix(openai): record image stream upstream error events * fix(openai): harden image stream relay * fix(openai): return image JSON errors * fix(relay): reset stream status per scanner run * fix(relay): drop upstream credit passthrough * fix(openai): keep image errors minimal * fix(openai): keep image error status from response --------- Co-authored-by: CaIon <i@caion.me>
…API (QuantumNous#4608) * fix(openai): support streaming image relay * fix(openai): keep image edit multipart body reusable * test(openai): cover image stream usage details * test(openai): cover image edit fallback stream field * fix(openai): wrap image json fallback as stream * fix(relay): support OpenAI image streaming * fix(openai): record image stream upstream error events * fix(openai): harden image stream relay * fix(openai): return image JSON errors * fix(relay): reset stream status per scanner run * fix(relay): drop upstream credit passthrough * fix(openai): keep image errors minimal * fix(openai): keep image error status from response --------- Co-authored-by: CaIon <i@caion.me>
Important
📝 变更描述 / Description
本 PR 修复 OpenAI 图片生成/编辑在
stream=true场景下没有按流式响应处理的问题。此前
ImageRequest.IsStream()固定返回false,即使客户端传入stream=true,图片接口仍会走非流式响应路径。对于耗时较长的图片生成/编辑请求,下游在上游完成前收不到任何响应数据,容易被 CDN / Cloudflare 等入口超时断开。本次改动让图片请求正确识别
stream参数,并在 OpenAI 图片生成与图片编辑接口中增加 SSE 转发处理:上游返回的event:/data:行会原样写回客户端,兼容image_generation.partial_image、完成事件和[DONE],同时从流式数据中提取 usage,保持计费链路可用。另外,图片编辑接口使用
multipart/form-data。校验阶段如果直接调用c.MultipartForm(),会消费原始请求体,后续转发到 OpenAI 时可能导致 multipart 内容不可复用。本 PR 改为使用可复用的 multipart 解析方式,并回填c.Request.MultipartForm/c.Request.PostForm,确保校验后仍能完整重建并转发图片、mask 和普通表单字段。🚀 变更类型 / Type of change
🔗 关联任务 / Related Issue
102 Processing为长 JSON 生成请求保活;本 PR 改为支持 OpenAI Images 原生 SSE 流式语义。✅ 提交前检查项 / Checklist
main。📸 运行证明 / Proof of Work
本地测试命令:
go test ./model ./controller ./relay/helper ./relay/channel/openai ./dto测试结果:
PR 分支:
提交:
Summary by CodeRabbit
New Features
Bug Fixes
Tests