Skip to content

Commit aea5953

Browse files
authored
改为使用工具模块
1 parent 3e0455c commit aea5953

1 file changed

Lines changed: 5 additions & 85 deletions

File tree

astrbot/core/provider/entities.py

Lines changed: 5 additions & 85 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,5 @@
11
from __future__ import annotations
22

3-
import base64
43
import enum
54
import json
65
from dataclasses import dataclass, field
@@ -21,6 +20,7 @@
2120
from astrbot.core.agent.tool import ToolSet
2221
from astrbot.core.db.po import Conversation
2322
from astrbot.core.message.message_event_result import MessageChain
23+
from astrbot.core.utils.image_utils import encode_image_to_base64_url
2424
from astrbot.core.utils.io import download_image_by_url
2525

2626

@@ -189,12 +189,13 @@ async def assemble_context(self) -> dict:
189189
for image_url in self.image_urls:
190190
if image_url.startswith("http"):
191191
image_path = await download_image_by_url(image_url)
192-
image_data = await self._encode_image_bs64(image_path)
192+
# 统一通过公共工具函数编码,自动检测 MIME 类型
193+
image_data = await encode_image_to_base64_url(image_path)
193194
elif image_url.startswith("file:///"):
194195
image_path = image_url.replace("file:///", "")
195-
image_data = await self._encode_image_bs64(image_path)
196+
image_data = await encode_image_to_base64_url(image_path)
196197
else:
197-
image_data = await self._encode_image_bs64(image_url)
198+
image_data = await encode_image_to_base64_url(image_url)
198199
if not image_data:
199200
logger.warning(f"图片 {image_url} 得到的结果为空,将忽略。")
200201
continue
@@ -214,87 +215,6 @@ async def assemble_context(self) -> dict:
214215
# 否则返回多模态格式
215216
return {"role": "user", "content": content_blocks}
216217

217-
@staticmethod
218-
def _detect_mime_type(header_bytes: bytes) -> str:
219-
"""根据文件头魔术字节(magic bytes)检测图片的实际 MIME 类型。
220-
221-
依次匹配常见图片格式的文件头特征,均不匹配时回退到 image/jpeg
222-
以保持向后兼容。支持的格式:JPEG、PNG、GIF、WebP、BMP、TIFF、
223-
ICO、SVG、AVIF、HEIF/HEIC。
224-
225-
Args:
226-
header_bytes: 文件头原始字节,建议至少传入 16 字节。
227-
228-
Returns:
229-
对应的 MIME 类型字符串,例如 "image/png"。
230-
"""
231-
if len(header_bytes) >= 3 and header_bytes[:3] == b'\xff\xd8\xff':
232-
return "image/jpeg"
233-
if len(header_bytes) >= 8 and header_bytes[:8] == b'\x89PNG\r\n\x1a\n':
234-
return "image/png"
235-
if len(header_bytes) >= 4 and header_bytes[:4] == b'GIF8':
236-
return "image/gif"
237-
# WebP: RIFF????WEBP
238-
if len(header_bytes) >= 12 and header_bytes[:4] == b'RIFF' and header_bytes[8:12] == b'WEBP':
239-
return "image/webp"
240-
if len(header_bytes) >= 2 and header_bytes[:2] == b'BM':
241-
return "image/bmp"
242-
# TIFF: 小端 (II) 或大端 (MM)
243-
if len(header_bytes) >= 4 and header_bytes[:4] in (b'II\x2a\x00', b'MM\x00\x2a'):
244-
return "image/tiff"
245-
if len(header_bytes) >= 4 and header_bytes[:4] == b'\x00\x00\x01\x00':
246-
return "image/x-icon"
247-
# SVG 为文本格式,检测头部是否含有 <svg 标签
248-
if b'<svg' in header_bytes[:256].lower():
249-
return "image/svg+xml"
250-
# AVIF: ftyp box brand = avif
251-
if len(header_bytes) >= 12 and header_bytes[4:12] == b'ftypavif':
252-
return "image/avif"
253-
# HEIF/HEIC: ftyp box,brand 为 heic/heix/hevc/hevx/mif1
254-
if len(header_bytes) >= 12 and header_bytes[4:8] == b'ftyp':
255-
brand = header_bytes[8:12]
256-
if brand in (b'heic', b'heix', b'hevc', b'hevx', b'mif1'):
257-
return "image/heif"
258-
# 无法识别,回退到 image/jpeg
259-
return "image/jpeg"
260-
261-
async def _encode_image_bs64(self, image_url: str) -> str:
262-
"""将图片转换为 base64 Data URL,自动检测实际 MIME 类型。
263-
264-
原实现硬编码 image/jpeg,会导致 PNG 等格式在严格校验的接口上报错。
265-
现通过读取文件头魔术字节来推断正确的 MIME 类型。
266-
267-
Args:
268-
image_url: 本地文件路径,或以 "base64://" 开头的 base64 字符串。
269-
270-
Returns:
271-
形如 "data:image/png;base64,..." 的 Data URL 字符串。
272-
"""
273-
if image_url.startswith("base64://"):
274-
raw_b64 = image_url[len("base64://"):]
275-
# 从 base64 数据中解码少量字节以检测实际格式,
276-
# 取前 32 个 base64 字符可解码出约 24 字节的原始数据,足以覆盖所有魔术字节
277-
try:
278-
sample = raw_b64[:32]
279-
# 确保 base64 填充正确,避免解码报错
280-
missing_padding = len(sample) % 4
281-
if missing_padding:
282-
sample += '=' * (4 - missing_padding)
283-
header_bytes = base64.b64decode(sample)
284-
mime_type = self._detect_mime_type(header_bytes)
285-
except Exception:
286-
# 解码失败时安全回退
287-
mime_type = "image/jpeg"
288-
return f"data:{mime_type};base64,{raw_b64}"
289-
290-
with open(image_url, "rb") as f:
291-
# 先读取文件头用于格式检测,再 seek 回起点读取完整内容
292-
header_bytes = f.read(16)
293-
mime_type = self._detect_mime_type(header_bytes)
294-
f.seek(0)
295-
image_bs64 = base64.b64encode(f.read()).decode("utf-8")
296-
return f"data:{mime_type};base64,{image_bs64}"
297-
298218

299219
@dataclass
300220
class TokenUsage:

0 commit comments

Comments
 (0)