feat: 插件开发 ozon 端主体完成

This commit is contained in:
Joey
2026-08-16 17:32:43 +08:00
parent 1591d5e35a
commit b57933e983
32 changed files with 1960 additions and 512 deletions
+30 -10
View File
@@ -23,6 +23,18 @@ from services.prompt import build_prompt, build_context, type_name
log = logging.getLogger("suite.generator")
def _raise_api_error(resp, provider: str):
"""HTTP 错误时抛出带 API 错误码/信息的异常(响应体里有真正的失败原因)。"""
if resp.is_success:
return
try:
body = resp.json()
detail = f"{body.get('code', '')}: {body.get('message', '')}".strip(': ')
except Exception: # noqa: BLE001
detail = resp.text[:200]
raise RuntimeError(f"{provider} API HTTP {resp.status_code}{detail or '无错误详情'}")
# 参考图选择:material 用第 2 张(背面/细节),其余用第 1 张(正面)
TYPE_REF_INDEX = {
"material": 1,
@@ -81,12 +93,12 @@ async def _resolve_ref(url: str) -> str:
# ── Provider:豆包 Seedream(火山方舟)────────────────────────────────────
async def generate_doubao(prompt: str, ref_images: list[str], size: str = "2048x2048") -> bytes:
async def generate_doubao(prompt: str, ref_images: list[str], size: str = "2048x2048", model: str | None = None) -> bytes:
s = get_settings()
if not s.ark_api_key:
raise RuntimeError("未配置 ARK_API_KEY.env")
body = {
"model": s.ark_image_model,
"model": model or s.ark_image_model,
"prompt": prompt.rstrip(". ") + ". " + _DOUBAO_ANTI_AI,
"size": size,
"response_format": "url",
@@ -101,7 +113,7 @@ async def generate_doubao(prompt: str, ref_images: list[str], size: str = "2048x
headers={"Authorization": f"Bearer {s.ark_api_key}", "Content-Type": "application/json"},
json=body,
)
resp.raise_for_status()
_raise_api_error(resp, "豆包")
img_url = resp.json()["data"][0]["url"]
dl = await client.get(img_url, timeout=s.request_timeout)
dl.raise_for_status()
@@ -140,11 +152,12 @@ async def _tongyi_poll_task(client: httpx.AsyncClient, key: str, task_id: str, m
raise TimeoutError(f"通义异步任务超时 ({max_wait}s): task_id={task_id}")
async def generate_tongyi(prompt: str, ref_images: list[str], size: str = "2048*2048") -> bytes:
async def generate_tongyi(prompt: str, ref_images: list[str], size: str = "2048*2048", model: str | None = None) -> bytes:
s = get_settings()
if not s.dashscope_api_key:
raise RuntimeError("未配置 DASHSCOPE_API_KEY.env")
is_wan = _is_wan_model(s.dashscope_model)
model = model or s.dashscope_model
is_wan = _is_wan_model(model)
url = s.dashscope_base_url or (
"https://dashscope.aliyuncs.com/api/v1/services/aigc/image-generation/generation"
if is_wan
@@ -163,11 +176,11 @@ async def generate_tongyi(prompt: str, ref_images: list[str], size: str = "2048*
if is_wan:
headers["X-DashScope-Async"] = "enable"
body = {"model": s.dashscope_model, "input": {"messages": [{"role": "user", "content": content}]}, "parameters": params}
body = {"model": model, "input": {"messages": [{"role": "user", "content": content}]}, "parameters": params}
async with httpx.AsyncClient(timeout=s.request_timeout, verify=False) as client:
resp = await client.post(url, headers=headers, json=body)
resp.raise_for_status()
_raise_api_error(resp, "通义")
data = resp.json()
if is_wan:
task_id = data.get("output", {}).get("task_id", "")
@@ -267,7 +280,11 @@ async def run_suite(suite_id: str) -> None:
raw = suite.context if not product else (product.raw or {})
ctx = build_context(raw or {}, fallback_name=product.name if product else "")
size = _image_size(provider_name, suite.ratio, is_wan=_is_wan_model(settings.dashscope_model))
model = suite.model or (
settings.dashscope_model if provider_name == "tongyi" else settings.ark_image_model
)
is_wan = provider_name == "tongyi" and _is_wan_model(model)
size = _image_size(provider_name, suite.ratio, is_wan=is_wan)
# 任务列表:方案(逐张)优先,旧路径按 types
if suite.plan:
@@ -290,12 +307,15 @@ async def run_suite(suite_id: str) -> None:
db.add(image_row)
await db.flush()
try:
prompt = build_prompt(type_id, ctx, suite.style_set, suite.lang, extra=job)
prompt = build_prompt(
type_id, ctx, suite.style_set, suite.lang,
extra=job, style_prompt=suite.style_prompt,
)
if product:
refs = await _select_ref_images(db, product.id, type_id)
else:
refs = _refs_for_job(list(suite.ref_images or []), job)
data = await generator(prompt, refs, size=size)
data = await generator(prompt, refs, size=size, model=model)
key = storage.write_bytes(data, key_prefix=f"suites/{suite.id}", ext=".jpg")
image_row.stored_url = storage.public_url(key)
image_row.status = STATUS_OK