From 30b22d376284f4d1fc4a99849074e52fcc850df4 Mon Sep 17 00:00:00 2001 From: ZhouYongyou <128128010+zhouyongyou@users.noreply.github.com> Date: Wed, 5 Nov 2025 00:32:48 +0800 Subject: [PATCH] =?UTF-8?q?fix(decision):=20extract=20fullwidth=20chars=20?= =?UTF-8?q?BEFORE=20regex=20matching=20=F0=9F=90=9B=20Problem:=20-=20AI=20?= =?UTF-8?q?returns=20JSON=20with=20fullwidth=20characters:=20=EF=BC=BB?= =?UTF-8?q?=EF=BD=9B=20-=20Regex=20\[=20cannot=20match=20fullwidth=20?= =?UTF-8?q?=EF=BC=BB=20-=20extractDecisions()=20fails=20with=20"=E6=97=A0?= =?UTF-8?q?=E6=B3=95=E6=89=BE=E5=88=B0JSON=E6=95=B0=E7=BB=84=E8=B5=B7?= =?UTF-8?q?=E5=A7=8B"=20=F0=9F=94=A7=20Root=20Cause:=20-=20fixMissingQuote?= =?UTF-8?q?s()=20was=20called=20AFTER=20regex=20matching=20-=20If=20regex?= =?UTF-8?q?=20fails=20to=20match=20fullwidth=20chars,=20fix=20function=20n?= =?UTF-8?q?ever=20executes=20=E2=9C=85=20Solution:=20-=20Call=20fixMissing?= =?UTF-8?q?Quotes(s)=20BEFORE=20regex=20matching=20(line=20461)=20-=20Conv?= =?UTF-8?q?ert=20fullwidth=20to=20halfwidth=20first:=20=EF=BC=BB=E2=86=92[?= =?UTF-8?q?,=20=EF=BD=9B=E2=86=92{=20-=20Then=20regex=20can=20successfully?= =?UTF-8?q?=20match=20the=20JSON=20array=20=F0=9F=93=8A=20Impact:=20-=20Fi?= =?UTF-8?q?xes=20"=E6=97=A0=E6=B3=95=E6=89=BE=E5=88=B0JSON=E6=95=B0?= =?UTF-8?q?=E7=BB=84=E8=B5=B7=E5=A7=8B"=20error=20-=20Supports=20AI=20resp?= =?UTF-8?q?onses=20with=20fullwidth=20JSON=20characters=20-=20Backward=20c?= =?UTF-8?q?ompatible=20with=20halfwidth=20JSON=20This=20fix=20is=20identic?= =?UTF-8?q?al=20to=20z-dev=20commit=203676cc0?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- decision/engine.go | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/decision/engine.go b/decision/engine.go index 572397b8..92aece8d 100644 --- a/decision/engine.go +++ b/decision/engine.go @@ -456,12 +456,16 @@ func extractDecisions(response string) ([]Decision, error) { s := removeInvisibleRunes(response) s = strings.TrimSpace(s) + // 🔧 關鍵修復:在正則匹配之前就先修復全角字符! + // 否則正則表達式 \[ 無法匹配全角的 [ + s = fixMissingQuotes(s) + // 1) 优先从 ```json 代码块中提取 reFence := regexp.MustCompile(`(?is)` + "```json\\s*(\\[\\s*\\{.*?\\}\\s*\\])\\s*```") if m := reFence.FindStringSubmatch(s); m != nil && len(m) > 1 { jsonContent := strings.TrimSpace(m[1]) jsonContent = compactArrayOpen(jsonContent) // 把 "[ {" 规整为 "[{" - jsonContent = fixMissingQuotes(jsonContent) + jsonContent = fixMissingQuotes(jsonContent) // 二次修復(防止 regex 提取後還有全角) if err := validateJSONFormat(jsonContent); err != nil { return nil, fmt.Errorf("JSON格式验证失败: %w\nJSON内容: %s\n完整响应:\n%s", err, jsonContent, response) } @@ -473,16 +477,16 @@ func extractDecisions(response string) ([]Decision, error) { } // 2) 退而求其次:全文寻找首个对象数组 + // 注意:此時 s 已經過 fixMissingQuotes(),全角字符已轉換為半角 reArray := regexp.MustCompile(`(?is)\[\s*\{.*?\}\s*\]`) jsonContent := strings.TrimSpace(reArray.FindString(s)) if jsonContent == "" { - return nil, fmt.Errorf("无法找到JSON数组") + return nil, fmt.Errorf("无法找到JSON数组起始(已嘗試修復全角字符)\n原始響應前200字符: %s", s[:min(200, len(s))]) } - // 🔧 先修复全角字符和引号问题(必须在验证之前!) - // 修复常见的JSON格式错误:全角字符、缺少引号的字段值等 + // 🔧 規整格式(此時全角字符已在前面修復過) jsonContent = compactArrayOpen(jsonContent) - jsonContent = fixMissingQuotes(jsonContent) + jsonContent = fixMissingQuotes(jsonContent) // 二次修復(防止 regex 提取後還有殘留全角) // 🔧 验证 JSON 格式(检测常见错误) if err := validateJSONFormat(jsonContent); err != nil {