Files
yingqing/docs/todo/后端管线全景图.html
T
seaislee1209andClaude Opus 4.8 8af2df709c docs(todo): 后端生成管线全景结构图(HTML 白板)— 每步 AI/代码/数据 标清
3 个侦察 agent 实测代码录制:脚本→资产→故事板→视频 全链。可视化页面标明每步触发/读取/
提示词拼装/AI还是纯代码/参考图引用/落点。关键澄清:角色场景=AI一次产出非程序提取;
AI 只在 5 处真调(①脚本text ②③图 ④故事板图 ⑤Seedance视频),其余纯代码。

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-19 15:02:14 +08:00

308 lines
21 KiB
HTML
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
<!doctype html>
<html lang="zh-CN">
<head>
<meta charset="utf-8" />
<meta name="viewport" content="width=device-width, initial-scale=1" />
<title>AirShelf 后端生成管线 · 全景结构图</title>
<style>
:root{
--bg:#f9f9f9; --surface:#fff; --ink:#262626; --line:#ededed; --line2:#e6e6e6;
--heat:#fa5d19; --heat12:rgba(250,93,25,.12); --heat20:rgba(250,93,25,.20);
--a48:rgba(38,38,38,.48); --a56:rgba(38,38,38,.56); --a64:rgba(38,38,38,.64); --a8:rgba(0,0,0,.05);
--code:#5a6b7b; --code-bg:#eef2f5; --data:#2f7d6b; --data-bg:#e7f3ef;
--mono:'SFMono-Regular',Consolas,'Liberation Mono',Menlo,monospace;
--sans:Inter,'Alibaba PuHuiTi','PingFang SC','Microsoft YaHei',system-ui,sans-serif;
}
*{box-sizing:border-box}
body{margin:0;background:var(--bg);color:var(--ink);font-family:var(--sans);line-height:1.6;font-size:14px;-webkit-font-smoothing:antialiased}
.wrap{max-width:1180px;margin:0 auto;padding:40px 28px 80px}
h1{font-size:30px;font-weight:600;letter-spacing:-.02em;margin:0 0 6px}
.sub{font-family:var(--mono);font-size:12px;color:var(--a48);letter-spacing:.04em;margin-bottom:24px}
.lead{font-size:14px;color:var(--a64);max-width:760px;margin:0 0 28px}
.lead b{color:var(--ink);font-weight:600}
/* legend */
.legend{display:flex;gap:18px;flex-wrap:wrap;background:var(--surface);border:1px solid var(--line);border-radius:8px;padding:14px 18px;margin-bottom:32px}
.legend .it{display:flex;align-items:center;gap:8px;font-size:13px}
.dot{width:11px;height:11px;border-radius:3px;flex-shrink:0}
.dot.ai{background:var(--heat)} .dot.code{background:var(--code)} .dot.data{background:var(--data)}
.tag{display:inline-flex;align-items:center;gap:5px;font-family:var(--mono);font-size:11px;padding:2px 8px;border-radius:4px;letter-spacing:.02em;white-space:nowrap}
.tag.ai{background:var(--heat12);color:var(--heat);border:1px solid var(--heat20)}
.tag.code{background:var(--code-bg);color:var(--code)}
.tag.data{background:var(--data-bg);color:var(--data)}
/* stage card */
.stage{background:var(--surface);border:1px solid var(--line);border-radius:8px;padding:0;margin-bottom:14px;overflow:hidden;position:relative}
.stage::before{content:'';position:absolute;left:0;top:0;bottom:0;width:3px;background:var(--heat)}
.st-h{display:flex;align-items:center;gap:14px;padding:18px 24px 14px}
.st-no{width:30px;height:30px;border-radius:8px;background:var(--ink);color:#fff;display:grid;place-items:center;font-weight:600;font-size:14px;flex-shrink:0}
.st-name{font-size:17px;font-weight:600}
.st-trigger{margin-left:auto;font-family:var(--mono);font-size:11px;color:var(--a48);text-align:right;letter-spacing:.02em}
.st-trigger b{color:var(--heat);font-weight:500}
.st-body{padding:0 24px 22px;display:grid;grid-template-columns:1fr;gap:16px}
/* flow row */
.flow{display:flex;align-items:center;gap:7px;flex-wrap:wrap;padding:12px 14px;background:var(--bg);border-radius:8px}
.flow .arr{color:var(--a48);font-size:13px}
.blk{margin-top:2px}
.blk .lbl{font-family:var(--mono);font-size:11px;color:var(--a48);text-transform:uppercase;letter-spacing:.06em;margin-bottom:6px}
.prompt{font-family:var(--mono);font-size:12px;line-height:1.75;background:#1f2430;color:#d7dde6;border-radius:8px;padding:14px 16px;white-space:pre-wrap;overflow-x:auto}
.prompt .h{color:#ffb38a} .prompt .v{color:#86d0b6} .prompt .c{color:#6f7e8f}
.grid2{display:grid;grid-template-columns:1fr 1fr;gap:16px}
@media(max-width:760px){.grid2{grid-template-columns:1fr}}
.kv{font-size:13px}
.kv .k{font-family:var(--mono);font-size:11px;color:var(--a48);letter-spacing:.04em}
.kv .row{padding:6px 0;border-bottom:1px solid var(--line)}
.kv .row:last-child{border-bottom:0}
.note{font-size:12.5px;color:var(--a56);background:var(--heat12);border-radius:8px;padding:10px 14px;border-left:2px solid var(--heat)}
.arrow-down{text-align:center;color:var(--a48);font-size:18px;margin:-4px 0 2px}
.dataflow{font-family:var(--mono);font-size:11px;color:var(--data);text-align:center;margin:2px 0 8px;letter-spacing:.02em}
/* tables */
table{width:100%;border-collapse:collapse;background:var(--surface);border:1px solid var(--line);border-radius:8px;overflow:hidden;font-size:13px}
th,td{text-align:left;padding:11px 14px;border-bottom:1px solid var(--line);vertical-align:top}
th{background:var(--bg);font-family:var(--mono);font-size:11px;color:var(--a48);text-transform:uppercase;letter-spacing:.04em;font-weight:500}
tr:last-child td{border-bottom:0}
.sec-h{font-size:20px;font-weight:600;margin:40px 0 14px;letter-spacing:-.01em}
.break{background:var(--surface);border:1px solid var(--line);border-left:3px solid var(--heat);border-radius:8px;padding:14px 18px;margin-bottom:10px}
.break .t{font-weight:600;font-size:14px;margin-bottom:3px}
.break .d{font-size:13px;color:var(--a64)}
.break .fix{font-size:12.5px;color:var(--heat);font-family:var(--mono);margin-top:5px}
code{font-family:var(--mono);font-size:12px;background:var(--a8);padding:1px 5px;border-radius:4px;color:var(--ink)}
</style>
</head>
<body>
<div class="wrap">
<h1>AirShelf 后端生成管线 · 全景结构图</h1>
<div class="sub">// 商品 → 脚本 → 基础资产 → 故事板 → 视频 → 导出 &nbsp;·&nbsp; 2026-06-19 实测代码录制</div>
<p class="lead">看清楚每一步:<b>触发</b>(哪个端点)→ <b>读了哪些数据</b><b>提示词怎么拼</b><b>哪步是 AI / 哪步是纯代码</b><b>参考图怎么引</b><b>落到哪</b>
橙色=真在调 AI 模型,灰色=纯程序在跑,绿色=数据落点。</p>
<div class="legend">
<div class="it"><span class="dot ai"></span><b>AI 调用</b> —— LLM / 图像 / 视频模型真在生效</div>
<div class="it"><span class="dot code"></span><b>纯代码</b> —— 规范化 / 校验 / 拼提示词 / 调度 / 落库</div>
<div class="it"><span class="dot data"></span><b>数据落点</b> —— DB 表 / project.metadata</div>
</div>
<!-- 阶段1 -->
<div class="stage">
<div class="st-h"><div class="st-no">1</div><div class="st-name">脚本 Agent</div>
<div class="st-trigger"><b>POST</b> /script-agent-stream/ · SSE 流式<br>模式 auto / theme / revise(可精准改1镜)</div></div>
<div class="st-body">
<div class="flow">
<span class="tag code">选文本模型</span><span class="arr"></span>
<span class="tag code">load_ecommerce_skill 拼系统提示词</span><span class="arr"></span>
<span class="tag code">拼商品+卖点上下文</span><span class="arr"></span>
<span class="tag code">预扣额度</span><span class="arr"></span>
<span class="tag ai">① 1 次 text LLM 流式调用</span><span class="arr"></span>
<span class="tag code">抽JSON+规范化兜底</span><span class="arr"></span>
<span class="tag data">落库</span>
</div>
<div class="grid2">
<div class="blk">
<div class="lbl">AI 节点 ① · 唯一一次 AI 调用</div>
<div class="kv">
<div class="row"><span class="k">能力</span> &nbsp;text(纯文本,无图无视频)</div>
<div class="row"><span class="k">底座</span> &nbsp;火山直连 VolcanoArk · 或中转站 OpenAICompatible(看模型 provider)</div>
<div class="row"><span class="k">模型</span> &nbsp;请求指定 或 最早创建的 active 文本模型</div>
<div class="row"><span class="k">系统提示词</span> &nbsp;SKILL.md + 5 篇 references + 运行时输出协议</div>
</div>
</div>
<div class="blk">
<div class="lbl">AI 一次性产出(同一次调用里)</div>
<div class="prompt"><span class="h">entities[]</span> 角色/场景/商品,每个带:
id · type · name · <span class="v">visual_prompt</span>(给图模型的生图词) · ref_index
<span class="h">segments[]</span> 每 15 秒一镜:
index · role(钩子/痛点/卖点/CTA) · narration(口播)
· visual(画面) · product_exposure · <span class="v">entity_refs</span>(本镜引用哪些实体)</div>
</div>
</div>
<div class="note"><b>关键澄清:角色/场景/商品 = AI 生成脚本时「一次性吐出」的,不是后面用程序/正则单独提取。</b>
skill 里把 entities 写进同一份输出契约,要求 AI 为每个实体自动写一份 <code>visual_prompt</code>(小白不用打字)。后端只做规范化兜底,不再二次调 AI。</div>
<div class="blk">
<div class="lbl">纯代码步骤(无 AI)</div>
<div style="font-size:13px;color:var(--a64)">
<span class="tag code">normalize_draft</span> 抽 JSON+对抗字段名漂移 ·
<span class="tag code">_merge_single_segment</span> 精准改1镜只换该镜 ·
<span class="tag code">_map_entities_to_project_metadata</span> 把 entities 拆进 metadata ·
<span class="tag code">计费 reserve→charge</span>
</div>
</div>
<div class="blk">
<div class="lbl">数据落点</div>
<div style="font-size:13px">
<span class="tag data">ScriptVersion</span> content=完整JSON · metadata={entities 全量} &nbsp;
<span class="tag data">ScriptSegment(每镜)</span> narration · visual_prompt · role · entity_refs · dialogue &nbsp;
<span class="tag data">project.metadata</span> cast · <b>cast_prompts</b>{名:visual_prompt} · scenes · scene_prompts · <b>script_entities</b>(全量)
</div>
</div>
</div>
</div>
<div class="arrow-down"></div>
<div class="dataflow">project.metadata.cast_prompts / scene_prompts / script_entities &nbsp;——传给下游——→</div>
<!-- 阶段2 -->
<div class="stage">
<div class="st-h"><div class="st-no">2</div><div class="st-name">基础资产(人物 / 场景 / 商品图)</div>
<div class="st-trigger"><b>POST</b> /generate-base-asset/ · 202秒回<br>真出图在后台 worker</div></div>
<div class="st-body">
<div class="flow">
<span class="tag data">前端读 metadata.cast_prompts[名]</span><span class="arr"></span>
<span class="tag code">当 prompt + label=实体名 POST</span><span class="arr"></span>
<span class="tag code">按 kind 拼最终 prompt</span><span class="arr"></span>
<span class="tag ai">② 图像模型出 1 张图</span><span class="arr"></span>
<span class="tag code">落 Asset + 归组 + 自动采用</span><span class="arr"></span>
<span class="tag ai">③ 人物再出三视图</span>
</div>
<div class="grid2">
<div class="blk">
<div class="lbl">提示词拼装(按 kind 三选一)</div>
<div class="prompt"><span class="c"># 商品(有主图)→ image_edit 锁包装</span>
参考图1是「商品名」真实主图,严格参照包装/配色/Logo
生成三视图:正面/侧面/背面,纯白底,16:9 + <span class="v">{前端prompt}</span>
<span class="c"># 人物 → 文生图,固定模板包裹</span>
电商真人模特,氛围正面全身照,<span class="v">{visual_prompt}</span>,
自然妆容,柔和影棚光,单人,纯色背景
<span class="c"># 场景 → 原样透传</span>
<span class="v">{visual_prompt}</span>(不加模板)</div>
</div>
<div class="blk">
<div class="lbl">AI 节点 ②③</div>
<div class="kv">
<div class="row"><span class="k">底座</span> &nbsp;默认图像模型 = <b>tokenssr:gpt-image-2</b>(中转站 OpenAICompatible)</div>
<div class="row"><span class="k">商品</span> &nbsp;<span class="tag ai">image_edit</span> images=[商品主图] · 锁一致性</div>
<div class="row"><span class="k">人物/场景</span> &nbsp;<span class="tag ai">image_generation</span> 纯文生图</div>
<div class="row"><span class="k">三视图</span> &nbsp;<span class="tag ai">image_edit</span> 据立绘/主图 → 1 张 16:9 横图(4 视角并排)</div>
</div>
<div class="note" style="margin-top:12px"><b>label = 实体名</b>:生成时 <code>group.metadata.label</code> = 前端传的实体 tag。下游故事板就靠它把参考图对回实体。</div>
</div>
</div>
<div class="blk">
<div class="lbl">数据落点 + 推进</div>
<div style="font-size:13px"><span class="tag data">BaseAssetGroup</span> kind · metadata.label=实体名 · candidate_assets · adopted_asset &nbsp;·&nbsp;
三种 kind(人+场景+商品)都有采用资产 → 推进到故事板阶段</div>
</div>
</div>
</div>
<div class="arrow-down"></div>
<div class="dataflow">segment.entity_refs + 已采用 BaseAssetGroup(按 label) &nbsp;——→ 选参考图</div>
<!-- 阶段3 -->
<div class="stage">
<div class="st-h"><div class="st-no">3</div><div class="st-name">故事板(分镜图)</div>
<div class="st-trigger"><b>POST</b> /generate-storyboard + /poll-storyboard<br>HTTP秒回 + 后台线程出图(非 celery)· 最多 4 并发</div></div>
<div class="st-body">
<div class="flow">
<span class="tag code">取本镜 15s 脚本(visual/narration/露出)</span><span class="arr"></span>
<span class="tag code">_storyboard_reference_images 选参考图</span><span class="arr"></span>
<span class="tag code">拼 @图N + 导演指令 + 分镜脚本</span><span class="arr"></span>
<span class="tag ai">④ image_edit 多图合成</span><span class="arr"></span>
<span class="tag data">落 StoryboardFrame</span>
</div>
<div class="grid2">
<div class="blk">
<div class="lbl">提示词拼装(refs 版 · 实际走这个)</div>
<div class="prompt"><span class="h">【设定】</span>@图1是小美(角色),@图2是耳机(商品),
@图3是地铁(场景)。
根据以下脚本生成导演故事板,用于指导 seedance 视频生成。
{项目级 cast/scene/色调摘要}
<span class="h">【分镜脚本】</span>(本段约 15 秒)
画面:<span class="v">{segment.visual_prompt}</span>
口播:<span class="v">{segment.narration}</span>
商品露出:<span class="v">{segment.product_exposure}</span>
请严格保持各参考图同一张脸/同一商品外观配色;
9:16 竖屏,一镜一图。</div>
</div>
<div class="blk">
<div class="lbl">参考图怎么选 · _storyboard_reference_images</div>
<div class="kv">
<div class="row"><span class="k">来源</span> &nbsp;本镜 entity_refs → script_entities 查实体 → 找已采用的 BaseAssetGroup</div>
<div class="row"><span class="k">匹配</span> &nbsp;先精确(kind + label==实体名)→ 匹不到放宽到"同类任一组"</div>
<div class="row"><span class="k">商品兜底</span> &nbsp;无商品组 → <b>直接用商品真实主图</b>(已修)</div>
<div class="row"><span class="k">上限/顺序</span> &nbsp;最多 4 张 · 角色→商品→场景(与 @图N 对齐)</div>
</div>
<div class="blk" style="margin-top:12px">
<div class="lbl">AI 节点 ④</div>
<div style="font-size:13px"><span class="tag ai">image_edit</span> tokenssr:gpt-image-2(中转站)· multipart 上传 N 张参考图合成 · 1024×1536</div>
</div>
</div>
</div>
<div class="note">无参考图时降级走纯文生图 <code>build_storyboard_frame_prompt</code>(没有 @图N、没有"锁脸锁商品"那句)→ 一致性会丢。所以参考图配齐很关键。</div>
</div>
</div>
<div class="arrow-down"></div>
<div class="dataflow">同一套参考图引擎 + 末位多追加 1 张「故事板帧」&nbsp;——→</div>
<!-- 阶段4 -->
<div class="stage">
<div class="st-h"><div class="st-no">4</div><div class="st-name">视频(Seedance 片段)</div>
<div class="st-trigger"><b>POST</b> /submit-video-segment + /poll-video-segment<br>同步提交 + celery 轮询(+前端兜底)· 510 分钟/段</div></div>
<div class="st-body">
<div class="flow">
<span class="tag code">取同一镜脚本</span><span class="arr"></span>
<span class="tag code">_video_reference_images(=故事板那套+追加故事板帧)</span><span class="arr"></span>
<span class="tag code">拼 @图N + @故事板图 + 脚本 + 规格</span><span class="arr"></span>
<span class="tag ai">⑤ Seedance 出视频(带音)</span><span class="arr"></span>
<span class="tag data">落 VideoSegmentVersion</span>
</div>
<div class="grid2">
<div class="blk">
<div class="lbl">提示词拼装 · build_video_segment_prompt</div>
<div class="prompt"><span class="h">【设定】</span>@图1是小美(角色),@图2是地铁(场景),
@图3是耳机(商品)。
<span class="h">【分镜】</span>根据@图4分镜图生成「商品名」短视频。
<span class="h">【脚本】</span><span class="v">{画面/口播/露出}</span> + {用户额外提示}
15s · 9:16 竖屏电商带货,镜头稳定,商品露出清晰,
节奏有转化感。</div>
</div>
<div class="blk">
<div class="lbl">AI 节点 ⑤</div>
<div class="kv">
<div class="row"><span class="k">底座</span> &nbsp;<b>火山直连 VolcanoArk</b>(create_video_task)</div>
<div class="row"><span class="k">能力</span> &nbsp;<span class="tag ai">video / Seedance</span> · 9:16 · 720p</div>
<div class="row"><span class="k">参考图</span> &nbsp;角色→场景→商品 + 末位故事板帧,作 reference_image 传入</div>
<div class="row"><span class="k">音频</span> &nbsp;<b>generate_audio=True</b>(直接出音效+人物声,不是哑片)</div>
<div class="row"><span class="k">降级</span> &nbsp;带参考图被拒 → 退纯文生视频(文本含旁白/画面)</div>
</div>
</div>
</div>
</div>
</div>
<!-- AI 调用总表 -->
<div class="sec-h">AI 调用总清单(整条管线一共在哪几处真调了模型)</div>
<table>
<tr><th>#</th><th>阶段</th><th>能力</th><th>底座 / 模型</th><th>输入</th><th>纯代码 or AI</th></tr>
<tr><td></td><td>脚本</td><td>text(LLM 流式)</td><td>火山/中转站 · 文本模型</td><td>skill 系统词 + 商品上下文</td><td><span class="tag ai">AI</span> 脚本+实体一次产出</td></tr>
<tr><td></td><td>基础资产·商品</td><td>image_edit</td><td>中转站 gpt-image-2</td><td>商品主图 + 模板词</td><td><span class="tag ai">AI</span></td></tr>
<tr><td></td><td>基础资产·人物/场景</td><td>image_generation</td><td>中转站 gpt-image-2</td><td>实体 visual_prompt</td><td><span class="tag ai">AI</span></td></tr>
<tr><td></td><td>基础资产·三视图</td><td>image_edit</td><td>中转站 gpt-image-2</td><td>立绘/主图</td><td><span class="tag ai">AI</span></td></tr>
<tr><td></td><td>故事板分镜图</td><td>image_edit 多图合成</td><td>中转站 gpt-image-2</td><td>N 张参考图 + 分镜脚本</td><td><span class="tag ai">AI</span></td></tr>
<tr><td></td><td>视频片段</td><td>video(Seedance)</td><td>火山直连 VolcanoArk</td><td>参考图+故事板帧+脚本</td><td><span class="tag ai">AI</span></td></tr>
</table>
<p class="lead" style="margin-top:14px">其余全是<b>纯代码</b>:选模型、拼提示词、选参考图、规范化兜底、归组、落库、轮询调度、计费预扣结算。
<b>「提取角色/场景」不是单独的程序步骤</b>——它跟脚本一起由 AI ① 产出;后面所有图/视频用的"角色/场景/商品提示词"都源自 ① 那次,中途纯代码搬运、不再二次问 AI。</p>
<!-- 断点 -->
<div class="sec-h">你 demo 撞到的断点 · 落在链路哪一步</div>
<div class="break">
<div class="t">① 商品参考图丢失 &nbsp;<span class="tag code">阶段3/4 选参考图</span></div>
<div class="d">商品天生有真实主图,但旧逻辑非要"商品基础资产组"才认,没生成就丢掉商品参考。实测某项目每镜要 3 张只给 2 张。</div>
<div class="fix">✓ 已修:无商品组时直接用商品主图兜底(实测 2/3 → 3/3)</div>
</div>
<div class="break">
<div class="t">② 实体没全生成基础资产 &nbsp;<span class="tag code">阶段2</span></div>
<div class="d">脚本 ① 提取了角色+场景+商品,但第二步常只生成一部分(漏场景/漏第二角色)→ 没生成的实体在阶段3/4 自然没参考图。实测某项目每镜要 4 张只给 1 张。</div>
<div class="fix">待修:第二步明确标出"还差哪几个实体没基础资产"(前端 UX 提示)</div>
</div>
<div class="break">
<div class="t">③ 资产标签 ≠ 脚本实体名 &nbsp;<span class="tag code">阶段2→3 接线</span></div>
<div class="d">阶段3 按 <code>label==实体名</code> 精确匹配参考图。若生成的资产被标成别的名(如脚本"通勤女主"、资产"通勤打工人"),精确匹配失败、退到"同类随便挑一个",多角色就挑错人。</div>
<div class="fix">待定:从实体卡生成时强制 label=实体名 + 清理"·三视图"这类占位组的误匹配</div>
</div>
</div>
</body>
</html>