mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-02 01:10:23 +08:00
fix: Antigravity 跨格式转换链 tool call ID 完整传递
修复通过 Antigravity 适配器发送请求到 Claude 模型时 tool_use 块缺少必需 id 字段的问题。 根本原因:在跨格式转换链 (Gemini → Internal → Claude/Gemini) 中, functionCall 和 functionResponse 的 id 字段没有被正确传递。 修复内容: - internal.py: ToolResultBlock 增加 tool_name 字段,区分工具名称和 call id - gemini.py: 读取/输出 functionCall 和 functionResponse 时保留 id 字段 - gemini.py: 流式响应中正确传递 tool_id - envelope.py: 同时支持 camelCase/snake_case 两种命名风格
This commit is contained in:
@@ -106,7 +106,8 @@ class ToolResultBlock:
|
||||
"""工具结果内容块"""
|
||||
|
||||
type: ContentType = field(default=ContentType.TOOL_RESULT, init=False)
|
||||
tool_use_id: str = "" # 对应的 ToolUseBlock.tool_id
|
||||
tool_use_id: str = "" # 对应的 ToolUseBlock.tool_id(用于 Claude/Antigravity id 字段)
|
||||
tool_name: str | None = None # 工具名称(用于 Gemini function_response.name 字段)
|
||||
# 工具输出可能是纯文本,也可能是结构化 JSON(Gemini functionResponse 等)
|
||||
output: Any = None
|
||||
content_text: str | None = None
|
||||
|
||||
@@ -480,14 +480,14 @@ class GeminiNormalizer(FormatNormalizer):
|
||||
parts.append({"text": b.text})
|
||||
continue
|
||||
if isinstance(b, ToolUseBlock):
|
||||
parts.append(
|
||||
{
|
||||
"functionCall": {
|
||||
"name": b.tool_name,
|
||||
"args": b.tool_input or {},
|
||||
}
|
||||
}
|
||||
)
|
||||
fc: dict[str, Any] = {
|
||||
"name": b.tool_name,
|
||||
"args": b.tool_input or {},
|
||||
}
|
||||
# 保留 tool_id 用于 Claude/Antigravity 兼容
|
||||
if b.tool_id:
|
||||
fc["id"] = b.tool_id
|
||||
parts.append({"functionCall": fc})
|
||||
continue
|
||||
if isinstance(b, ImageBlock):
|
||||
if b.data and b.media_type:
|
||||
@@ -672,6 +672,10 @@ class GeminiNormalizer(FormatNormalizer):
|
||||
if not isinstance(args, dict):
|
||||
args = {}
|
||||
|
||||
# 优先使用 Antigravity/Claude 注入的 id
|
||||
fc_id = func_call.get("id")
|
||||
tool_id = fc_id if isinstance(fc_id, str) and fc_id else None
|
||||
|
||||
block_index = int(ss.get("next_block_index") or 2)
|
||||
ss["next_block_index"] = block_index + 1
|
||||
|
||||
@@ -679,7 +683,7 @@ class GeminiNormalizer(FormatNormalizer):
|
||||
ContentBlockStartEvent(
|
||||
block_index=block_index,
|
||||
block_type=ContentType.TOOL_USE,
|
||||
tool_id=None,
|
||||
tool_id=tool_id,
|
||||
tool_name=name or None,
|
||||
)
|
||||
)
|
||||
@@ -687,7 +691,7 @@ class GeminiNormalizer(FormatNormalizer):
|
||||
events.append(
|
||||
ToolCallDeltaEvent(
|
||||
block_index=block_index,
|
||||
tool_id="",
|
||||
tool_id=tool_id or "",
|
||||
input_delta=json.dumps(args, ensure_ascii=False),
|
||||
)
|
||||
)
|
||||
@@ -801,6 +805,7 @@ class GeminiNormalizer(FormatNormalizer):
|
||||
tool_blocks[int(event.block_index)] = {
|
||||
"name": event.tool_name or "",
|
||||
"json": "",
|
||||
"id": event.tool_id or "", # 保留 tool_id 用于 Claude/Antigravity 兼容
|
||||
}
|
||||
return out
|
||||
|
||||
@@ -842,6 +847,7 @@ class GeminiNormalizer(FormatNormalizer):
|
||||
|
||||
name = str(entry.get("name") or "")
|
||||
raw_json = str(entry.get("json") or "")
|
||||
tool_id = str(entry.get("id") or "")
|
||||
args: dict[str, Any] = {}
|
||||
if raw_json:
|
||||
try:
|
||||
@@ -851,7 +857,11 @@ class GeminiNormalizer(FormatNormalizer):
|
||||
except json.JSONDecodeError:
|
||||
args = {}
|
||||
|
||||
out.append(base_chunk([{"functionCall": {"name": name, "args": args}}]))
|
||||
fc: dict[str, Any] = {"name": name, "args": args}
|
||||
# 保留 tool_id 用于 Claude/Antigravity 兼容
|
||||
if tool_id:
|
||||
fc["id"] = tool_id
|
||||
out.append(base_chunk([{"functionCall": fc}]))
|
||||
return out
|
||||
|
||||
if isinstance(event, MessageStopEvent):
|
||||
@@ -1286,9 +1296,15 @@ class GeminiNormalizer(FormatNormalizer):
|
||||
args = func_call.get("args")
|
||||
if not isinstance(args, dict):
|
||||
args = {}
|
||||
# 优先使用 Antigravity/Claude 注入的 id,回退到生成的 id
|
||||
fc_id = func_call.get("id")
|
||||
if isinstance(fc_id, str) and fc_id:
|
||||
tool_id = fc_id
|
||||
else:
|
||||
tool_id = f"toolu_{name}" if name else "toolu_0"
|
||||
blocks.append(
|
||||
ToolUseBlock(
|
||||
tool_id=f"toolu_{name}" if name else "toolu_0",
|
||||
tool_id=tool_id,
|
||||
tool_name=name,
|
||||
tool_input=args,
|
||||
extra={"gemini": part},
|
||||
@@ -1314,9 +1330,17 @@ class GeminiNormalizer(FormatNormalizer):
|
||||
else:
|
||||
output = response
|
||||
|
||||
# 优先使用 Antigravity/Claude 注入的 id,回退到 name
|
||||
fr_id = func_resp.get("id")
|
||||
if isinstance(fr_id, str) and fr_id:
|
||||
tool_use_id = fr_id
|
||||
else:
|
||||
tool_use_id = name
|
||||
|
||||
blocks.append(
|
||||
ToolResultBlock(
|
||||
tool_use_id=name,
|
||||
tool_use_id=tool_use_id,
|
||||
tool_name=name or None, # 保留工具名称用于输出
|
||||
output=output,
|
||||
content_text=content_text,
|
||||
is_error=False,
|
||||
@@ -1372,11 +1396,16 @@ class GeminiNormalizer(FormatNormalizer):
|
||||
continue
|
||||
|
||||
if isinstance(b, ToolUseBlock) and role == "model":
|
||||
parts.append({"function_call": {"name": b.tool_name, "args": b.tool_input or {}}})
|
||||
fc: dict[str, Any] = {"name": b.tool_name, "args": b.tool_input or {}}
|
||||
# 保留 tool_id 用于 Claude/Antigravity 兼容
|
||||
if b.tool_id:
|
||||
fc["id"] = b.tool_id
|
||||
parts.append({"function_call": fc})
|
||||
continue
|
||||
|
||||
if isinstance(b, ToolResultBlock) and role == "user":
|
||||
# 兼容旧转换器:name 直接使用 tool_use_id,response 固定包一层 result
|
||||
# name 使用 tool_name(工具名称),id 使用 tool_use_id(call id)
|
||||
# 如果没有 tool_name,回退到 tool_use_id 以保持向后兼容
|
||||
value: Any
|
||||
if b.content_text is not None:
|
||||
value = b.content_text
|
||||
@@ -1385,14 +1414,16 @@ class GeminiNormalizer(FormatNormalizer):
|
||||
else:
|
||||
value = b.output
|
||||
|
||||
parts.append(
|
||||
{
|
||||
"function_response": {
|
||||
"name": b.tool_use_id,
|
||||
"response": {"result": value},
|
||||
}
|
||||
}
|
||||
)
|
||||
# 优先使用 tool_name 作为 name,回退到 tool_use_id
|
||||
name = b.tool_name if b.tool_name else b.tool_use_id
|
||||
fr: dict[str, Any] = {
|
||||
"name": name,
|
||||
"response": {"result": value},
|
||||
}
|
||||
# 保留 tool_use_id 作为 id 用于 Claude/Antigravity 兼容
|
||||
if b.tool_use_id:
|
||||
fr["id"] = b.tool_use_id
|
||||
parts.append({"function_response": fr})
|
||||
continue
|
||||
|
||||
return {"role": role, "parts": parts}
|
||||
|
||||
@@ -161,8 +161,8 @@ def _inject_claude_tool_ids_request(inner_request: dict[str, Any], model: str) -
|
||||
if not isinstance(part, dict):
|
||||
continue
|
||||
|
||||
# 1. functionCall(Assistant 请求调用工具)
|
||||
fc = part.get("functionCall")
|
||||
# 1. functionCall(Assistant 请求调用工具)- 支持 camelCase 和 snake_case
|
||||
fc = part.get("functionCall") or part.get("function_call")
|
||||
if isinstance(fc, dict) and fc.get("id") is None:
|
||||
name = fc.get("name", "unknown")
|
||||
if not isinstance(name, str):
|
||||
@@ -171,8 +171,8 @@ def _inject_claude_tool_ids_request(inner_request: dict[str, Any], model: str) -
|
||||
fc["id"] = f"call_{name}_{count}"
|
||||
name_counters[name] = count + 1
|
||||
|
||||
# 2. functionResponse(User 回复工具结果)
|
||||
fr = part.get("functionResponse")
|
||||
# 2. functionResponse(User 回复工具结果)- 支持 camelCase 和 snake_case
|
||||
fr = part.get("functionResponse") or part.get("function_response")
|
||||
if isinstance(fr, dict) and fr.get("id") is None:
|
||||
name = fr.get("name", "unknown")
|
||||
if not isinstance(name, str):
|
||||
|
||||
Reference in New Issue
Block a user