feat: AI生图功能丰富
This commit is contained in:
@@ -7,6 +7,8 @@ from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, patch
|
||||
|
||||
import httpx
|
||||
|
||||
SERVER_DIR = Path(__file__).resolve().parents[1]
|
||||
if str(SERVER_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(SERVER_DIR))
|
||||
@@ -58,6 +60,46 @@ class WorkerCancellationTests(unittest.IsolatedAsyncioTestCase):
|
||||
cancelled.assert_awaited_once_with(claim.item_id)
|
||||
waiting.assert_not_awaited()
|
||||
|
||||
async def test_provider_poll_timeout_keeps_existing_task_for_retry(self) -> None:
|
||||
claim = ClaimedItem(
|
||||
job_id="00000000-0000-0000-0000-000000000001",
|
||||
item_id="00000000-0000-0000-0000-000000000002",
|
||||
job_kind="image_edit",
|
||||
product_id=None,
|
||||
provider="pcore",
|
||||
model="gpt-image-2",
|
||||
platform="ozon",
|
||||
lang="ru",
|
||||
ratio="3:4",
|
||||
style_set=5,
|
||||
request_payload={"context": {"title": "test"}, "ref_images": []},
|
||||
item_payload={"kind": "custom", "title": "AI生图"},
|
||||
provider_task_id="provider-task",
|
||||
provider_task_kind="edits",
|
||||
)
|
||||
settings = SimpleNamespace(
|
||||
image_provider="pcore",
|
||||
dashscope_model="wan2.7-image-pro",
|
||||
rightapi_image_model="gpt-image-2",
|
||||
ark_image_model="doubao-seedream-4-5-251128",
|
||||
)
|
||||
|
||||
with (
|
||||
patch("services.job_worker.get_settings", return_value=settings),
|
||||
patch("services.job_worker.build_context", return_value={}),
|
||||
patch("services.job_worker.build_prompt", return_value="prompt"),
|
||||
patch("services.job_worker._refs_for_job", return_value=[]),
|
||||
patch("services.job_worker.advance_generation", new=AsyncMock(side_effect=httpx.ConnectTimeout("timeout"))),
|
||||
patch("services.job_worker.mark_waiting_provider", new=AsyncMock()) as waiting,
|
||||
patch("services.job_worker.mark_item_failed", new=AsyncMock()) as failed,
|
||||
):
|
||||
await process_item(claim)
|
||||
|
||||
waiting.assert_awaited_once_with(
|
||||
claim.item_id, "provider-task", "edits", delay_seconds=30,
|
||||
)
|
||||
failed.assert_not_awaited()
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
"""单张编辑的新参数必须进入同一次生图任务,且主图顺序与尺寸准确。"""
|
||||
from __future__ import annotations
|
||||
|
||||
import sys
|
||||
import unittest
|
||||
from pathlib import Path
|
||||
from types import SimpleNamespace
|
||||
from unittest.mock import AsyncMock, patch
|
||||
from fastapi import HTTPException
|
||||
|
||||
SERVER_DIR = Path(__file__).resolve().parents[1]
|
||||
if str(SERVER_DIR) not in sys.path:
|
||||
sys.path.insert(0, str(SERVER_DIR))
|
||||
|
||||
from api.suite import suite_image_edit, _with_dimensions
|
||||
from schemas.suite import ImageEditSingleRequest, ProductDimensions
|
||||
from services.generator import _image_size
|
||||
from services.prompts.common import build_context
|
||||
from services.prompts.single_edit import build_single_edit_prompt
|
||||
|
||||
|
||||
class SingleEditRequestTests(unittest.IsolatedAsyncioTestCase):
|
||||
async def test_main_and_two_auxiliary_images_keep_order_in_one_item(self) -> None:
|
||||
request = ImageEditSingleRequest(
|
||||
main_image_url="main", reference_image_urls=["detail", "scene"],
|
||||
prompt="替换背景", model="gpt-image-2@pcore", translate_text=True,
|
||||
target_language="ar", background="reference", ratio="4:5",
|
||||
)
|
||||
job = AsyncMock(return_value=SimpleNamespace(id="task-id"))
|
||||
with patch("api.suite.create_image_job", job):
|
||||
response = await suite_image_edit(request)
|
||||
self.assertEqual(response.suite_id, "task-id")
|
||||
payload = job.await_args.kwargs
|
||||
self.assertEqual(payload["ratio"], "4:5")
|
||||
self.assertEqual(payload["lang"], "ar")
|
||||
self.assertEqual(payload["provider"], "pcore")
|
||||
self.assertEqual(payload["model"], "gpt-image-2")
|
||||
self.assertEqual(len(payload["item_payloads"]), 1)
|
||||
self.assertEqual([image["url"] for image in payload["request_payload"]["ref_images"]], ["main", "detail", "scene"])
|
||||
self.assertTrue(payload["request_payload"]["single_edit"]["translate_text"])
|
||||
|
||||
async def test_structured_edit_rejects_other_provider(self) -> None:
|
||||
with self.assertRaises(HTTPException) as raised:
|
||||
await suite_image_edit(ImageEditSingleRequest(
|
||||
main_image_url="main", model="nano-banana-2",
|
||||
))
|
||||
self.assertEqual(raised.exception.status_code, 400)
|
||||
|
||||
|
||||
class SingleEditPromptTests(unittest.TestCase):
|
||||
def test_translation_background_and_partial_dimensions_share_one_prompt(self) -> None:
|
||||
context = build_context(_with_dimensions(
|
||||
{"title": "AI 精修"}, ProductDimensions(width=8, unit="cm"), required=True,
|
||||
))
|
||||
prompt = build_single_edit_prompt(
|
||||
requirements="保留商品颜色", config={"translate_text": True, "target_language": "ar", "background": "scene", "background_description": "warm kitchen"},
|
||||
context=context, style_set=2, style_prompt=None, ratio="3:4", is_size_chart=True, reference_count=2,
|
||||
)
|
||||
self.assertIn("Image 1 is the fixed main product image", prompt)
|
||||
self.assertIn("Images 2 through 3 are auxiliary", prompt)
|
||||
self.assertIn("Arabic", prompt)
|
||||
self.assertIn("right-to-left", prompt)
|
||||
self.assertIn("warm kitchen", prompt)
|
||||
self.assertIn("width: 8.0 cm", prompt)
|
||||
self.assertIn("Do not draw, label, or show an arrow for: length, height", prompt)
|
||||
|
||||
def test_four_to_five_size_does_not_fall_back_to_square(self) -> None:
|
||||
for provider, is_wan, model in (
|
||||
("pcore", True, "gpt-image-2"),
|
||||
("rightapi", True, "gpt-image-2"),
|
||||
("doubao", True, "seedream"),
|
||||
("tongyi", True, "wan2.6-image"),
|
||||
("tongyi", False, "qwen-image-3.0-pro"),
|
||||
):
|
||||
with self.subTest(provider=provider, model=model):
|
||||
width, height = map(int, _image_size(provider, "4:5", is_wan, model).replace("*", "x").split("x"))
|
||||
self.assertEqual(width * 5, height * 4)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
unittest.main()
|
||||
Reference in New Issue
Block a user