Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
74 changes: 74 additions & 0 deletions relay/channel/openai/helper.go
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package openai

import (
"encoding/json"
"fmt"
"strings"

Expand Down Expand Up @@ -242,3 +243,76 @@ func sendResponsesStreamData(c *gin.Context, streamResponse dto.ResponsesStreamR
}
_ = helper.ResponseChunkData(c, streamResponse, data)
}

// isOpenAITextStreamErrorChunk detects whether an SSE data payload represents a
// terminal error from the upstream (e.g. rate_limit_exceeded embedded in a
// streaming response with HTTP 200). Such chunks are not valid message content
// and, when detected before any data has been sent to the client, should abort
// the stream so the retry loop can try another channel.
//
// Detection logic (in order):
// 1. A top-level "error" field containing a JSON object with a non-empty message.
// 2. A "code" field whose value matches a known upstream failure code
// (rate_limit_exceeded, server_error, etc.).
// 3. A "type" field of "error" or "upstream_error".
func isOpenAITextStreamErrorChunk(data string) (bool, string) {
if data == "" {
return false, ""
}

var payload struct {
Error json.RawMessage `json:"error"`
Code string `json:"code"`
Type string `json:"type"`
Message string `json:"message"`
}
if err := common.UnmarshalJsonStr(data, &payload); err != nil {
return false, ""
}

// 1. Standard OpenAI error envelope: {"error": {"message": "...", ...}}
if len(payload.Error) > 0 {
var oaiErr types.OpenAIError
if err := common.Unmarshal(payload.Error, &oaiErr); err == nil && oaiErr.Message != "" {
return true, oaiErr.Message
}
}

// 2. Known failure codes embedded in SSE data chunks
if isKnownUpstreamErrorCode(payload.Code) {
msg := payload.Message
if msg == "" {
msg = fmt.Sprintf("upstream error code: %s", payload.Code)
}
return true, msg
}

// 3. Explicit error/upstream_error type
payloadType := strings.ToLower(strings.TrimSpace(payload.Type))
if payloadType == "error" || payloadType == "upstream_error" {
msg := payload.Message
if msg == "" {
msg = "upstream stream returned error event"
}
return true, msg
}

return false, ""
}
Comment on lines +258 to +301

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🎯 Functional Correctness | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
fd -t f 'json.go' common
rg -nP -A 20 'func UnmarshalJsonStr' common

Repository: QuantumNous/new-api

Length of output: 1015


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '--- openai helper relevant section ---\n'
sed -n '230,330p' relay/channel/openai/helper.go

printf '\n--- common/json.go ---\n'
sed -n '1,60p' common/json.go

printf '\n--- isKnownUpstreamErrorCode usages/implementation ---\n'
rg -n -B 5 -A 25 'func isKnownUpstream errorCode|isKnownUpstreamErrorCode' relay common || true

printf '\n--- deterministic Go stdlib partial field mismatch probe ---\n'
cat > /tmp/decode_probe.go <<'GO'
package main

import (
	"encoding/json"
	"fmt"
)

type Payload struct {
	Error   json.RawMessage `json:"error"`
	Code    string          `json:"code"`
	Type    string          `json:"type"`
	Message string          `json:"message"`
}

func main() {
	input := `{"error":{"message":"oops"},"code":42,"message":"msg"}`
	var p Payload
	err := json.Unmarshal([]byte(input), &p)
	fmt.Printf("input=%q\n", input)
	fmt.Printf("err=%T: %v\n", err, err)
	fmt.Printf("p.Error=%q\n", p.Error)
	fmt.Printf("len(p.Error)=%d\n", len(p.Error))
	fmt.Printf("p.Message=%q\n", p.Message)
}
GO
if command -v go >/dev/null 2>&1; then
  go run /tmp/decode_probe.go
else
  echo "go not available"
fi

Repository: QuantumNous/new-api

Length of output: 248


🏁 Script executed:

#!/bin/bash
set -euo pipefail

printf '%s\n' '--- openai helper relevant section ---'
sed -n '230,330p' relay/channel/openai/helper.go

printf '%s\n' ''
printf '%s\n' '--- common/json.go ---'
sed -n '1,60p' common/json.go

printf '%s\n' ''
printf '%s\n' '--- isKnownUpstreamErrorCode usages/implementation ---'
rg -n -B 5 -A 25 'func isKnownUpstream errorCode|isKnownUpstreamErrorCode' relay common || true

printf '%s\n' ''
printf '%s\n' '--- deterministic Go stdlib partial field mismatch probe ---'
cat > /tmp/decode_probe.go <<'GO'
package main

import (
	"encoding/json"
	"fmt"
)

type Payload struct {
	Error   json.RawMessage `json:"error"`
	Code    string          `json:"code"`
	Type    string          `json:"type"`
	Message string          `json:"message"`
}

func main() {
	input := `{"error":{"message":"oops"},"code":42,"message":"msg"}`
	var p Payload
	err := json.Unmarshal([]byte(input), &p)
	fmt.Printf("input=%q\n", input)
	fmt.Printf("err=%T: %v\n", err, err)
	fmt.Printf("p.Error=%q\n", p.Error)
	fmt.Printf("len(p.Error)=%d\n", len(p.Error))
	fmt.Printf("p.Message=%q\n", p.Message)
}
GO
if command -v go >/dev/null 2>&1; then
  go run /tmp/decode_probe.go
else
  echo "go not available"
fi

Repository: QuantumNous/new-api

Length of output: 7010


🏁 Script executed:

#!/bin/bash
set -euo pipefail

cat > /tmp/decode_probe2.go <<'GO'
package main

import (
	"encoding/json"
	"fmt"
	"strings"
)

type Payload struct {
	Error   json.RawMessage `json:"error"`
	Code    string          `json:"code"`
	Type    string          `json:"type"`
	Message string          `json:"message"`
}

func main() {
	inputs := []string{
		`{"error":{"message":"oops"},"code":42,"message":"msg"}`,
		`{"error":{"message":"oops"},"type":403,"message":"msg"}`,
		`{"error":{"message":"oops"},"code":"server_error","message":"msg"}`,
		`{"error":22,"code":"server_error","message":"msg"}`,
	}
	for _, s := range inputs {
		var p Payload
		err := json.Unmarshal([]byte(s), &p)
		fmt.Printf("err=%T: %v | Error=%q | Code=%q | Type=%q | Message=%q\n",
			err, err, p.Error, p.Code, p.Type, p.Message)

		after := strings.ToLower(strings.TrimSpace(p.Type))
		fmt.Printf("  after lower trim: %q matches error/upstream_error=%v\n", after, after == "error" || after == "upstream_error")
	}
}
GO
go run /tmp/decode_probe2.go

Repository: QuantumNous/new-api

Length of output: 921


Parse the top-level fields after unmarshaling instead of abandoning partial payloads.

UnmarshalJsonStr delegates to stdlib json.Unmarshal, which populates valid fields such as error.message before returning an UnmarshalTypeError for a mismatched field. When code or type is a number (e.g. 403), this helper returns false, "" and hides the OpenAI error envelope. Use json.RawMessage for optional top-level fields and parse only the fields needed by each detection path.

🤖 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/helper.go` around lines 258 - 301, The
isOpenAITextStreamErrorChunk helper currently discards partially unmarshaled
payloads when optional top-level code or type fields have incompatible JSON
types. Change the payload fields used for code, type, and message parsing to
json.RawMessage, allow unmarshaling to succeed for unrelated malformed fields,
and parse each raw field only within its corresponding detection path while
preserving standard error-envelope detection and known-code/type behavior.


// knownUpstreamErrorCodes lists SSE payload "code" values that represent a
// terminal upstream failure (not deliverable content).
var knownUpstreamErrorCodes = map[string]bool{
"rate_limit_exceeded": true,
"rate_limit_reached": true,
"server_error": true,
"internal_error": true,
"upstream_error": true,
}

func isKnownUpstreamErrorCode(code string) bool {
if code == "" {
return false
}
return knownUpstreamErrorCodes[code]
}
22 changes: 22 additions & 0 deletions relay/channel/openai/relay-openai.go
Original file line number Diff line number Diff line change
Expand Up @@ -144,9 +144,31 @@ func OaiStreamHandler(c *gin.Context, info *relaycommon.RelayInfo, resp *http.Re
logger.LogError(c, "error processing stream token data: "+err.Error())
sr.Error(err)
}

// Detect upstream business errors embedded in the SSE stream (e.g.
// rate_limit_exceeded). When detected before any data has been
// sent to the client (SendResponseCount == 0), abort the stream so
// the caller can retry with a different channel.
if info.SendResponseCount == 0 {
if isErr, errMsg := isOpenAITextStreamErrorChunk(data); isErr {
logger.LogError(c, "upstream stream returned error before sending data: "+errMsg)
sr.Stop(fmt.Errorf("upstream stream error: %s", errMsg))
return
}
}
}
})

// Return an error when the stream failed before any data was sent to the
// client so the retry loop can fall back to another channel.
if info.StreamStatus != nil && !info.StreamStatus.IsNormalEnd() && info.SendResponseCount == 0 {
return usage, types.NewOpenAIError(
fmt.Errorf("upstream stream failed before sending data: %s", info.StreamStatus.Summary()),
types.ErrorCodeBadResponseStatusCode,
http.StatusInternalServerError,
)
}

// 对音频模型,从倒数第二个stream data中提取usage信息
if isAudioModel && secondLastStreamData != "" {
var streamResp struct {
Expand Down
Loading