mirror of
https://github.com/fawney19/Aether.git
synced 2026-09-03 09:50:21 +08:00
fix: 补充 OpenAI CLI normalizer 图片格式双向转换
- 将 _image_url_to_block 从 OpenAINormalizer 提升到基类 FormatNormalizer - OpenAI CLI normalizer 新增 ImageBlock 的解析和输出支持 - 修复 Chat -> CLI 跨格式路由时图片内容丢失 Co-Authored-By: LewisPen <LewisPen@nyadoo.com>
This commit is contained in:
@@ -8,7 +8,13 @@
|
|||||||
from abc import ABC, abstractmethod
|
from abc import ABC, abstractmethod
|
||||||
from typing import Any
|
from typing import Any
|
||||||
|
|
||||||
from .internal import FormatCapabilities, InternalError, InternalRequest, InternalResponse
|
from .internal import (
|
||||||
|
FormatCapabilities,
|
||||||
|
ImageBlock,
|
||||||
|
InternalError,
|
||||||
|
InternalRequest,
|
||||||
|
InternalResponse,
|
||||||
|
)
|
||||||
from .internal_video import InternalVideoPollResult, InternalVideoRequest, InternalVideoTask
|
from .internal_video import InternalVideoPollResult, InternalVideoRequest, InternalVideoTask
|
||||||
from .stream_events import InternalStreamEvent
|
from .stream_events import InternalStreamEvent
|
||||||
from .stream_state import StreamState
|
from .stream_state import StreamState
|
||||||
@@ -98,6 +104,17 @@ class FormatNormalizer(ABC):
|
|||||||
"""将内部错误表示转换为格式特定错误"""
|
"""将内部错误表示转换为格式特定错误"""
|
||||||
raise NotImplementedError
|
raise NotImplementedError
|
||||||
|
|
||||||
|
# ============ 图片工具方法 ============
|
||||||
|
|
||||||
|
@staticmethod
|
||||||
|
def _image_url_to_block(url: str) -> ImageBlock:
|
||||||
|
"""将 image_url 字符串转换为 ImageBlock(支持 data URL 和外部 URL)"""
|
||||||
|
if url.startswith("data:") and ";base64," in url:
|
||||||
|
header, _, data = url.partition(",")
|
||||||
|
media_type = header.split(";")[0].split(":", 1)[-1]
|
||||||
|
return ImageBlock(data=data, media_type=media_type)
|
||||||
|
return ImageBlock(url=url)
|
||||||
|
|
||||||
# ============ 视频转换(可选) ============
|
# ============ 视频转换(可选) ============
|
||||||
|
|
||||||
def video_request_to_internal(self, request: dict[str, Any]) -> InternalVideoRequest:
|
def video_request_to_internal(self, request: dict[str, Any]) -> InternalVideoRequest:
|
||||||
|
|||||||
@@ -1476,13 +1476,6 @@ class OpenAINormalizer(FormatNormalizer):
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
def _image_url_to_block(self, url: str) -> ImageBlock:
|
|
||||||
if url.startswith("data:") and ";base64," in url:
|
|
||||||
header, _, data = url.partition(",")
|
|
||||||
media_type = header.split(";")[0].split(":", 1)[-1]
|
|
||||||
return ImageBlock(data=data, media_type=media_type)
|
|
||||||
return ImageBlock(url=url)
|
|
||||||
|
|
||||||
def _role_from_openai(self, role: str) -> Role:
|
def _role_from_openai(self, role: str) -> Role:
|
||||||
if role == "user":
|
if role == "user":
|
||||||
return Role.USER
|
return Role.USER
|
||||||
|
|||||||
@@ -24,6 +24,7 @@ from src.core.api_format.conversion.internal import (
|
|||||||
ContentType,
|
ContentType,
|
||||||
ErrorType,
|
ErrorType,
|
||||||
FormatCapabilities,
|
FormatCapabilities,
|
||||||
|
ImageBlock,
|
||||||
InstructionSegment,
|
InstructionSegment,
|
||||||
InternalError,
|
InternalError,
|
||||||
InternalMessage,
|
InternalMessage,
|
||||||
@@ -1111,6 +1112,15 @@ class OpenAICliNormalizer(FormatNormalizer):
|
|||||||
if text:
|
if text:
|
||||||
blocks.append(TextBlock(text=text))
|
blocks.append(TextBlock(text=text))
|
||||||
continue
|
continue
|
||||||
|
if ptype in ("input_image", "output_image"):
|
||||||
|
image_url = part.get("image_url") or part.get("url") or ""
|
||||||
|
if isinstance(image_url, str) and image_url:
|
||||||
|
img = self._image_url_to_block(image_url)
|
||||||
|
img.extra.update(self._extract_extra(part, {"type", "image_url", "url"}))
|
||||||
|
blocks.append(img)
|
||||||
|
else:
|
||||||
|
blocks.append(UnknownBlock(raw_type=ptype, payload=part))
|
||||||
|
continue
|
||||||
blocks.append(UnknownBlock(raw_type=ptype or "unknown", payload=part))
|
blocks.append(UnknownBlock(raw_type=ptype or "unknown", payload=part))
|
||||||
return blocks
|
return blocks
|
||||||
|
|
||||||
@@ -1180,7 +1190,7 @@ class OpenAICliNormalizer(FormatNormalizer):
|
|||||||
if system_to_developer and role == "system":
|
if system_to_developer and role == "system":
|
||||||
role = "developer"
|
role = "developer"
|
||||||
content_items: list[dict[str, Any]] = []
|
content_items: list[dict[str, Any]] = []
|
||||||
has_text = False
|
has_content = False
|
||||||
|
|
||||||
for block in msg.content:
|
for block in msg.content:
|
||||||
if isinstance(block, (ToolUseBlock, ToolResultBlock)):
|
if isinstance(block, (ToolUseBlock, ToolResultBlock)):
|
||||||
@@ -1189,13 +1199,30 @@ class OpenAICliNormalizer(FormatNormalizer):
|
|||||||
continue # 已在上面处理
|
continue # 已在上面处理
|
||||||
if isinstance(block, UnknownBlock):
|
if isinstance(block, UnknownBlock):
|
||||||
continue # 跳过其他未知块
|
continue # 跳过其他未知块
|
||||||
|
if isinstance(block, ImageBlock):
|
||||||
|
if block.data and block.media_type:
|
||||||
|
image_url = f"data:{block.media_type};base64,{block.data}"
|
||||||
|
elif block.url:
|
||||||
|
image_url = block.url
|
||||||
|
else:
|
||||||
|
continue
|
||||||
|
image_type = "output_image" if role == "assistant" else "input_image"
|
||||||
|
item: dict[str, Any] = {
|
||||||
|
"type": image_type,
|
||||||
|
"image_url": image_url,
|
||||||
|
}
|
||||||
|
if block.extra.get("detail"):
|
||||||
|
item["detail"] = block.extra["detail"]
|
||||||
|
content_items.append(item)
|
||||||
|
has_content = True
|
||||||
|
continue
|
||||||
if isinstance(block, TextBlock) and block.text:
|
if isinstance(block, TextBlock) and block.text:
|
||||||
# assistant 角色使用 output_text,其他角色使用 input_text
|
# assistant 角色使用 output_text,其他角色使用 input_text
|
||||||
text_type = "output_text" if role == "assistant" else "input_text"
|
text_type = "output_text" if role == "assistant" else "input_text"
|
||||||
content_items.append({"type": text_type, "text": block.text})
|
content_items.append({"type": text_type, "text": block.text})
|
||||||
has_text = True
|
has_content = True
|
||||||
|
|
||||||
if has_text:
|
if has_content:
|
||||||
out.append({"type": "message", "role": role, "content": content_items})
|
out.append({"type": "message", "role": role, "content": content_items})
|
||||||
|
|
||||||
return out
|
return out
|
||||||
|
|||||||
Reference in New Issue
Block a user