From c0aab630a03ac6e8da34af8d9153120760591d3c Mon Sep 17 00:00:00 2001 From: ThreeFish Date: Thu, 4 Jun 2026 10:25:25 +0800 Subject: [PATCH] =?UTF-8?q?fix(session-title):=20=E4=BF=AE=E5=A4=8D=20=20=E6=A0=87=E7=AD=BE=E5=8C=85=E8=A3=B9=E7=94=A8=E6=88=B7?= =?UTF-8?q?=E6=96=87=E6=9C=AC=E5=AF=BC=E8=87=B4=E6=A0=87=E9=A2=98=E5=85=A8?= =?UTF-8?q?=E9=83=A8=E5=9B=9E=E9=80=80=E5=88=B0=E5=85=83=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E7=9A=84=E9=97=AE=E9=A2=98;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 当用户实际输入被 标签完整包裹时,首轮噪声剥离会将 整个 块连同用户文本一并删除,导致 Level 1 始终返回空。 新增二次回退机制:若首轮结果为空,仅去除 外壳标签, 保留内部文本后重新剥离噪声,确保用户文本可见。 🤖 Generated with [Claude Code](https://github.com/claude), [CodeX](https://openai.com), [Gemini](https://github.com/apps/gemini-code-assist) Co-Authored-By: Aurelius Huang --- src/coding/proxy/routing/executor.py | 31 +++++++++++++++++++++---- tests/test_router_executor.py | 34 ++++++++++++++++++++++++++++ 2 files changed, 61 insertions(+), 4 deletions(-) diff --git a/src/coding/proxy/routing/executor.py b/src/coding/proxy/routing/executor.py index f900a2d..20d9c51 100644 --- a/src/coding/proxy/routing/executor.py +++ b/src/coding/proxy/routing/executor.py @@ -71,6 +71,11 @@ flags=re.DOTALL | re.IGNORECASE, ) +# 标签需要特殊处理:当用户文本在 标签内部时, +# 完整块剥离会连同用户文本一起删除。此模式仅去除外壳标签(保留内容), +# 用于首轮完整剥离结果为空时的二次回退提取。 +_SESSION_TAG_WRAPPER = re.compile(r"]*>", flags=re.IGNORECASE) + # Slash command 子标签:用于识别 /commit、/review 等命令式调用, # 合成"命令 + 参数"式标题。 _CMD_NAME_PATTERN = re.compile(r"(.*?)", flags=re.DOTALL) @@ -80,6 +85,9 @@ r".*?", flags=re.DOTALL ) +# 空白折叠 +_WHITESPACE_PATTERN = re.compile(r"\s+") + def _sanitize_user_text(raw: str) -> str: """剔除 Claude Code 注入的系统级 XML 块,还原真实用户输入。 @@ -88,8 +96,10 @@ def _sanitize_user_text(raw: str) -> str: 1. Slash command 优先识别 — 若检测到 ,合成"命令 + 参数" 式标题(因为残留文本通常为空,直接取标签内容更有意义)。 2. 通用噪声剥离 — 移除已知白名单内的 system-reminder 等标签。 - 3. 残留 command-* 包裹清除 — 兜底去除 command-message 等次要标签。 - 4. 前后空白归一化 — 折叠连续空白为单空格,便于 30 字截断。 + 3. 二次回退 — 若首轮剥离后为空,说明用户文本可能在 + 标签内部;此时仅去除外壳标签,保留内部文本再做噪声剥离。 + 4. 残留 command-* 包裹清除 — 兜底去除 command-message 等次要标签。 + 5. 前后空白归一化 — 折叠连续空白为单空格。 """ if not raw: return "" @@ -107,9 +117,22 @@ def _sanitize_user_text(raw: str) -> str: # 阶段二: 通用噪声剥离 cleaned = _NOISE_TAG_PATTERN.sub("", raw) cleaned = _CMD_WRAPPER_PATTERN.sub("", cleaned) + cleaned = _WHITESPACE_PATTERN.sub(" ", cleaned).strip() + if cleaned: + return cleaned + + # 阶段三: 二次回退 + # 当首轮全部剥离为空时,用户文本很可能被 标签完整包裹。 + # 此时不去除 块,而是仅剥掉外壳标签,保留内部文本后重新剥离。 + if " 标签包裹用户文本的二次回退 ── + + def test_session_tag_wrapping_user_text(self): + """当 标签包裹用户文本时,二次回退应提取内部文本. + + 注: session 元数据可能残留在标题前部,但用户文本现在可见, + 远优于完全回退到 '[Session] model_name'. + """ + raw = "session metadata\n用户真实提问内容" + result = _sanitize_user_text(raw) + assert "用户真实提问内容" in result + + def test_session_tag_wrapping_with_inner_noise(self): + """ 内部混合噪声标签时,二次回退应正确剥离噪声.""" + raw = ( + "session_key: abc\n" + "噪声内容" + "用户真实输入" + "" + ) + result = _sanitize_user_text(raw) + assert "用户真实输入" in result + assert "噪声内容" not in result + + def test_session_tag_prefix_still_works(self): + """用户文本在 标签之后(原有行为)仍正确.""" + raw = "metadata用户文本在外部" + assert _sanitize_user_text(raw) == "用户文本在外部" + + def test_all_noise_inside_session_tag(self): + """ 内部全是噪声时,二次回退仍返回空.""" + raw = "纯噪声" + assert _sanitize_user_text(raw) == "" + class TestExtractSessionTitle: """``_extract_session_title`` — 端到端从 CanonicalRequest 抽取标题."""