Skip to content

Commit 0db1854

Browse files
committed
fix(codebuddy): 指纹中和支持文本块形态 content(B1.3 收尾)
2026-09-21 直证(28+8 请求交错对照,6s 间隔排除频率窗口假因): - 块形态 content([{type:text}])的指纹同样触发 11128——原实现只处理 字符串形态,块形态请求会绕过中和直接被拒 - 替换占位符后的块形态/system/assistant/billing 残留形态全部通过 - 小写变体也触发(上游大小写不敏感);glm-5.3-flash 复验与模型无关 sanitize_channel_markers 拆出 _neutralize_text,str 与文本块列表共用; 非文本块/非 str 块内文/user 角色照旧不动。
1 parent 616f854 commit 0db1854

3 files changed

Lines changed: 61 additions & 11 deletions

File tree

‎TECHNICAL.md‎

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -184,6 +184,8 @@ class ErrKind(StrEnum):
184184
> 会话一旦把指纹写进历史,后续每轮(含压缩请求本身)都持续 11128。
185185
> 处理:出站前 `sanitize_channel_markers` 把指纹替换为占位符
186186
> (`CODEBUDDY_SANITIZE_CHANNEL_MARKERS=false` 关闭),客户端会话历史不受影响。
187+
> `content` 为文本块列表(`[{"type": "text", ...}]`)时逐块处理:2026-09-21
188+
> 直证块形态指纹同样触发 11128,替换占位符后通过。
187189
> 曾误落 INVALID → 跳过上游全部凭证、零重试直接 400(`invalid_request`),是
188190
> deepseek-v4.1-flash / glm-5.3-flash 报「not available on any configured
189191
> upstream」的根因。

‎src/provider/codebuddy/client.py‎

Lines changed: 27 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -92,14 +92,25 @@ def _clean_history_tool_calls(body: dict[str, Any]) -> None:
9292
]
9393

9494

95+
def _neutralize_text(text: str) -> tuple[str, int]:
96+
"""单段正文的中和:返回(替换后文本, 命中处数)。"""
97+
hits = 0
98+
for marker in CHANNEL_MARKERS:
99+
if marker in text:
100+
hits += text.count(marker)
101+
text = text.replace(marker, _CHANNEL_MARKER_STANDIN)
102+
return text, hits
103+
104+
95105
def sanitize_channel_markers(body: dict[str, Any]) -> int:
96106
"""中和出站 system/assistant 正文里的「伪装其他厂商官方客户端」指纹。
97107
98108
上游内容风控(11128 Illegal API invocation)对该类指纹整单拒绝:
99109
与凭证无关(换号无效)、确定性复现、user/tool 角色 / tool_calls 参数 /
100110
reasoning 均不触发,仅 system/assistant 的 content 命中(2026-09 实测,
101-
见 TECHNICAL.md §3.2)。只改写出站副本,客户端会话历史保持原样。
102-
返回替换处数(用于日志与测试)。
111+
见 TECHNICAL.md §3.2)。content 为文本块列表({"type": "text", ...})
112+
时逐块处理——块形态指纹同样触发(2026-09-21 直证)。只改写出站副本,
113+
客户端会话历史保持原样。返回替换处数(用于日志与测试)。
103114
"""
104115
messages = body.get("messages")
105116
if not isinstance(messages, list):
@@ -111,15 +122,20 @@ def sanitize_channel_markers(body: dict[str, Any]) -> int:
111122
if message.get("role") not in ("system", "assistant"):
112123
continue
113124
content = message.get("content")
114-
if not isinstance(content, str) or not content:
115-
continue
116-
replaced = content
117-
for marker in CHANNEL_MARKERS:
118-
if marker in replaced:
119-
replaced_total += replaced.count(marker)
120-
replaced = replaced.replace(marker, _CHANNEL_MARKER_STANDIN)
121-
if replaced != content:
122-
message["content"] = replaced
125+
if isinstance(content, str):
126+
replaced, hits = _neutralize_text(content)
127+
if hits:
128+
replaced_total += hits
129+
message["content"] = replaced
130+
elif isinstance(content, list):
131+
for block in content:
132+
if not (isinstance(block, dict) and block.get("type") == "text"
133+
and isinstance(block.get("text"), str)):
134+
continue
135+
replaced, hits = _neutralize_text(block["text"])
136+
if hits:
137+
replaced_total += hits
138+
block["text"] = replaced
123139
return replaced_total
124140

125141

‎tests/test_m1b_codebuddy.py‎

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1806,6 +1806,38 @@ def test_sanitize_channel_markers_neutralizes_prompt_roles_only():
18061806
assert marker in args # tool_calls 参数不动
18071807

18081808

1809+
def test_sanitize_channel_markers_neutralizes_text_blocks():
1810+
"""content 为文本块列表时逐块中和(2026-09-21 实测块形态指纹同样 11128)。"""
1811+
from src.provider.codebuddy.client import CHANNEL_MARKERS, sanitize_channel_markers
1812+
1813+
marker = CHANNEL_MARKERS[0]
1814+
body = {"messages": [
1815+
{"role": "system", "content": [
1816+
{"type": "text", "text": f"前缀 {marker} 后缀"},
1817+
{"type": "text", "text": "无指纹"},
1818+
{"type": "image_url", "image_url": {"url": marker}}, # 非文本块不动
1819+
{"type": "text", "text": 123}, # 非 str 不动
1820+
"junk", # 非 dict 不动
1821+
]},
1822+
{"role": "assistant", "content": [
1823+
{"type": "text", "text": marker},
1824+
{"type": "text", "text": f"{marker} x{marker}"},
1825+
]},
1826+
{"role": "user", "content": [{"type": "text", "text": marker}]}, # user 不动
1827+
]}
1828+
assert sanitize_channel_markers(body) == 4
1829+
blocks = body["messages"][0]["content"]
1830+
assert blocks[0]["text"] == "前缀 [external-client-identity] 后缀"
1831+
assert blocks[1]["text"] == "无指纹"
1832+
assert blocks[2]["image_url"]["url"] == marker
1833+
assert blocks[3]["text"] == 123
1834+
assert blocks[4] == "junk"
1835+
assistant_blocks = body["messages"][1]["content"]
1836+
assert assistant_blocks[0]["text"] == "[external-client-identity]"
1837+
assert assistant_blocks[1]["text"] == "[external-client-identity] x[external-client-identity]"
1838+
assert body["messages"][2]["content"][0]["text"] == marker
1839+
1840+
18091841
def test_sanitize_channel_markers_counts_repeats_and_skips_noise():
18101842
"""同条消息多指纹多出处计数;非 list/非 dict/非 str content 安全跳过。"""
18111843
from src.provider.codebuddy.client import CHANNEL_MARKERS, sanitize_channel_markers

0 commit comments

Comments
 (0)