Skip to content

Commit 35dee36

Browse files
authored
diag(executor): 为语义拒绝路径增加请求体可疑参数诊断日志; (#244)
在 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)
1 parent 5e1a51d commit 35dee36

1 file changed

Lines changed: 67 additions & 1 deletion

File tree

src/coding/proxy/routing/executor.py

Lines changed: 67 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,58 @@
4848
logger = logging.getLogger(__name__)
4949

5050

51+
def _build_semantic_rejection_diagnostic(body: dict[str, Any]) -> str:
52+
"""构建语义拒绝的请求体诊断上下文.
53+
54+
在 semantic rejection 日志中附加请求体的可疑参数快照,
55+
用于定位供应商参数校验失败的具体祸根参数。
56+
"""
57+
parts: list[str] = []
58+
# 顶层不兼容参数
59+
for key in ("thinking", "extended_thinking", "reasoning_effort"):
60+
if key in body:
61+
val = body[key]
62+
parts.append(f"{key}={val!r:.80}")
63+
# 会话历史中的 thinking blocks
64+
thinking_count = 0
65+
for msg in body.get("messages", []):
66+
content = msg.get("content")
67+
if not isinstance(content, list):
68+
continue
69+
for block in content:
70+
if isinstance(block, dict) and block.get("type") in (
71+
"thinking",
72+
"redacted_thinking",
73+
):
74+
thinking_count += 1
75+
if thinking_count:
76+
parts.append(f"thinking_blocks_in_history={thinking_count}")
77+
# cache_control 存在检测
78+
has_cc = False
79+
for section in (
80+
body.get("system", []) if isinstance(body.get("system"), list) else [],
81+
*(
82+
m.get("content", [])
83+
for m in body.get("messages", [])
84+
if isinstance(m.get("content"), list)
85+
),
86+
body.get("tools", []),
87+
):
88+
if isinstance(section, list):
89+
for item in section:
90+
if isinstance(item, dict) and "cache_control" in item:
91+
has_cc = True
92+
break
93+
if has_cc:
94+
break
95+
if has_cc:
96+
parts.append("cache_control_fields=present")
97+
# 模型 + 消息数
98+
parts.append(f"model={body.get('model', 'N/A')}")
99+
parts.append(f"messages={len(body.get('messages', []))}")
100+
return f" [{', '.join(parts)}]" if parts else ""
101+
102+
51103
def _log_http_error_detail(
52104
tier_name: str,
53105
exc: Exception,
@@ -601,12 +653,14 @@ async def execute_message(
601653
)
602654

603655
if not is_last and is_semantic:
656+
diagnostic = _build_semantic_rejection_diagnostic(body)
604657
logger.warning(
605-
"Tier %s semantic rejection (type=%s, msg=%s), "
658+
"Tier %s semantic rejection (type=%s, msg=%s)%s, "
606659
"trying next tier without recording failure",
607660
tier.name,
608661
resp.error_type or resp.status_code,
609662
(resp.error_message or "N/A")[:200],
663+
diagnostic,
610664
)
611665
failed_tier_name = tier.name
612666
continue
@@ -838,6 +892,18 @@ async def _handle_http_error(
838892
)
839893

840894
if semantic_rejection and not is_last:
895+
if request_body is not None:
896+
diagnostic = _build_semantic_rejection_diagnostic(request_body)
897+
logger.warning(
898+
"Tier %s stream semantic rejection (type=%s, msg=%s)%s, "
899+
"trying next tier without recording failure",
900+
tier.name,
901+
error.get("type") if isinstance(error, dict) else None,
902+
(error.get("message") if isinstance(error, dict) else "N/A")[
903+
:200
904+
],
905+
diagnostic,
906+
)
841907
return True, tier.name, exc
842908

843909
rl_info = parse_rate_limit_headers(

0 commit comments

Comments
 (0)