fix output
This commit is contained in:
parent
a5d057f220
commit
670c1b37c1
|
|
@ -273,7 +273,7 @@ func LoadEnvConfig(e EnvSource, cwd string) LoadedEnv {
|
|||
TLSCertPath: resolveAbs(getEnvVal(e, []string{"CURSOR_BRIDGE_TLS_CERT"}), cwd),
|
||||
TLSKeyPath: resolveAbs(getEnvVal(e, []string{"CURSOR_BRIDGE_TLS_KEY"}), cwd),
|
||||
SessionsLogPath: sessionsLogPath,
|
||||
ChatOnlyWorkspace: envBool(e, []string{"CURSOR_BRIDGE_CHAT_ONLY_WORKSPACE"}, false),
|
||||
ChatOnlyWorkspace: envBool(e, []string{"CURSOR_BRIDGE_CHAT_ONLY_WORKSPACE"}, true),
|
||||
Verbose: envBool(e, []string{"CURSOR_BRIDGE_VERBOSE"}, false),
|
||||
MaxMode: envBool(e, []string{"CURSOR_BRIDGE_MAX_MODE"}, false),
|
||||
ConfigDirs: configDirs,
|
||||
|
|
|
|||
|
|
@ -167,12 +167,14 @@ func HandleAnthropicMessages(w http.ResponseWriter, r *http.Request, cfg config.
|
|||
})
|
||||
|
||||
if hasTools {
|
||||
// tools 模式:先即時串流文字,一旦偵測到 tool call 標記就切換為累積模式
|
||||
toolCallMarkerRe := regexp.MustCompile(`<tool_call>|<function_calls>`)
|
||||
toolCallMarkerRe := regexp.MustCompile(`行政法规|<function_calls>`)
|
||||
var toolCallMode bool
|
||||
|
||||
textBlockOpen := false
|
||||
textBlockIndex := 0
|
||||
thinkingOpen := false
|
||||
thinkingBlockIndex := 0
|
||||
blockCount := 0
|
||||
|
||||
p = parser.CreateStreamParserWithThinking(
|
||||
func(text string) {
|
||||
|
|
@ -188,17 +190,26 @@ func HandleAnthropicMessages(w http.ResponseWriter, r *http.Request, cfg config.
|
|||
writeEvent(map[string]interface{}{"type": "content_block_stop", "index": textBlockIndex})
|
||||
textBlockOpen = false
|
||||
}
|
||||
if thinkingOpen {
|
||||
writeEvent(map[string]interface{}{"type": "content_block_stop", "index": thinkingBlockIndex})
|
||||
thinkingOpen = false
|
||||
}
|
||||
toolCallMode = true
|
||||
return
|
||||
}
|
||||
if !textBlockOpen {
|
||||
textBlockIndex = 0
|
||||
if !textBlockOpen && !thinkingOpen {
|
||||
textBlockIndex = blockCount
|
||||
writeEvent(map[string]interface{}{
|
||||
"type": "content_block_start",
|
||||
"index": textBlockIndex,
|
||||
"content_block": map[string]string{"type": "text", "text": ""},
|
||||
})
|
||||
textBlockOpen = true
|
||||
blockCount++
|
||||
}
|
||||
if thinkingOpen {
|
||||
writeEvent(map[string]interface{}{"type": "content_block_stop", "index": thinkingBlockIndex})
|
||||
thinkingOpen = false
|
||||
}
|
||||
writeEvent(map[string]interface{}{
|
||||
"type": "content_block_delta",
|
||||
|
|
@ -208,23 +219,34 @@ func HandleAnthropicMessages(w http.ResponseWriter, r *http.Request, cfg config.
|
|||
},
|
||||
func(thinking string) {
|
||||
accumulatedThinking += thinking
|
||||
chunkNum++
|
||||
if toolCallMode {
|
||||
return
|
||||
}
|
||||
if !thinkingOpen {
|
||||
thinkingBlockIndex = blockCount
|
||||
writeEvent(map[string]interface{}{
|
||||
"type": "content_block_start",
|
||||
"index": thinkingBlockIndex,
|
||||
"content_block": map[string]string{"type": "thinking", "thinking": ""},
|
||||
})
|
||||
thinkingOpen = true
|
||||
blockCount++
|
||||
}
|
||||
writeEvent(map[string]interface{}{
|
||||
"type": "content_block_delta",
|
||||
"index": thinkingBlockIndex,
|
||||
"delta": map[string]string{"type": "thinking_delta", "thinking": thinking},
|
||||
})
|
||||
},
|
||||
func() {
|
||||
logger.LogTrafficResponse(cfg.Verbose, model, accumulated, true)
|
||||
parsed := toolcall.ExtractToolCalls(accumulated, toolNames)
|
||||
|
||||
blockIndex := 0
|
||||
if accumulatedThinking != "" {
|
||||
writeEvent(map[string]interface{}{
|
||||
"type": "content_block_start", "index": blockIndex,
|
||||
"content_block": map[string]string{"type": "thinking", "thinking": ""},
|
||||
})
|
||||
writeEvent(map[string]interface{}{
|
||||
"type": "content_block_delta", "index": blockIndex,
|
||||
"delta": map[string]string{"type": "thinking_delta", "thinking": accumulatedThinking},
|
||||
})
|
||||
writeEvent(map[string]interface{}{"type": "content_block_stop", "index": blockIndex})
|
||||
blockIndex++
|
||||
if thinkingOpen {
|
||||
writeEvent(map[string]interface{}{"type": "content_block_stop", "index": thinkingBlockIndex})
|
||||
thinkingOpen = false
|
||||
}
|
||||
|
||||
if parsed.HasToolCalls() {
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ func CreateStreamParser(onText func(string), onDone func()) Parser {
|
|||
// CreateStreamParserWithThinking 建立串流解析器,支援思考過程輸出。
|
||||
// onThinking 可為 nil,表示忽略思考過程。
|
||||
func CreateStreamParserWithThinking(onText func(string), onThinking func(string), onDone func()) Parser {
|
||||
// accumulated 是所有已輸出內容的串接
|
||||
accumulatedText := ""
|
||||
accumulatedThinking := ""
|
||||
done := false
|
||||
|
|
@ -58,37 +59,37 @@ func CreateStreamParserWithThinking(onText func(string), onThinking func(string)
|
|||
}
|
||||
}
|
||||
|
||||
// 處理思考過程 delta
|
||||
if onThinking != nil && fullThinking != "" {
|
||||
if fullThinking == accumulatedThinking {
|
||||
// 重複的完整思考文字,跳過
|
||||
} else if len(fullThinking) > len(accumulatedThinking) && fullThinking[:len(accumulatedThinking)] == accumulatedThinking {
|
||||
// 處理思考過程(不因去重而 return,避免跳過同行的文字內容)
|
||||
if onThinking != nil && fullThinking != "" && fullThinking != accumulatedThinking {
|
||||
// 增量模式:新內容以 accumulated 為前綴
|
||||
if len(fullThinking) >= len(accumulatedThinking) && fullThinking[:len(accumulatedThinking)] == accumulatedThinking {
|
||||
delta := fullThinking[len(accumulatedThinking):]
|
||||
if delta != "" {
|
||||
onThinking(delta)
|
||||
}
|
||||
accumulatedThinking = fullThinking
|
||||
} else {
|
||||
// 獨立片段:直接輸出,但 accumulated 要串接
|
||||
onThinking(fullThinking)
|
||||
accumulatedThinking += fullThinking
|
||||
accumulatedThinking = accumulatedThinking + fullThinking
|
||||
}
|
||||
}
|
||||
|
||||
// 處理一般文字 delta
|
||||
if fullText == "" {
|
||||
// 處理一般文字
|
||||
if fullText == "" || fullText == accumulatedText {
|
||||
return
|
||||
}
|
||||
// 若此訊息文字等於已累積內容(重複的完整文字),跳過
|
||||
if fullText == accumulatedText {
|
||||
return
|
||||
}
|
||||
// 若此訊息是已累積內容的延伸,只輸出新的 delta
|
||||
if len(fullText) > len(accumulatedText) && fullText[:len(accumulatedText)] == accumulatedText {
|
||||
// 增量模式:新內容以 accumulated 為前綴
|
||||
if len(fullText) >= len(accumulatedText) && fullText[:len(accumulatedText)] == accumulatedText {
|
||||
delta := fullText[len(accumulatedText):]
|
||||
if delta != "" {
|
||||
onText(delta)
|
||||
}
|
||||
accumulatedText = fullText
|
||||
} else {
|
||||
// 獨立的 token fragment(一般情況),直接輸出
|
||||
// 獨立片段:直接輸出,但 accumulated 要串接
|
||||
onText(fullText)
|
||||
accumulatedText += fullText
|
||||
accumulatedText = accumulatedText + fullText
|
||||
}
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -278,3 +278,27 @@ func TestStreamParserWithThinkingDeduplication(t *testing.T) {
|
|||
t.Fatalf("expected thinkings=['A','B'], got %v", thinkings)
|
||||
}
|
||||
}
|
||||
|
||||
// TestStreamParserThinkingDuplicateButTextStillEmitted 驗證 bug 修復:
|
||||
// 當 thinking 重複(去重跳過)但同一行有 text 時,text 仍必須輸出。
|
||||
func TestStreamParserThinkingDuplicateButTextStillEmitted(t *testing.T) {
|
||||
var texts []string
|
||||
var thinkings []string
|
||||
p := CreateStreamParserWithThinking(
|
||||
func(text string) { texts = append(texts, text) },
|
||||
func(thinking string) { thinkings = append(thinkings, thinking) },
|
||||
func() {},
|
||||
)
|
||||
|
||||
// 第一行:thinking="思考中" + text(thinking 為新增,兩者都應輸出)
|
||||
p.Parse(makeThinkingAndTextLine("思考中", "第一段"))
|
||||
// 第二行:thinking 與上一行相同(去重),但 text 是新的,text 仍應輸出
|
||||
p.Parse(makeThinkingAndTextLine("思考中", "第二段"))
|
||||
|
||||
if len(thinkings) != 1 || thinkings[0] != "思考中" {
|
||||
t.Fatalf("expected thinkings=['思考中'], got %v", thinkings)
|
||||
}
|
||||
if len(texts) != 2 || texts[0] != "第一段" || texts[1] != "第二段" {
|
||||
t.Fatalf("expected texts=['第一段','第二段'], got %v", texts)
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,8 @@ func (p *ParsedResponse) HasToolCalls() bool {
|
|||
return len(p.ToolCalls) > 0
|
||||
}
|
||||
|
||||
var toolCallTagRe = regexp.MustCompile(`(?s)<tool_call>\s*(\{.*?\})\s*</tool_call>`)
|
||||
// Modified regex to handle nested JSON
|
||||
var toolCallTagRe = regexp.MustCompile(`(?s)行政法规\s*(\{(?:[^{}]|\{[^{}]*\})*\})\s*ugalakh`)
|
||||
var antmlFunctionCallsRe = regexp.MustCompile(`(?s)<function_calls>\s*(.*?)\s*</function_calls>`)
|
||||
var antmlInvokeRe = regexp.MustCompile(`(?s)<invoke\s+name="([^"]+)">\s*(.*?)\s*</invoke>`)
|
||||
var antmlParamRe = regexp.MustCompile(`(?s)<parameter\s+name="([^"]+)">(.*?)</parameter>`)
|
||||
|
|
|
|||
Loading…
Reference in New Issue