From fa0e8b4f25bb49a37adae9a223302250c4988436 Mon Sep 17 00:00:00 2001 From: ThreeFish Date: Sat, 23 May 2026 23:20:26 +0800 Subject: [PATCH] =?UTF-8?q?diag(executor):=20=E4=B8=BA=E8=AF=AD=E4=B9=89?= =?UTF-8?q?=E6=8B=92=E7=BB=9D=E8=B7=AF=E5=BE=84=E5=A2=9E=E5=8A=A0=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E4=BD=93=E5=8F=AF=E7=96=91=E5=8F=82=E6=95=B0=E8=AF=8A?= =?UTF-8?q?=E6=96=AD=E6=97=A5=E5=BF=97;?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 在 execute_message 和 execute_stream 的 semantic rejection 日志中 附加请求体参数快照(thinking/extended_thinking/reasoning_effort 顶层参数、 会话历史中 thinking blocks 数量、cache_control 存在情况、模型名、消息数), 用于定位 zhipu glm-4.7 [1210] 参数校验拒绝的具体祸根参数。 🤖 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 | 68 +++++++++++++++++++++++++++- 1 file changed, 67 insertions(+), 1 deletion(-) diff --git a/src/coding/proxy/routing/executor.py b/src/coding/proxy/routing/executor.py index 7eac6c3..74273af 100644 --- a/src/coding/proxy/routing/executor.py +++ b/src/coding/proxy/routing/executor.py @@ -48,6 +48,58 @@ logger = logging.getLogger(__name__) +def _build_semantic_rejection_diagnostic(body: dict[str, Any]) -> str: + """构建语义拒绝的请求体诊断上下文. + + 在 semantic rejection 日志中附加请求体的可疑参数快照, + 用于定位供应商参数校验失败的具体祸根参数。 + """ + parts: list[str] = [] + # 顶层不兼容参数 + for key in ("thinking", "extended_thinking", "reasoning_effort"): + if key in body: + val = body[key] + parts.append(f"{key}={val!r:.80}") + # 会话历史中的 thinking blocks + thinking_count = 0 + for msg in body.get("messages", []): + content = msg.get("content") + if not isinstance(content, list): + continue + for block in content: + if isinstance(block, dict) and block.get("type") in ( + "thinking", + "redacted_thinking", + ): + thinking_count += 1 + if thinking_count: + parts.append(f"thinking_blocks_in_history={thinking_count}") + # cache_control 存在检测 + has_cc = False + for section in ( + body.get("system", []) if isinstance(body.get("system"), list) else [], + *( + m.get("content", []) + for m in body.get("messages", []) + if isinstance(m.get("content"), list) + ), + body.get("tools", []), + ): + if isinstance(section, list): + for item in section: + if isinstance(item, dict) and "cache_control" in item: + has_cc = True + break + if has_cc: + break + if has_cc: + parts.append("cache_control_fields=present") + # 模型 + 消息数 + parts.append(f"model={body.get('model', 'N/A')}") + parts.append(f"messages={len(body.get('messages', []))}") + return f" [{', '.join(parts)}]" if parts else "" + + def _log_http_error_detail( tier_name: str, exc: Exception, @@ -601,12 +653,14 @@ async def execute_message( ) if not is_last and is_semantic: + diagnostic = _build_semantic_rejection_diagnostic(body) logger.warning( - "Tier %s semantic rejection (type=%s, msg=%s), " + "Tier %s semantic rejection (type=%s, msg=%s)%s, " "trying next tier without recording failure", tier.name, resp.error_type or resp.status_code, (resp.error_message or "N/A")[:200], + diagnostic, ) failed_tier_name = tier.name continue @@ -838,6 +892,18 @@ async def _handle_http_error( ) if semantic_rejection and not is_last: + if request_body is not None: + diagnostic = _build_semantic_rejection_diagnostic(request_body) + logger.warning( + "Tier %s stream semantic rejection (type=%s, msg=%s)%s, " + "trying next tier without recording failure", + tier.name, + error.get("type") if isinstance(error, dict) else None, + (error.get("message") if isinstance(error, dict) else "N/A")[ + :200 + ], + diagnostic, + ) return True, tier.name, exc rl_info = parse_rate_limit_headers(