One Episode Wrapped: Stringing Six Stages Into an End-to-End Pipeline and Tallying the First Bill
String script, assets, shots, voiceover, and assembly into one line a single command can run end to end, measure the time and cost of each stage, and find the pipeline's three most fragile points right now.
Today's Goals
- Produce one complete episode from a single sentence with one command, and know exactly where it's stuck if any step fails
- Measure the time and cost of each stage, and point out where the bottleneck is
- List the pipeline's three fragile points right now, and say which day of week two fixes each one
Over six days you got six stages running individually, but your hands still sit between them: after the script you copy the JSON into the next script by hand; after dubbing you check by hand that the files exist. Today, take your hands off. When you finish, scroll back up and tick the three goals off.
Plain-Language Walkthrough
The day an episode wraps is when the problems start
Film crews call the last shot the wrap, and everyone applauds. Anyone who has done post knows the problems start at that moment: material scattered across three drives with different naming, the sound recordist's timecode two frames off from camera, an NG take whose filename differs from the good one by a single letter.
String six stages together and you hit exactly those things. None of them existed while you debugged each stage alone, because your brain was doing the handoffs: you knew where the last script wrote its files, you knew which JSON to feed the next step. Connected up, that knowledge has to move out of your head and into the code.
The first thing to bite is artifact paths. Running the image script alone, output/first-frame.png is perfectly fine. Connected up, the second run overwrites the first one's output; a mid-run failure leaves you unable to tell which files on disk belong to this attempt; and comparing "did changing the sentence make it better" is impossible because both runs' artifacts are mixed together.
There is one fix, and the rule has to be laid down from the start: allocate a run identifier per run and hang every artifact under it.
work/
└── runs/run-20260907-001/
├── run.json this run's metadata and cost ledger
├── script/ script, character cards
├── assets/ character look tests
├── shots/<shotId>/ first frame, shot video, voice
├── timeline/ the timeline table
└── output/ cut, subtitles, coverThe layout looks plain, but it settles three problems at once: two runs never overwrite each other; leftovers from a failure are obvious; and reproducing a result is a matter of zipping one directory and sending it to a colleague.
// One run's directory layout. The whole pipeline gets paths through this object;
// any place that concatenates a path string directly is a latent bug.
export class Run {
constructor(id) {
this.id = id
this.dir = `work/runs/${id}`
this.costs = []
}
path(...parts) {
return [this.dir, ...parts].join('/')
}
// Record spend the moment it happens, not by tallying after the run finishes -
// a tally done afterwards is always wrong when the run failed.
record(entry) {
this.costs.push({ ...entry, runId: this.id, at: new Date().toISOString() })
}
}
export function newRunId(now = new Date()) {
const p = (n, w = 2) => String(n).padStart(w, '0')
const stamp = `${now.getFullYear()}${p(now.getMonth() + 1)}${p(now.getDate())}`
return `run-${stamp}-${p(Math.floor((now.getHours() * 60 + now.getMinutes()) % 1000), 3)}`
}# One run's directory layout. The whole pipeline gets paths through this object;
# any place that concatenates a path string directly is a latent bug.
from datetime import datetime
class Run:
def __init__(self, run_id: str):
self.id = run_id
self.dir = f"work/runs/{run_id}"
self.costs: list[dict] = []
def path(self, *parts: str) -> str:
return "/".join([self.dir, *parts])
# Record spend the moment it happens, not by tallying after the run finishes -
# a tally done afterwards is always wrong when the run failed.
def record(self, entry: dict) -> None:
self.costs.append({**entry, "runId": self.id, "at": datetime.now().isoformat()})
def new_run_id(now: datetime | None = None) -> str:
now = now or datetime.now()
seq = (now.hour * 60 + now.minute) % 1000
return f"run-{now:%Y%m%d}-{seq:03d}"The second problem is log flooding. Six stages each print their own logs, one command scrolls hundreds of lines past, and when something breaks you cannot tell which stage broke it. Today's logging rule is simple too: each stage prints one line on start and one line on finish with its elapsed time and artifact count, and pushes the rest into files. What stays on the terminal should be a progress table you can scan at a glance, not a running diary.
So what most deserves measuring once things are connected? Not "did it run," but how much time and how much money each step took. Those two numbers decide what you fix first next week — and they will most likely overturn your intuition.
One itemized bill beats ten pieces of advice
Here is a real run's bill, four shots, a twenty-one-second episode:
Bill (share of elapsed time / estimated spend)
stage elapsed share calls artifacts estimated
script 32ms 1% 1 2 CNY 0.0000
assets 93ms 2% 2 2 CNY 0.0500
first-frame 120ms 2% 3 3 CNY 0.0750
clip 2518ms █████████ 47% 3 3 CNY 10.4950
voice 236ms █ 4% 5 7 CNY 0.0193
compose 2403ms █████████ 44% 0 2 CNY 0.0000
total 5402ms CNY 10.6393How should this be read? One thing must be said first: those spend figures are estimates. In offline mode every real amount is 0, so the program converts usage using the official pay-as-you-go rates — 0.025 per image, 0.50 per second of 768P video. For speech the vendor publishes only resource-package pricing, so that figure is an approximation derived from a package, not a listed rate. A bill may be an estimate, but it must say that it is one — that is the piece of professionalism most often skipped when building cost observability.
The first conclusion is glaring: more than ninety-eight percent of this episode's money went to video. The script is nearly free, voice is under two cents, five images together are twelve and a half cents, and three shots of video are ten and a half. That ratio is not specific to this course; it is the basic shape of AI video production.
A great deal follows from it, enough to state on its own line: every engineering decision on this pipeline orbits the question of how to call the video API one fewer time. Idempotency, caching, reference-image reuse, draft tier before final tier, budget circuit breakers — every one of next week's topics traces back to this.
Read the elapsed column carefully. In offline mode compose looks slowest, because the video API is stubbed and local ffmpeg becomes the bulk of the work. In real mode a clip queues for minutes and that column is completely rewritten. So you must know which of the bill's numbers mean anything offline and which do not, or you will optimize against a fake picture. And one more layer: those millisecond figures came from one particular machine and yours will differ. What is reproducible in this table is the call counts, the artifact counts and the estimated spend (the same input always computes the same result); what is not reproducible is the elapsed time — use it only for the relative relationship between stages, never as a benchmark to copy.
The aggregation code is simple; only one thing deserves attention — keep the estimation separate from real amounts.
// Unit prices from the official pay-as-you-go page, in CNY. The speech figure is an
// approximation derived from a resource package, not a listed rate.
const PRICE = { imagePerItem: 0.025, videoPerSecond: 0.5, ttsPerCharacter: 0.00035 }
// Use the real amount if the call really ran; only estimate offline. Never mix the two,
// or the books will never balance.
export function estimateCny(entry) {
if (entry.costCny > 0) return entry.costCny
if (entry.kind === 'image') return entry.units * PRICE.imagePerItem
if (entry.kind === 'video') return entry.units * PRICE.videoPerSecond
if (entry.kind === 'tts') return entry.units * PRICE.ttsPerCharacter
return 0
}
export function buildBill(reports, costs) {
return reports.map((r) => {
const mine = costs.filter((c) => c.nodeId === r.id)
return {
nodeId: r.id,
ms: r.ms,
calls: mine.length,
artifacts: r.artifacts.length,
estimatedCny: Number(mine.reduce((s, c) => s + estimateCny(c), 0).toFixed(4)),
}
})
}# Unit prices from the official pay-as-you-go page, in CNY. The speech figure is an
# approximation derived from a resource package, not a listed rate.
PRICE = {"image_per_item": 0.025, "video_per_second": 0.5, "tts_per_character": 0.00035}
# Use the real amount if the call really ran; only estimate offline. Never mix the two.
def estimate_cny(entry: dict) -> float:
if entry["costCny"] > 0:
return entry["costCny"]
key = {"image": "image_per_item", "video": "video_per_second", "tts": "tts_per_character"}.get(entry["kind"])
return entry["units"] * PRICE[key] if key else 0.0
def build_bill(reports: list[dict], costs: list[dict]) -> list[dict]:
bill = []
for r in reports:
mine = [c for c in costs if c["nodeId"] == r["id"]]
bill.append({
"nodeId": r["id"],
"ms": r["ms"],
"calls": len(mine),
"artifacts": len(r["artifacts"]),
"estimatedCny": round(sum(estimate_cny(c) for c in mine), 4),
})
return billStep four blows up: today we use the dumbest possible approach
Now do something counterintuitive: make the pipeline fail on purpose. Break the video stage — the fourth one — deliberately, and see how this line behaves today.
> script: script and character cards ... 32ms, 2 artifacts
> assets: character look tests ... 80ms, 2 artifacts
> frames: first frame per shot ... 158ms, 4 artifacts
> clips: shot videos ... 625ms FAILED
clip failed: (injected failnode4) the clip stage failed: the vendor returned a non-retryable error
Pain report
failed stage: clip
artifacts already produced: 7, sitting safely in work/runs/run-20260907-741
money already spent, estimated: CNY 3.1250
the only option now: run the whole command again from the top.
Which means that CNY 3.1250 gets spent a second time, inevitably, not by bad luck.Three details deserve staring at.
First, eight artifacts are still on disk and none of them help. The script, the character cards, two look tests, four first frames — all intact, all directly reusable files. But this pipeline does not know they are reusable, so a re-run regenerates every one of them.
Second, the failing stage spent money too. Video is called shot by shot, and the first shot had already gone out when it blew up; those three yuan were genuinely paid. If the bill only counted successful stages, that money would vanish from the post-mortem — so this course's bill records failed nodes too, with an artifact count of 0 and a spend that is not 0. In real incident reviews this matters enormously: you must be able to answer "how much did this outage burn."
Third, the program can do nothing about it. All it can do is record what finished, print a report, and exit. No retry, no cache, no resume.
That is deliberate. Add retries and caching casually today and you never feel how much their absence hurts, and tomorrow's workflow engine becomes a thing you learn without knowing why. So today's failure policy has exactly three actions: record, report, exit.
One thing must be right already, though: a failure must not discard the elapsed time and spend of the stages that did finish. That is what decides whether your pain report has numbers in it.
// The dumbest sequential runner: record as you go, stop on error.
// The point is not "how it runs" but "what you still hold when it breaks" - an
// implementation that rejects all the way out throws away the earlier stages' elapsed
// time and spend, which is exactly what you most need on a failure.
export async function runSequential(stages) {
const reports = []
for (const node of topoSort(stages)) {
const started = Date.now()
try {
const result = await node.run()
reports.push({ id: node.id, ms: Date.now() - started, artifacts: result.artifacts })
} catch (err) {
// The failing stage goes into the bill too: it has usually already made a paid call.
reports.push({ id: node.id, ms: Date.now() - started, artifacts: [], note: 'failed, artifacts void' })
return { reports, failedAt: node.id, error: err }
}
}
return { reports }
}# The dumbest sequential runner: record as you go, stop on error.
# The point is not "how it runs" but "what you still hold when it breaks" - an
# implementation that re-raises straight out throws away the earlier stages' elapsed
# time and spend, which is exactly what you most need on a failure.
import time
def run_sequential(stages: list) -> dict:
reports = []
for node in topo_sort(stages):
started = time.time()
try:
result = node.run()
reports.append({"id": node.id, "ms": int((time.time() - started) * 1000), "artifacts": result["artifacts"]})
except Exception as err:
# The failing stage goes into the bill too: it has usually already paid for a call.
reports.append({"id": node.id, "ms": int((time.time() - started) * 1000), "artifacts": [], "note": "failed"})
return {"reports": reports, "failedAt": node.id, "error": err}
return {"reports": reports}The first screening: where to point your eyes
It runs, the books are done, and now comes the least technical and most often skipped step of the day: watch the cut from start to finish.
The most dangerous state for an automated pipeline is not an error; it is producing garbage with every light green. Every stage returned success, every file is present, and the episode is unwatchable. So this step cannot be skipped, and it needs a checklist rather than a vibe.
Pass one, continuity: is the same character the same face across four shots? This is where AI short drama collapses most easily, and it is what D3's reference-image techniques exist to solve. Pass two, sync: does the picture cut away before a line finishes? Do subtitle and sound start at the same instant? Pass three, picture quality: is any shot obviously darker, softer or off in color compared to the others? Pass four, the cover: can you tell from that frame what the episode is about?
From script to system: the three pieces next week fills
This line runs, but it is a script, not a system. What is missing? Sort today's problems by nature and they fall into exactly three groups.
One: reliability. Any failed step means starting over, and artifacts you already paid for are thrown away. The CNY 3.1250 on that pain report is the price, against CNY 10.6393 for a whole episode — and in real work you hit this several times a day. D8's workflow engine fills it: an idempotency key per node, content-addressed artifacts on disk, re-runs reduced to a set difference, and a second run whose paid-call count should be 0.
Two: throughput. Six stages in a straight line means one episode costs the sum of six segments, and multiple episodes just queue. But you cannot simply crank concurrency to the maximum, because the vendor has quotas — the speech API allows twenty requests per minute for topped-up accounts, and one episode with forty storyboard entries means forty syntheses, so without a gate you will hit the limit. D9 fills it: per-vendor concurrency gates, token buckets, priority queues.
Three: control. Right now the cut's quality depends on human eyes, with nowhere to record the findings, and cost can only be tallied after the fact rather than intercepted before it. D10's review desk, D11's QC and compliance, and D12's cost routing and budget circuit breakers fill it.
Source Reading
Hands-On Lab
Before starting, confirm all six earlier labs still run individually, especially the ffmpeg one — once connected, an error is buried in hundreds of log lines, and checking separately saves a lot of time. Today's lab needs no API key; offline, all six stages execute for real and only the four network egress points are stubbed.
- Run the full flow once, confirm the six stages complete in order, and open the cut in this run's directory.
- Read the bill, name the most expensive stage and its percentage of total spend, then explain why the elapsed column is untrustworthy offline.
- Change the sentence and run again, compare the two run directories, and confirm they neither overwrite nor contaminate each other.
- Fail the fourth stage deliberately, read the pain report, and write down the "money already spent" figure — tomorrow you turn it into 0.
- Watch the whole cut against the screening checklist, write down every quality problem you find, and tag each with the stage responsible for it.
Interview Questions
Today's 3 questions are in the question bank below, focused on the engineering problems that only appear after connecting stages, measuring time and cost per stage, and identifying and ranking fragile points. Read the analysis before the key points — practicing the derivation beats memorizing them. The cn / global labels let you pick by target market.
Checklist and Tomorrow
- I can produce one complete episode from a single sentence with one command, and know exactly where it's stuck if any step fails
- I can measure the time and cost of each stage, and point out where the bottleneck is
- I can list the pipeline's three fragile points right now, and say which day of week two fixes each one
- I can explain why every artifact must hang under a run identifier
- I can say which of the bill's numbers are untrustworthy offline, and why
- All 5 lab acceptance criteria pass
- I can answer at least 2 of the 3 interview questions without looking at the key points
Tomorrow (D8) we swap this sequential script for a stateful workflow engine: an idempotency key per node, content-addressed artifacts on disk, and a re-run after failure that only completes the unfinished part. Why is it first in week two? Because the number on today's pain report is directly quantifiable waste, and video is ninety-eight percent of cost — fix the piece a number can prove is the least error-prone principle in engineering prioritization.
Interview questions
In a multi-step generation pipeline, one step fails. What behavior do you want the system to have?一条多步骤的生成流水线,中间某一步失败了,你希望系统有什么行为?
Common in ChinaCommon overseasIntermediate#pipeline-reliability#idempotency#error-handlingHow to reason about it · think before answering
- The discriminator is whether you answer in layers. People who just say 'retry' assume all failures are transient. Anyone who has run one of these asks first: is this failure retryable, because that decides everything downstream.
- Split the behavior into three layers: what to do immediately, what to do for this run, and what to do for the next run. Immediately: classify the error and retry with bounds. Only rate limits, timeouts and 5xx deserve backoff; auth failures, insufficient balance and content-policy rejections will fail a hundred more times.
- For this run: preserve the value already produced. Persist artifacts, elapsed time and spend for every completed step, including the money the failing step itself already burned. An implementation that just rethrows loses exactly the data a post-mortem needs.
- For the next run: do not pay twice. Give every node an idempotency key, store artifacts content-addressed, and make a rerun a set difference — skip what is done, redo only what is not. The bar is hard: the second run should make zero paid API calls.
- This matters more in generative pipelines than in ordinary backends because per-step cost is extreme. Measured on one episode in this course, the video step is 98 percent of total spend, so a full rerun burns over ten yuan, predictably rather than occasionally.
- Expect the follow-up 'what goes into the idempotency key'. Answer: model id, prompt, duration and resolution — anything that changes the artifact — plus an implementation version and the fingerprints of all dependencies. Never the run id, a timestamp or a random value.
分析过程 · 先想清楚再作答
- 这题的区分度在于你会不会分层回答。只说「重试」的人默认失败都是瞬时的;真正做过的人会先问一句:这次失败是可重试的还是不可重试的,因为这一条决定了后面所有动作。
- 先把行为拆成三层:立刻要做的、这一次运行要做的、下一次运行要做的。立刻要做的是错误分类与有界重试,只有限流、超时、五开头这类瞬时错误才值得退避重试,鉴权失败、余额不足、内容审核不通过重试一百次也是白烧钱。
- 这一次运行要做的是保住已经产生的价值:把已完成步骤的产物、耗时、花费全部落盘,包括失败那一步自己已经花掉的钱。一个直接向上抛的实现会把这些一起丢掉,而它们恰恰是复盘时最该看的。
- 下一次运行要做的是不重复花钱:每个节点算一个幂等键,产物按内容寻址落盘,重跑时先做一次差集,已完成的跳过、只补做没做完的。判据非常硬——第二次运行的付费接口调用次数应当是 0。
- 在生成式流水线里这一条比传统后端更要紧,因为单步成本高得离谱:本课量过一集的账,视频那一环占了全部花费的九成八,从头重跑一次就是白烧十块多,而且是必然的,不是偶然的。
- 可预期的追问是「幂等键里该放什么」。答:模型 id、提示词、时长分辨率这类会影响产物的输入,加上实现版本号和全部依赖的指纹;绝不能放运行标识、时间戳、随机数,放了就永远不命中。
Key points
- Classify errors first: only retryable ones get backoff. Auth, balance and content-policy failures gain nothing from retries.
- On failure, preserve completed steps' artifacts, timings and spend, including what the failing step itself already cost.
- The next run uses idempotency keys and content-addressed artifacts to compute a set difference and redo only what is missing.
- The acceptance bar is zero paid API calls on the second run, not 'no errors in the log'.
- Per-step cost is extreme in generative pipelines, so this work converts directly into money on the bill.
答题要点
- 先做错误分类:可重试的才退避重试,鉴权、余额、内容审核这类重试没有意义。
- 失败时保住已完成步骤的产物、耗时与花费,失败那一步自己花的钱也要记。
- 下一次运行靠幂等键与内容寻址的产物做差集,只补做没做完的部分。
- 验收判据是第二次运行的付费接口调用次数为 0,而不是「日志里没报错」。
- 生成式流水线单步成本极高,这一条的收益能直接换算成账单上的金额。
How do you measure the cost of a generation pipeline, and what besides money should you measure?怎么度量一条生成流水线的成本?除了钱还要量什么?
Common in ChinaCommon overseasBasic#observability#cost-accounting#pipeline-designHow to reason about it · think before answering
- This looks like a giveaway, but the real question is 'besides money'. Anyone who reports a single total cannot make an optimization decision, because a total does not say where to act.
- First decide the granularity: break it down per stage. One number carries no information; a per-stage table immediately shows where the money and the time went. Measured on one episode here: five images cost 0.125 yuan, voice under two cents, three video shots 10.5 yuan — video is 98 percent. You only see that broken down.
- Second, measure three things besides money: elapsed time decides how many episodes per day, call count decides whether you hit provider rate limits, and artifact count is the crudest completeness check — four shots should yield four clips, and a missing one means something failed silently.
- Third, separate estimates from real spend. Offline or in load tests you have no real amounts, so derive them from published unit prices — but label them as estimates, and never mix the two on one code path or the books will never reconcile.
- Also worth flagging: offline timing rankings are usually fake. With the APIs stubbed, local encoding becomes the biggest slice, and optimizing against that chart targets the wrong thing.
- Expect the follow-up 'what do you optimize first'. Answer: whatever has a number attached. Here it is waste from failed reruns, because it equals money on the bill. Concurrency comes second — before output is stable, concurrency only burns money faster.
分析过程 · 先想清楚再作答
- 这题看着是送分题,题眼其实在「除了钱」。只报一个总金额的人,做不出任何优化决策,因为总金额不告诉你该动哪里。
- 第一步是确定度量的粒度:**按环节摊开**。一个总数没有信息量,一张按环节分列的表能立刻告诉你钱花在哪、时间花在哪。本课量过一集:五张图一毛二五、配音不到两分、三个镜头的视频十块五,视频占了九成八——这个结论只有摊开才看得见。
- 第二步是把「钱」之外的三样一起量:耗时决定一天能出几集;调用次数决定会不会撞上厂商的速率限制;产物数是最朴素的完整性校验,四个镜头就该有四个视频,少一个说明某处静默失败了。
- 第三步是把估算和真实分开。离线或压测时拿不到真实金额,可以按公开单价折算,但**必须标明它是折算值**,而且折算逻辑和真实金额不能混在一条路径上算,否则账永远对不上。
- 还要提醒一句常被忽略的:离线模式下的耗时排名往往是假的。接口被打了桩,本地的编码步骤反而成了大头,照着这张图做优化会优化错地方。
- 可预期的追问是「量完之后先优化哪一项」。答:先优化能被数字证明收益的那一项。这个场景里是失败重跑造成的浪费,因为它直接等于账单上的金额;并发排第二,因为在产出还不稳定时并发只会让你更快地烧钱。
Key points
- Break the cost down per stage; a single total cannot tell you where to act.
- Besides money, measure elapsed time, call count and artifact count — throughput, rate limits and completeness.
- Keep estimated and real spend on separate paths, and always label estimates as estimates.
- Offline timing rankings are unreliable; do not optimize against a stubbed profile.
- Prioritize by which improvement has a number attached, not by intuition.
答题要点
- 按环节摊开,不要只给一个总数,否则无法定位该优化哪里。
- 除了金额还要量耗时、调用次数、产物数,各自对应吞吐、限流、完整性。
- 估算与真实金额分开计算,估算必须标明是折算值。
- 注意离线模式下耗时排名不可信,别照着假图做优化。
- 优化顺序按「收益能不能被数字证明」排,不按直觉排。
After chaining several individually working steps into one pipeline, which problems appear that single-step debugging never shows?把多个已经各自跑通的环节串成一条流水线之后,哪些问题是单独调试时看不见的?
Common in ChinaCommon overseasDeep dive#integration#pipeline-design#observabilityHow to reason about it · think before answering
- This tests integration instinct. If the answer is only 'interfaces do not line up', you have only integrated synchronous pure functions. In generative pipelines the integration problems live in state and artifacts, not in signatures.
- The framing question is: during single-step debugging, who does the gluing? Your head does. You know where the last script wrote its files and which blob to feed forward. Chaining forces that implicit knowledge into code, and whatever you fail to move becomes an integration bug.
- That yields three concrete classes. First, artifact paths and naming: a fixed output path is fine in isolation, but the second run overwrites the first, and on failure you cannot tell which files belong to which attempt. The fix is a run id that every artifact hangs under.
- Second, partial intermediate state: a step produces incomplete output without erroring, the next step accepts it, and the error propagates until it explodes far from its origin. The fix is a completeness assertion after every step, such as an expected artifact count.
- Third, observability: six stages each log their own way, hundreds of lines scroll past, and you cannot tell which stage failed. The fix is one log contract — a scannable progress table on the terminal, details pushed to files.
- Expect the follow-up 'how do you catch these earlier'. Answer: agree on three things before chaining — the artifact directory layout, each step's input/output contract, and the log format. Fix those and most integration bugs never get written.
分析过程 · 先想清楚再作答
- 这题考的是系统集成的直觉。回答里如果只有「接口对不上」,说明你只集成过同步的纯函数;生成式流水线的集成问题主要出在状态和产物上,不在接口签名上。
- 拆解的角度是:单独调试时,是谁在做衔接?答案是你的脑子。你知道上一个脚本把文件写到哪、知道该拿哪份数据喂下一步。串起来之后这些隐式知识必须搬进代码,而搬漏的地方就是集成问题的来源。
- 由此可以推出三类具体问题。第一类是产物路径与命名:单独跑时随手写一个固定输出路径没问题,串起来跑第二遍就把第一遍覆盖了,失败时也分不清哪些文件属于哪一次。解法是每次运行分配一个运行标识,所有产物挂在它下面。
- 第二类是中间态:某一步的产物不完整但没报错,下一步照单全收,错误一路往下传,最后在离源头很远的地方炸掉。解法是每一步产出后做完整性校验,比如按数量断言。
- 第三类是可观测性:六个环节各打各的日志,几百行滚过去,出了事看不出是哪一环。解法是统一日志规格,终端上只留一张能一眼扫完的进度表,细节压到文件里。
- 可预期的追问是「怎么提前发现这些问题」。答:串联之前先约定三件事——产物目录布局、每一步的输入输出契约、日志规格。这三件事定下来,绝大多数集成问题在写代码时就被挡住了。
Key points
- In isolation a human does the gluing; chaining means moving that implicit knowledge into code.
- Artifact paths and naming: assign a run id and hang every artifact under it to avoid overwrites and confusion.
- Incomplete intermediate state that does not error propagates far before exploding; assert completeness after every step.
- Log flooding: adopt one log contract, keep a progress table on the terminal and push details to files.
- Prevent it by agreeing on directory layout, per-step I/O contracts and log format before chaining anything.
答题要点
- 单独调试时是人脑在做衔接,串联的本质是把隐式知识搬进代码。
- 产物路径与命名:每次运行一个运行标识,所有产物挂在它下面,避免覆盖与混淆。
- 中间态不完整却不报错,错误会传到很远的地方才炸;每一步产出后做完整性校验。
- 日志淹没:统一日志规格,终端只留进度表,细节压到文件。
- 预防手段是串联之前先定好目录布局、输入输出契约与日志规格三件事。