@@ -59,6 +59,7 @@ type LLMProxy struct {
5959 usecase domain.ProxyUsecase
6060 cfg * config.Config
6161 client * http.Client
62+ streamClient * http.Client
6263 logger * slog.Logger
6364 requestLogPath string // 请求日志保存路径
6465}
@@ -83,7 +84,6 @@ func NewLLMProxy(
8384 logger .Warn ("解析保持连接时间失败, 使用默认值 60s" , "error" , err )
8485 }
8586
86- // 创建HTTP客户端
8787 client := & http.Client {
8888 Timeout : timeout ,
8989 Transport : & http.Transport {
@@ -98,6 +98,18 @@ func NewLLMProxy(
9898 },
9999 }
100100
101+ streamClient := & http.Client {
102+ Timeout : 60 * time .Second ,
103+ Transport : & http.Transport {
104+ MaxIdleConns : cfg .LLMProxy .StreamClientPoolSize ,
105+ MaxConnsPerHost : cfg .LLMProxy .StreamClientPoolSize ,
106+ MaxIdleConnsPerHost : cfg .LLMProxy .StreamClientPoolSize ,
107+ IdleConnTimeout : 24 * time .Hour ,
108+ TLSHandshakeTimeout : 10 * time .Second ,
109+ ExpectContinueTimeout : 1 * time .Second ,
110+ },
111+ }
112+
101113 // 获取日志配置
102114 requestLogPath := ""
103115 if cfg .LLMProxy .RequestLogPath != "" {
@@ -111,6 +123,7 @@ func NewLLMProxy(
111123 return & LLMProxy {
112124 usecase : usecase ,
113125 client : client ,
126+ streamClient : streamClient ,
114127 cfg : cfg ,
115128 requestLogPath : requestLogPath ,
116129 logger : logger ,
@@ -174,12 +187,12 @@ type Ctx struct {
174187func (p * LLMProxy ) handle (ctx context.Context , fn func (ctx * Ctx , log * RequestResponseLog ) error ) {
175188 // 获取用户ID
176189 userID := "unknown"
177- if id , ok := ctx .Value (logger .UserIDKey ).(string ); ok {
190+ if id , ok := ctx .Value (logger.UserIDKey {} ).(string ); ok {
178191 userID = id
179192 }
180193
181194 requestID := "unknown"
182- if id , ok := ctx .Value (logger .RequestIDKey ).(string ); ok {
195+ if id , ok := ctx .Value (logger.RequestIDKey {} ).(string ); ok {
183196 requestID = id
184197 }
185198
@@ -203,11 +216,11 @@ func (p *LLMProxy) handle(ctx context.Context, fn func(ctx *Ctx, log *RequestRes
203216 }
204217
205218 if err := fn (c , l ); err != nil {
206- p .logger .With ("userID" , userID , "requestID" , requestID , "sourceip " , sourceip ).ErrorContext (ctx , "处理请求失败" , "error" , err )
219+ p .logger .With ("source_ip " , sourceip ).ErrorContext (ctx , "处理请求失败" , "error" , err )
207220 l .Error = err .Error ()
208221 }
209222
210- p .saveRequestResponseLog (l )
223+ go p .saveRequestResponseLog (l )
211224}
212225
213226func (p * LLMProxy ) HandleCompletion (ctx context.Context , w http.ResponseWriter , req domain.CompletionRequest ) {
@@ -585,10 +598,6 @@ func (p *LLMProxy) handleChatCompletionStream(ctx context.Context, w http.Respon
585598 return err
586599 }
587600
588- prompt := p .getPrompt (ctx , req )
589- mode := req .Metadata ["mode" ]
590- taskID := req .Metadata ["task_id" ]
591-
592601 upstream := m .APIBase + endpoint
593602 log .UpstreamURL = upstream
594603
@@ -606,9 +615,7 @@ func (p *LLMProxy) handleChatCompletionStream(ctx context.Context, w http.Respon
606615
607616 newReq .Header .Set ("Content-Type" , "application/json" )
608617 newReq .Header .Set ("Accept" , "text/event-stream" )
609- if m .APIKey != "" && m .APIKey != "none" {
610- newReq .Header .Set ("Authorization" , "Bearer " + m .APIKey )
611- }
618+ newReq .Header .Set ("Authorization" , "Bearer " + m .APIKey )
612619
613620 // 保存请求头(去除敏感信息)
614621 requestHeaders := make (map [string ][]string )
@@ -622,22 +629,26 @@ func (p *LLMProxy) handleChatCompletionStream(ctx context.Context, w http.Respon
622629 }
623630 log .RequestHeader = requestHeaders
624631
625- p .logger .With (
632+ logger := p .logger .With (
633+ "request_id" , c .RequestID ,
634+ "source_ip" , c .SourceIP ,
626635 "upstreamURL" , upstream ,
627636 "modelName" , m .ModelName ,
628637 "modelType" , consts .ModelTypeLLM ,
629638 "apiBase" , m .APIBase ,
630- "work_mode" , mode ,
639+ )
640+
641+ logger .With (
642+ "upstreamURL" , upstream ,
631643 "requestHeader" , newReq .Header ,
632644 "requestBody" , req ,
633- "taskID" , taskID ,
634645 "messages" , cvt .Filter (req .Messages , func (i int , v openai.ChatCompletionMessage ) (openai.ChatCompletionMessage , bool ) {
635646 return v , v .Role != "system"
636647 }),
637648 ).DebugContext (ctx , "转发流式请求到上游API" )
638649
639650 // 发送请求
640- resp , err := p .client .Do (newReq )
651+ resp , err := p .streamClient .Do (newReq )
641652 if err != nil {
642653 p .logger .With ("upstreamURL" , upstream ).WarnContext (ctx , "发送上游流式请求失败" , "error" , err )
643654 return fmt .Errorf ("发送上游请求失败: %w" , err )
@@ -655,17 +666,16 @@ func (p *LLMProxy) handleChatCompletionStream(ctx context.Context, w http.Respon
655666 log .Latency = time .Since (startTime ).Milliseconds ()
656667
657668 // 在debug级别记录错误的流式响应内容
658- p . logger .With (
669+ logger .With (
659670 "statusCode" , resp .StatusCode ,
660671 "responseHeader" , resp .Header ,
661672 "responseBody" , string (responseBody ),
662673 ).DebugContext (ctx , "上游流式响应错误原始内容" )
663674
664675 var errorResp ErrResp
665676 if err := json .Unmarshal (responseBody , & errorResp ); err == nil {
666- p . logger .With (
677+ logger .With (
667678 "endpoint" , endpoint ,
668- "upstreamURL" , upstream ,
669679 "requestBody" , newReq ,
670680 "statusCode" , resp .StatusCode ,
671681 "errorType" , errorResp .Error .Type ,
@@ -677,9 +687,8 @@ func (p *LLMProxy) handleChatCompletionStream(ctx context.Context, w http.Respon
677687 return fmt .Errorf ("上游API返回错误: %s" , errorResp .Error .Message )
678688 }
679689
680- p . logger .With (
690+ logger .With (
681691 "endpoint" , endpoint ,
682- "upstreamURL" , upstream ,
683692 "requestBody" , newReq ,
684693 "statusCode" , resp .StatusCode ,
685694 "responseBody" , string (responseBody ),
@@ -688,12 +697,10 @@ func (p *LLMProxy) handleChatCompletionStream(ctx context.Context, w http.Respon
688697 return fmt .Errorf ("上游API返回非200状态码: %d, 响应: %s" , resp .StatusCode , string (responseBody ))
689698 }
690699
691- // 更新日志信息
692700 log .StatusCode = resp .StatusCode
693701 log .ResponseHeader = resp .Header
694702
695- // 在debug级别记录流式响应头信息
696- p .logger .With (
703+ logger .With (
697704 "statusCode" , resp .StatusCode ,
698705 "responseHeader" , resp .Header ,
699706 ).DebugContext (ctx , "上游流式响应头信息" )
@@ -705,78 +712,18 @@ func (p *LLMProxy) handleChatCompletionStream(ctx context.Context, w http.Respon
705712 w .Header ().Set ("Transfer-Encoding" , "chunked" )
706713 w .Header ().Set ("X-Accel-Buffering" , "no" )
707714
708- rc := & domain.RecordParam {
709- RequestID : c .RequestID ,
710- TaskID : taskID ,
711- UserID : c .UserID ,
712- ModelID : m .ID ,
713- ModelType : consts .ModelTypeLLM ,
714- WorkMode : mode ,
715- Prompt : prompt ,
716- Role : consts .ChatRoleAssistant ,
717- }
718-
719- ch := make (chan []byte , 1024 )
720- defer close (ch )
721-
722- go func (rc * domain.RecordParam ) {
723- if rc .Prompt != "" {
724- urc := rc .Clone ()
725- urc .Role = consts .ChatRoleUser
726- urc .Completion = urc .Prompt
727- if err := p .usecase .Record (context .Background (), urc ); err != nil {
728- p .logger .With ("modelID" , m .ID , "modelName" , m .ModelName , "modelType" , consts .ModelTypeLLM ).
729- WarnContext (ctx , "插入流式记录失败" , "error" , err )
730- }
731- }
732-
733- for line := range ch {
734- if bytes .HasPrefix (line , []byte ("data:" )) {
735- line = bytes .TrimPrefix (line , []byte ("data: " ))
736- line = bytes .TrimSpace (line )
737- if len (line ) == 0 {
738- continue
739- }
740-
741- if bytes .Equal (line , []byte ("[DONE]" )) {
742- break
743- }
744-
745- var t openai.ChatCompletionStreamResponse
746- if err := json .Unmarshal (line , & t ); err != nil {
747- p .logger .With ("line" , string (line )).WarnContext (ctx , "解析流式数据失败" , "error" , err )
748- continue
749- }
750-
751- p .logger .With ("response" , t ).DebugContext (ctx , "流式响应数据" )
752- if len (t .Choices ) > 0 {
753- rc .Completion += t .Choices [0 ].Delta .Content
754- }
755- if t .Usage != nil {
756- rc .InputTokens = int64 (t .Usage .PromptTokens )
757- rc .OutputTokens = int64 (t .Usage .CompletionTokens )
758- }
759- }
760- }
761-
762- p .logger .With ("record" , rc ).DebugContext (ctx , "流式记录" )
763- if err := p .usecase .Record (context .Background (), rc ); err != nil {
764- p .logger .With ("modelID" , m .ID , "modelName" , m .ModelName , "modelType" , consts .ModelTypeLLM ).
765- WarnContext (ctx , "插入流式记录失败" , "error" , err )
766- }
767- }(rc )
768-
769- err = streamRead (ctx , resp .Body , func (line []byte ) error {
770- ch <- line
771- if _ , err := w .Write (line ); err != nil {
772- return fmt .Errorf ("写入响应失败: %w" , err )
773- }
774- if f , ok := w .(http.Flusher ); ok {
775- f .Flush ()
776- }
777- return nil
778- })
779- return err
715+ recorder := NewChatRecorder (
716+ ctx ,
717+ c ,
718+ p .usecase ,
719+ m ,
720+ req ,
721+ resp .Body ,
722+ w ,
723+ p .logger .With ("module" , "ChatRecorder" ),
724+ )
725+ defer recorder .Close ()
726+ return recorder .Stream ()
780727 })
781728}
782729
0 commit comments