style: auto-format with black + isort + ruff + prettier [skip ci-format-check]
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (push) Successful in 1s
CI/CD Pipeline / Check if frontend-only change (push) Has been skipped
CI/CD Pipeline / Check push changed paths (pull_request) Has been skipped
CI/CD Pipeline / Dedup Check - skip PR tests when covered by push pipeline (pull_request) Successful in 3s
CI/CD Pipeline / Check if frontend-only change (pull_request) Successful in 4s
CI/CD Pipeline / Frontend Lint (push) Has been skipped
CI/CD Pipeline / PR Build API Image (push) Has been skipped
CI/CD Pipeline / PR Build Web Image (push) Has been skipped
CI/CD Pipeline / Validate - Style (pull_request) Has been skipped
CI/CD Pipeline / Validate - Security (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (push) Has been skipped
CI/CD Pipeline / Validate - Python (mypy + alembic) (pull_request) Has been skipped
CI/CD Pipeline / Check push changed paths (push) Successful in 17s
CI/CD Pipeline / Build Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / Frontend Lint (pull_request) Has been skipped
CI/CD Pipeline / Frontend Unit Tests (pull_request) Has been skipped
CI/CD Pipeline / PR Build Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build API Image (pull_request) Successful in 51s
CI/CD Pipeline / Build Staging API Image (push) Successful in 1m0s
CI/CD Pipeline / Retag skipped Staging API Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (pull_request) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production API Image (pull_request) Has been skipped
CI/CD Pipeline / Build Production Web Image (pull_request) Has been skipped
CI/CD Pipeline / PR Build Worker Image (pull_request) Successful in 1m8s
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (pull_request) Has been skipped
CI/CD Pipeline / Build Production Worker Image (pull_request) Has been skipped
CI/CD Pipeline / Staging E2E Tests (pull_request) Has been skipped
CI/CD Pipeline / ACR Image Cleanup (pull_request) Has been skipped
CI/CD Pipeline / Deploy Production (pull_request) Has been skipped
CI/CD Pipeline / Production Browser E2E (pull_request) Has been skipped
CI/CD Pipeline / Staging API Integration Tests (pull_request) Has been skipped
CI/CD Pipeline / CI Gate (pull_request) Successful in 2s
CI/CD Pipeline / Canary Release to Production (pull_request) Has been skipped
CI/CD Pipeline / Build Staging Worker Image (push) Successful in 34s
CI/CD Pipeline / Build Staging Web Image (push) Successful in 1m16s
CI/CD Pipeline / Retag skipped Staging API Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Web Image (push) Has been skipped
CI/CD Pipeline / Retag skipped Staging Worker Image (push) Has been skipped
Preview Deploy / Deploy Preview Environment (pull_request) Successful in 1m58s
PR Automation / Auto Approve on CI Green (pull_request) Successful in 3m6s
PR Automation / Auto Merge on CI Green + Approved (pull_request) Has been skipped
CI/CD Pipeline / Deploy Staging (Watchtower auto-deploy) (push) Successful in 1m16s
CI/CD Pipeline / Staging E2E Tests (push) Has been cancelled
CI/CD Pipeline / Validate - Python (mypy + alembic) (push) Has been cancelled
CI/CD Pipeline / Unit Tests (push) Has been cancelled
CI/CD Pipeline / Validate - Style (push) Has been cancelled
CI/CD Pipeline / Validate - Security (push) Has been cancelled
CI/CD Pipeline / Build Production API Image (push) Has been cancelled
CI/CD Pipeline / Build Production Web Image (push) Has been cancelled
CI/CD Pipeline / Build Production Worker Image (push) Has been cancelled
CI/CD Pipeline / Deploy Production (push) Has been cancelled
CI/CD Pipeline / Production Browser E2E (push) Has been cancelled
CI/CD Pipeline / Canary Release to Production (push) Has been cancelled
CI/CD Pipeline / CI Gate (push) Has been cancelled
CI/CD Pipeline / Integration Tests (push) Has been cancelled
CI/CD Pipeline / Frontend Unit Tests (push) Has been cancelled
CI/CD Pipeline / ACR Image Cleanup (push) Has been cancelled
CI/CD Pipeline / Staging API Integration Tests (push) Has been cancelled
AI Code Review / AI Code Review (pull_request) Has been cancelled

This commit is contained in:
CI Bot
2026-10-05 09:38:17 +00:00
parent 2d823a9255
commit dd420c556f
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: