Compare commits

...

1 Commits

Author SHA1 Message Date
CI Bot c6d02c7fe2 style: auto-format with black + isort + ruff + prettier [skip ci-format-check] 2026-10-05 09:33:40 +00:00
4 changed files with 59 additions and 23 deletions
+2 -1
View File
@@ -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
@@ -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
@@ -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]
@@ -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: