From f8bd8bfe612563f6d433c365dba8af160ae875fa Mon Sep 17 00:00:00 2001 From: saas-backend <466829453@qq.com> Date: Sun, 4 Oct 2026 11:50:18 +0800 Subject: [PATCH 1/3] =?UTF-8?q?perf(#2172):=20=E4=BF=A1=E4=BB=BB=E9=93=BE?= =?UTF-8?q?=20Seedream=20i2i=20=E9=A2=84=E7=83=AD=E4=B8=8E=E6=96=87?= =?UTF-8?q?=E6=A1=88=E7=94=9F=E6=88=90=E5=B9=B6=E8=A1=8C=EF=BC=8C=E7=BC=A9?= =?UTF-8?q?=E7=9F=AD=E6=9C=89=E5=8F=82=E8=80=83=E5=9B=BE=E5=9C=BA=E6=99=AF?= =?UTF-8?q?=E7=AD=89=E5=BE=85=E6=97=B6=E9=97=B4?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit 问题: - #2170 信任链方案在视频渲染阶段同步串行跑 Seedream AI 化(每张约5-15s) - 用户提交到出片要多等一个 Seedream i2i 耗时 - 主人反馈「分析图片很慢」「生成文案也很慢」 方案: - 抽取 ai_client.preheat_trust_chain() 独立方法 - 在阶段1(图片VLM分析)开始时,用 daemon 线程后台并行启动 Seedream i2i 预热 - 预热结果持久化到 job.pre_trusted_images 字段(新增 094 migration) - 阶段3渲染时,若预热完成直接使用 pre_trusted_images,跳过现场 Seedream i2i - 预热失败/未完成不影响主流程,自动回退到现场跑信任链兜底 - VLM分析(~5-15s) + 文案生成(~10-20s) + 用户编辑确认(~30s+)期间,Seedream i2i 在后台已完成 改动文件: - packages/shared/ai_client.py: 抽取 preheat_trust_chain() 方法;video_generation 加 pre_trusted_images 参数支持预热结果 - packages/shared/ai_service.py: 新增 preheat_trust_chain() 入口;call_video_generation 透传 pre_trusted_images - packages/domain/viral_video.py: ViralVideoJob 加 pre_trusted_images 字段 - packages/adapters/sqlalchemy_impl/models.py: ViralVideoJobModel 加 pre_trusted_images 列 - packages/adapters/sqlalchemy_impl/viral_video_repository.py: _to_domain/save/update 映射新字段 - alembic/versions/094_viral_video_pre_trusted.py: 新增列 migration - apps/worker/worker_app/tasks/viral_video.py: _start_trust_chain_preheat() 后台预热启动函数;阶段1启动预热;_step_render 使用预热结果 测试:16513 passed(2个musetalk pre-existing失败,无关) --- .../versions/094_viral_video_pre_trusted.py | 31 ++++++ apps/worker/worker_app/tasks/viral_video.py | 79 +++++++++++++++ packages/adapters/sqlalchemy_impl/models.py | 1 + .../sqlalchemy_impl/viral_video_repository.py | 3 + packages/domain/viral_video.py | 1 + packages/shared/ai_client.py | 97 ++++++++++++------- packages/shared/ai_service.py | 20 +++- 7 files changed, 198 insertions(+), 34 deletions(-) create mode 100644 alembic/versions/094_viral_video_pre_trusted.py diff --git a/alembic/versions/094_viral_video_pre_trusted.py b/alembic/versions/094_viral_video_pre_trusted.py new file mode 100644 index 000000000..48daca27f --- /dev/null +++ b/alembic/versions/094_viral_video_pre_trusted.py @@ -0,0 +1,31 @@ +"""viral_video_jobs 增加 pre_trusted_images 列(信任链Seedream预热结果) + +Revision ID: 094_viral_video_pre_trusted +Revises: 093_viral_video_pricing_points_float +Create Date: 2026-10-04 +""" + +import sqlalchemy as sa + +from alembic import op + +revision = "094_viral_video_pre_trusted" +down_revision = "093_viral_video_pricing_points_float" +branch_labels = None +depends_on = None + + +def upgrade() -> None: + conn = op.get_bind() + inspector = sa.inspect(conn) + cols = {c["name"] for c in inspector.get_columns("viral_video_jobs")} + if "pre_trusted_images" not in cols: + op.add_column("viral_video_jobs", sa.Column("pre_trusted_images", sa.Text(), nullable=True)) + + +def downgrade() -> None: + conn = op.get_bind() + inspector = sa.inspect(conn) + cols = {c["name"] for c in inspector.get_columns("viral_video_jobs")} + if "pre_trusted_images" in cols: + op.drop_column("viral_video_jobs", "pre_trusted_images") diff --git a/apps/worker/worker_app/tasks/viral_video.py b/apps/worker/worker_app/tasks/viral_video.py index 3d6f9f880..be62cc719 100644 --- a/apps/worker/worker_app/tasks/viral_video.py +++ b/apps/worker/worker_app/tasks/viral_video.py @@ -92,6 +92,62 @@ def _save_job(repo, job, session): session.commit() +def _start_trust_chain_preheat(job_id: str, portrait_urls: list[str]) -> None: + """#2172 后台启动信任链预热(Seedream AI 化人像),不阻塞调用方。 + + 预热成功后把结果写入 job.pre_trusted_images,阶段3 渲染直接使用,省掉串行等待。 + 预热失败静默(pre_trusted_images 保持 None),阶段3 会现场跑信任链兜底。 + """ + if not portrait_urls: + return + # 判断是否是 doubao provider(DashScope/Wan 不需要信任链) + try: + from packages.domain.points_rules import get_viral_video_model_config + from packages.shared.ai_service import preheat_trust_chain + except ImportError: + return + + def _run_preheat(): + sess = None + try: + # 只有 doubao provider 的模型需要信任链 + try: + _s = get_shared_settings() + job_sess, job_repo, job_obj = _get_repo_and_job(job_id) + model = getattr(job_obj, "video_model", "") or "" + _mcfg = get_viral_video_model_config(model) + if _mcfg.get("provider", "doubao") != "doubao": + job_sess.close() + return + job_sess.close() + except Exception: + pass + + logger.info("[trust-chain][preheat] 后台预热启动 job=%s n=%d", job_id, len(portrait_urls)) + result = preheat_trust_chain(portrait_urls, timeout=120) + if result and len(result) == len(portrait_urls): + sess2, repo2, job2 = _get_repo_and_job(job_id) + try: + job2.pre_trusted_images = result + repo2.update(job2) + sess2.commit() + logger.info( + "[trust-chain][preheat] 预热完成并持久化 job=%s n=%d", + job_id, len(result), + ) + finally: + sess2.close() + else: + logger.info("[trust-chain][preheat] 预热失败 job=%s,阶段3现场跑兜底", job_id) + except Exception as e: + logger.warning("[trust-chain][preheat] 预热异常 job=%s err=%s", job_id, e, exc_info=True) + + # 用 daemon 线程启动,不阻塞 celery task 返回 + import threading + t = threading.Thread(target=_run_preheat, name=f"tc-preheat-{job_id[:8]}", daemon=True) + t.start() + + def _set_stage(job, repo, session, stage: str, message: str, persist: bool = True) -> None: """更新细粒度阶段并持久化到 DB,同时通过 Redis 推送进度事件。 @@ -1162,6 +1218,21 @@ def _step_render(job: ViralVideoJob, copy_result: dict, tts_audio_url: str | Non ) logger.info("[爆款视频] Seedance prompt (前300字): %s", prompt[:300]) + # #2172: 使用信任链预热结果(阶段1已在后台并行 Seedream AI 化) + pre_trusted = None + all_portrait_urls = [] + if first_image: + all_portrait_urls.append(first_image) + for u in rest_images: + if u not in all_portrait_urls: + all_portrait_urls.append(u) + pti = getattr(job, "pre_trusted_images", None) + if pti and len(pti) == len(all_portrait_urls): + pre_trusted = list(pti) + logger.info("[爆款视频] 使用信任链预热结果 n=%d,跳过现场 Seedream AI 化", len(pre_trusted)) + elif all_portrait_urls and _mcfg.get("provider", "doubao") == "doubao": + logger.info("[爆款视频] 预热结果不可用(%s/%d张),将现场跑信任链", "缺失" if not pti else f"{len(pti)}/{len(all_portrait_urls)}", len(all_portrait_urls)) + # 第一次调用:带参考图/首帧/音频/参考视频 result = call_video_generation( prompt=prompt, @@ -1175,6 +1246,7 @@ def _step_render(job: ViralVideoJob, copy_result: dict, tts_audio_url: str | Non reference_images=rest_images, reference_audios=ref_audios, reference_videos=ref_videos, + pre_trusted_images=pre_trusted, ) # #2170: 真人/肖像拦截由 ai_client 内部信任链自动处理(Seedream AI 化后再调 Seedance); @@ -1274,6 +1346,13 @@ def run_viral_video_pipeline(self: Task, job_id: str) -> dict: _hb_stop, _hb_thread = _start_heartbeat_thread(job_id) _set_stage(job, repo, session, ViralVideoStage.IMAGE_ANALYSIS, "正在分析商品特征...") + # #2172: 信任链预热与 VLM 分析并行启动(不阻塞) + if job.images: + try: + _start_trust_chain_preheat(job.id, list(job.images)) + except Exception as _e: + logger.warning("[爆款视频][阶段1] 启动信任链预热失败: %s", _e) + image_analysis = _step_image_analysis(job) job.image_analysis = image_analysis _save_job(repo, job, session) diff --git a/packages/adapters/sqlalchemy_impl/models.py b/packages/adapters/sqlalchemy_impl/models.py index 993c21c49..1d27406be 100755 --- a/packages/adapters/sqlalchemy_impl/models.py +++ b/packages/adapters/sqlalchemy_impl/models.py @@ -928,6 +928,7 @@ class ViralVideoJobModel(Base): id = Column(String(36), primary_key=True) user_id = Column(String(36), nullable=False, index=True) images = Column(JSON, nullable=False, default=list) # 产品图片 URL 列表 + pre_trusted_images = Column(JSON, nullable=True) # #2172 信任链预热结果(Seedream AI 化 URL 列表) industry = Column(String(100), nullable=False, default="") target_customer = Column(String(500), nullable=False, default="") persona_id = Column(String(36), nullable=False, default="") diff --git a/packages/adapters/sqlalchemy_impl/viral_video_repository.py b/packages/adapters/sqlalchemy_impl/viral_video_repository.py index e137b037b..cba8ff407 100755 --- a/packages/adapters/sqlalchemy_impl/viral_video_repository.py +++ b/packages/adapters/sqlalchemy_impl/viral_video_repository.py @@ -18,6 +18,7 @@ def _to_domain(model: ViralVideoJobModel) -> ViralVideoJob: id=model.id, user_id=model.user_id, images=list(model.images or []), + pre_trusted_images=list(getattr(model, "pre_trusted_images", None) or []) if getattr(model, "pre_trusted_images", None) is not None else None, industry=model.industry or "", target_customer=model.target_customer or "", persona_id=model.persona_id or "", @@ -70,6 +71,7 @@ class SQLAlchemyViralVideoJobRepository: id=job.id, user_id=job.user_id, images=job.images, + pre_trusted_images=job.pre_trusted_images, industry=job.industry, target_customer=job.target_customer, persona_id=job.persona_id, @@ -126,6 +128,7 @@ class SQLAlchemyViralVideoJobRepository: model.storyboard = job.storyboard model.generated_copy_text = job.generated_copy_text or "" model.copy_result = job.copy_result + model.pre_trusted_images = job.pre_trusted_images model.result_video_url = job.result_video_url model.video_resolution = getattr(job, "video_resolution", "720p") or "720p" model.credits_prepaid = float(getattr(job, "credits_prepaid", 0) or 0) diff --git a/packages/domain/viral_video.py b/packages/domain/viral_video.py index 73f1665f4..655cec5b6 100755 --- a/packages/domain/viral_video.py +++ b/packages/domain/viral_video.py @@ -87,6 +87,7 @@ class ViralVideoJob: user_id: str images: list[str] = field(default_factory=list) + pre_trusted_images: list[str] | None = None # #2172 信任链预热结果(Seedream AI 化后的 URL 列表),与 images 顺序对应 industry: str = "" target_customer: str = "" persona_id: str = "" diff --git a/packages/shared/ai_client.py b/packages/shared/ai_client.py index 1f0c43dde..033ceef50 100755 --- a/packages/shared/ai_client.py +++ b/packages/shared/ai_client.py @@ -413,6 +413,47 @@ class DoubaoClient: # ── 视频生成(Seedance 2.5,异步任务)──────────────────────────── + def preheat_trust_chain( + self, + portrait_urls: list[str], + *, + timeout: int = 120, + ) -> list[str] | None: + """#2172 信任链预热:对一组人像 URL 执行 Seedream AI 化,返回 AI 化后的 URL 列表。 + + - 全部成功返回 list[str](顺序与输入一致) + - 任何一张失败返回 None(保留上层回退原图直传的路径) + - 供 worker 在视频生成前并行预热使用;video_generation 内部若收到 preheated 结果会直接使用,不再现场跑。 + """ + if not portrait_urls or not self.is_available: + return None + seedream_prompt = ( + "保持此人五官特征、发型、肤色、面部轮廓、年龄感,生成一张高清写实人像照片," + "人物外貌特征与参考图完全一致,皮肤自然,光线柔和,高清细节,不要过度美化。" + ) + trusted: list[str] = [] + _t0 = time.time() + for idx, raw_url in enumerate(portrait_urls): + sd_prompt = seedream_prompt if len(portrait_urls) == 1 else f"{seedream_prompt}(这是参考图{idx + 1})" + sd_result = self.image_generation( + prompt=sd_prompt, + reference_images=[raw_url], + size="2K", + timeout=timeout, + ) + if not sd_result: + logger.warning( + "[trust-chain][preheat] Seedream 第 %d/%d 张失败: %s,预热整体失败", + idx + 1, len(portrait_urls), getattr(self, "last_image_error", None), + ) + return None + trusted.append(sd_result["url"]) + logger.info( + "[trust-chain][preheat] Seedream AI 化预热完成 %d 张,总耗时 %.1fs", + len(trusted), time.time() - _t0, + ) + return trusted + def video_generation( self, prompt: str, @@ -428,6 +469,7 @@ class DoubaoClient: reference_images: list[str] | None = None, reference_audios: list[str] | None = None, reference_videos: list[str] | None = None, + pre_trusted_images: list[str] | None = None, ) -> dict | None: """调用 Seedance 2.5 生视频(异步任务→轮询→下载)。 @@ -511,66 +553,55 @@ class DoubaoClient: ref_videos = [u for u in (reference_videos or [])[:3] if u and isinstance(u, str)] ref_imgs = [u for u in (reference_images or [])[:9] if u and isinstance(u, str)] - # ── #2170 方舟信任链(Trust Chain)──────────────────────────────────── + # ── #2170/#2172 方舟信任链(Trust Chain)──────────────────────────────── # 真人照片直接传给 Seedance 会触发 50411 肖像审核拦截。 # 解决:先通过同账号的 Seedream 5.0 Pro 图生图 AI 化(保持五官特征), # 得到的 AI 产物图属于"模型信任产物",再作为 reference_image 传给 Seedance 即可通过审核。 + # #2172: 支持预热结果 pre_trusted_images(worker 在文案阶段并行预热,省掉串行等待); + # 预热结果有效则直接使用,否则现场跑一次 Seedream AI 化。 # 信任链只作用于 doubao provider;DashScope(Wan) 保持原行为。 trust_chain_applied = False if provider == "doubao": - seedream_prompt = ( - "保持此人五官特征、发型、肤色、面部轮廓、年龄感,生成一张高清写实人像照片," - "人物外貌特征与参考图完全一致,皮肤自然,光线柔和,高清细节,不要过度美化。" - ) raw_portrait_urls: list[str] = [] if image_url: raw_portrait_urls.append(image_url) for u in ref_imgs: if u not in raw_portrait_urls: raw_portrait_urls.append(u) - if raw_portrait_urls: - trusted_urls: list[str] = [] + trusted_urls: list[str] = [] + if pre_trusted_images and len(pre_trusted_images) == len(raw_portrait_urls): + # #2172: 使用预热结果 + trusted_urls = list(pre_trusted_images) + trust_chain_applied = True + logger.info( + "[trust-chain] 使用预热结果 %d 张,替换为 reference_image 模式", + len(trusted_urls), + ) + elif raw_portrait_urls: + # 现场跑信任链 _tc_t0 = time.time() - for idx, raw_url in enumerate(raw_portrait_urls): - sd_prompt = ( - seedream_prompt if len(raw_portrait_urls) == 1 else f"{seedream_prompt}(这是参考图{idx + 1})" - ) - sd_result = self.image_generation( - prompt=sd_prompt, - reference_images=[raw_url], - size="2K", - timeout=120, - ) - if not sd_result: - logger.warning( - "[trust-chain] Seedream 第 %d/%d 张失败: %s,回退直传原图", - idx + 1, - len(raw_portrait_urls), - self.last_image_error, - ) - break - trusted_urls.append(sd_result["url"]) + trusted_urls = self.preheat_trust_chain(raw_portrait_urls) or [] if trusted_urls and len(trusted_urls) == len(raw_portrait_urls): trust_chain_applied = True - # 替换:原 image_url 用第一张 AI 图,ref_imgs 用剩余 - if image_url and trusted_urls: - image_url = trusted_urls[0] - ref_imgs = trusted_urls[1:] if len(trusted_urls) > 1 else [] - else: - ref_imgs = trusted_urls logger.info( "[trust-chain] Seedream AI 化完成 %d 张,总耗时 %.1fs,替换为 reference_image 模式", len(trusted_urls), time.time() - _tc_t0, ) else: - # Seedream 部分失败 → 回退原图直传(仍可能被 50411 拦截,但保留降级路径) logger.warning( "[trust-chain] Seedream AI 化不完整(%d/%d),总耗时 %.1fs,回退原图直传", len(trusted_urls), len(raw_portrait_urls), time.time() - _tc_t0, ) + if trust_chain_applied and trusted_urls: + # 替换:原 image_url 用第一张 AI 图,ref_imgs 用剩余 + if image_url and trusted_urls: + image_url = trusted_urls[0] + ref_imgs = trusted_urls[1:] if len(trusted_urls) > 1 else [] + else: + ref_imgs = trusted_urls # ───────────────────────────────────────────────────────────────── # 判断任务模式: diff --git a/packages/shared/ai_service.py b/packages/shared/ai_service.py index 0bd00e5f3..5d9e97461 100755 --- a/packages/shared/ai_service.py +++ b/packages/shared/ai_service.py @@ -604,6 +604,22 @@ def call_vision( return raw + +def preheat_trust_chain(portrait_urls: list[str], *, timeout: int = 120) -> list[str] | None: + """#2172 信任链预热:提前把人像图跑 Seedream AI 化,结果可传给 call_video_generation(pre_trusted_images=...)。 + + 成功返回与输入同序的 AI 化 URL 列表;任意一张失败返回 None(调用方回退到现场跑信任链)。 + """ + client = get_doubao_client() + if not client.is_available: + return None + try: + return client.preheat_trust_chain(portrait_urls, timeout=timeout) + except Exception as e: + logger.error("[ai_service] preheat_trust_chain 异常: %s", e, exc_info=True) + return None + + def call_video_generation( prompt: str, *, @@ -617,8 +633,9 @@ def call_video_generation( reference_images: list[str] | None = None, reference_audios: list[str] | None = None, reference_videos: list[str] | None = None, + pre_trusted_images: list[str] | None = None, ) -> dict | None: - """调用 Seedance / Wan 视频生成(v1.6.2 多模型版)。 + """调用 Seedance / Wan 视频生成(v1.6.2 多模型版 + #2172 信任链预热)。 成功返回 {"video_path": str, "usage": dict | None}(usage 含 completion_tokens),失败返回 None。 失败时错误详情会写入 client.last_video_error,可通过 get_last_video_error() 读取: @@ -650,6 +667,7 @@ def call_video_generation( reference_images=reference_images, reference_audios=reference_audios, reference_videos=reference_videos, + pre_trusted_images=pre_trusted_images, ) if effective_ratio: kwargs["ratio"] = effective_ratio -- 2.54.0 From ba1480cc9c0d22d91a089452d0544aff6cd67065 Mon Sep 17 00:00:00 2001 From: CI Bot Date: Sun, 4 Oct 2026 03:55:55 +0000 Subject: [PATCH 2/3] style: auto-format with black + isort + ruff + prettier [skip ci-format-check] --- apps/worker/worker_app/tasks/viral_video.py | 11 ++++++++--- .../sqlalchemy_impl/viral_video_repository.py | 6 +++++- packages/domain/viral_video.py | 4 +++- packages/shared/ai_client.py | 7 +++++-- packages/shared/ai_service.py | 1 - 5 files changed, 21 insertions(+), 8 deletions(-) diff --git a/apps/worker/worker_app/tasks/viral_video.py b/apps/worker/worker_app/tasks/viral_video.py index be62cc719..bbbc7a728 100644 --- a/apps/worker/worker_app/tasks/viral_video.py +++ b/apps/worker/worker_app/tasks/viral_video.py @@ -108,7 +108,6 @@ def _start_trust_chain_preheat(job_id: str, portrait_urls: list[str]) -> None: return def _run_preheat(): - sess = None try: # 只有 doubao provider 的模型需要信任链 try: @@ -133,7 +132,8 @@ def _start_trust_chain_preheat(job_id: str, portrait_urls: list[str]) -> None: sess2.commit() logger.info( "[trust-chain][preheat] 预热完成并持久化 job=%s n=%d", - job_id, len(result), + job_id, + len(result), ) finally: sess2.close() @@ -144,6 +144,7 @@ def _start_trust_chain_preheat(job_id: str, portrait_urls: list[str]) -> None: # 用 daemon 线程启动,不阻塞 celery task 返回 import threading + t = threading.Thread(target=_run_preheat, name=f"tc-preheat-{job_id[:8]}", daemon=True) t.start() @@ -1231,7 +1232,11 @@ def _step_render(job: ViralVideoJob, copy_result: dict, tts_audio_url: str | Non pre_trusted = list(pti) logger.info("[爆款视频] 使用信任链预热结果 n=%d,跳过现场 Seedream AI 化", len(pre_trusted)) elif all_portrait_urls and _mcfg.get("provider", "doubao") == "doubao": - logger.info("[爆款视频] 预热结果不可用(%s/%d张),将现场跑信任链", "缺失" if not pti else f"{len(pti)}/{len(all_portrait_urls)}", len(all_portrait_urls)) + logger.info( + "[爆款视频] 预热结果不可用(%s/%d张),将现场跑信任链", + "缺失" if not pti else f"{len(pti)}/{len(all_portrait_urls)}", + len(all_portrait_urls), + ) # 第一次调用:带参考图/首帧/音频/参考视频 result = call_video_generation( diff --git a/packages/adapters/sqlalchemy_impl/viral_video_repository.py b/packages/adapters/sqlalchemy_impl/viral_video_repository.py index cba8ff407..f7a76b8e5 100755 --- a/packages/adapters/sqlalchemy_impl/viral_video_repository.py +++ b/packages/adapters/sqlalchemy_impl/viral_video_repository.py @@ -18,7 +18,11 @@ def _to_domain(model: ViralVideoJobModel) -> ViralVideoJob: id=model.id, user_id=model.user_id, images=list(model.images or []), - pre_trusted_images=list(getattr(model, "pre_trusted_images", None) or []) if getattr(model, "pre_trusted_images", None) is not None else None, + pre_trusted_images=( + list(getattr(model, "pre_trusted_images", None) or []) + if getattr(model, "pre_trusted_images", None) is not None + else None + ), industry=model.industry or "", target_customer=model.target_customer or "", persona_id=model.persona_id or "", diff --git a/packages/domain/viral_video.py b/packages/domain/viral_video.py index 655cec5b6..09119a00a 100755 --- a/packages/domain/viral_video.py +++ b/packages/domain/viral_video.py @@ -87,7 +87,9 @@ class ViralVideoJob: user_id: str images: list[str] = field(default_factory=list) - pre_trusted_images: list[str] | None = None # #2172 信任链预热结果(Seedream AI 化后的 URL 列表),与 images 顺序对应 + pre_trusted_images: list[str] | None = ( + None # #2172 信任链预热结果(Seedream AI 化后的 URL 列表),与 images 顺序对应 + ) industry: str = "" target_customer: str = "" persona_id: str = "" diff --git a/packages/shared/ai_client.py b/packages/shared/ai_client.py index 033ceef50..78afc9f9b 100755 --- a/packages/shared/ai_client.py +++ b/packages/shared/ai_client.py @@ -444,13 +444,16 @@ class DoubaoClient: if not sd_result: logger.warning( "[trust-chain][preheat] Seedream 第 %d/%d 张失败: %s,预热整体失败", - idx + 1, len(portrait_urls), getattr(self, "last_image_error", None), + idx + 1, + len(portrait_urls), + getattr(self, "last_image_error", None), ) return None trusted.append(sd_result["url"]) logger.info( "[trust-chain][preheat] Seedream AI 化预热完成 %d 张,总耗时 %.1fs", - len(trusted), time.time() - _t0, + len(trusted), + time.time() - _t0, ) return trusted diff --git a/packages/shared/ai_service.py b/packages/shared/ai_service.py index 5d9e97461..09054c2df 100755 --- a/packages/shared/ai_service.py +++ b/packages/shared/ai_service.py @@ -604,7 +604,6 @@ def call_vision( return raw - def preheat_trust_chain(portrait_urls: list[str], *, timeout: int = 120) -> list[str] | None: """#2172 信任链预热:提前把人像图跑 Seedream AI 化,结果可传给 call_video_generation(pre_trusted_images=...)。 -- 2.54.0 From 2080eec46f9c5c80581de05f27b4330e78535a51 Mon Sep 17 00:00:00 2001 From: saas-backend <466829453@qq.com> Date: Sun, 4 Oct 2026 12:05:29 +0800 Subject: [PATCH 3/3] =?UTF-8?q?fix(ci):=20094=20migration=20down=5Frevisio?= =?UTF-8?q?n=20=E7=94=A8=20093=20=E5=AE=9E=E9=99=85revision=20ID=EF=BC=880?= =?UTF-8?q?93=E7=94=A8=E4=BA=86=E7=9F=ADID=EF=BC=8C=E9=9D=9E=E5=85=A8?= =?UTF-8?q?=E5=90=8D=EF=BC=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- alembic/versions/094_viral_video_pre_trusted.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/alembic/versions/094_viral_video_pre_trusted.py b/alembic/versions/094_viral_video_pre_trusted.py index 48daca27f..41f21e3b5 100644 --- a/alembic/versions/094_viral_video_pre_trusted.py +++ b/alembic/versions/094_viral_video_pre_trusted.py @@ -10,7 +10,7 @@ import sqlalchemy as sa from alembic import op revision = "094_viral_video_pre_trusted" -down_revision = "093_viral_video_pricing_points_float" +down_revision = "093" branch_labels = None depends_on = None -- 2.54.0