From 3353af0fb8f01f83f17e88f6fd8af9ef59ccaa45 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Mon, 5 Oct 2026 11:55:06 +0000 Subject: [PATCH] style: auto-format with black + isort + ruff + prettier [skip ci-format-check] --- .../worker_app/tasks/vision/_provider.py | 2 + .../worker_app/tasks/vision/fast_path.py | 14 ++- .../worker_app/tasks/vision/vlm_fallback.py | 92 +++++++++++++------ .../worker_app/tasks/vision/vlm_fast_json.py | 33 +++++-- 4 files changed, 103 insertions(+), 38 deletions(-) diff --git a/apps/worker/worker_app/tasks/vision/_provider.py b/apps/worker/worker_app/tasks/vision/_provider.py index c3df59132..15ead68fe 100644 --- a/apps/worker/worker_app/tasks/vision/_provider.py +++ b/apps/worker/worker_app/tasks/vision/_provider.py @@ -6,6 +6,7 @@ - ark: doubao-seed-2-1-lite-260915 / doubao-seed-2-1-pro-260915 - dashscope: qwen3.8-flash / qwen3.7-plus """ + from __future__ import annotations import logging @@ -70,6 +71,7 @@ def api_key() -> str | None: if PROVIDER == "ark": try: from packages.shared import get_shared_settings + return get_shared_settings().doubao_api_key except Exception: return os.environ.get("DOUBAO_API_KEY") diff --git a/apps/worker/worker_app/tasks/vision/fast_path.py b/apps/worker/worker_app/tasks/vision/fast_path.py index 45bc75b1f..84dad5ec2 100644 --- a/apps/worker/worker_app/tasks/vision/fast_path.py +++ b/apps/worker/worker_app/tasks/vision/fast_path.py @@ -85,7 +85,9 @@ def analyze_image_v2(idx: int, img_url: str) -> dict[str, Any]: assembled["_fast_elapsed"] = round(fast_elapsed, 2) logger.info( "[vision.v2] 图片 #%d fast命中 provider=%s elapsed=%.2fs pp=%s", - idx, _provider.get_provider(), fast_elapsed, + idx, + _provider.get_provider(), + fast_elapsed, (assembled.get("portrait_prompt") or "")[:40], ) return assembled @@ -101,7 +103,9 @@ def analyze_image_v2(idx: int, img_url: str) -> dict[str, Any]: logger.info("[vision.v2] 图片 #%d pro兜底命中 total=%.2fs", idx, time.time() - t0) return pro_result - logger.warning("[vision.v2] 图片 #%d 全路径失败 provider=%s elapsed=%.2fs", idx, _provider.get_provider(), time.time() - t0) + logger.warning( + "[vision.v2] 图片 #%d 全路径失败 provider=%s elapsed=%.2fs", idx, _provider.get_provider(), time.time() - t0 + ) out = dict(_FALLBACK_RESULT) out["_source"] = "v2_all_failed" out["text_on_package"] = ocr_result[:8] @@ -117,7 +121,11 @@ def analyze_images_v2(img_urls: list[str]) -> list[dict[str, Any]]: logger.info( "[vision.v2] 开始图片分析 n=%d workers=%d provider=%s fast_timeout=%.0fs pro_timeout=%.0fs", - len(img_urls), workers, _provider.get_provider(), _FAST_TIMEOUT, _PRO_TIMEOUT, + len(img_urls), + workers, + _provider.get_provider(), + _FAST_TIMEOUT, + _PRO_TIMEOUT, ) t0 = time.time() with ThreadPoolExecutor(max_workers=workers) as pool: diff --git a/apps/worker/worker_app/tasks/vision/vlm_fallback.py b/apps/worker/worker_app/tasks/vision/vlm_fallback.py index 89715dd1c..b88af23c9 100644 --- a/apps/worker/worker_app/tasks/vision/vlm_fallback.py +++ b/apps/worker/worker_app/tasks/vision/vlm_fallback.py @@ -109,26 +109,47 @@ def _xml_to_product(raw: str, idx: int) -> dict[str, Any]: text_list = [x.strip() for x in re.split(r"[,,;;]", top_text) if x.strip()] if top_text else [] summary = _xml_attr("product", "summary", raw) or f"{brand} {name}" return { - "name": name, "brand": brand, "category": category, - "appearance": appearance, "packaging": packaging, - "text_on_package": text_list, "key_features": feat_list, - "scene": scene, "mood": mood, "portrait_prompt": portrait_prompt, - "summary": summary, "_source": "vlm_pro_xml", + "name": name, + "brand": brand, + "category": category, + "appearance": appearance, + "packaging": packaging, + "text_on_package": text_list, + "key_features": feat_list, + "scene": scene, + "mood": mood, + "portrait_prompt": portrait_prompt, + "summary": summary, + "_source": "vlm_pro_xml", } if portrait_prompt != "无人像": return { - "name": "未识别", "brand": "无法判断", "category": "无法判断", - "appearance": "无法判断", "packaging": "无法判断", - "text_on_package": [], "key_features": ["无法判断"], - "scene": scene, "mood": mood, "portrait_prompt": portrait_prompt, - "summary": "未识别", "_source": "vlm_pro_no_product", + "name": "未识别", + "brand": "无法判断", + "category": "无法判断", + "appearance": "无法判断", + "packaging": "无法判断", + "text_on_package": [], + "key_features": ["无法判断"], + "scene": scene, + "mood": mood, + "portrait_prompt": portrait_prompt, + "summary": "未识别", + "_source": "vlm_pro_no_product", } return { - "name": "未识别", "brand": "无法判断", "category": "无法判断", - "appearance": "无法判断", "packaging": "无法判断", - "text_on_package": [], "key_features": ["无法判断"], - "scene": scene, "mood": mood, "portrait_prompt": "无人像", - "summary": "未识别", "_source": "vlm_pro_no_tag", + "name": "未识别", + "brand": "无法判断", + "category": "无法判断", + "appearance": "无法判断", + "packaging": "无法判断", + "text_on_package": [], + "key_features": ["无法判断"], + "scene": scene, + "mood": mood, + "portrait_prompt": "无人像", + "summary": "未识别", + "_source": "vlm_pro_no_tag", } @@ -162,6 +183,7 @@ def _assemble_pp_from_json(obj: dict[str, Any]) -> str: def _call_dashscope_pro(img_url: str, idx: int, timeout: int) -> dict[str, Any] | None: """DashScope qwen3.7-plus 兜底,直接 httpx 发精简 JSON prompt。""" import httpx + t0 = time.time() api_key = _provider.api_key() if not api_key: @@ -172,10 +194,13 @@ def _call_dashscope_pro(img_url: str, idx: int, timeout: int) -> dict[str, Any] "model": _provider.pro_model(), "messages": [ {"role": "system", "content": _DS_PRO_SYSTEM}, - {"role": "user", "content": [ - {"type": "image_url", "image_url": {"url": img_url}}, - {"type": "text", "text": _DS_PRO_USER}, - ]}, + { + "role": "user", + "content": [ + {"type": "image_url", "image_url": {"url": img_url}}, + {"type": "text", "text": _DS_PRO_USER}, + ], + }, ], "temperature": 0.3, "max_tokens": DEFAULT_MAX_TOKENS, @@ -191,7 +216,9 @@ def _call_dashscope_pro(img_url: str, idx: int, timeout: int) -> dict[str, Any] ) elapsed = time.time() - t0 if r.status_code != 200: - logger.warning("[vision.vlm] dashscope pro HTTP %d elapsed=%.1fs body=%s", r.status_code, elapsed, r.text[:200]) + logger.warning( + "[vision.vlm] dashscope pro HTTP %d elapsed=%.1fs body=%s", r.status_code, elapsed, r.text[:200] + ) return None data = r.json() raw = (data.get("choices") or [{}])[0].get("message", {}).get("content") @@ -201,14 +228,17 @@ def _call_dashscope_pro(img_url: str, idx: int, timeout: int) -> dict[str, Any] usage = data.get("usage") or {} logger.info( "[vision.vlm] dashscope pro 完成 idx=%d elapsed=%.1fs in=%d out=%d", - idx, elapsed, usage.get("prompt_tokens", 0), usage.get("completion_tokens", 0), + idx, + elapsed, + usage.get("prompt_tokens", 0), + usage.get("completion_tokens", 0), ) text = _strip_code_fence(raw) l, r_pos = text.find("{"), text.rfind("}") if l < 0 or r_pos <= l: logger.warning("[vision.vlm] dashscope pro 无JSON elapsed=%.1fs head=%s", elapsed, raw[:200]) return None - obj = json.loads(text[l:r_pos+1]) + obj = json.loads(text[l : r_pos + 1]) if not isinstance(obj, dict): return None scene = obj.get("scene") or "通用" @@ -218,17 +248,21 @@ def _call_dashscope_pro(img_url: str, idx: int, timeout: int) -> dict[str, Any] brand = obj.get("brand") or "无法判断" category = obj.get("category") or ("非产品图" if obj.get("has_person") else "无法判断") return { - "name": name, "brand": brand, "category": category, + "name": name, + "brand": brand, + "category": category, "appearance": obj.get("outfit") or "无法判断", "packaging": "无法判断", "text_on_package": [], "key_features": obj.get("key_features") or ["无法判断"], - "scene": scene, "mood": mood, "portrait_prompt": pp, + "scene": scene, + "mood": mood, + "portrait_prompt": pp, "summary": f"{brand} {name}" if name != "未识别" else "未识别", "_source": "vlm_pro_dashscope_json", } except Exception as e: - logger.warning("[vision.vlm] dashscope pro 异常 idx=%d elapsed=%.1fs err=%s", idx, time.time()-t0, e) + logger.warning("[vision.vlm] dashscope pro 异常 idx=%d elapsed=%.1fs err=%s", idx, time.time() - t0, e) return None @@ -237,7 +271,9 @@ def _call_ark_pro(img_url: str, idx: int, model: str | None, timeout: int) -> di t0 = time.time() try: from packages.application.viral_video.prompt_loader import ( - get_template, render_system_prompt, render_user_prompt, + get_template, + render_system_prompt, + render_user_prompt, ) from packages.shared.ai_client import get_doubao_client except ImportError as e: @@ -266,7 +302,7 @@ def _call_ark_pro(img_url: str, idx: int, model: str | None, timeout: int) -> di model=use_model, ) except Exception as e: - logger.warning("[vision.vlm] ark pro 调用失败 idx=%d elapsed=%.1fs err=%s", idx, time.time()-t0, e) + logger.warning("[vision.vlm] ark pro 调用失败 idx=%d elapsed=%.1fs err=%s", idx, time.time() - t0, e) client.max_retries = _orig_retries return None client.max_retries = _orig_retries @@ -278,7 +314,7 @@ def _call_ark_pro(img_url: str, idx: int, model: str | None, timeout: int) -> di l, r_pos = text.find("{"), text.rfind("}") if l >= 0 and r_pos > l: try: - obj = json.loads(text[l:r_pos+1]) + obj = json.loads(text[l : r_pos + 1]) if isinstance(obj, dict): logger.info("[vision.vlm] ark pro JSON 完成 idx=%d elapsed=%.1fs", idx, elapsed) return { diff --git a/apps/worker/worker_app/tasks/vision/vlm_fast_json.py b/apps/worker/worker_app/tasks/vision/vlm_fast_json.py index 47e885d38..5c376b5ee 100644 --- a/apps/worker/worker_app/tasks/vision/vlm_fast_json.py +++ b/apps/worker/worker_app/tasks/vision/vlm_fast_json.py @@ -122,7 +122,9 @@ def call_fast_json( if resp.status_code == 400: logger.warning( "[vision.v2] fast_json HTTP 400 provider=%s elapsed=%.1fs body=%s", - _provider.get_provider(), elapsed, resp.text[:200], + _provider.get_provider(), + elapsed, + resp.text[:200], ) if _provider.is_400_thinking_error(resp.text[:500]): _provider.pop_thinking_param(payload) @@ -141,7 +143,10 @@ def call_fast_json( elif resp.status_code != 200: logger.warning( "[vision.v2] fast_json HTTP %d provider=%s elapsed=%.1fs body=%s", - resp.status_code, _provider.get_provider(), elapsed, resp.text[:200], + resp.status_code, + _provider.get_provider(), + elapsed, + resp.text[:200], ) return None data = resp.json() @@ -156,8 +161,12 @@ def call_fast_json( reasoning_tokens = ctd.get("reasoning_tokens", 0) logger.info( "[vision.v2] fast_json 完成 provider=%s model=%s elapsed=%.1fs in=%d out=%d reasoning=%d", - _provider.get_provider(), use_model, elapsed, - usage.get("prompt_tokens", 0), usage.get("completion_tokens", 0), reasoning_tokens, + _provider.get_provider(), + use_model, + elapsed, + usage.get("prompt_tokens", 0), + usage.get("completion_tokens", 0), + reasoning_tokens, ) text = _strip_code_fence(raw) l = text.find("{") @@ -174,11 +183,21 @@ def call_fast_json( return None logger.info( "[vision.v2] fast_json 完成 provider=%s model=%s elapsed=%.1fs has_person=%s has_product=%s category=%s", - _provider.get_provider(), use_model, elapsed, - obj.get("has_person"), obj.get("has_product"), obj.get("category"), + _provider.get_provider(), + use_model, + elapsed, + obj.get("has_person"), + obj.get("has_product"), + obj.get("category"), ) return obj except Exception as e: elapsed = time.time() - t0 - logger.warning("[vision.v2] fast_json 异常 provider=%s elapsed=%.1fs err=%s", _provider.get_provider(), elapsed, e, exc_info=True) + logger.warning( + "[vision.v2] fast_json 异常 provider=%s elapsed=%.1fs err=%s", + _provider.get_provider(), + elapsed, + e, + exc_info=True, + ) return None