Sub2API v1.0 - AI API 网关(二开初始版本,基于上游 Wei-Shaw/sub2api)
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
Release / update-version (push) Has been cancelled
Release / build-frontend (push) Has been cancelled
Release / release (push) Has been cancelled
Release / sync-version-file (push) Has been cancelled
CI / shell (push) Canceled after 0s
CI / test (push) Canceled after 0s
CI / frontend (push) Canceled after 0s
CI / golangci-lint (push) Canceled after 0s
Security Scan / backend-security (push) Canceled after 0s
Security Scan / frontend-security (push) Canceled after 0s
This commit is contained in:
@@ -0,0 +1,74 @@
|
||||
package service
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
"strings"
|
||||
)
|
||||
|
||||
const (
|
||||
// OpenAIUpstreamHTTP2StreamErrorCode is returned to OpenAI-compatible clients
|
||||
// when an upstream HTTP/2 response stream is reset after the request started.
|
||||
OpenAIUpstreamHTTP2StreamErrorCode = "upstream_http2_stream_error"
|
||||
OpenAIUpstreamStreamReadErrorCode = "upstream_stream_read_error"
|
||||
)
|
||||
|
||||
type openAIUpstreamStreamReadError struct {
|
||||
cause error
|
||||
clientCode string
|
||||
clientMessage string
|
||||
}
|
||||
|
||||
func (e *openAIUpstreamStreamReadError) Error() string {
|
||||
return fmt.Sprintf("stream usage incomplete: %v", e.cause)
|
||||
}
|
||||
|
||||
func (e *openAIUpstreamStreamReadError) Unwrap() error { return e.cause }
|
||||
|
||||
func newOpenAIUpstreamStreamReadError(err error) error {
|
||||
code, message := classifyOpenAIUpstreamStreamReadError(err)
|
||||
return &openAIUpstreamStreamReadError{
|
||||
cause: err,
|
||||
clientCode: code,
|
||||
clientMessage: message,
|
||||
}
|
||||
}
|
||||
|
||||
// shouldClassifyOpenAIUpstreamStreamReadError excludes cancellation and
|
||||
// response-size enforcement from upstream retry.
|
||||
func shouldClassifyOpenAIUpstreamStreamReadError(err error, contexts ...context.Context) bool {
|
||||
if err == nil || errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) || errors.Is(err, ErrUpstreamResponseBodyTooLarge) {
|
||||
return false
|
||||
}
|
||||
for _, ctx := range contexts {
|
||||
if ctx != nil && ctx.Err() != nil {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
// OpenAIUpstreamStreamReadErrorDetails returns the stable, sanitized client
|
||||
// classification attached to an upstream stream read failure.
|
||||
func OpenAIUpstreamStreamReadErrorDetails(err error) (code, message string, ok bool) {
|
||||
var streamErr *openAIUpstreamStreamReadError
|
||||
if !errors.As(err, &streamErr) || streamErr == nil {
|
||||
return "", "", false
|
||||
}
|
||||
return streamErr.clientCode, streamErr.clientMessage, true
|
||||
}
|
||||
|
||||
func classifyOpenAIUpstreamStreamReadError(err error) (code, message string) {
|
||||
if err != nil {
|
||||
lower := strings.ToLower(err.Error())
|
||||
// net/http's HTTP/2 stream error is unexported. Its stable text contains
|
||||
// "stream error: stream ID ..."; match only the transport signature and
|
||||
// never pass the original text to the client.
|
||||
if strings.Contains(lower, "stream error: stream id ") ||
|
||||
(strings.Contains(lower, "http2:") && strings.Contains(lower, "stream")) {
|
||||
return OpenAIUpstreamHTTP2StreamErrorCode, "Upstream HTTP/2 stream failed"
|
||||
}
|
||||
}
|
||||
return OpenAIUpstreamStreamReadErrorCode, "Upstream response stream was interrupted"
|
||||
}
|
||||
Reference in New Issue
Block a user