@@ -59,10 +59,7 @@ func mergeToolCallDeltas(existing []schema.ToolCall, deltas []schema.ToolCall) [
5959// @Success 200 {object} schema.OpenAIResponse "Response"
6060// @Router /v1/chat/completions [post]
6161func ChatEndpoint (cl * config.ModelConfigLoader , ml * model.ModelLoader , evaluator * templates.Evaluator , startupOptions * config.ApplicationConfig , natsClient mcpTools.MCPNATSClient ) echo.HandlerFunc {
62- var id , textContentToReturn string
63- var created int
64-
65- process := func (s string , req * schema.OpenAIRequest , config * config.ModelConfig , loader * model.ModelLoader , responses chan schema.OpenAIResponse , extraUsage bool ) error {
62+ process := func (s string , req * schema.OpenAIRequest , config * config.ModelConfig , loader * model.ModelLoader , responses chan schema.OpenAIResponse , extraUsage bool , id string , created int ) error {
6663 initialMessage := schema.OpenAIResponse {
6764 ID : id ,
6865 Created : created ,
@@ -119,7 +116,7 @@ func ChatEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, evaluator
119116 close (responses )
120117 return err
121118 }
122- processTools := func (noAction string , prompt string , req * schema.OpenAIRequest , config * config.ModelConfig , loader * model.ModelLoader , responses chan schema.OpenAIResponse , extraUsage bool ) error {
119+ processTools := func (noAction string , prompt string , req * schema.OpenAIRequest , config * config.ModelConfig , loader * model.ModelLoader , responses chan schema.OpenAIResponse , extraUsage bool , id string , created int , textContentToReturn * string ) error {
123120 // Detect if thinking token is already in prompt or template
124121 var template string
125122 if config .TemplateConfig .UseTokenizerTemplate {
@@ -308,18 +305,18 @@ func ChatEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, evaluator
308305 xlog .Debug ("[ChatDeltas] Using pre-parsed tool calls from C++ autoparser" , "count" , len (deltaToolCalls ))
309306 functionResults = deltaToolCalls
310307 // Use content/reasoning from deltas too
311- textContentToReturn = functions .ContentFromChatDeltas (chatDeltas )
308+ * textContentToReturn = functions .ContentFromChatDeltas (chatDeltas )
312309 reasoning = functions .ReasoningFromChatDeltas (chatDeltas )
313310 } else {
314311 // Fallback: parse tool calls from raw text (no chat deltas from backend)
315312 xlog .Debug ("[ChatDeltas] no pre-parsed tool calls, falling back to Go-side text parsing" )
316313 reasoning = extractor .Reasoning ()
317314 cleanedResult := extractor .CleanedContent ()
318- textContentToReturn = functions .ParseTextContent (cleanedResult , config .FunctionsConfig )
315+ * textContentToReturn = functions .ParseTextContent (cleanedResult , config .FunctionsConfig )
319316 cleanedResult = functions .CleanupLLMResult (cleanedResult , config .FunctionsConfig )
320317 functionResults = functions .ParseFunctionCall (cleanedResult , config .FunctionsConfig )
321318 }
322- xlog .Debug ("[ChatDeltas] final tool call decision" , "tool_calls" , len (functionResults ), "text_content" , textContentToReturn )
319+ xlog .Debug ("[ChatDeltas] final tool call decision" , "tool_calls" , len (functionResults ), "text_content" , * textContentToReturn )
323320 noActionToRun := len (functionResults ) > 0 && functionResults [0 ].Name == noAction || len (functionResults ) == 0
324321
325322 switch {
@@ -412,7 +409,7 @@ func ChatEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, evaluator
412409 Choices : []schema.Choice {{
413410 Delta : & schema.Message {
414411 Role : "assistant" ,
415- Content : & textContentToReturn ,
412+ Content : textContentToReturn ,
416413 ToolCalls : []schema.ToolCall {
417414 {
418415 Index : i ,
@@ -437,9 +434,9 @@ func ChatEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, evaluator
437434 }
438435
439436 return func (c echo.Context ) error {
440- textContentToReturn = ""
441- id = uuid .New ().String ()
442- created = int (time .Now ().Unix ())
437+ var textContentToReturn string
438+ id : = uuid .New ().String ()
439+ created : = int (time .Now ().Unix ())
443440
444441 input , ok := c .Get (middleware .CONTEXT_LOCALS_KEY_LOCALAI_REQUEST ).(* schema.OpenAIRequest )
445442 if ! ok || input .Model == "" {
@@ -666,9 +663,9 @@ func ChatEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, evaluator
666663
667664 go func () {
668665 if ! shouldUseFn {
669- ended <- process (predInput , input , config , ml , responses , extraUsage )
666+ ended <- process (predInput , input , config , ml , responses , extraUsage , id , created )
670667 } else {
671- ended <- processTools (noActionName , predInput , input , config , ml , responses , extraUsage )
668+ ended <- processTools (noActionName , predInput , input , config , ml , responses , extraUsage , id , created , & textContentToReturn )
672669 }
673670 }()
674671
@@ -747,6 +744,14 @@ func ChatEndpoint(cl *config.ModelConfigLoader, ml *model.ModelLoader, evaluator
747744 }
748745 }
749746
747+ // Drain responses channel to unblock the background goroutine if it's
748+ // still trying to send (e.g., after client disconnect). The goroutine
749+ // calls close(responses) when done, which terminates the drain.
750+ if input .Context .Err () != nil {
751+ go func () { for range responses {} }()
752+ <- ended
753+ }
754+
750755 // MCP streaming tool execution: if we collected MCP tool calls, execute and loop
751756 if hasMCPToolsStream && toolsCalled && len (collectedToolCalls ) > 0 {
752757 var hasMCPCalls bool
0 commit comments