fix(conversion): 修复 openai cli tool 调用空 call_id 导致上游失败

- 在 FormatConversionRegistry.convert_request 前统一修复 InternalRequest 中空的 tool_id/tool_use_id

- 为空 ID 自动生成 call_auto_N,并将 tool_result 关联到最近待匹配的 tool_call

- 修正 OpenAICliNormalizer 中 function_call_output 的内部角色为 USER,确保输出 openai:chat 时保留 tool_call_id

- 新增 openai:cli <-> openai:chat 空 call_id 回归测试,覆盖工具调用关联场景
This commit is contained in:
hemo94931
2026-02-13 12:32:29 +08:00
parent 4c83780b5e
commit e80bcabccf
3 changed files with 120 additions and 1 deletions

View File

@@ -1054,7 +1054,7 @@ class OpenAICliNormalizer(FormatNormalizer):
content_text=content_text,
extra={"openai_cli": self._extract_extra(item, {"type", "call_id", "id", "output"})},
)
return InternalMessage(role=Role.TOOL, content=[result_block])
return InternalMessage(role=Role.USER, content=[result_block])
def _parse_reasoning_item(self, item: dict[str, Any]) -> InternalMessage:
summary_parts: list[str] = []

View File

@@ -15,6 +15,7 @@ from collections.abc import Generator
from contextlib import contextmanager
from typing import Any
from src.core.api_format.conversion.internal import InternalRequest, ToolResultBlock, ToolUseBlock
from src.core.api_format.conversion.exceptions import FormatConversionError
from src.core.api_format.conversion.normalizer import FormatNormalizer
from src.core.api_format.conversion.stream_state import StreamState
@@ -60,6 +61,40 @@ class FormatConversionRegistry:
raise FormatConversionError(format_id, format_id, f"未注册 Normalizer: {format_id}")
return normalizer
def _repair_internal_tool_call_ids(self, internal: InternalRequest) -> None:
"""修复 InternalRequest 中空的 tool id/tool_use_id避免上游校验报错。"""
pending_tool_ids: list[str] = []
auto_counter = 0
def next_tool_id() -> str:
nonlocal auto_counter
auto_counter += 1
return f"call_auto_{auto_counter}"
for message in internal.messages:
for block in message.content:
if isinstance(block, ToolUseBlock):
tool_id = str(block.tool_id or "").strip()
if not tool_id:
tool_id = next_tool_id()
block.tool_id = tool_id
pending_tool_ids.append(tool_id)
continue
if isinstance(block, ToolResultBlock):
tool_use_id = str(block.tool_use_id or "").strip()
if tool_use_id:
block.tool_use_id = tool_use_id
if tool_use_id in pending_tool_ids:
pending_tool_ids.remove(tool_use_id)
continue
if pending_tool_ids:
block.tool_use_id = pending_tool_ids.pop(0)
else:
block.tool_use_id = next_tool_id()
# ==================== 请求/响应转换(严格) ====================
def convert_request(
@@ -81,6 +116,7 @@ class FormatConversionRegistry:
):
try:
internal = src.request_to_internal(request)
self._repair_internal_tool_call_ids(internal)
return tgt.request_from_internal(internal, target_variant=target_variant)
except Exception as e:
raise FormatConversionError(source_format, target_format, str(e)) from e