Memoria 开发记录 29:故事生成不是拼标签——VLM 描述、OCR 与可视化进度
前言
相册故事生成的目标不是把几张照片串起来,也不是把标签列表拼成文案。用户期待的是“这段回忆被理解了”:画面里有什么、照片之间有什么关系、地点和时间如何形成叙事。
早期实现更偏向 tags only:照片标签作为主要输入,生成故事时缺少 OCR、caption、地点和更具体的视觉描述。这会让文案显得泛泛而谈,例如“这是一次美好的旅行”,但说不出照片里真正出现了什么。
因此故事生成链路需要把 VLM 描述、OCR、标签和基础元数据合并成更完整的材料。
Tags 为什么不够
标签适合检索和聚类,但不适合单独承担叙事。因为标签通常是离散概念:
travel
food
beach
group photo
document
它们能说明“属于哪类”,却不一定说明“画面具体是什么”。同样是 travel,可能是海边、古城、夜景、车站、酒店、餐厅。故事生成需要更细粒度的可见事实。
VLM caption 可以补上这一层:
画面描述:几个人站在海边广场前,远处有红色地标雕塑
视觉标签:city square, sea, landmark
OCR文字:青岛 / 五四广场
这些信息比单个标签更适合交给 LLM 组织成文案。
OCR 的价值
OCR 在相册里并不只服务于“搜文档”。很多照片中的文字本身就是回忆线索:
- 餐厅招牌;
- 景区门票;
- 路牌;
- 学校名称;
- 展览标题;
- 活动海报;
- 手写祝福。
如果 OCR 只运行但不入库、不参与检索、不进入故事材料,那它就是纯成本。当前更合理的链路是:
OCR 结果入库
-> 搜索时作为文本特征
-> 故事生成时作为素材摘要
-> VLM 描述中作为补充事实
OCR 不应该盖过视觉内容,但可以显著提高故事的具体性。
等待页面也需要真实预览
故事生成是长任务,等待页不是装饰。它需要告诉用户系统正在处理哪些照片、进度到哪里、结果是否与选择有关。
之前等待页出现过所有图片都是同一张或占位图的问题,根本原因是预览引用不稳定,甚至依赖 path。对于相册应用来说,系统资产 ID 才是稳定句柄。
改造后的等待页使用 asset:<assetId> 作为预览引用,再通过已选照片集合匹配缩略图。这样不需要临时路径,也不依赖缓存文件是否存在。
selected photos-> assetId-> progress preview refs-> MediaThumbnail(assetId)
这符合前面“从 path 转向 assetId”的整体方向。
默认模式应体现最完整能力
如果已经有本地 caption、OCR、tags 和 LLM 组织能力,默认生成模式就不应该停留在 tags only。默认路径应该代表当前系统最好的稳定能力,保留编辑和高级设置,但不要让用户一开始就面对过多选项。
更合适的默认流程是:
选图
-> 本地 VLM/caption 补充描述
-> tags + OCR + caption + 时间地点汇总
-> LLM 生成故事结构
-> 视频生成与导出
编辑能力仍然保留,但不应该喧宾夺主。默认生成效果要先合理。
删除旧路径比兼容更重要
故事链路中曾经存在多种旧实现和 fallback。过多兼容分支会让问题难以定位:同一张图有时走 assetId,有时走 path,有时走缓存,有时走占位图。
这次改造删除了不少旧代码,目标是让数据流更短:
照片实体
-> 资产 ID
-> 缩略图/分析结果
-> 故事材料
-> 进度预览
路径越少,越容易验证每一步是否正确。
总结
故事生成不是把标签交给 LLM,而是把可验证的照片事实组织成叙事。VLM、OCR、tags、时间和地点各自提供不同粒度的信息,缺一层都会影响最终效果。
关键经验包括:
- tags 适合分类,不足以支撑具体叙事;
- OCR 应入库并参与搜索与故事材料;
- VLM 描述能提供比标签更细的画面事实;
- 等待页必须使用 assetId 稳定预览;
- 默认生成模式应使用最完整的稳定链路;
- 删除旧 path/fallback 分支有助于减少不可复现问题。
对应提交:0d3fd19、b58ece8、3830bb7。
