145 lines
5.7 KiB
Python
145 lines
5.7 KiB
Python
from __future__ import annotations
|
|
|
|
import json
|
|
import time
|
|
import unittest
|
|
from pathlib import Path
|
|
|
|
import requests
|
|
|
|
from test.utils import save_image
|
|
from utils.log import logger
|
|
|
|
AUTH_KEY = "chatgpt2api"
|
|
BASE_URL = "http://localhost:8000"
|
|
ASSETS_DIR = Path(__file__).resolve().parents[1] / "assets"
|
|
|
|
|
|
def load_asset_bytes(name: str) -> bytes:
|
|
return (ASSETS_DIR / name).read_bytes()
|
|
|
|
|
|
def summarize_chunk(chunk: dict[str, object]) -> dict[str, object]:
|
|
data = chunk.get("data")
|
|
data_items = data if isinstance(data, list) else []
|
|
return {
|
|
"object": chunk.get("object"),
|
|
"index": chunk.get("index"),
|
|
"total": chunk.get("total"),
|
|
"created": chunk.get("created"),
|
|
"finish_reason": chunk.get("finish_reason"),
|
|
"progress_text": chunk.get("progress_text"),
|
|
"upstream_event_type": chunk.get("upstream_event_type"),
|
|
"data_count": len(data_items),
|
|
"has_b64_json": any(isinstance(item, dict) and bool(item.get("b64_json")) for item in data_items),
|
|
}
|
|
|
|
|
|
class ImageEditsTests(unittest.TestCase):
|
|
def test_image_edit_http(self):
|
|
"""测试图片编辑的非流式 HTTP 调用。"""
|
|
response = requests.post(
|
|
f"{BASE_URL}/v1/images/edits",
|
|
headers={"Authorization": f"Bearer {AUTH_KEY}"},
|
|
data={
|
|
"model": "gpt-image-2",
|
|
"prompt": "参考输入图片,保持人物主体和二次元插画风格不变,让女孩怀里抱着一只可爱的小猫,画面自然协调。",
|
|
"n": "1",
|
|
"response_format": "b64_json",
|
|
},
|
|
files={"image": ("chery_studio.png", load_asset_bytes("chery_studio.png"), "image/png")},
|
|
timeout=300,
|
|
)
|
|
self.assertEqual(response.status_code, 200, response.text)
|
|
payload = response.json()
|
|
saved_paths = []
|
|
for index, item in enumerate(payload.get("data") or [], start=1):
|
|
b64_json = str((item or {}).get("b64_json") or "")
|
|
if b64_json:
|
|
saved_paths.append(save_image(b64_json, f"images_edits_non_stream_{index}"))
|
|
self.assertGreater(len(saved_paths), 0, "非流式接口未输出图片。")
|
|
logger.info({
|
|
"event": "test_images_edits_non_stream_done",
|
|
"status_code": response.status_code,
|
|
"created": payload.get("created"),
|
|
"saved_paths": [str(path) for path in saved_paths],
|
|
"image_count": len(saved_paths),
|
|
})
|
|
|
|
def test_image_edit_stream_http(self):
|
|
"""测试图片编辑的流式 HTTP 调用。"""
|
|
response = requests.post(
|
|
f"{BASE_URL}/v1/images/edits",
|
|
headers={"Authorization": f"Bearer {AUTH_KEY}"},
|
|
data={
|
|
"model": "gpt-image-2",
|
|
"prompt": "请提取两张输入界面截图中的 6 个任务,并把这 6 个任务整合排版到同一张图里,做成一张清晰的中文任务总览海报,标题明确,六个任务分区展示,版面整洁。",
|
|
"n": "1",
|
|
"response_format": "b64_json",
|
|
"stream": "true",
|
|
},
|
|
files=[
|
|
("image", ("image.png", load_asset_bytes("image.png"), "image/png")),
|
|
("image", ("image_edit.png", load_asset_bytes("image_edit.png"), "image/png")),
|
|
],
|
|
stream=True,
|
|
timeout=300,
|
|
)
|
|
image_items: list[dict[str, object]] = []
|
|
stream_errors: list[dict[str, object]] = []
|
|
started_at = time.time()
|
|
self.assertEqual(response.status_code, 200, response.text)
|
|
self.assertTrue(
|
|
response.headers.get("content-type", "").startswith("text/event-stream"),
|
|
response.headers.get("content-type", ""),
|
|
)
|
|
logger.info({
|
|
"event": "test_images_edits_stream_start",
|
|
"status_code": response.status_code,
|
|
"content_type": response.headers.get("content-type"),
|
|
})
|
|
try:
|
|
for line in response.iter_lines():
|
|
if not line:
|
|
continue
|
|
text = line.decode("utf-8", errors="replace")
|
|
if not text.startswith("data:"):
|
|
continue
|
|
payload = text[5:].strip()
|
|
if payload == "[DONE]":
|
|
break
|
|
try:
|
|
chunk = json.loads(payload)
|
|
except Exception:
|
|
continue
|
|
elapsed = time.time() - started_at
|
|
if isinstance(chunk.get("error"), dict):
|
|
stream_errors.append(chunk["error"])
|
|
logger.info({
|
|
"event": "test_images_edits_stream_chunk",
|
|
"elapsed_seconds": round(elapsed, 2),
|
|
"chunk": summarize_chunk(chunk),
|
|
})
|
|
data = chunk.get("data")
|
|
if isinstance(data, list):
|
|
image_items.extend(item for item in data if isinstance(item, dict))
|
|
finally:
|
|
response.close()
|
|
|
|
saved_paths = []
|
|
for index, item in enumerate(image_items, start=1):
|
|
b64_json = str(item.get("b64_json") or "")
|
|
if b64_json:
|
|
saved_paths.append(save_image(b64_json, f"images_edits_stream_{index}"))
|
|
self.assertFalse(stream_errors, f"流式接口返回错误: {stream_errors}")
|
|
self.assertGreater(len(saved_paths), 0, "流式接口未输出图片。")
|
|
logger.info({
|
|
"event": "test_images_edits_stream_done",
|
|
"saved_paths": [str(path) for path in saved_paths],
|
|
"image_count": len(saved_paths),
|
|
})
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|