flipped-aurora/gin-vue-admin · error
上游大模型流式服务返回非 2xx: status=%d content-type=%s body=%s
Error message
上游大模型流式服务返回非 2xx: status=%d content-type=%s body=%s
What it means
streamLLMAsSSE proxies a streaming LLM upstream to the client as SSE. When the upstream HTTP response status is outside 200-299, it treats the proxy as failed and returns this error including the upstream status, Content-Type and a truncated body preview so the caller can surface what the upstream rejected.
Source
Thrown at server/api/v1/system/sys_auto_code_sse.go:63
return
}
response.FailWithMessage(err.Error(), c)
}
}
func (autoApi *AutoCodeApi) streamLLMAsSSE(c *gin.Context, llm common.JSONMap) error {
res, err := autoCodeService.LLMAutoStream(c.Request.Context(), llm)
if err != nil {
return fmt.Errorf("调用上游大模型失败: %w", err)
}
defer res.Body.Close()
if res.StatusCode < 200 || res.StatusCode >= 300 {
body, readErr := io.ReadAll(res.Body)
if readErr != nil {
return fmt.Errorf("上游大模型流式服务返回非 2xx: status=%d content-type=%s read-body-err=%w", res.StatusCode, res.Header.Get("Content-Type"), readErr)
}
return fmt.Errorf("上游大模型流式服务返回非 2xx: status=%d content-type=%s body=%s", res.StatusCode, res.Header.Get("Content-Type"), previewResponseBody(body))
}
ct := res.Header.Get("Content-Type")
logger.WithCtx(c.Request.Context()).Mod("biz").Field("status", res.StatusCode).Field("content-type", ct).Info("LLMAutoSSE 上游返回成功,开始 SSE 流式转发")
// 如果上游返回的不是 SSE 流(可能是 blocking 模式返回的 JSON),直接读取并转发
if !strings.Contains(ct, "text/event-stream") && !strings.Contains(ct, "text/plain") {
body, readErr := io.ReadAll(res.Body)
if readErr != nil {
return fmt.Errorf("读取上游非流式响应失败: %w", readErr)
}
logger.WithCtx(c.Request.Context()).Mod("biz").Field("body_preview", previewResponseBody(body)).Warn("LLMAutoSSE 上游返回非 SSE 流,Content-Type: "+ct+", 将以单次事件转发")
flusher, ok := c.Writer.(http.Flusher)
if !ok {
return errors.New("当前响应不支持流式输出")
}
prepareSSEHeaders(c)View on GitHub (pinned to 3136500ef3)
Solutions
- Read the status and body preview in the error to identify the upstream rejection cause
- Verify the upstream API key/token configured for the LLM service is valid and not expired
- Check request payload (model name, params) matches the upstream API contract
- If 429, reduce request rate or wait and retry; if 5xx, check upstream service health
Example fix
// before
res, _ := http.DefaultClient.Do(req)
// after
res, err := http.DefaultClient.Do(req)
if err != nil { return err }
if res.StatusCode < 200 || res.StatusCode >= 300 {
b, _ := io.ReadAll(res.Body)
return fmt.Errorf("upstream %d: %s", res.StatusCode, string(b))
} Defensive patterns
Strategy: validation
Validate before calling
if res.StatusCode < 200 || res.StatusCode >= 300 {
b, _ := io.ReadAll(res.Body)
return fmt.Errorf("upstream %d: %s", res.StatusCode, string(b))
} Try / catch
if err := streamLLMAsSSE(c, ...); err != nil {
var statusErr *UpstreamStatusError
if errors.As(err, &statusErr) { /* inspect statusErr.Code/Body */ }
} Prevention
- Validate the LLM API key at service startup with a cheap health/list-models call
- Log upstream status and body preview on every non-2xx
- Set sensible request timeouts and rate limits to avoid 429s
When it happens
Trigger: The HTTP request to the LLM upstream completes but res.StatusCode < 200 or >= 300, e.g. upstream returns 401 for a bad/expired API key, 429 for rate limiting, or 500 from the model provider.
Common situations: Wrong or expired LLM API key in config; upstream service down or overloaded (5xx); request payload rejected (400 invalid model/params); quota exhausted (429).
Related errors
- 读取上游非流式响应失败: %w
- 调用上游大模型流式服务失败: %w
- LLM request failed
- result.Msg
- 上游大模型流式服务返回非 2xx: status=%d content-type=%s read-body-err=%w
AI-assisted analysis of flipped-aurora/gin-vue-admin@3136500ef3 (2026-08-31).
Data as JSON: /api/errors/873e2c456fd81444.
Report an issue: GitHub.