Spaces:
Paused
Paused
fix: improve streaming tool_call block detection and add debug logging
Browse filesSupport multiple close markers (</tool_call>, </think>, next <tool_call>)
when detecting tool call block boundaries in streaming mode. Add debug
logging for stream payload inspection.
- internal/handler/chat.go +41 -4
internal/handler/chat.go
CHANGED
|
@@ -101,6 +101,7 @@ func handleStreamResponse(w http.ResponseWriter, body io.ReadCloser, completionI
|
|
| 101 |
logger.LogDebug("[Upstream] %s", line)
|
| 102 |
|
| 103 |
if !strings.HasPrefix(line, "data: ") {
|
|
|
|
| 104 |
continue
|
| 105 |
}
|
| 106 |
|
|
@@ -111,9 +112,12 @@ func handleStreamResponse(w http.ResponseWriter, body io.ReadCloser, completionI
|
|
| 111 |
|
| 112 |
var upstreamData model.UpstreamData
|
| 113 |
if err := json.Unmarshal([]byte(payload), &upstreamData); err != nil {
|
|
|
|
| 114 |
continue
|
| 115 |
}
|
| 116 |
|
|
|
|
|
|
|
| 117 |
if upstreamData.Data.Phase == "done" {
|
| 118 |
break
|
| 119 |
}
|
|
@@ -424,14 +428,37 @@ func handleStreamResponse(w http.ResponseWriter, body io.ReadCloser, completionI
|
|
| 424 |
sendContentChunk(w, flusher, completionID, modelName, safeContent)
|
| 425 |
}
|
| 426 |
}
|
| 427 |
-
//
|
|
|
|
| 428 |
closeIdx := strings.Index(promptToolBuffer, "</tool_call>")
|
| 429 |
-
|
| 430 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 431 |
break
|
| 432 |
}
|
|
|
|
| 433 |
// 提取完整块
|
| 434 |
-
blockEnd := closeIdx + len("</tool_call>")
|
| 435 |
block := promptToolBuffer[:blockEnd]
|
| 436 |
promptToolBuffer = promptToolBuffer[blockEnd:]
|
| 437 |
|
|
@@ -586,9 +613,12 @@ func handleNonStreamResponse(w http.ResponseWriter, body io.ReadCloser, completi
|
|
| 586 |
|
| 587 |
var upstreamData model.UpstreamData
|
| 588 |
if err := json.Unmarshal([]byte(payload), &upstreamData); err != nil {
|
|
|
|
| 589 |
continue
|
| 590 |
}
|
| 591 |
|
|
|
|
|
|
|
| 592 |
if upstreamData.Data.Phase == "done" {
|
| 593 |
break
|
| 594 |
}
|
|
@@ -762,3 +792,10 @@ func sendContentChunk(w http.ResponseWriter, flusher http.Flusher, completionID,
|
|
| 762 |
fmt.Fprintf(w, "data: %s\n\n", data)
|
| 763 |
flusher.Flush()
|
| 764 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 101 |
logger.LogDebug("[Upstream] %s", line)
|
| 102 |
|
| 103 |
if !strings.HasPrefix(line, "data: ") {
|
| 104 |
+
logger.LogInfo("[DEBUG-Stream] non-data line: %s", truncate(line, 200))
|
| 105 |
continue
|
| 106 |
}
|
| 107 |
|
|
|
|
| 112 |
|
| 113 |
var upstreamData model.UpstreamData
|
| 114 |
if err := json.Unmarshal([]byte(payload), &upstreamData); err != nil {
|
| 115 |
+
logger.LogInfo("[DEBUG-Stream] JSON parse error: %v, payload=%s", err, truncate(payload, 300))
|
| 116 |
continue
|
| 117 |
}
|
| 118 |
|
| 119 |
+
logger.LogInfo("[DEBUG-Stream] phase=%s delta_content_len=%d edit_content_len=%d", upstreamData.Data.Phase, len(upstreamData.Data.DeltaContent), len(upstreamData.Data.EditContent))
|
| 120 |
+
|
| 121 |
if upstreamData.Data.Phase == "done" {
|
| 122 |
break
|
| 123 |
}
|
|
|
|
| 428 |
sendContentChunk(w, flusher, completionID, modelName, safeContent)
|
| 429 |
}
|
| 430 |
}
|
| 431 |
+
// 查找闭合标签:</tool_call>、</think>、或下一个 <tool_call>
|
| 432 |
+
afterOpen := promptToolBuffer[len("<tool_call>"):]
|
| 433 |
closeIdx := strings.Index(promptToolBuffer, "</tool_call>")
|
| 434 |
+
thinkCloseIdx := strings.Index(afterOpen, "</think>")
|
| 435 |
+
nextOpenIdx := strings.Index(afterOpen, "<tool_call>")
|
| 436 |
+
|
| 437 |
+
// 选择最近的闭合位置
|
| 438 |
+
blockEnd := -1
|
| 439 |
+
if closeIdx != -1 {
|
| 440 |
+
blockEnd = closeIdx + len("</tool_call>")
|
| 441 |
+
}
|
| 442 |
+
if thinkCloseIdx != -1 {
|
| 443 |
+
candidate := len("<tool_call>") + thinkCloseIdx + len("</think>")
|
| 444 |
+
if blockEnd == -1 || candidate < blockEnd {
|
| 445 |
+
blockEnd = candidate
|
| 446 |
+
}
|
| 447 |
+
}
|
| 448 |
+
if nextOpenIdx != -1 {
|
| 449 |
+
// 下一个 <tool_call> 隐式关闭当前块
|
| 450 |
+
candidate := len("<tool_call>") + nextOpenIdx
|
| 451 |
+
if blockEnd == -1 || candidate < blockEnd {
|
| 452 |
+
blockEnd = candidate
|
| 453 |
+
}
|
| 454 |
+
}
|
| 455 |
+
|
| 456 |
+
if blockEnd == -1 {
|
| 457 |
+
// 未找到任何闭合标记,等待更多数据
|
| 458 |
break
|
| 459 |
}
|
| 460 |
+
|
| 461 |
// 提取完整块
|
|
|
|
| 462 |
block := promptToolBuffer[:blockEnd]
|
| 463 |
promptToolBuffer = promptToolBuffer[blockEnd:]
|
| 464 |
|
|
|
|
| 613 |
|
| 614 |
var upstreamData model.UpstreamData
|
| 615 |
if err := json.Unmarshal([]byte(payload), &upstreamData); err != nil {
|
| 616 |
+
logger.LogInfo("[DEBUG-NonStream] JSON parse error: %v, payload=%s", err, truncate(payload, 200))
|
| 617 |
continue
|
| 618 |
}
|
| 619 |
|
| 620 |
+
logger.LogInfo("[DEBUG-NonStream] phase=%s delta_content_len=%d edit_content_len=%d", upstreamData.Data.Phase, len(upstreamData.Data.DeltaContent), len(upstreamData.Data.EditContent))
|
| 621 |
+
|
| 622 |
if upstreamData.Data.Phase == "done" {
|
| 623 |
break
|
| 624 |
}
|
|
|
|
| 792 |
fmt.Fprintf(w, "data: %s\n\n", data)
|
| 793 |
flusher.Flush()
|
| 794 |
}
|
| 795 |
+
|
| 796 |
+
func truncate(s string, maxLen int) string {
|
| 797 |
+
if len(s) <= maxLen {
|
| 798 |
+
return s
|
| 799 |
+
}
|
| 800 |
+
return s[:maxLen] + "..."
|
| 801 |
+
}
|