The Script Agent: Turning a Single Sentence Into Structured Data — Character Cards, Scenes, and Shots
Get the model to produce script data a program can consume directly instead of a piece of prose, add a reviewer role that polishes it against judgeable criteria, and distill settings shared across episodes into a bible file.
今日目标
- 能用 schema 约束模型输出,让剧本直接落成程序可读的人物卡、场景与分镜数据
- 能给剧本 Agent 加一个评审角色,用可判定的标准让稿子在循环里变好
- 能把人物与世界观设定抽出来单独存放,保证后面每一集都不跑偏
昨天那张任务图上,除了剧本节点,其余五个节点吃的全是分镜数据里的字段。今天进编剧室,把那份数据真正生产出来——读完回到页面顶部把三条勾掉。
小白版讲解
散文与数据的分界:为什么剧本必须先变成结构化数据
让模型「写个短剧剧本」,你会得到一段读起来很像样的散文:场景描写、人物对白、情绪提示,一气呵成。人看着舒服,程序完全没法用。
想想剧组是怎么工作的。编剧交出来的是剧本,但没有任何一个部门直接拿剧本干活。剧本先要变成一张分镜表:第几号镜、什么景别、多长、谁在场、画面里有什么、镜头怎么动、说哪句词。美术看「画面里有什么」,摄影看「镜头怎么动」,录音看「说哪句词」,剪辑看「多长」。一张表拆给六个部门,每个部门只读自己那几列。
我们的流水线是同一件事,只不过部门变成了六个节点。图像节点要读 visual,视频节点要读 camera 和 durationSec,配音节点要读 dialogue 和角色的 voiceId,时间轴节点要读 durationSec。一段散文里这些信息全都有,但混在一起就等于没有——你没法从「他缓缓走向窗边,声音低了下去」这句话里可靠地切出「运镜是缓慢推近、时长六秒、台词是那半句」。
有人会想:那用正则或者再叫一次模型去解析散文不就行了。可以,但你要为此付两笔钱:一次生成的钱、一次解析的钱,而且解析这一步的失败率会随剧本风格波动,你永远不知道下一集会不会解析出个空数组。更好的做法是从一开始就让模型直接产出数据结构,把「写」和「排版」合成一步。
所以今天的分界线是这么划的:
| 交给模型的 | 交给程序的 |
|---|---|
| 想出情节、人物、台词、画面 | 规定输出长什么样、检查它是否合格 |
| 判断钩子够不够、动机成不成立 | 判断时长、字数、镜号、引用是否合法 |
这条线后面每一节都会用到。能被程序判定的事,一件都不要交给模型。
一张分镜卡该有哪些字段
字段不是越多越好,判据只有一个:下游有没有节点真的读它。 本课的分镜卡定死七个字段,全课十四天不再增删:
type Shot = {
id: string // 镜号,形如 s01,本集内唯一 —— 它同时是产物目录名与缓存键的一部分
sceneId: string // 场景 id,指向本集场景表里的一条
shotSize: 'wide' | 'medium' | 'close' // 景别只有三档,不要引入第四档
durationSec: number // 计划时长,第 5 天可能被真实配音时长回写
characters: string[] // 出场角色 id,第 3 天靠它决定把哪张定妆图当参考图
visual: string // 画面描述,进图像与视频提示词
camera: string // 运镜描述,进视频提示词
dialogue: { characterId: string; text: string }[] // 台词,进配音
}from typing import Literal, TypedDict
class Line(TypedDict):
characterId: str
text: str
class Shot(TypedDict):
id: str # 镜号,形如 s01,本集内唯一 —— 它同时是产物目录名与缓存键的一部分
sceneId: str # 场景 id,指向本集场景表里的一条
shotSize: Literal["wide", "medium", "close"] # 景别只有三档,不要引入第四档
durationSec: int # 计划时长,第 5 天可能被真实配音时长回写
characters: list[str] # 出场角色 id,第 3 天靠它决定把哪张定妆图当参考图
visual: str # 画面描述,进图像与视频提示词
camera: str # 运镜描述,进视频提示词
dialogue: list[Line] # 台词,进配音有三个约定值得单独说。
景别只有三档。 影视里的景别能分到七八档,但我们的下游只是把它拼进提示词,多分几档模型也分辨不出来,反而给校验和后面的统计增加分支。枚举值的数量应该由下游的分辨能力决定,不是由行业习惯决定。
时长字段一律以 Sec 或 Ms 结尾。 后面第 5 天配音时长是毫秒、第 6 天时间轴是毫秒,而分镜的计划时长是秒。一个裸的 duration 混在中间,迟早有人乘错一千倍——那会是一个非常安静的 bug,成片长度变成正确值的一千分之一。
镜号是 s01 而不是 1。 它要当目录名、当文件名前缀、当第 8 天缓存键的一部分。补零之后排序才和肉眼顺序一致,这一条在你有十个以上镜头时才会感谢自己。
工程代价是字段一旦定下来就很难改。今天写进 Shot 的每一个键,后面十二天都有代码在读它;到第 10 天审核台上线,还会有一个前端页面在渲染它。所以定字段这一步宁可慢一点,把「下游谁读它」逐个问一遍。
用 schema 逼模型闭嘴:三条路和各自的翻车方式
让模型输出合法 JSON,业界大致有三条路。
第一条:提示词约束加本地校验。 在系统提示里写死「只输出 JSON、不要任何解释」,拿到回复后自己抠出 JSON、自己校验、不合格就把问题喂回去让它重来一次。翻车方式是模型不听话——它会加寒暄、加解释,或者用围栏把 JSON 包起来。
第二条:厂商的 JSON 模式或结构化输出参数。 一些厂商在请求里提供了强制 JSON 或按 schema 约束解码的参数,由服务端保证输出可解析。翻车方式是各家的支持程度和字段名都不一样,具体有没有、叫什么,以官方文档为准;一旦用上,你就把这段代码绑在了这一家身上。
第三条:借工具调用的参数 schema。 把「提交剧本」定义成一个工具,参数 schema 就是你的数据结构,让模型去「调用」它。翻车方式是有些实现只把 schema 当强烈建议,仍然可能给出不合法的参数。
本课走第一条,理由和昨天那层 provider 抽象一脉相承:它不依赖任何一家的扩展字段,换厂商这段代码一行都不用改。代价是你必须自己写两样东西——抠 JSON 和校验。
抠 JSON 的写法要能对付「前后有寒暄」和「被围栏包住」这两种最常见的情况;校验不过时,把带路径的问题原样喂回去让模型只修这些,比换一个更大的模型有用得多:
// 找第一个花括号或方括号,再找最后一个配对的收尾符号,中间那段才拿去解析。
// 直接 JSON.parse(整段回复) 在真实模型上十次有三次会炸。
function extractJson(text: string): unknown {
const fenced = /```(?:json)?\s*([\s\S]*?)```/.exec(text)
const body = fenced ? fenced[1] : text
const start = body.search(/[[{]/)
const close = body[start] === '{' ? '}' : ']'
return JSON.parse(body.slice(start, body.lastIndexOf(close) + 1))
}
// 校验不过就把「路径 加 原因」喂回去重来一次;两次都不过才算真失败。
async function ask(text, system, user, parse) {
for (let attempt = 1; attempt <= 2; attempt++) {
const raw = await text.complete({ system, user })
const issues = []
const value = parse(extractJson(raw.text), issues)
if (issues.length === 0) return value
if (attempt === 2) throw new Error('schema 校验两次都没过')
user += `\n\n上一版有这些字段问题,请只修这些,别改别的:\n${issues.map((i) => `- ${i.path}:${i.message}`).join('\n')}`
}
}import json
import re
# 找第一个花括号或方括号,再找最后一个配对的收尾符号,中间那段才拿去解析。
# 直接 json.loads(整段回复) 在真实模型上十次有三次会炸。
def extract_json(text: str):
fenced = re.search(r"```(?:json)?\s*(.*?)```", text, re.S)
body = fenced.group(1) if fenced else text
start = min((i for i in (body.find("["), body.find("{")) if i >= 0), default=-1)
close = "}" if body[start] == "{" else "]"
return json.loads(body[start : body.rfind(close) + 1])
# 校验不过就把「路径 加 原因」喂回去重来一次;两次都不过才算真失败。
async def ask(text, system, user, parse):
for attempt in (1, 2):
raw = await text.complete(system=system, user=user)
issues: list[dict] = []
value = parse(extract_json(raw["text"]), issues)
if not issues:
return value
if attempt == 2:
raise ValueError("schema 校验两次都没过")
lines = "\n".join(f"- {i['path']}:{i['message']}" for i in issues)
user += f"\n\n上一版有这些字段问题,请只修这些,别改别的:\n{lines}"写手与评审:让第二个角色拿着评分表挑毛病
一次生成一版,不满意就整段重来——这是最贵的改稿方式,因为每次都在为已经写对的九成付钱。剧组不这么干:编剧交稿,制片和导演拿着一张单子逐条挑,编剧只改被挑出来的那几处。
我们把这套搬进来,就是写手加评审两个角色。但这里有一个关键的工程判断,也是今天最想让你带走的一句话:
评审不是一个模型,而是「代码判硬伤 加 模型评软伤」两层。
硬伤是能被程序判定的:每镜有没有 camera、时长在不在 3 到 8 秒、单条台词有没有超过 25 字、人物卡里的人有没有出场、全集总时长在不在 45 到 90 秒。这五条代码几行就查完,不花钱、不会漏、结论稳定。软伤是程序判不了的:开场三秒有没有钩子、人物动机成不成立、结尾留不留人——这才该交给模型。
// 五条硬伤,每条都对应后面某一天的真实约束,不是凑数
function checkHard(ep, characters) {
const issues = []
for (const s of ep.shots) {
if (!s.camera.trim()) issues.push(`[camera] ${s.id} 缺运镜描述,第 4 天没东西写进提示词`)
if (s.durationSec < 3 || s.durationSec > 8) issues.push(`[duration] ${s.id} 时长越界`)
for (const d of s.dialogue) {
if (d.text.length > 25) issues.push(`[dialogue] ${s.id} 台词 ${d.text.length} 字,超过 25 字`)
}
}
return issues
}
// 硬伤七成、软分三成:机器能判定的东西,不该被模型的一句好评盖过去
const hardScore = Math.max(0, 100 - hard.length * 8)
const score = Math.round(hardScore * 0.7 + softScore * 0.3)# 五条硬伤,每条都对应后面某一天的真实约束,不是凑数
def check_hard(ep, characters):
issues = []
for s in ep["shots"]:
if not s["camera"].strip():
issues.append(f"[camera] {s['id']} 缺运镜描述,第 4 天没东西写进提示词")
if not 3 <= s["durationSec"] <= 8:
issues.append(f"[duration] {s['id']} 时长越界")
for d in s["dialogue"]:
if len(d["text"]) > 25:
issues.append(f"[dialogue] {s['id']} 台词 {len(d['text'])} 字,超过 25 字")
return issues
# 硬伤七成、软分三成:机器能判定的东西,不该被模型的一句好评盖过去
hard_score = max(0, 100 - len(hard) * 8)
score = round(hard_score * 0.7 + soft_score * 0.3)注意每条硬伤开头那个方括号里的分类代码。它不是给人看的,是给写手看的:写手拿到 [camera] 就知道这一类要改什么,改完下一轮这一类就消失了。评语带分类,改稿才是收敛的;一句「写得不够好」只会让模型整段重写,把已经改对的地方再改坏。
实验里离线模式的分数是 69、86、91 三轮往上走,每一轮硬伤条数真的在减少——因为那五条规则是本地算出来的,不是编出来的。你换一句话去跑,分数曲线的形状一样,但内容全变了,这就是「业务逻辑真的执行了」的样子。
世界观档案:跨集一致性的问题第一次出现
到这里为止我们只做了一集。可这门课的终点是一季五集,问题马上就来了:第二集里,主角还是不是同一个人?
短剧观众对这件事极其敏感。名字变了、职业变了、性格前后不搭,弹幕立刻就来了。而模型是没有记忆的——你第二次调用它时,它对第一集一无所知。
解决办法很朴素:把「跨集不变的东西」抽出来单独存一份,每一集生成前都读进去。这份东西就是世界观档案,我们把它落在 script/world.json,人物卡落在 script/characters.json:
world.json:标题、一句话梗概、基调、设定、几条硬规则(比如「这件事只有主角知道」「每集结尾必须留一个没答完的问题」)。characters.json:每个角色的 id、名字、外貌、性格、音色 id。
外貌和音色为什么也在这里? 因为它们不只是设定,是下游的输入参数:外貌描述会原样进第 3 天的图像提示词,音色 id 会原样进第 5 天的语音接口。把它们和名字放在同一份档案里,一致性问题就在一个文件里解决了,而不是散在三个地方各写一遍。
第 14 天会真正把这份档案用起来:五集连续生成,每一集都带着同一份 world.json 与 characters.json 进去,人物与风格才不会跨集漂移。今天你只要保证它被单独存下来,并且里面的字段名和下游要用的完全一致。
什么时候该停:三个出口,缺一不可
生成加评审的循环有一个致命的诱惑:既然每一轮都在变好,那就多跑几轮吧。这是烧钱最快的一种写法。
一个能上生产的循环必须有三个出口:
出口一,达标就停。 分数到了阈值立刻收工。别追求满分——本课实验里,循环停在 91 分时,[dialogue] 那条硬伤其实还留着一条。这是故意的:阈值的意思是「够用了」,不是「完美了」,追最后那几分的成本远高于收益。
出口二,到次数上限就停。 评分标准写严一点,模型就可能永远够不到阈值,没有上限的循环会一直跑下去。到上限时要做两件事:交出分数最高的那一稿(不是最后一稿,评审是有波动的),并且明确打印一句「没达标但到上限了」,让使用者知道该人工介入。
出口三,人来拍板。 这一条今天只留接口,第 10 天的审核台才真正实现。判据是:当剩下的问题都是软伤时,就该交给人。 硬伤循环能修,软伤的评分本身就是模型给的,让模型自己评自己改,转几圈也只是在原地打转。
工程代价是这三个出口都需要参数,而参数需要调。阈值定高了轮数用满、定低了稿子不能看;轮数上限定大了烧钱、定小了永远差一口气。实验里把它们做成了 PASS_SCORE 与 MAX_ROUNDS 两个环境变量,你应该真的去改一改它们,看分数曲线怎么变——这比读十遍这一节有用。
源码导读
动手实验
今天不需要装 ffmpeg,也不会调用图像、视频、语音接口,只用到四类 provider 里的文本那一类;全程 MOCK=1 就够。starter/ 原样能跑,但分数四轮都不动、跑满上限、定稿里留着一个 12 秒的镜头和一句 40 字的台词——四个编号练习分别对应这些症状。
- 先跑 solution 一遍,把三轮分数与每轮的硬伤列表看一遍,再打开
drafts/目录 diff 相邻两轮的稿子,看清楚每一轮到底改了什么。 - 回到 starter 做练习 1:把另外四条硬伤补进评分表,跑起来第一轮的硬伤条数应该从 3 条涨到 5 条。
- 做练习 2:把最终分改成硬伤七成、软分三成的加权分,这时分数才会随硬伤减少而上升。
- 做练习 3:给循环补上「达标就停」和「跑满上限交最好一稿」两个出口,确认它停在第 3 轮而不是第 4 轮。
- 做练习 4:补上引用完整性检查,用
INJECT=badref验证坏稿被拦住;最后换三句自己的一句话跑,确认字段不缺、内容会变。
面试题
今天 3 道题在下方题库区,侧重结构化输出的实现路径与失败模式、生成加评审循环的收敛条件、以及跨集一致性的存放位置。展开后先看分析过程再看要点——照着推导练,比背要点管用。标注「国内高频 / 海外高频」方便按目标市场取舍。
检查清单与明日预告
- 能用 schema 约束模型输出,让剧本直接落成程序可读的人物卡、场景与分镜数据
- 能给剧本 Agent 加一个评审角色,用可判定的标准让稿子在循环里变好
- 能把人物与世界观设定抽出来单独存放,保证后面每一集都不跑偏
- 能说出结构化输出的三条实现路径,以及本课为什么选了第一条
- 能说清校验为什么必须分「判类型」和「判引用完整性」两层
- 实验的 5 条验收标准全部通过
- 3 道面试题不看要点也能答出至少 2 道
明天(D3)我们去美术组:接通图像生成接口,按今天这份分镜数据批量产出角色定妆图与场景图。为什么是这个顺序?因为图像节点吃的正是今天定下来的两个字段——人物卡里的 appearance 和分镜里的 visual。而一到生成画面,一个今天还只是隐患的问题就会当场炸开:生成模型没有记忆,第二张图里的主角很可能换了张脸。 D3 会讲清楚这件事的成因,以及参考图、随机种子、提示词模板三种锁人手段各自能锁住什么、锁不住什么。
Interview questions
How do you get a model to emit valid structured data reliably, and what do you do when schema validation fails?怎么让模型稳定输出合法的结构化数据?schema 校验失败时你会怎么处理?
Common in ChinaCommon overseasBasic#structured-output#schema-validationHow to reason about it · think before answering
- The real question is the second half. Answering only use JSON mode signals you have never run this in production, because all the work happens after validation fails.
- Lay out three paths: prompt constraints plus local validation; a vendor's JSON mode or structured-output parameter; or defining the data structure as a tool's parameter schema. Vendor support and field names differ, so the latter two bind that code to one vendor.
- State the selection rule: cross-vendor or offline-capable means path one, paying with your own JSON extraction and validator; single-vendor and success-rate-driven means use their structured output. Extraction must handle code fences and surrounding chatter — parsing the whole reply directly breaks often.
- Handle failure as a ladder, not just a retry: feed the path-annotated issues back and ask it to fix only those (more effective than upgrading the model); then degrade to a minimal required-fields-only structure; then fail the round and persist the artifact for a human — never swallow the error and return an empty array.
- High-signal point: validate in two layers. Type and range checks catch malformed data but not wrong references — a nonexistent scene id or a duplicate shot number passes typing and explodes downstream. Referential integrity needs its own pass.
- Likely follow-up: how many retries? Two. The first covers a disobedient model; if it still fails with concrete issues in hand, the prompt or the schema itself is wrong and more retries just buy the same error.
分析过程 · 先想清楚再作答
- 这题的题眼在后半句。前半句答「用 JSON 模式」就结束的人,等于说自己没在生产里跑过——真正的活儿全在校验失败之后。
- 先把三条路摆开:提示词约束加本地校验;厂商提供的 JSON 模式或结构化输出参数;把数据结构定义成工具的参数 schema 让模型去调。各家对后两条的支持程度和字段名都不一样,选它就等于把这段代码绑在某一家上。
- 给出选择依据:要跨厂商、要能离线跑,就选第一条,代价是自己写抠 JSON 与校验;只服务一家且追求成功率,就用那一家的结构化输出。抠 JSON 这一步必须处理围栏与前后寒暄,直接解析整段回复在真实模型上很容易炸。
- 校验失败的处理是一条阶梯,别只答重试:把带路径的问题原样喂回去让它只修这些(比换更大的模型有效);仍不过就降级到只要必填字段的最小结构;再不过就整轮失败并留档,让人来看,而不是吞掉异常返回一个空数组。
- 还有一条区分度很高:校验要分两层。判类型与范围只能挡住格式错,挡不住写错对象——引用了不存在的场景 id、镜号重复,这类稿子能通过类型检查,然后在下游某一步才爆。引用完整性必须单独查一遍。
- 可预期的追问:重试几次合适?两次。第一次是模型没听话,第二次带着具体问题还改不对,说明是提示词或 schema 本身有问题,再重试只是花钱买同一个错误。
Key points
- Three paths: prompt plus local validation, vendor structured output, or tool parameter schema — the latter two bind you to a vendor
- JSON extraction must handle code fences and surrounding prose; never parse the whole reply directly
- Failure handling is a ladder: feed back path-annotated issues, degrade to a minimal structure, then fail the round and persist for a human
- Validate in two layers — types and ranges, then referential integrity and id uniqueness
- Cap retries at two; beyond that the prompt or schema is wrong, not luck
答题要点
- 三条路:提示词加本地校验、厂商结构化输出参数、工具参数 schema,后两条会绑定厂商
- 抠 JSON 要处理围栏与前后寒暄,不能直接解析整段回复
- 失败处理是阶梯:带路径的问题喂回去只修这些、降级到最小结构、整轮失败留档给人
- 校验分两层,类型与范围之外必须单独查引用完整性与 id 唯一性
- 重试上限两次,再不过说明是提示词或 schema 的问题,不是运气问题
In a generator-plus-reviewer loop, how do you define convergence so it does not burn budget indefinitely?生成加评审这种双角色循环,收敛条件该怎么定才不会一直烧钱?
Common in ChinaCommon overseasDeep dive#agent-loop#cost-controlHow to reason about it · think before answering
- This screens whether you have ever made such a loop actually terminate. Answering only set a max round count scores nothing — that prevents an infinite loop, it is not convergence design. The signal is naming three exits plus how the reviewer itself is built.
- Start with the reviewer: it should not be one model but two layers. Machine-checkable defects (missing fields, out-of-range numbers, length limits, invalid references) go to code; only the judgment calls go to the model. This decides score stability — a pure-model reviewer can swing by ten-plus points on the same draft, and then convergence is meaningless.
- Then the three exits: stop on threshold (the threshold means good enough, not perfect — chasing the last few points costs far more than it returns); stop at the round cap, handing back the highest-scoring draft rather than the last one, because review scores fluctuate; and escalate to a human once only judgment-call issues remain, since a model reviewing and revising itself just circles.
- Also cover the scoring weights: hard defects should dominate, say seventy percent, with the model's soft score at thirty. Otherwise one flattering model review outweighs five real field errors and the loop declares success on round one.
- Conclusion and cost: all three exits need parameters, and parameters need empirical tuning. Too high a threshold burns every round; too low ships an unusable draft. Plot the score curve before shipping and confirm it rises monotonically.
- Likely follow-up: how do you know it is improving rather than oscillating? Track the hard-defect count — it is deterministic, while the score jitters. If hard defects do not fall, the writer is not acting on feedback, and the fix is feedback granularity: tag each issue with a category so the model knows which class to repair.
分析过程 · 先想清楚再作答
- 这题在考「你有没有让这种循环真的停下来过」。只答「设一个最大轮数」拿不到分,那只是防死循环,不是收敛设计。区分度在于你能不能说出三个出口以及评审本身该怎么构造。
- 先拆评审:评审不该是一个模型,而是两层——能被程序判定的硬伤用代码查(字段缺失、数值越界、长度超限、引用不合法),程序判不了的软伤才交给模型。这一步决定了分数稳不稳定:全交给模型,同一份稿子两次评分能差十几分,循环就没有收敛可言。
- 再说三个出口:达标就停(阈值是「够用」不是「完美」,追最后几分成本远高于收益);到轮数上限就停,而且要交出历史最高分那一稿而不是最后一稿,因为评审有波动;剩下的问题全是软伤时转人工,因为让模型自己评自己改只会原地打转。
- 还要说计分方式:硬伤应该占大头(比如七成),软分占小头。否则模型一句好评就能盖过五条实打实的字段问题,循环会在第一轮就假装达标。
- 结论加代价:三个出口都需要参数,而参数必须实测调。阈值高了轮数用满,低了稿子不能看;上限大了烧钱,小了永远差一口气。上线前要把分数曲线画出来看它是不是单调上升。
- 可预期的追问:怎么知道循环真的在变好而不是在抖动?看硬伤条数,它是确定性的;分数会抖,硬伤条数不会。硬伤降不下去就说明写手根本没在按意见改,问题出在意见的粒度上——意见要带分类标签,模型才知道该改哪一类。
Key points
- Split the reviewer: code judges hard defects, the model judges only judgment calls — otherwise scores are unstable and nothing converges
- Three exits: stop on threshold, stop at the round cap returning the best draft, escalate to a human when only soft issues remain
- Weight hard defects heavily so a flattering model review cannot mask real field errors
- Tag each review issue with a category so the writer repairs one class at a time
- Measure convergence by hard-defect count, not score — the score jitters, the count does not
答题要点
- 评审分两层:硬伤用代码判,软伤才交给模型,否则分数不稳定、循环无从收敛
- 三个出口:达标就停、到轮数上限交历史最高分那一稿、只剩软伤时转人工
- 计分让硬伤占大头,避免模型一句好评盖过实打实的字段问题
- 评语必须带分类标签,写手才能只改那一类,改稿才是收敛的
- 观测收敛看硬伤条数而不是分数,分数会抖、硬伤条数是确定的
To keep character definitions consistent across many episodes, where do you store that state and how do you use it?多集内容要保持人物设定一致,你会把这份设定放在哪、怎么用?
Common in ChinaCommon overseasIntermediate#state-management#consistencyHow to reason about it · think before answering
- The crux is that models have no memory. Answering just concatenate previous episodes into the context invites a fatal follow-up: context grows linearly with episode count, so by episode five you pay repeatedly for four full episodes, and the model may still miss details.
- Break it down by separating what is invariant across episodes from what is recomputed each time. Invariant: the world, each character's appearance, personality, voice id, and a few hard rules. Recomputed: scenes and shots. Extract the invariant part into its own file and load it verbatim before generating each episode.
- Add the commonly missed point: the fields in that file are not only lore, they are downstream input parameters. Appearance text goes straight into image prompts, the voice id goes straight into the speech API. Keeping them beside the name means consistency is solved in one file rather than restated in three places.
- Choose the storage boundary by write frequency: the profile is written once and read many times, while the shot list is rewritten on every run. Mixing lifetimes in one file makes it impossible to rerun one episode without disturbing the others.
- Conclusion and cost: the profile itself can drift. Change a character's appearance mid-season and previously generated assets no longer match, so version the profile and include that version in the asset cache key — editing the profile then invalidates exactly the affected assets. That is only possible because it lives on its own.
- Likely follow-up: should you use a vector store? Usually not. Cross-episode canon is small, structured, and must be injected in full; retrieval risks dropping the one line that matters. Retrieval fits large corpora where only a few relevant items are needed.
分析过程 · 先想清楚再作答
- 这题的题眼是「模型没有记忆」。答成「把前一集的输出拼进上下文」的人会被追问到崩——上下文会随集数线性膨胀,第五集时你在为前四集的全文反复付费,而且模型仍然可能漏读。
- 怎么拆:先分辨哪些是「跨集不变」的,哪些是「每集重算」的。不变的是世界观、人物外貌、性格、音色与几条硬规则;每集重算的是场景与分镜。把不变的那部分抽成单独的档案文件,每一集生成前原样读进去。
- 接着说一个容易被忽略的点:档案里的字段不只是设定,还是**下游的输入参数**。外貌描述要原样进图像提示词,音色 id 要原样进语音接口。所以它们必须和名字放在同一份档案里,一致性问题才是在一个文件里解决的,而不是散在三处各写一遍。
- 存放位置的判据是写入频率:档案一次生成、多次读取,分镜每跑一次就重写。生命周期不同的数据放同一个文件,你就没法只重跑一集而不动其他集。按写入频率切分文件,是这类流水线最省事的一条习惯。
- 结论与代价:档案本身也会漂——中途改了人物外貌,之前生成的资产就对不上了。所以档案要有版本,且资产的缓存键要包含档案版本,改档案等于让相关资产失效。这条也是把它单独存放才做得到的。
- 可预期的追问:那要不要上向量库做检索?多数情况下不需要。跨集共享的设定是**有限的、结构化的、必须全量注入的**,检索反而可能漏掉关键一条。检索适合的是「素材库很大且只需要相关几条」的场景。
Key points
- Models are stateless; cross-episode consistency comes from an external profile, not from stuffing prior episodes into context
- Split by invariant versus recomputed: world and character profiles persist, scenes and shots are regenerated per episode
- Appearance text and voice id are downstream input parameters, so they belong beside the character's name
- Split files by write frequency — a read-mostly profile versus a rewritten shot list — or you cannot rerun one episode alone
- Version the profile and fold that version into the asset cache key so edits invalidate exactly the affected assets
答题要点
- 模型没有记忆,跨集一致性靠外部档案而不是把前几集拼进上下文
- 按「跨集不变」与「每集重算」切分:世界观与人物卡是档案,场景与分镜每集重来
- 档案里的外貌与音色 id 同时是下游的输入参数,所以必须和名字放在一起
- 按写入频率切分文件,档案读多写少,分镜每次重写,混在一起就没法只重跑一集
- 档案要有版本并进资产缓存键,改设定才能精确地让相关资产失效
Comments
Sign in to join the discussion
No comments yet — be the first.