素材工房 · 后端交接

Guidance 闭环 + 成片链路:这轮后端改了什么

对着 handoff §23.18–§23.20 的最终结论,把两个成片链路的 bug 修了, 并把"消化闭环 + 建 Agent 模型 + 检索字段合并"三件事落地。三个 commit,一条迁移,只上了 test/st 库。

2026-07-09 · 分支 st/test · commits 5051eb1 / 57fd35b / ebb1582 · 迁移 0033

2
成片链路 bug 修复
4
§23 结论落地
3
commits · st/test
0033
迁移(仅 test 库)

图例  已实现并自测 待别人决定 / 待做

Part 1 · commit 5051eb1

成片链路两个 bug

① creative→delivery 转发丢字段 → 30s 需求只出 15s

现象:用户要 30s 多段成片,实际只拿到 1 段 15s,而且全程无报错——delivery 任务状态还是 done

根因:src/lib/delivery-creative-payload.tspickDeliveryHandoff 走一份写死的白名单 DELIVERY_HANDOFF_KEYSsegments / target_duration_seconds / segment_count / segment_plan 等 19 个新契约字段从没进过白名单,转发给下游时被静默丢掉。下游 st2_delivery"只信机器可读字段、不从文字猜",拿到残缺数据判成非长表单,只出 1 段。

修法:delivery_handoff 是 st2_creative 写给 st2_delivery 的机器契约,Material Board 只是中继、不该按白名单把关。改成整对象透传(深拷贝),只对我们自己拥有 shape 的大对象(creative_unit / *_refs / gameplay_*)保留裁剪。

// 原:每个 key 过静态白名单,新字段永远漏
-  if (!DELIVERY_HANDOFF_KEYS.has(key)) continue
// 新:全字段透传,只裁剪已知大对象
+  const cloned = pickAllowedMetaValue(key, child)   // 未知 key → 深拷贝

自测:用真实 handoff 样本跑 bundle,24→22 字段(丢的 2 个是空数组 refs,本就该丢),segments(3 段)/ target_duration_seconds(30)/ segment_count(3) 全部保留。

② 失败「成片制作」点重试 → 派发失败(未知错误)

根因:那条失败任务是 delivery run。任务中心/创意过程的重试按钮对失败 delivery 传的是 delivery run 自己的 id,但 retry-priority 端点的查询写死 ar.stage = 'creative' → 查不到 → 报错。端点本身已经支持"创意成功、成片失败"这个场景(has_failed_delivery),只是它要的是 creative run id

修法:进事务先解析一次——delivery run id 顺 board.source_run_id 回它的 creative run;creative id 原样透传,其它/垃圾 id 仍走干净的 404。之后逻辑不变:重跑创意链,cron 自动接一条新成片。

// 失败 delivery dd32ba80 → 它的板 bdbbe31b → 源创意 run a7dc6802
SELECT CASE WHEN r.stage='delivery' THEN cb.source_run_id
            ELSE r.id END AS creative_run_id ...

自测:对 test 库跑解析,dd32ba80(delivery)→ a7dc6802(creative)✓,creative id → 自身 ✓。

Part 2 · commit 57fd35b · §23.19 / §23.20

消化引擎:全量信号 + 对象粒度

PM 反馈消化引擎有两个配套的取数缺口——一个是"记不记录",一个是"记录了但没传给判断逻辑"。

§23.19 全量记录信号

digestGuidanceForAgent() 拉信号时原本过滤了 note IS NOT NULL,把所有没打字的点赞/点踩/加推(不分热点/成片)全挡在消化材料外。改成不在取数层过滤:全部纳入,权重交给判断逻辑——prompt 里把"仅操作、未附文字"标为弱信号,反映倾向但不单凭它改规则,量大的纯点击不稀释真正的文字反馈。

§23.20 对象粒度

原查询只带 stage(哪个 tab),丢了"针对哪一条具体内容"。补上 ref_item_id/item_id + join 出标题 + 该内容的 source_run_id(追溯到产出它的具体任务)。prompt 用 《标题》+任务号 标注每条反馈是"针对某一条具体内容"还是"针对整体/流程",让消化能定向优化对应环节。

// prompt 里每条反馈/信号现在带对象标签
1. 【成片·《甜怖一秒反转》·任务fd4cd855】加推 —— (仅操作、未附文字 —— 弱信号)
2. 【整体/流程】[创意方案] 物理逻辑错了很多,镜头没按 board 来

