diff --git a/apps/worker/worker_app/tasks/viral_video.py b/apps/worker/worker_app/tasks/viral_video.py index 6c652ca78..68155919e 100644 --- a/apps/worker/worker_app/tasks/viral_video.py +++ b/apps/worker/worker_app/tasks/viral_video.py @@ -24,7 +24,6 @@ from __future__ import annotations import json import logging import os -import re import tempfile import threading import time @@ -347,6 +346,7 @@ def _normalize_image_url(raw: str, idx: int) -> str: storage_key = url.lstrip("/") try: from packages.shared.storage import get_storage_service + url = get_storage_service().get_url(storage_key) except Exception as _e: raise ValueError(f"图片 #{idx} storage_key={storage_key!r} 转公网URL失败: {_e}") from _e @@ -386,6 +386,7 @@ def _step_image_analysis(job: ViralVideoJob) -> dict: # 整个阶段关闭底层 httpx 重试,避免线程里出现不可控等待 try: from packages.shared.ai_client import get_doubao_client as _gdc + _cli = _gdc() _orig_retries = _cli.max_retries _cli.max_retries = 0 diff --git a/apps/worker/worker_app/tasks/vision/__init__.py b/apps/worker/worker_app/tasks/vision/__init__.py index acb6960ff..dfc55ccda 100644 --- a/apps/worker/worker_app/tasks/vision/__init__.py +++ b/apps/worker/worker_app/tasks/vision/__init__.py @@ -1,3 +1,4 @@ # -*- coding: utf-8 -*- """V2 图片分析:火山OCR专用API + doubao-lite强约束JSON并行,单次pro VLM兜底。""" + from .fast_path import analyze_image_v2, analyze_images_v2 # noqa: F401 diff --git a/apps/worker/worker_app/tasks/vision/fast_path.py b/apps/worker/worker_app/tasks/vision/fast_path.py index 6b6585ebf..6bedcd237 100644 --- a/apps/worker/worker_app/tasks/vision/fast_path.py +++ b/apps/worker/worker_app/tasks/vision/fast_path.py @@ -27,10 +27,17 @@ _OCR_TIMEOUT = float(os.environ.get("VISION_V2_OCR_TIMEOUT", "6")) _PRO_TIMEOUT = float(os.environ.get("VISION_V2_PRO_TIMEOUT", "45")) _FALLBACK_RESULT = { - "name": "未识别", "brand": "无法判断", "category": "非产品图", - "appearance": "无法判断", "packaging": "无法判断", "text_on_package": [], - "key_features": ["无法判断"], "scene": "通用", "mood": "", - "portrait_prompt": "无法判断", "summary": "未识别", + "name": "未识别", + "brand": "无法判断", + "category": "非产品图", + "appearance": "无法判断", + "packaging": "无法判断", + "text_on_package": [], + "key_features": ["无法判断"], + "scene": "通用", + "mood": "", + "portrait_prompt": "无法判断", + "summary": "未识别", } @@ -75,7 +82,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命中 elapsed=%.2fs pp=%s", - idx, fast_elapsed, (assembled.get("portrait_prompt") or "")[:40], + idx, + fast_elapsed, + (assembled.get("portrait_prompt") or "")[:40], ) return assembled @@ -88,11 +97,11 @@ def analyze_image_v2(idx: int, img_url: str) -> dict[str, Any]: pro_result["_pro_elapsed"] = round(time.time() - pro_t0, 2) if ocr_result and not pro_result.get("text_on_package"): pro_result["text_on_package"] = ocr_result[:8] - logger.info("[vision.v2] 图片 #%d pro兜底命中 total=%.2fs", idx, time.time()-t0) + logger.info("[vision.v2] 图片 #%d pro兜底命中 total=%.2fs", idx, time.time() - t0) return pro_result # 最终:返回最小可用结果 - logger.warning("[vision.v2] 图片 #%d 全路径失败 elapsed=%.2fs", idx, time.time()-t0) + logger.warning("[vision.v2] 图片 #%d 全路径失败 elapsed=%.2fs", idx, time.time() - t0) out = dict(_FALLBACK_RESULT) out["_source"] = "v2_all_failed" out["text_on_package"] = ocr_result[:8] diff --git a/apps/worker/worker_app/tasks/vision/vlm_fallback.py b/apps/worker/worker_app/tasks/vision/vlm_fallback.py index 90701630b..bb6e0481a 100644 --- a/apps/worker/worker_app/tasks/vision/vlm_fallback.py +++ b/apps/worker/worker_app/tasks/vision/vlm_fallback.py @@ -3,6 +3,7 @@ 设计原则:简单、直接、无竞速、无复杂超时逻辑。只在 fast_json 结果不可用时调用。 """ + from __future__ import annotations import json @@ -85,26 +86,48 @@ def _xml_to_product(raw: str, idx: int) -> dict[str, Any]: if pp_attr and pp_attr != "无人像": portrait_prompt = pp_attr 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, + "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": "未识别", + "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", } @@ -151,7 +174,7 @@ def call_pro_vlm( model=use_model, ) except Exception as e: - logger.warning("[vision.vlm] 图片 #%d pro VLM 调用失败 elapsed=%.1fs err=%s", idx, time.time()-t0, e) + logger.warning("[vision.vlm] 图片 #%d pro VLM 调用失败 elapsed=%.1fs err=%s", idx, time.time() - t0, e) return None elapsed = time.time() - t0 @@ -163,7 +186,7 @@ def call_pro_vlm( l, r = text.find("{"), text.rfind("}") if l >= 0 and r > l: try: - obj = json.loads(text[l:r+1]) + obj = json.loads(text[l : r + 1]) if isinstance(obj, dict): logger.info("[vision.vlm] 图片 #%d pro VLM JSON 完成 elapsed=%.1fs", idx, elapsed) return { @@ -189,7 +212,9 @@ def call_pro_vlm( result["_pro_elapsed"] = round(elapsed, 2) logger.info( "[vision.vlm] 图片 #%d pro VLM XML 完成 elapsed=%.2fs pp=%s", - idx, elapsed, (result.get("portrait_prompt") or "")[:40], + idx, + elapsed, + (result.get("portrait_prompt") or "")[:40], ) return result except Exception as e: