mirror of
https://github.com/AstrBotDevs/AstrBot.git
synced 2026-10-16 15:06:04 +08:00
* fix(provider): disable OpenAI SDK built-in retries under AstrBot retry layer (#9663) AsyncOpenAI/AsyncAzureOpenAI inherit the SDK default max_retries=2, which nests underneath AstrBot's tenacity-based retry_provider_request(). As a result provider_settings.request_max_retries no longer reflects the true attempt count (e.g. request_max_retries=1 still sends up to 3 HTTP requests) and delays fallback-provider switchover. Pass max_retries=0 so AstrBot's retry layer is the single source of truth for retries. * test(provider): add retry-count regression test, parameterize client retry tests Address review feedback on #9669: merge the near-identical official/Azure client construction tests into a parametrized test, and add a regression test asserting _query() performs exactly request_max_retries attempts through retry_provider_request() when the call keeps failing, guarding against multiplicative retries being reintroduced. * fix(media): guard oversized image inputs and attachment uploads 为模型图片输入与聊天附件上传增加大小护栏 / Add size guards for model image inputs and chat attachment uploads: - _convert_image_bytes_sync 复用原始编码字节的分支增加 2MB 上限;更大的 输入重编码,输出始终受像素尺寸与质量约束 - prepare_model_image 在读入前检查文件大小,超过 32MB 跳过并记录日志 - 聊天附件上传上限 100MB:路由先检查 Content-Length,服务层落盘后复核 实际大小 - Reuse the original encoded bytes in _convert_image_bytes_sync only for stills up to 2MB; larger inputs are re-encoded so the output stays bounded by pixel size and quality. - prepare_model_image checks the file size before reading and skips inputs above 32MB with a warning log. - Chat attachment uploads are capped at 100MB: the route checks Content-Length first, and save_uploaded_file re-checks the on-disk size after saving. Tests: tests/test_media_utils.py, tests/test_chat_route.py * fix(media): sniff image headers instead of decoding for MIME detection MIME 探测改为读取文件头魔数,不再解码整个图片 / Detect image MIME types by reading header magic bytes instead of decoding the whole file: - detect_image_mime_type 仅读取前 32 字节识别 PNG/JPEG/GIF/WebP/BMP/ TIFF/AVIF,内存占用与文件大小无关 - 聊天附件上传上限从 100MB 放宽到 512MB - detect_image_mime_type reads only the first 32 bytes and recognizes PNG/JPEG/GIF/WebP/BMP/TIFF/AVIF, so memory usage no longer scales with file size - Chat attachment upload limit raised from 100MB to 512MB Tests: tests/test_media_utils.py * test: remove obsolete image assertion and fix upload fixtures * test: mock plugin marketplace requests in dashboard tests --------- Co-authored-by: RuochenPan <badbatch0x01@gmail.com>
83 lines
2.4 KiB
Python
83 lines
2.4 KiB
Python
from io import BytesIO
|
|
from types import SimpleNamespace
|
|
|
|
import pytest
|
|
from PIL import Image as PILImage
|
|
|
|
from astrbot.dashboard.services.chat_service import (
|
|
WEBCHAT_IMAGE_MIME_TYPES,
|
|
ChatService,
|
|
)
|
|
|
|
|
|
@pytest.mark.asyncio
|
|
async def test_webchat_upload_uses_detected_image_type(tmp_path):
|
|
image_buffer = BytesIO()
|
|
PILImage.new("RGB", (2, 2), (255, 0, 0)).save(image_buffer, format="JPEG")
|
|
|
|
class FakeUploadFile:
|
|
filename = "pasted.png"
|
|
content_type = "image/png"
|
|
content_length = None
|
|
|
|
async def save(self, destination):
|
|
with open(destination, "wb") as output:
|
|
output.write(image_buffer.getvalue())
|
|
|
|
class FakeDatabase:
|
|
def __init__(self):
|
|
self.inserted = None
|
|
|
|
async def insert_attachment(self, path, type, mime_type):
|
|
self.inserted = {
|
|
"path": path,
|
|
"type": type,
|
|
"mime_type": mime_type,
|
|
}
|
|
return SimpleNamespace(attachment_id="attachment-1", path=path)
|
|
|
|
fake_db = FakeDatabase()
|
|
service = ChatService.__new__(ChatService)
|
|
service.db = fake_db
|
|
service.attachments_dir = str(tmp_path)
|
|
|
|
result = await service.save_uploaded_file(FakeUploadFile())
|
|
|
|
assert result["filename"].endswith(".jpg")
|
|
assert fake_db.inserted["mime_type"] == "image/jpeg"
|
|
assert fake_db.inserted["type"] == "image"
|
|
assert (tmp_path / result["filename"]).exists()
|
|
assert not (tmp_path / "pasted.png").exists()
|
|
|
|
|
|
@pytest.mark.parametrize(
|
|
("filename", "expected_mime_type"),
|
|
[(f"photo{ext}", mime_type) for ext, mime_type in WEBCHAT_IMAGE_MIME_TYPES.items()],
|
|
)
|
|
@pytest.mark.asyncio
|
|
async def test_resolve_webchat_file_uses_image_extension_mime_type(
|
|
tmp_path,
|
|
monkeypatch,
|
|
filename,
|
|
expected_mime_type,
|
|
):
|
|
monkeypatch.setattr(
|
|
"astrbot.dashboard.services.chat_service.get_astrbot_data_path",
|
|
lambda: str(tmp_path),
|
|
)
|
|
service = ChatService(
|
|
SimpleNamespace(),
|
|
SimpleNamespace(
|
|
conversation_manager=None,
|
|
platform_message_history_manager=None,
|
|
umop_config_router=None,
|
|
),
|
|
)
|
|
file_path = tmp_path / "attachments" / filename
|
|
file_path.write_bytes(b"image-bytes")
|
|
|
|
resolved_path, mime_type = await service.resolve_webchat_file(filename)
|
|
|
|
assert resolved_path == str(file_path.resolve(strict=False))
|
|
assert mime_type == expected_mime_type
|