{"record":{"id":"b96f44100884cf33","repo":"flipped-aurora/gin-vue-admin","slug":"error-b96f44","errorCode":null,"errorMessage":"当前响应不支持流式输出","messagePattern":"当前响应不支持流式输出","errorType":"error_code","errorClass":null,"httpStatus":500,"severity":"error","filePath":"server/api/v1/system/sys_auto_code_sse.go","lineNumber":79,"sourceCode":"\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)\n\t\tc.Status(http.StatusOK)\n\n\t\tvar payload any\n\t\tif err := json.Unmarshal(body, &payload); err != nil {\n\t\t\tpayload = string(body)\n\t\t}\n\t\tif err := renderSSE(c, sse.Event{Event: \"message\", Data: payload}); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tif err := renderSSE(c, sse.Event{Event: \"done\", Data: gin.H{\"done\": true}}); err != nil {\n\t\t\treturn err\n\t\t}\n\t\tflusher.Flush()\n\t\treturn nil\n\t}\n","sourceCodeStart":61,"sourceCodeEnd":97,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/api/v1/system/sys_auto_code_sse.go#L61-L97","documentation":"streamLLMAsSSE converts a non-stream upstream LLM response into a single SSE event. Before writing anything it asserts the gin ResponseWriter implements http.Flusher; if not, it returns '当前响应不支持流式输出' and no headers/status are sent.","triggerScenarios":"Upstream returned a non-SSE (buffered) response, the code reads it fully, then c.Writer.(http.Flusher) fails when preparing the single-event SSE reply.","commonSituations":"Middleware replacing Context.Writer with a non-Flushing wrapper; embedding gin handlers inside another framework (e.g. wrapped by prometheus/recovery writers); test recorders without Flush support.","solutions":["Ensure no middleware wraps c.Writer with a type lacking Flush on the LLMAutoSSE route","Make custom ResponseWriter wrappers embed gin.ResponseWriter so Flush is inherited","Run the route against the plain gin engine to isolate the wrapper causing it"],"exampleFix":null,"handlingStrategy":"validation","validationCode":"func assertFlushable(c *gin.Context) error {\n  if _, ok := c.Writer.(http.Flusher); !ok {\n    return errors.New(\"当前响应不支持流式输出: writer is \" + fmt.Sprintf(\"%T\", c.Writer))\n  }\n  return nil\n}\n// call at the top of any SSE handler","typeGuard":null,"tryCatchPattern":"if err := streamLLMAsSSE(c, upstream); err != nil {\n  if !c.Writer.Written() {\n    c.JSON(http.StatusInternalServerError, gin.H{\"code\": 7, \"msg\": err.Error()})\n  }\n}","preventionTips":["Run an integration test that streams LLMAutoSSE and asserts chunked flushing","Avoid global middleware that swaps Context.Writer (logging/recovery wrappers included)","Include the writer's concrete type in the error for faster diagnosis"],"tags":["gin","sse","streaming","backend","go"],"backgroundTag":"response-writer-not-flusher","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}