| 1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586 |
- 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"
- // OpenAIUpstreamStreamTruncatedCode is returned when an upstream SSE stream
- // closes *cleanly* before delivering any terminal signal. A clean EOF carries
- // no transport error, so without this classification a truncated generation is
- // indistinguishable from a successful one.
- OpenAIUpstreamStreamTruncatedCode = "upstream_stream_truncated"
- )
- // ErrOpenAIUpstreamStreamTruncated marks an upstream SSE stream that ended at
- // EOF — without a read error — before any terminal signal arrived.
- var ErrOpenAIUpstreamStreamTruncated = errors.New("upstream stream ended before any terminal chunk")
- 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 {
- if errors.Is(err, ErrOpenAIUpstreamStreamTruncated) {
- return OpenAIUpstreamStreamTruncatedCode, "Upstream response stream ended before completion"
- }
- 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"
- }
|