1010 zhipu → anthropic : prepare_zhipu_to_anthropic (剥离 thinking + tool pairing)
1111 zhipu → copilot : prepare_zhipu_to_copilot (剥离 thinking + cache_control + tool pairing)
1212 copilot → zhipu : prepare_copilot_to_zhipu (剥离 thinking + cache_control + 移除 thinking 参数 + tool pairing)
13+ anthropic → zhipu : prepare_anthropic_to_zhipu (剥离 server_tool_use + thinking + cache_control + 移除 thinking 参数 + tool pairing)
1314"""
1415
1516from __future__ import annotations
@@ -284,7 +285,13 @@ def _remove_vendor_blocks(body: dict[str, Any], block_types: set[str]) -> int:
284285 removed += 1
285286 continue
286287 new_content .append (block )
287- if removed :
288+ if content != new_content :
289+ if not new_content :
290+ new_content = [{"type" : "text" , "text" : "[vendor_block_removed]" }]
291+ logger .info (
292+ "Inserted placeholder text block after stripping "
293+ "vendor blocks to avoid empty message content" ,
294+ )
288295 message ["content" ] = new_content
289296 return removed
290297
@@ -358,16 +365,17 @@ def infer_source_vendor_from_body(body: dict[str, Any]) -> str | None:
358365 """从请求 body 内容推断源供应商(仅在无会话上下文时作为兜底).
359366
360367 启发式(按置信度排序):
361- - 出现 ``srvtoolu_*`` 格式的 ``tool_use.id`` → zhipu
362- - 出现 ``server_tool_use`` / ``server_tool_use_delta`` 类型的 content block → zhipu
368+ - 出现 ``srvtoolu_*`` 格式的 ID → zhipu
369+ - 出现 ``server_tool_use_delta`` 类型的 content block → zhipu
370+ - 出现 ``server_tool_use`` 块 + ``toolu_*`` ID → anthropic(beta 功能产物)
363371
364372 原则: 只读扫描不修改 body;无匹配返回 None(视作纯净无需跨供应商清洗)。
365373
366374 Args:
367375 body: Anthropic Messages 请求体。
368376
369377 Returns:
370- 推断的源供应商名称(当前仅支持 ``"zhipu"``),无法推断返回 None。
378+ 推断的源供应商名称(``"zhipu"`` 或 ``"anthropic "``),无法推断返回 None。
371379 """
372380 for message in body .get ("messages" , []):
373381 if not isinstance (message , dict ):
@@ -379,18 +387,35 @@ def infer_source_vendor_from_body(body: dict[str, Any]) -> str | None:
379387 if not isinstance (block , dict ):
380388 continue
381389 block_type = block .get ("type" )
382- if block_type in _ZHIPU_SERVER_TOOL_USE_TYPES :
383- return "zhipu"
384390 block_id = block .get ("id" )
391+ tool_use_id = block .get ("tool_use_id" )
392+
393+ # Zhipu: server_tool_use_delta 是 zhipu 私有流式块(无歧义)
394+ if block_type == "server_tool_use_delta" :
395+ return "zhipu"
396+
397+ # srvtoolu_* ID(无论 block type)→ zhipu
385398 if isinstance (block_id , str ) and _ANTHROPIC_SERVER_TOOL_USE_ID_RE .match (
386399 block_id
387400 ):
388401 return "zhipu"
389- tool_use_id = block .get ("tool_use_id" )
390402 if isinstance (tool_use_id , str ) and _ANTHROPIC_SERVER_TOOL_USE_ID_RE .match (
391403 tool_use_id
392404 ):
393405 return "zhipu"
406+
407+ # server_tool_use 块 + toolu_* ID → Anthropic beta 功能
408+ if (
409+ block_type == "server_tool_use"
410+ and isinstance (block_id , str )
411+ and _ANTHROPIC_TOOL_USE_ID_RE .match (block_id )
412+ ):
413+ return "anthropic"
414+
415+ # server_tool_use 块 + 非 toolu_/srvtoolu_ ID → 按类型兜底归 zhipu
416+ if block_type == "server_tool_use" :
417+ return "zhipu"
418+
394419 return None
395420
396421
@@ -438,6 +463,61 @@ def prepare_copilot_to_zhipu(
438463 return prepared , adaptations
439464
440465
466+ # ── anthropic → zhipu 转换通道 ────────────────────────────────────
467+
468+ # Anthropic beta 特有的 server_tool_use 块类型(web search, computer use 等).
469+ # 这些块在 Anthropic API 中有效,但 zhipu GLM-5 的兼容端点不支持。
470+ # 注意: 这与 zhipu 自己的 server_tool_use(使用 srvtoolu_* ID)是不同的概念,
471+ # 但它们共用同一个 type 名称 "server_tool_use"。
472+ _ANTHROPIC_BETA_BLOCK_TYPES = {"server_tool_use" }
473+
474+
475+ def prepare_anthropic_to_zhipu (
476+ body : dict [str , Any ],
477+ ) -> tuple [dict [str , Any ], list [str ]]:
478+ """anthropic → zhipu 转换: 清理 anthropic 产物以适配 GLM-5.
479+
480+ Anthropic API 可能产生的非兼容产物:
481+ - ``server_tool_use`` blocks(web search / computer use 等 beta 功能)
482+ - ``thinking`` / ``redacted_thinking`` blocks(含 Anthropic 签发的 signature)
483+ - ``cache_control`` 字段
484+ - 顶层 ``thinking`` / ``extended_thinking`` 参数
485+
486+ Returns:
487+ (prepared_body, adaptations) — adaptations 为应用的变换描述列表。
488+ """
489+ prepared = copy .deepcopy (body )
490+ adaptations : list [str ] = []
491+
492+ # Step 1: 剥离 anthropic 的 server_tool_use blocks(web search, computer use 等)
493+ removed_stu = _remove_vendor_blocks (prepared , _ANTHROPIC_BETA_BLOCK_TYPES )
494+ if removed_stu :
495+ adaptations .append (f"removed_{ removed_stu } _server_tool_use_blocks" )
496+
497+ # Step 2: 剥离 thinking/redacted_thinking blocks
498+ stripped = strip_thinking_blocks (prepared )
499+ if stripped :
500+ adaptations .append (f"stripped_{ stripped } _thinking_blocks" )
501+
502+ # Step 3: 移除 cache_control 字段
503+ removed_cc = _strip_cache_control (prepared )
504+ if removed_cc :
505+ adaptations .append (f"removed_{ removed_cc } _cache_control_fields" )
506+
507+ # Step 4: 移除顶层 thinking/extended_thinking 参数(GLM-5 不支持)
508+ for param in ("thinking" , "extended_thinking" ):
509+ if param in prepared :
510+ del prepared [param ]
511+ adaptations .append (f"removed_{ param } _param" )
512+
513+ # Step 5: 强制 tool_use/tool_result 配对
514+ pairing_fixes = enforce_anthropic_tool_pairing (prepared .get ("messages" , []))
515+ if pairing_fixes :
516+ adaptations .extend (pairing_fixes )
517+
518+ return prepared , adaptations
519+
520+
441521# ── zhipu → copilot 转换通道 ─────────────────────────────────────
442522
443523
@@ -544,3 +624,4 @@ def prepare_zhipu_to_anthropic(
544624VENDOR_TRANSITIONS [("zhipu" , "anthropic" )] = prepare_zhipu_to_anthropic
545625VENDOR_TRANSITIONS [("zhipu" , "copilot" )] = prepare_zhipu_to_copilot
546626VENDOR_TRANSITIONS [("copilot" , "zhipu" )] = prepare_copilot_to_zhipu
627+ VENDOR_TRANSITIONS [("anthropic" , "zhipu" )] = prepare_anthropic_to_zhipu
0 commit comments