自测:对真实 agent 跑新查询,无备注 boost 已纳入、标题 + source_run_id 正确 join(hot 素材无 source_run_id 时 COALESCE 兜住)。

Part 3 · commit 57fd35b · 待决2

建 Agent 时 fork 私有创意单元

隐患:POST /crawl/slots 没指定 Kit 时,直接把系统默认单元的同一行 id 写进 crawl_slots.creative_unit_id。所有用默认包的 Agent 共用同一条 creative_units 记录 → 一个 Agent 的消化改 steer_text 会串到别人。

根治(PM 定的方向,比消化时机 A/B/C 更彻底):建 Agent 时复制一份私有单元(字段 + creative_unit_refs 到新行),从创建那刻起就独立。visibility=private / auto_gen=true(不进 Kit 选择器,是绑给本 Agent 的副本、不是可复用资产包)。

自测:rollback 事务里确认产出 private / 非默认 / auto_gen 的私有副本。

Part 4 · commit ebb1582 · 待决1 = F1 + F2 · 迁移 0033

检索字段合并为单一文档 + 保存即润色

0027 当初把检索拆成 search_goal(用户手改)+ search_guidance(AI 改),PM 拍板合回单一「检索文档」(用户可编辑 + 保存 AI 润色 + 定时消化优化)。

落法:尽量不动表

概念落到哪动作
单一检索文档复用 search_guidancesearch_goal 弃用,0033 backfill 并入
title(Agent 名)复用 display_name本就是 AI 生成、稳定、可改名,与 §23.1 对 title 定义吻合,不加冗余列
迁移 0033只做数据 backfill加法、幂等、仅 test/st 库

F1 字段合并

F2 润色 API

新增 polishSearchDoc()(cron.ts export):保存检索文档后在 PATCH 内跑一次 AI 润色改写,以用户文本为准只做语言梳理,返回润色后文本 + polished 标记。best-effort:key 缺失 / 失败 / 超时则保留用户原文,绝不丢内容;在事务外调用不占 DB 锁。

自测:0033 已应用且幂等(backfill 1 行,重跑 0);PATCH 的 UPDATE + 版本 INSERT(动态 changed 字面量 + jsonb 快照)在 rollback 事务跑通;老前端 PATCH search_goal 走别名仍写合并文档。typecheck + wrangler dry-run 全过。

Part 5

我的问题 / 待你们拍板

这几条我没自作主张,要产品/前端/DBA 拍:

Q1 · 需前端配合
F2 润色:同步还是异步?
现在做成 PATCH 内同步跑(存原文 → 润色 → 覆盖 → 返回),保存会多等约 2–5s。若要"先秒存、润色异步回填"的体感,得改成异步 + 前端轮询。先按同步版给了。
Q2 · 需 DBA / 发布
prod 迁移谁上、什么时候?
0026–0033 全部只在 test/st 库(ep-noisy-wave),没上 prod。这套 guidance / 产品隔离 / 合字段的表结构上 prod 前,prod 站点用不了。
Q3 · 需产品决定
存量 Agent 要不要批量 fork?
待决2 的私有单元 fork 只对新建 Agent 生效。存量共用「ST 默认资产包」的 Agent 还在共享 steer_text,消化仍会互相串。要彻底消除得写个一次性脚本给存量也 fork。
Q4 · 前端团队(枫顺/顾一鸣)
C1–C5 / D1 前端还没接
单文档编辑 UI、C2 润色动态呈现(润色中过渡态 → 替换)、双时效文案,按新 GET/PATCH 形状接:title / search_doc / 返回里的 polished 标记。
Q5 · 需产品确认语义
成片重试 = 重跑整条创意链
修好的重试是"重跑创意链再接新成片"(端点既定行为),不是"保留创意方案、只按反馈重做成片"。若要后者,是另一套 /api/delivery/:id/retry 的活,可另说。
Q6 · 需 PM / 前端认可
title 复用 display_name,没加独立列
我判断 display_name 的语义(AI 生成、稳定、显式改名)与 §23.1 对 title 的定义逐字吻合,就没加冗余的 title 列。若坚持要独立列,再加一条迁移即可。

附录

改动清单 · 怎么验证

commit做了什么主要文件
5051eb1成片链路两 bugdelivery-creative-payload.ts · v7.ts
57fd35b§23.19/20 消化 + 待决2 forkcron.ts · api.ts
ebb1582F1 合字段 + F2 润色cron.ts · guidance.ts · v7.ts · campaign-detail-page.tsx · migrations/0033

三个 commit 已推 st/test,CI 自动部署 sozai-st。验证入口: