Skip to content
Merged
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
68 changes: 64 additions & 4 deletions astrbot/core/astr_main_agent.py
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
from __future__ import annotations

import asyncio
import base64
import copy
import datetime
import io
import json
import os
import time
import zoneinfo
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

为了支持 _do_compress_sync 函数中建议的 uuid.uuid4().hex 用法,需要导入 uuid 模块。

Suggested change
import zoneinfo
import zoneinfo
import uuid

from collections.abc import Coroutine
from dataclasses import dataclass, field

from PIL import Image as PILImage

from astrbot.core import logger, sp
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

为了支持 _compress_image_internal 函数中建议的 get_astrbot_temp_path() 用法,需要从 astrbot.core.utils.astrbot_path 导入此函数。

Suggested change
from astrbot.core import logger, sp
from astrbot.core import logger, sp
from astrbot.core.utils.astrbot_path import get_astrbot_temp_path

from astrbot.core.agent.handoff import HandoffTool
from astrbot.core.agent.mcp_client import MCPTool
Expand Down Expand Up @@ -448,7 +453,7 @@ async def _ensure_img_caption(
caption = await _request_img_caption(
image_caption_provider,
cfg,
req.image_urls,
[await _compress_image_internal(url) for url in req.image_urls],
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Outdated
plugin_context,
)
if caption:
Expand All @@ -458,6 +463,9 @@ async def _ensure_img_caption(
req.image_urls = []
except Exception as exc: # noqa: BLE001
logger.error("处理图片描述失败: %s", exc)
req.extra_user_content_parts.append(TextPart(text="图片解析失败"))
finally:
req.image_urls = []
Comment on lines +471 to +472


def _append_quoted_image_attachment(req: ProviderRequest, image_path: str) -> None:
Expand Down Expand Up @@ -523,7 +531,11 @@ async def _process_quote_message(
if prov and isinstance(prov, Provider):
llm_resp = await prov.text_chat(
prompt=IMAGE_CAPTION_DEFAULT_PROMPT,
image_urls=[await image_seg.convert_to_file_path()],
image_urls=[
await _compress_image_internal(
await image_seg.convert_to_file_path()
)
],
)
if llm_resp.completion_text:
content_parts.append(
Expand Down Expand Up @@ -936,7 +948,9 @@ async def build_main_agent(
# media files attachments
for comp in event.message_obj.message:
if isinstance(comp, Image):
image_path = await comp.convert_to_file_path()
image_path = await _compress_image_internal(
await comp.convert_to_file_path()
)
Comment thread
Soulter marked this conversation as resolved.
Outdated
req.image_urls.append(image_path)
req.extra_user_content_parts.append(
TextPart(text=f"[Image Attachment: path {image_path}]")
Expand All @@ -963,7 +977,9 @@ async def build_main_agent(
for reply_comp in comp.chain:
if isinstance(reply_comp, Image):
has_embedded_image = True
image_path = await reply_comp.convert_to_file_path()
image_path = await _compress_image_internal(
await reply_comp.convert_to_file_path()
)
req.image_urls.append(image_path)
_append_quoted_image_attachment(req, image_path)
elif isinstance(reply_comp, File):
Expand Down Expand Up @@ -1164,3 +1180,47 @@ async def build_main_agent(
provider=provider,
reset_coro=reset_coro if not apply_reset else None,
)

# 异步图片压缩
def _do_compress_sync(data: bytes, temp_dir: str) -> str:
"""同步执行图片压缩逻辑,由 asyncio.to_thread 调用"""

img = PILImage.open(io.BytesIO(data))
if img.mode in ("RGBA", "P"):
img = img.convert("RGB")
max_size = 1280
if max(img.size) > max_size:
img.thumbnail((max_size, max_size), PILImage.Resampling.LANCZOS)

timestamp = int(time.time() * 1000)
Comment thread
Soulter marked this conversation as resolved.
Outdated
save_path = os.path.join(temp_dir, f"compressed_{timestamp}.jpg")
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Outdated
img.save(save_path, "JPEG", quality=85, optimize=True)
Comment thread
Soulter marked this conversation as resolved.
Outdated
return save_path

# 压缩用户上传的大体积图片 未来可以提取为通用工具
async def _compress_image_internal(url_or_path: str) -> str:
Comment thread
sourcery-ai[bot] marked this conversation as resolved.
Outdated
try:
data = None
# 若为远程图片则直接返回原值 无需压缩
if url_or_path.startswith("http"):
return url_or_path
elif url_or_path.startswith("data:image"):
header, encoded = url_or_path.split(",", 1)
data = base64.b64decode(encoded)
elif os.path.exists(url_or_path):
if os.path.getsize(url_or_path) < 1024 * 1024:
return url_or_path
with open(url_or_path, "rb") as f:
data = f.read()

if not data:
return url_or_path

temp_dir = os.path.join(os.path.dirname(os.path.dirname(os.path.dirname(os.path.abspath(__file__)))), "data/temp")
Comment thread
Soulter marked this conversation as resolved.
Outdated
Comment thread
Soulter marked this conversation as resolved.
Outdated

# 使用 asyncio.to_thread 将同步阻塞的图片处理任务交给线程池
return await asyncio.to_thread(_do_compress_sync, data, temp_dir)
Comment thread
Soulter marked this conversation as resolved.
Outdated

except Exception as e:
logger.error("图片压缩失败: %s", e)
return url_or_path
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

high

新创建的压缩图片文件被保存到临时目录 (data/temp),但目前没有明确的清理机制。随着时间的推移,这个目录可能会积累大量文件,导致磁盘空间耗尽,尤其是在处理大量图片时。建议实现一个临时文件清理策略,例如定期清理旧文件、使用上下文管理器确保文件在使用后删除,或者设置临时目录的最大大小限制。

Loading