{"record":{"id":"873e2c456fd81444","repo":"flipped-aurora/gin-vue-admin","slug":"2xx-status-d-content-type-s-body-873e2c","errorCode":null,"errorMessage":"上游大模型流式服务返回非 2xx: status=%d content-type=%s body=%s","messagePattern":"上游大模型流式服务返回非 2xx: status=(.+?) content-type=(.+?) body=(.+?)","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/api/v1/system/sys_auto_code_sse.go","lineNumber":63,"sourceCode":"\t\t\treturn\n\t\t}\n\t\tresponse.FailWithMessage(err.Error(), c)\n\t}\n}\n\nfunc (autoApi *AutoCodeApi) streamLLMAsSSE(c *gin.Context, llm common.JSONMap) error {\n\tres, err := autoCodeService.LLMAutoStream(c.Request.Context(), llm)\n\tif err != nil {\n\t\treturn fmt.Errorf(\"调用上游大模型失败: %w\", err)\n\t}\n\tdefer res.Body.Close()\n\n\tif res.StatusCode < 200 || res.StatusCode >= 300 {\n\t\tbody, readErr := io.ReadAll(res.Body)\n\t\tif readErr != nil {\n\t\t\treturn fmt.Errorf(\"上游大模型流式服务返回非 2xx: status=%d content-type=%s read-body-err=%w\", res.StatusCode, res.Header.Get(\"Content-Type\"), readErr)\n\t\t}\n\t\treturn fmt.Errorf(\"上游大模型流式服务返回非 2xx: status=%d content-type=%s body=%s\", res.StatusCode, res.Header.Get(\"Content-Type\"), previewResponseBody(body))\n\t}\n\n\tct := res.Header.Get(\"Content-Type\")\n\tlogger.WithCtx(c.Request.Context()).Mod(\"biz\").Field(\"status\", res.StatusCode).Field(\"content-type\", ct).Info(\"LLMAutoSSE 上游返回成功，开始 SSE 流式转发\")\n\n\t// 如果上游返回的不是 SSE 流（可能是 blocking 模式返回的 JSON），直接读取并转发\n\tif !strings.Contains(ct, \"text/event-stream\") && !strings.Contains(ct, \"text/plain\") {\n\t\tbody, readErr := io.ReadAll(res.Body)\n\t\tif readErr != nil {\n\t\t\treturn fmt.Errorf(\"读取上游非流式响应失败: %w\", readErr)\n\t\t}\n\t\tlogger.WithCtx(c.Request.Context()).Mod(\"biz\").Field(\"body_preview\", previewResponseBody(body)).Warn(\"LLMAutoSSE 上游返回非 SSE 流，Content-Type: \"+ct+\", 将以单次事件转发\")\n\n\t\tflusher, ok := c.Writer.(http.Flusher)\n\t\tif !ok {\n\t\t\treturn errors.New(\"当前响应不支持流式输出\")\n\t\t}\n\t\tprepareSSEHeaders(c)","sourceCodeStart":45,"sourceCodeEnd":81,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/api/v1/system/sys_auto_code_sse.go#L45-L81","documentation":"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.","triggerScenarios":"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.","commonSituations":"Wrong or expired LLM API key in config; upstream service down or overloaded (5xx); request payload rejected (400 invalid model/params); quota exhausted (429).","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"],"exampleFix":"// before\nres, _ := http.DefaultClient.Do(req)\n// after\nres, err := http.DefaultClient.Do(req)\nif err != nil { return err }\nif res.StatusCode < 200 || res.StatusCode >= 300 {\n    b, _ := io.ReadAll(res.Body)\n    return fmt.Errorf(\"upstream %d: %s\", res.StatusCode, string(b))\n}","handlingStrategy":"validation","validationCode":"if res.StatusCode < 200 || res.StatusCode >= 300 {\n    b, _ := io.ReadAll(res.Body)\n    return fmt.Errorf(\"upstream %d: %s\", res.StatusCode, string(b))\n}","typeGuard":null,"tryCatchPattern":"if err := streamLLMAsSSE(c, ...); err != nil {\n    var statusErr *UpstreamStatusError\n    if errors.As(err, &statusErr) { /* inspect statusErr.Code/Body */ }\n}","preventionTips":["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"],"tags":["http","sse","upstream","proxy"],"backgroundTag":"upstream-non-2xx-response","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}