{"record":{"id":"7a8243b98e04e8ee","repo":"flipped-aurora/gin-vue-admin","slug":"w-7a8243","errorCode":null,"errorMessage":"读取上游流式响应失败: %w","messagePattern":"读取上游流式响应失败: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"server/api/v1/system/sys_auto_code.go","lineNumber":209,"sourceCode":"\tc.Writer.Header().Set(\"Connection\", \"keep-alive\")\n\tc.Writer.Header().Set(\"X-Accel-Buffering\", \"no\")\n\tc.Status(res.StatusCode)\n\tflusher.Flush()\n\n\tbuf := make([]byte, 32*1024)\n\tfor {\n\t\tn, readErr := res.Body.Read(buf)\n\t\tif n > 0 {\n\t\t\tif _, writeErr := c.Writer.Write(buf[:n]); writeErr != nil {\n\t\t\t\treturn fmt.Errorf(\"向客户端写入流式响应失败: %w\", writeErr)\n\t\t\t}\n\t\t\tflusher.Flush()\n\t\t}\n\t\tif readErr != nil {\n\t\t\tif errors.Is(readErr, io.EOF) {\n\t\t\t\treturn nil\n\t\t\t}\n\t\t\treturn fmt.Errorf(\"读取上游流式响应失败: %w\", readErr)\n\t\t}\n\t}\n}\n\nfunc copyLLMStreamHeaders(dst, src http.Header) {\n\tfor _, key := range []string{\n\t\t\"Content-Type\",\n\t\t\"Cache-Control\",\n\t\t\"Content-Encoding\",\n\t\t\"Content-Language\",\n\t\t\"X-Accel-Buffering\",\n\t} {\n\t\tif value := src.Get(key); value != \"\" {\n\t\t\tdst.Set(key, value)\n\t\t}\n\t}\n}\n","sourceCodeStart":191,"sourceCodeEnd":227,"githubUrl":"https://github.com/flipped-aurora/gin-vue-admin/blob/3136500ef380842b0eb6c4daa87c3f8a47fcf9e0/server/api/v1/system/sys_auto_code.go#L191-L227","documentation":"After writing each chunk, proxyLLMStream checks the Read error from the upstream body. If it is not io.EOF (i.e. the stream ended abnormally rather than cleanly), it returns fmt.Errorf(\"读取上游流式响应失败: %w\", readErr) — 'failed to read upstream streaming response' — wrapping the underlying read error so the client-facing caller of LLMAuto knows the upstream stream broke before completion.","triggerScenarios":"Mid-stream, res.Body.Read returns a non-EOF error: upstream LLM connection reset, provider timeout, TLS interruption, proxy between server and provider cutting the chunked response, or the provider aborting an over-long generation.","commonSituations":"Very long completions exceed provider/proxy stream limits; unstable network to the LLM endpoint; upstream rate-limit kills active streams; keep-alive idle timeouts on the outbound HTTP transport.","solutions":["Inspect the wrapped error: 'connection reset by peer'/'unexpected EOF' indicates upstream/network breakage; 'context deadline exceeded' indicates a timeout.","Increase the outbound HTTP client's timeout / configure its Transport (idle conn, response header timeout) for long streams.","Retry the generation once on transient read failures; make the client resume or regenerate gracefully on truncated answers.","Check provider status pages/logs — repeated mid-stream resets often come from the LLM service itself or an intermediary proxy."],"exampleFix":"// before\nclient := &http.Client{} // default timeouts too tight for long streams\n\n// after\nclient := &http.Client{\n    Timeout: 0, // stream-managed\n    Transport: &http.Transport{ResponseHeaderTimeout: 60 * time.Second, IdleConnTimeout: 90 * time.Second},\n}","handlingStrategy":"retry","validationCode":null,"typeGuard":null,"tryCatchPattern":"err := api.proxyLLMStream(c, payload)\nif err != nil {\n    if strings.Contains(err.Error(), \"读取上游流式响应失败\") {\n        // truncated stream: notify client to regenerate\n        c.SSEvent(\"error\", \"upstream stream interrupted\")\n        return nil\n    }\n    return err\n}","preventionTips":["Configure the outbound http.Client Transport for long-lived streams (no overall timeout)","Retry generation once on transient mid-stream read errors","Alert on recurring 'unexpected EOF' patterns — usually provider-side","Keep partial output on truncation so users lose nothing already generated"],"tags":["network","streaming","llm","gin-vue-admin"],"backgroundTag":"upstream-stream-interrupted","analyzedSha":"3136500ef380842b0eb6c4daa87c3f8a47fcf9e0","analyzedAt":"2026-08-31T13:50:02.721Z","schemaVersion":2},"datasetVersion":"2026-08-31T19:17:28.585Z"}