flipped-aurora/gin-vue-admin · error
上游大模型流式服务返回非 2xx: status=%d content-type=%s read-body-err=%w
Error message
上游大模型流式服务返回非 2xx: status=%d content-type=%s read-body-err=%w
What it means
proxyLLMStream in server/api/v1/system/sys_auto_code.go forwards an SSE/HTTP stream from the upstream LLM service to the client. After issuing the request it checks res.StatusCode; anything outside 200-299 is a failure. If reading the error body also fails (io.ReadAll returns readErr), it returns an error wrapping the status code, the upstream Content-Type, and the body-read error via %w, so the true network/IO cause is preserved in the chain.
Source
Thrown at server/api/v1/system/sys_auto_code.go:174
return true
}
if stream, ok := llm["stream"].(bool); ok && stream {
return true
}
return strings.Contains(strings.ToLower(c.GetHeader("Accept")), "text/event-stream")
}
func (autoApi *AutoCodeApi) proxyLLMStream(c *gin.Context, llm common.JSONMap) error {
res, err := autoCodeService.LLMAutoStream(c.Request.Context(), llm)
if err != nil {
return 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))
}
flusher, ok := c.Writer.(http.Flusher)
if !ok {
return errors.New("当前响应不支持流式输出")
}
copyLLMStreamHeaders(c.Writer.Header(), res.Header)
if c.Writer.Header().Get("Content-Type") == "" {
c.Writer.Header().Set("Content-Type", "text/event-stream; charset=utf-8")
}
if c.Writer.Header().Get("Cache-Control") == "" {
c.Writer.Header().Set("Cache-Control", "no-cache")
}
c.Writer.Header().Set("Connection", "keep-alive")
c.Writer.Header().Set("X-Accel-Buffering", "no")View on GitHub (pinned to 3136500ef3)
Solutions
- Log and inspect the wrapped read-body-err to find why the body was unreadable (connection reset vs already-closed body).
- Check the LLM service configuration (base URL, API key) against the upstream provider docs; a 401/403 body is often dropped because the stream aborts.
- Retry the request once; transient resets on error responses are common behind proxies.
- If you control the code, treat non-2xx with unreadable body the same as the readable case: report only status + content-type instead of failing on the read error.
Example fix
// before
body, readErr := io.ReadAll(res.Body)
if readErr != nil {
return fmt.Errorf("...read-body-err=%w", res.StatusCode, ct, readErr)
}
// after
body, readErr := io.ReadAll(io.LimitReader(res.Body, 4096))
if readErr != nil {
return fmt.Errorf("upstream non-2xx: status=%d content-type=%s (body unreadable)", res.StatusCode, ct)
} Defensive patterns
Strategy: retry
Validate before calling
req, _ := http.NewRequestWithContext(ctx, http.MethodPost, llmURL, body)
resp, err := client.Do(req)
if err != nil { return err }
if resp.StatusCode < 200 || resp.StatusCode >= 300 {
return fmt.Errorf("LLM endpoint unhealthy: status=%d", resp.StatusCode)
} Try / catch
err := api.proxyLLMStream(c, payload)
if err != nil {
var upErr *url.Error
if errors.As(err, &upErr) || strings.Contains(err.Error(), "read-body-err") {
// transient upstream failure: retry once or return 502
}
return err
} Prevention
- Health-check the LLM endpoint at startup and on interval
- Use io.LimitReader when reading error bodies so truncation can't fail the report
- Retry transient non-2xx statuses (429/5xx) with backoff
- Log status+content-type even when the body is unreadable
When it happens
Trigger: Calling LLMAuto which reaches proxyLLMStream, and the upstream LLM HTTP call returns 4xx/5xx (bad API key, rate limit, upstream outage) AND the response body cannot be read — e.g. the connection was reset mid-body, the body was already consumed/closed, or a chunked stream aborted before any bytes arrived.
Common situations: Expired or wrong LLM_API_KEY configured so upstream returns 401 and closes the stream abruptly; upstream proxy/CDN kills the connection on error; misconfigured LLM base URL pointing at a service that hangs up; upstream returns 429 and closes the chunked body.
Related errors
- 上游大模型流式服务返回非 2xx: status=%d content-type=%s body=%s
- 读取上游流式响应失败: %w
- 调用上游大模型失败: %w
- 上游大模型流式服务返回非 2xx: status=%d content-type=%s read-body-err=%w
- LLM stream request timed out
AI-assisted analysis of flipped-aurora/gin-vue-admin@3136500ef3 (2026-08-31).
Data as JSON: /api/errors/e4ea4d007342ad4e.
Report an issue: GitHub.