Page MenuHomePhabricator

TTS Section Generator Phase 3: containerization with baked NeMo cache, staging deployment, and the timeout and blob-write de-risking spikes
Closed, ResolvedPublic

Description

Following Phase 2 (T432198), the generator produces the full delivery artifact family (audio_opus, audio_mp3, captions_vtt, timestamps_json) with deterministic transcoding and a hardened error taxonomy, but until now it has been running locally on ML-Lab. In this task, we shall make the generator deployable on LiftWing.

  • Containerization with the NeMo grammar cache baked at image build. v0 managed NeMo's 60+ second grammar compilation operationally (a manual cache-initialization step, without which k8s readiness probes timed out and pods entered CrashLoopBackOff). We shall bake the compiled grammars into the image as a build step, killing that failure mode permanently: startup will load precompiled grammars in seconds, and the build shall assert NeMo initialized successfully, so an image that would silently fall back to regex normalization (a generation_version change in disguise) fails the build rather than shipping. Layers shall be ordered so the expensive bake caches independently of application-code changes (the known build risk is pynini wheel availability for the base image's Python; if the wheel match breaks, the build grows a compiler toolchain and gets slow, not broken). We shall also migrate the service startup hook to the FastAPI lifespan API and add compose configuration for local development. The production image shall be rebuilt on WMF base images per the deploying repo's conventions; the layer sequence is the property to preserve.
  • Staging deployment. We shall deploy the containerized generator to staging and settle, with the team, which cluster a non-KServe ML-owned HTTP service belongs on, along with its resource envelope. The service shall run single-worker and scale via replicas, keeping the concurrency story uniform with the isvc.
  • Spike 1: the request-timeout ceiling. Section length maps directly onto single-request duration, so the question is decided by the corpus tail. We shall write a corpus scanner (scripts/scan_corpus.py, committed with both a seeded-sample mode and a full-corpus --all mode) that enumerates the Featured Article list, samples it reproducibly, extracts and normalizes sections through the generator's own code path, and reports the section-length distribution converted to estimated isvc wall time against candidate ceilings (60/120/300/600 s), using the T430536-measured calibration (~15 chars per audio-second, RTF 0.27 full alignment). We shall then measure the scan's worst sections through the real staging isvc (scripts/spike_timeout.py, which shall also recalibrate the chars-per-audio-second and effective end-to-end RTF constants with measured values) and look up the actual LiftWing/Knative timeout configuration on the path. The decision this spike shall produce, in writing: provision/raise the timeout, or implement oversized-section splitting across multiple isvc calls (paragraph-boundary joins with a silence gap); splitting shall not be built unless the measured tail forces it. The scan shall double as a content-quality audit of section extraction and normalization against real corpus text; any defects it surfaces (extraction letting non-spoken content through, normalizer failures on real inputs) shall be fixed with regression tests and the sample rescanned with the same seed, since a distribution computed on buggy extraction would answer the timeout question wrongly, and corrected corpus-level figures (sections per article, total generatable sections) shall be folded into the intake document.
  • Spike 2: blob-write mode behind one interface. We shall implement artifact sinks selected by configuration at startup: inline (default, artifacts return as bytes_b64; LAC-native and storage-free), file (atomic tmp-plus-rename writes under the canonical revision-scoped key layout {wiki_id}/{page_id}/{rev_id}/{section_id}.{ext}, response carries blob_uri; the Phase 4 pilot's sink and the local stand-in for object storage), and s3 (interface-complete stub that fails loudly at startup until Data Persistence provisions the PoC bucket). A misconfigured sink shall fail the deploy, not the first request; the response schema shall differ by exactly one field (bytes_b64 vs blob_uri) between modes. This converts the blob-write question from a dependency into "both modes supported; Data Persistence's call."

Acceptance criteria: the image builds with the NeMo bake assertion passing; the service runs in staging; the unit suite is green in CI including sink tests and any scan-surfaced regression tests; SPIKE_ANSWERS.md exists in the repo containing the measured section-length distribution with its sampling caveat, the in-pod worst-case synthesis measurements, the actual infra timeout values on the path, and the written recommendation for each spike; and the Prep Pantry intake document is updated with both answers, with any corrected corpus figures carried into its Operations rows.

Scope of this task: deployment readiness and the two spikes only. Tracked separately:

  • Phase 4, batch pilot: the ~50 Featured Article pilot producing the measured numbers pack (real per-file artifact sizes, full-path latency distribution, skip/failure rates by taxonomy code) for the DE intake meeting and the maxReplicas capacity decision; it shall consume this task's file sink and scan tooling and replace the calibration-derived estimates with fully measured values.
  • S3 sink wiring: lands together with the Data Persistence PoC bucket and credentials, not before.
  • Full-corpus scan: the sampled distribution shall carry a stated extreme-tail caveat; the --all run that replaces the extrapolated corpus maximum with the true one may land here or in Phase 4, whichever executes first.
  • DE / Data Persistence / Traffic layers: batch pipeline, edit-stream trigger, generation-events stream, blob store plus index, and serving/CDN remain owned by their respective teams per the intake document.

Details

Related Changes in Gerrit:
SubjectAuthorRepoBranchLines +/-
Kevin Baziraoperations/puppetproduction+13 -0
Kevin Baziraoperations/puppetproduction+6 -0
Klausmanoperations/deployment-chartsmaster+10 -15
Kevin Baziraoperations/deployment-chartsmaster+1 -0
Kevin Baziraoperations/deployment-chartsmaster+10 -6
Kevin Baziramachinelearning/liftwing/inference-servicesmain+57 -34
Kevin Baziraoperations/deployment-chartsmaster+2 -2
Kevin Baziramachinelearning/liftwing/inference-servicesmain+1 -1
Kevin Baziraoperations/deployment-chartsmaster+2 -2
Kevin Baziramachinelearning/liftwing/inference-servicesmain+9 -1
Kevin Baziraoperations/deployment-chartsmaster+14 -0
Kevin Baziraoperations/deployment-chartsmaster+10 -2
Kevin Baziramachinelearning/liftwing/inference-servicesmain+37 -4
Kevin Baziraoperations/deployment-chartsmaster+6 -0
Kevin Baziraoperations/deployment-chartsmaster+3 -0
Kevin Baziraoperations/puppetproduction+1 -0
Klausmanoperations/puppetproduction+8 -0
Kevin Baziraoperations/deployment-chartsmaster+248 -0
Kevin Baziraoperations/deployment-chartsmaster+8 -0
Kevin Baziramachinelearning/liftwing/inference-servicesmain+1 -0
Kevin Baziramachinelearning/liftwing/inference-servicesmain+1 -1
Kevin Baziramachinelearning/liftwing/inference-servicesmain+19 -0
Kevin Baziraintegration/configmaster+15 -0
Kevin Baziramachinelearning/liftwing/inference-servicesmain+9 K -26
Show related patches Customize query in gerrit

Related Objects

Event Timeline

We have containerized the TTS section generator, and a local test of this image shows NeMo is active and generation_version reflects it:

$ curl -s "http://localhost:8181/sections?wiki_id=enwiki&page_id=9228&rev_id=1362915217" | python3 -c "
import sys, json
data = json.load(sys.stdin)
print(f'generation_version: {data[\"generation_version\"]}')
print(f'total sections: {len(data[\"sections\"])}')
print(f'generatable: {sum(1 for s in data[\"sections\"] if s[\"generatable\"])}')
"

generation_version: kokoro-v1.0+af_heart+norm-2026.07-nemo-98d86449
total sections: 38
generatable: 34
NOTE: generation_version contains -nemo- where phase 1 and 2 carried -regex-. This proves the baked NeMo grammars are active rather than the regex fallback.

Change #1311170 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[machinelearning/liftwing/inference-services@main] tts_section_generator: Add Phase 3 deployment readiness and de-risking spikes

https://gerrit.wikimedia.org/r/1311170

Change #1311315 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[machinelearning/liftwing/inference-services@main] tts-section-generator: Configure CI pipeline to run tests for this server

https://gerrit.wikimedia.org/r/1311315

Change #1311170 merged by jenkins-bot:

[machinelearning/liftwing/inference-services@main] tts_section_generator: Add Phase 3 deployment readiness and de-risking spikes

https://gerrit.wikimedia.org/r/1311170

Change #1311317 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[integration/config@master] inference-services: Add CI pipeline jobs for tts-section-generator server

https://gerrit.wikimedia.org/r/1311317

Change #1311317 merged by jenkins-bot:

[integration/config@master] inference-services: Add CI pipeline jobs for tts-section-generator server

https://gerrit.wikimedia.org/r/1311317

Change #1311315 merged by Kevin Bazira:

[machinelearning/liftwing/inference-services@main] tts-section-generator: Configure CI pipeline to run tests and publish this server

https://gerrit.wikimedia.org/r/1311315

Change #1311417 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[machinelearning/liftwing/inference-services@main] tts-section-generator: Fix image name

https://gerrit.wikimedia.org/r/1311417

Change #1311417 merged by Kevin Bazira:

[machinelearning/liftwing/inference-services@main] tts-section-generator: Fix image name

https://gerrit.wikimedia.org/r/1311417

Change #1311599 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[machinelearning/liftwing/inference-services@main] tts-section-generator: Add ffmpeg to production image

https://gerrit.wikimedia.org/r/1311599

Change #1311599 merged by jenkins-bot:

[machinelearning/liftwing/inference-services@main] tts-section-generator: Add ffmpeg to production image

https://gerrit.wikimedia.org/r/1311599

Change #1311749 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[operations/deployment-charts@master] ml-services: Add tts-section-generator deployment configs

https://gerrit.wikimedia.org/r/1311749

Change #1311750 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[operations/deployment-charts@master] admin_ng: Add tts-section-generator namespace to LiftWing

https://gerrit.wikimedia.org/r/1311750

Change #1311753 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[operations/puppet@production] tts-section-generator: Add deployment credentials and services proxy

https://gerrit.wikimedia.org/r/1311753

Change #1311794 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[operations/puppet@production] service::catalog: Add tts-section-generator

https://gerrit.wikimedia.org/r/1311794

Change #1311799 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[operations/puppet@production] conftool-data: Add tts-section-generator

https://gerrit.wikimedia.org/r/1311799

Change #1311750 merged by jenkins-bot:

[operations/deployment-charts@master] admin_ng: Add tts-section-generator namespace to LiftWing

https://gerrit.wikimedia.org/r/1311750

Change #1311749 merged by jenkins-bot:

[operations/deployment-charts@master] ml-services: Add tts-section-generator deployment configs

https://gerrit.wikimedia.org/r/1311749

Change #1311843 had a related patch set uploaded (by Klausman; author: Klausman):

[operations/puppet@production] profile::k8s::deployment_server: add config for tts-section-generator

https://gerrit.wikimedia.org/r/1311843

Change #1311843 merged by Klausman:

[operations/puppet@production] profile::k8s::deployment_server: add config for tts-section-generator

https://gerrit.wikimedia.org/r/1311843

Change #1312294 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[operations/deployment-charts@master] ml-services: Enable staging ingress for internal cluster access of tts-section-generator

https://gerrit.wikimedia.org/r/1312294

Change #1312294 merged by jenkins-bot:

[operations/deployment-charts@master] ml-services: Enable staging ingress for internal cluster access of tts-section-generator

https://gerrit.wikimedia.org/r/1312294

Change #1312296 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[operations/deployment-charts@master] ml-services: Enable prod ingress for internal cluster access of tts-section-generator

https://gerrit.wikimedia.org/r/1312296

Change #1312296 merged by jenkins-bot:

[operations/deployment-charts@master] ml-services: Enable prod ingress for internal cluster access of tts-section-generator

https://gerrit.wikimedia.org/r/1312296

Change #1312300 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[machinelearning/liftwing/inference-services@main] tts-section-generator: Route MediaWiki API calls through envoy services-proxy

https://gerrit.wikimedia.org/r/1312300

Change #1312300 merged by jenkins-bot:

[machinelearning/liftwing/inference-services@main] tts-section-generator: Route MediaWiki API calls through envoy services-proxy

https://gerrit.wikimedia.org/r/1312300

Change #1312301 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[operations/deployment-charts@master] ml-services: Enable MediaWiki API proxy for tts-section-generator

https://gerrit.wikimedia.org/r/1312301

Change #1312301 merged by jenkins-bot:

[operations/deployment-charts@master] ml-services: Enable MediaWiki API proxy for tts-section-generator

https://gerrit.wikimedia.org/r/1312301

Change #1312303 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[operations/deployment-charts@master] ml-services: add envoy services-proxy listener for mw-api-int in tts-section-generator

https://gerrit.wikimedia.org/r/1312303

Change #1312303 merged by jenkins-bot:

[operations/deployment-charts@master] ml-services: add envoy services-proxy listener for mw-api-int in tts-section-generator

https://gerrit.wikimedia.org/r/1312303

Change #1312305 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[machinelearning/liftwing/inference-services@main] tts-section-generator: use WMF CA bundle for isvc TLS verification

https://gerrit.wikimedia.org/r/1312305

Change #1312305 merged by jenkins-bot:

[machinelearning/liftwing/inference-services@main] tts-section-generator: use WMF CA bundle for isvc TLS verification

https://gerrit.wikimedia.org/r/1312305

Change #1312307 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[operations/deployment-charts@master] ml-services: update tts-section-generator image to server that uses WMF CA bundle for isvc TLS verification

https://gerrit.wikimedia.org/r/1312307

Change #1312307 merged by jenkins-bot:

[operations/deployment-charts@master] ml-services: update tts-section-generator image to server that uses WMF CA bundle for isvc TLS verification

https://gerrit.wikimedia.org/r/1312307

Change #1312309 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[machinelearning/liftwing/inference-services@main] tts-section-generator: use system CA bundle for isvc TLS verification

https://gerrit.wikimedia.org/r/1312309

Change #1312309 merged by jenkins-bot:

[machinelearning/liftwing/inference-services@main] tts-section-generator: use system CA bundle for isvc TLS verification

https://gerrit.wikimedia.org/r/1312309

Change #1312431 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[operations/deployment-charts@master] ml-services: update tts-section-generator image to server that uses system CA bundle for isvc TLS verification

https://gerrit.wikimedia.org/r/1312431

Change #1312431 merged by jenkins-bot:

[operations/deployment-charts@master] ml-services: update tts-section-generator image to server that uses system CA bundle for isvc TLS verification

https://gerrit.wikimedia.org/r/1312431

Thanks to @klausman for helping with the deployment configs. The tts-section-generator is now deployed in LiftWing staging (P94898). Both endpoints are up and running:

1./sections: section discovery for a given revision:

1$ curl "https://tts-section-generator.k8s-ml-staging.discovery.wmnet:31443/sections?wiki_id=enwiki&page_id=9228&rev_id=1362915217"
2
3{
4 "wiki_id": "enwiki",
5 "page_id": 9228,
6 "rev_id": 1362915217,
7 "revision_timestamp": "2026-07-07T00:02:54Z",
8 "generation_version": "kokoro-v1.0+af_heart+norm-2026.07-nemo-98d86449",
9 "sections": [
10 {
11 "section_id": "lead",
12 "title": "Lead",
13 "level": 1,
14 "generatable": true,
15 "char_count": 4124,
16 "content_sha256": "457971c72fa3de06f9d9a44f4e1db345e59f5c6e5a92df0459376560f94f15fe"
17 },
18 {
19 "section_id": "etymology",
20 "title": "Etymology",
21 "level": 2,
22 "generatable": true,
23 "char_count": 2749,
24 "content_sha256": "444a0358044f00d514d7d2f4d283bc0499ab4b351d3de9760e58b3e0647b6a35"
25 },
26 {
27 "section_id": "natural-history",
28 "title": "Natural history",
29 "level": 2,
30 "generatable": false,
31 "char_count": 0,
32 "skip_reason": "text_below_minimum"
33 },
34 {
35 "section_id": "formation",
36 "title": "Formation",
37 "level": 3,
38 "generatable": true,
39 "char_count": 1336,
40 "content_sha256": "c443baad3f582bfd0fbfba87c2dc1bd0de868b10d74c6287948c2fadbcd1a032"
41 },
42 {
43 "section_id": "after-formation",
44 "title": "After formation",
45 "level": 3,
46 "generatable": true,
47 "char_count": 2918,
48 "content_sha256": "d46867c01bd5577c71f2ddb9b7189bf637b69f40d6f2fde0caeb1674805247ff"
49 },
50 {
51 "section_id": "origin-of-life-and-evolution",
52 "title": "Origin of life and evolution",
53 "level": 3,
54 "generatable": true,
55 "char_count": 2487,
56 "content_sha256": "341e85b084330edd6e92150a5f09af1bfebf5949a1f861034a06d38f528a88c5"
57 },
58 {
59 "section_id": "future",
60 "title": "Future",
61 "level": 3,
62 "generatable": true,
63 "char_count": 1969,
64 "content_sha256": "f8a78d2f28d064e6390ab2ed139d63e2857fbf45a8fe085e9e65bf9b28bd960d"
65 },
66 {
67 "section_id": "composition-and-structure",
68 "title": "Composition and structure",
69 "level": 2,
70 "generatable": true,
71 "char_count": 651,
72 "content_sha256": "b642cef4f7af4f4106e52ab21fed34a20808ad86152bee25616c89595cf44069"
73 },
74 {
75 "section_id": "chemical-composition",
76 "title": "Chemical composition",
77 "level": 3,
78 "generatable": true,
79 "char_count": 1017,
80 "content_sha256": "e289e90f8fcdf9247f0e0043a871ee72a5ee410e1dbb9cca73c4e4856d643c7a"
81 },
82 {
83 "section_id": "internal-structure",
84 "title": "Internal structure",
85 "level": 3,
86 "generatable": true,
87 "char_count": 1490,
88 "content_sha256": "c7fc1ec3dbdeeffe0ac880c189ead44fff087dd18d508bb18c3be39eb05b8425"
89 },
90 {
91 "section_id": "internal-heat",
92 "title": "Internal heat",
93 "level": 3,
94 "generatable": true,
95 "char_count": 1528,
96 "content_sha256": "7b91a45b325c3c902f20003842273d00f6d578d6aafe603ea85f5d93fe17d262"
97 },
98 {
99 "section_id": "tectonic-plates",
100 "title": "Tectonic plates",
101 "level": 3,
102 "generatable": true,
103 "char_count": 2237,
104 "content_sha256": "08b86f38b63cb2de8277157090f4165340ae1b380345734d40f20f7d9f90c60f"
105 },
106 {
107 "section_id": "crust",
108 "title": "Crust",
109 "level": 3,
110 "generatable": true,
111 "char_count": 824,
112 "content_sha256": "fbc4fba2dab735d96e55914d1fb7fbb09edf38c884d7371982ac68692083c0ca"
113 },
114 {
115 "section_id": "surface",
116 "title": "Surface",
117 "level": 3,
118 "generatable": true,
119 "char_count": 2682,
120 "content_sha256": "dda61c0178226e02c9646790d1b2f8446e0c222f497567c29a5c09824b36d362"
121 },
122 {
123 "section_id": "surface-topography",
124 "title": "Surface topography",
125 "level": 3,
126 "generatable": true,
127 "char_count": 609,
128 "content_sha256": "b51b59d753e53aaa0466034a1f7b0ac98220dd8d61275f2bde0c2d07d95c9964"
129 },
130 {
131 "section_id": "shape",
132 "title": "Shape",
133 "level": 3,
134 "generatable": true,
135 "char_count": 1397,
136 "content_sha256": "fccb53cd0f7665fba18b0e7b452658415a65429b38293b5c681efd4e1e1093ff"
137 },
138 {
139 "section_id": "gravity-and-magnetic-field",
140 "title": "Gravity and magnetic field",
141 "level": 2,
142 "generatable": false,
143 "char_count": 0,
144 "skip_reason": "text_below_minimum"
145 },
146 {
147 "section_id": "magnetic-field",
148 "title": "Magnetic field",
149 "level": 3,
150 "generatable": true,
151 "char_count": 2259,
152 "content_sha256": "208bc3b0d18c86188753f3129c9c429683bb02b25a791d1e88879e66b5f7de33"
153 },
154 {
155 "section_id": "gravitational-field",
156 "title": "Gravitational field",
157 "level": 3,
158 "generatable": true,
159 "char_count": 821,
160 "content_sha256": "6678426accf5f4812bde654004f6f051d17bab8155b15125b1eab85180d15bfb"
161 },
162 {
163 "section_id": "moon-and-orbital-space",
164 "title": "Moon and orbital space",
165 "level": 2,
166 "generatable": false,
167 "char_count": 0,
168 "skip_reason": "text_below_minimum"
169 },
170 {
171 "section_id": "moon",
172 "title": "Moon",
173 "level": 3,
174 "generatable": true,
175 "char_count": 3167,
176 "content_sha256": "2f9361fded0dd7113930ef9b79d247f1155af3c4b6612d79256dab6bed6bbbb4"
177 },
178 {
179 "section_id": "asteroids-and-artificial-satellites",
180 "title": "Asteroids and artificial satellites",
181 "level": 3,
182 "generatable": true,
183 "char_count": 996,
184 "content_sha256": "7dedda8fc738bebcb2f7fb01c93446776c95375082e19c2f351475aec1dde3cc"
185 },
186 {
187 "section_id": "orbit-and-rotation",
188 "title": "Orbit and rotation",
189 "level": 2,
190 "generatable": false,
191 "char_count": 0,
192 "skip_reason": "text_below_minimum"
193 },
194 {
195 "section_id": "rotation",
196 "title": "Rotation",
197 "level": 3,
198 "generatable": true,
199 "char_count": 1520,
200 "content_sha256": "762648be353c692fa825c523f9632c6e3690b4ec2b4697dddcb6ed72ec6c2bd6"
201 },
202 {
203 "section_id": "orbit-and-location",
204 "title": "Orbit and location",
205 "level": 3,
206 "generatable": true,
207 "char_count": 2049,
208 "content_sha256": "42d93415408b371944bfe6196f22c7a364c936b3326aeb784897728fd86b848a"
209 },
210 {
211 "section_id": "axial-tilt-and-seasons",
212 "title": "Axial tilt and seasons",
213 "level": 3,
214 "generatable": true,
215 "char_count": 3629,
216 "content_sha256": "50a46f3eb3025054b85003c0957b454d34a567dcb63855657eddf2206ef636f8"
217 },
218 {
219 "section_id": "atmosphere-and-climate",
220 "title": "Atmosphere and climate",
221 "level": 2,
222 "generatable": true,
223 "char_count": 2096,
224 "content_sha256": "a7454927f42cb52e776d4a227a2a7f5f41a545829ddd914f8cedfad7708cbb41"
225 },
226 {
227 "section_id": "exosphere",
228 "title": "Exosphere",
229 "level": 3,
230 "generatable": true,
231 "char_count": 1228,
232 "content_sha256": "f4a8497eacab445c1f4be8f0af56fc1c08e68d95a413408c32e751bc16fa2f86"
233 },
234 {
235 "section_id": "upper-atmosphere",
236 "title": "Upper atmosphere",
237 "level": 3,
238 "generatable": true,
239 "char_count": 554,
240 "content_sha256": "b2808742dafdc7f7ec101b93d256eb1928d8996b77111942d28db6c67fa9f834"
241 },
242 {
243 "section_id": "troposphere",
244 "title": "Troposphere",
245 "level": 3,
246 "generatable": true,
247 "char_count": 635,
248 "content_sha256": "07dbadb1fa2e56ba841af8ba127934849ea170470e6deac72a78d5e6bf29d59e"
249 },
250 {
251 "section_id": "weather-and-climate",
252 "title": "Weather and climate",
253 "level": 3,
254 "generatable": true,
255 "char_count": 3084,
256 "content_sha256": "9ace8de836ba47cfdc4ce507bb2b21f8171b336ea552041dbe8fb82876ffad12"
257 },
258 {
259 "section_id": "hydrosphere",
260 "title": "Hydrosphere",
261 "level": 2,
262 "generatable": true,
263 "char_count": 3169,
264 "content_sha256": "b704ee0ba9913fc93ee461e0a689e575d30b19495a48c1cd4166fbfd6ae1184d"
265 },
266 {
267 "section_id": "biosphere",
268 "title": "Biosphere",
269 "level": 2,
270 "generatable": true,
271 "char_count": 1396,
272 "content_sha256": "40caa3ca0ffc0146502be7eb53dd8b0997d52f0ddb3dc89ee5c67b873604278f"
273 },
274 {
275 "section_id": "challenges-for-life-on-earth",
276 "title": "Challenges for life on Earth",
277 "level": 3,
278 "generatable": true,
279 "char_count": 937,
280 "content_sha256": "77aefd30e07a8783021925f918d9dfdb4ea4fabe64cd3358240815af608053f7"
281 },
282 {
283 "section_id": "human-geography",
284 "title": "Human geography",
285 "level": 2,
286 "generatable": true,
287 "char_count": 2216,
288 "content_sha256": "3875e3d36976d4739076ab84cdd8028ae4ebb145d6395ec032948e7e4a1a0ee7"
289 },
290 {
291 "section_id": "natural-resources-and-land-use",
292 "title": "Natural resources and land use",
293 "level": 3,
294 "generatable": true,
295 "char_count": 1652,
296 "content_sha256": "a49d7af6f3337e4fdbc5cce548c3eb208ce1b61cda80c213c62b020cb67f5334"
297 },
298 {
299 "section_id": "environmental-impact",
300 "title": "Environmental impact",
301 "level": 3,
302 "generatable": true,
303 "char_count": 1143,
304 "content_sha256": "53542fcbdc2c37b6928ea3f3c69e26d844c2b402b2d28294ff74fed33b20e6ec"
305 },
306 {
307 "section_id": "in-culture",
308 "title": "In culture",
309 "level": 2,
310 "generatable": true,
311 "char_count": 2140,
312 "content_sha256": "68fed6b288e4cb6e1157c62314da43ffdc382d5d1bd389926593c0c547493431"
313 }
314 ]
315}

2./generate-section: audio generation for a single section:
1$ curl -X POST "https://tts-section-generator.k8s-ml-staging.discovery.wmnet:31443/generate-section" \
2-H "Content-Type: application/json" \
3-d '{"wiki_id":"enwiki","page_id":9228,"rev_id":1362915217,"section_id":"upper-atmosphere"}'
4
5{
6 "artifacts": [
7 {
8 "wiki_id": "enwiki",
9 "page_id": 9228,
10 "rev_id": 1362915217,
11 "section_id": "upper-atmosphere",
12 "generation_version": "kokoro-v1.0+af_heart+norm-2026.07-nemo-98d86449",
13 "content_sha256": "b2808742dafdc7f7ec101b93d256eb1928d8996b77111942d28db6c67fa9f834",
14 "duration_ms": 33040.3,
15 "artifact_type": "audio_opus",
16 "media_type": "audio/ogg; codecs=opus",
17 "bytes_b64": "T2dnUwACAAAAAAAAAAABAAAAAAAAAF3....MDYKc3BhY2UuCg=="
18 },
19 {
20 "wiki_id": "enwiki",
21 "page_id": 9228,
22 "rev_id": 1362915217,
23 "section_id": "upper-atmosphere",
24 "generation_version": "kokoro-v1.0+af_heart+norm-2026.07-nemo-98d86449",
25 "content_sha256": "b2808742dafdc7f7ec101b93d256eb1928d8996b77111942d28db6c67fa9f834",
26 "duration_ms": 33040.3,
27 "artifact_type": "timestamps_json",
28 "media_type": "application/json",
29 "timestamps": [
30 {
31 "word": "The",
32 "start_ms": 40.0,
33 "end_ms": 100.0
34 },
35 {
36 "word": "upper",
37 "start_ms": 200.0,
38 "end_ms": 380.0
39 },
40 {
41 "word": "atmosphere,",
42 "start_ms": 500.0,
43 "end_ms": 1020.0
44 },
45 {
46 "word": "the",
47 "start_ms": 1300.0,
48 "end_ms": 1380.0
49 },
50 {
51 "word": "atmosphere",
52 "start_ms": 1520.0,
53 "end_ms": 2020.0
54 },
55 {
56 "word": "above",
57 "start_ms": 2120.0,
58 "end_ms": 2300.0
59 },
60 {
61 "word": "the",
62 "start_ms": 2340.0,
63 "end_ms": 2400.0
64 },
65 {
66 "word": "troposphere,",
67 "start_ms": 2440.0,
68 "end_ms": 3020.0
69 },
70 {
71 "word": "is",
72 "start_ms": 3500.0,
73 "end_ms": 3560.0
74 },
75 {
76 "word": "usually",
77 "start_ms": 3720.0,
78 "end_ms": 4020.0
79 },
80 {
81 "word": "divided",
82 "start_ms": 4060.0,
83 "end_ms": 4480.0
84 },
85 {
86 "word": "into",
87 "start_ms": 4560.0,
88 "end_ms": 4680.0
89 },
90 {
91 "word": "the",
92 "start_ms": 4740.0,
93 "end_ms": 4800.0
94 },
95 {
96 "word": "stratosphere,",
97 "start_ms": 4840.0,
98 "end_ms": 5500.0
99 },
100 {
101 "word": "mesosphere,",
102 "start_ms": 5840.0,
103 "end_ms": 6380.0
104 },
105 {
106 "word": "and",
107 "start_ms": 6660.0,
108 "end_ms": 6720.0
109 },
110 {
111 "word": "thermosphere.",
112 "start_ms": 6780.0,
113 "end_ms": 8340.0
114 },
115 {
116 "word": "Each",
117 "start_ms": 8420.0,
118 "end_ms": 8640.0
119 },
120 {
121 "word": "layer",
122 "start_ms": 8640.0,
123 "end_ms": 8960.0
124 },
125 {
126 "word": "has",
127 "start_ms": 9000.0,
128 "end_ms": 9100.0
129 },
130 {
131 "word": "a",
132 "start_ms": 9120.0,
133 "end_ms": 9140.0
134 },
135 {
136 "word": "different",
137 "start_ms": 9140.0,
138 "end_ms": 9560.0
139 },
140 {
141 "word": "lapse",
142 "start_ms": 9720.0,
143 "end_ms": 10360.0
144 },
145 {
146 "word": "rate,",
147 "start_ms": 10480.0,
148 "end_ms": 10640.0
149 },
150 {
151 "word": "defining",
152 "start_ms": 10640.0,
153 "end_ms": 11000.0
154 },
155 {
156 "word": "the",
157 "start_ms": 11040.0,
158 "end_ms": 11200.0
159 },
160 {
161 "word": "rate",
162 "start_ms": 11200.0,
163 "end_ms": 11460.0
164 },
165 {
166 "word": "of",
167 "start_ms": 11480.0,
168 "end_ms": 11680.0
169 },
170 {
171 "word": "change",
172 "start_ms": 11780.0,
173 "end_ms": 12040.0
174 },
175 {
176 "word": "in",
177 "start_ms": 12040.0,
178 "end_ms": 12140.0
179 },
180 {
181 "word": "temperature",
182 "start_ms": 12180.0,
183 "end_ms": 12720.0
184 },
185 {
186 "word": "with",
187 "start_ms": 13460.0,
188 "end_ms": 13600.0
189 },
190 {
191 "word": "height.",
192 "start_ms": 13620.0,
193 "end_ms": 13840.0
194 },
195 {
196 "word": "Within",
197 "start_ms": 13840.0,
198 "end_ms": 14160.0
199 },
200 {
201 "word": "the",
202 "start_ms": 14180.0,
203 "end_ms": 14280.0
204 },
205 {
206 "word": "stratosphere",
207 "start_ms": 14320.0,
208 "end_ms": 15180.0
209 },
210 {
211 "word": "is",
212 "start_ms": 15240.0,
213 "end_ms": 15320.0
214 },
215 {
216 "word": "the",
217 "start_ms": 15340.0,
218 "end_ms": 15460.0
219 },
220 {
221 "word": "ozone",
222 "start_ms": 15980.0,
223 "end_ms": 16260.0
224 },
225 {
226 "word": "layer,",
227 "start_ms": 16340.0,
228 "end_ms": 16580.0
229 },
230 {
231 "word": "a",
232 "start_ms": 16640.0,
233 "end_ms": 16660.0
234 },
235 {
236 "word": "component",
237 "start_ms": 16660.0,
238 "end_ms": 17120.0
239 },
240 {
241 "word": "that",
242 "start_ms": 17120.0,
243 "end_ms": 17420.0
244 },
245 {
246 "word": "partially",
247 "start_ms": 17420.0,
248 "end_ms": 17920.0
249 },
250 {
251 "word": "shields",
252 "start_ms": 17980.0,
253 "end_ms": 18360.0
254 },
255 {
256 "word": "the",
257 "start_ms": 18420.0,
258 "end_ms": 18500.0
259 },
260 {
261 "word": "surface",
262 "start_ms": 18500.0,
263 "end_ms": 18980.0
264 },
265 {
266 "word": "from",
267 "start_ms": 19080.0,
268 "end_ms": 19280.0
269 },
270 {
271 "word": "ultraviolet",
272 "start_ms": 19300.0,
273 "end_ms": 20240.0
274 },
275 {
276 "word": "light",
277 "start_ms": 20300.0,
278 "end_ms": 20740.0
279 },
280 {
281 "word": "and",
282 "start_ms": 20740.0,
283 "end_ms": 20960.0
284 },
285 {
286 "word": "thus",
287 "start_ms": 20960.0,
288 "end_ms": 21120.0
289 },
290 {
291 "word": "is",
292 "start_ms": 21140.0,
293 "end_ms": 21280.0
294 },
295 {
296 "word": "important",
297 "start_ms": 21320.0,
298 "end_ms": 22020.0
299 },
300 {
301 "word": "for",
302 "start_ms": 22020.0,
303 "end_ms": 22240.0
304 },
305 {
306 "word": "life",
307 "start_ms": 22240.0,
308 "end_ms": 22240.0
309 },
310 {
311 "word": "on",
312 "start_ms": 22240.0,
313 "end_ms": 22240.0
314 },
315 {
316 "word": "Earth.",
317 "start_ms": 22240.0,
318 "end_ms": 22240.0
319 },
320 {
321 "word": "The",
322 "start_ms": 22265.666666666664,
323 "end_ms": 22325.666666666664
324 },
325 {
326 "word": "K\u00e1rm\u00e1n",
327 "start_ms": 22525.666666666664,
328 "end_ms": 23205.666666666664
329 },
330 {
331 "word": "line,",
332 "start_ms": 23205.666666666664,
333 "end_ms": 23805.666666666664
334 },
335 {
336 "word": "defined",
337 "start_ms": 23945.666666666664,
338 "end_ms": 24445.666666666664
339 },
340 {
341 "word": "as",
342 "start_ms": 24445.666666666664,
343 "end_ms": 24505.666666666664
344 },
345 {
346 "word": "one",
347 "start_ms": 24585.666666666664,
348 "end_ms": 24685.666666666664
349 },
350 {
351 "word": "hundred",
352 "start_ms": 24705.666666666664,
353 "end_ms": 25045.666666666664
354 },
355 {
356 "word": "kilometers",
357 "start_ms": 25125.666666666664,
358 "end_ms": 26325.666666666664
359 },
360 {
361 "word": "(sixty",
362 "start_ms": 26385.666666666664,
363 "end_ms": 26685.666666666664
364 },
365 {
366 "word": "two",
367 "start_ms": 26765.666666666664,
368 "end_ms": 27045.666666666664
369 },
370 {
371 "word": "miles)",
372 "start_ms": 27045.666666666664,
373 "end_ms": 27465.666666666664
374 },
375 {
376 "word": "above",
377 "start_ms": 27485.666666666664,
378 "end_ms": 27745.666666666664
379 },
380 {
381 "word": "Earth's",
382 "start_ms": 27765.666666666664,
383 "end_ms": 28085.666666666664
384 },
385 {
386 "word": "surface,",
387 "start_ms": 28125.666666666664,
388 "end_ms": 28985.666666666664
389 },
390 {
391 "word": "is",
392 "start_ms": 29045.666666666664,
393 "end_ms": 29145.666666666664
394 },
395 {
396 "word": "a",
397 "start_ms": 29145.666666666664,
398 "end_ms": 29185.666666666664
399 },
400 {
401 "word": "working",
402 "start_ms": 29205.666666666664,
403 "end_ms": 29565.666666666664
404 },
405 {
406 "word": "definition",
407 "start_ms": 29605.666666666664,
408 "end_ms": 30165.666666666664
409 },
410 {
411 "word": "for",
412 "start_ms": 30205.666666666664,
413 "end_ms": 30265.666666666664
414 },
415 {
416 "word": "the",
417 "start_ms": 30345.666666666664,
418 "end_ms": 30505.666666666664
419 },
420 {
421 "word": "boundary",
422 "start_ms": 30525.666666666664,
423 "end_ms": 31085.666666666664
424 },
425 {
426 "word": "between",
427 "start_ms": 31085.666666666664,
428 "end_ms": 31325.666666666664
429 },
430 {
431 "word": "the",
432 "start_ms": 31445.666666666664,
433 "end_ms": 31625.666666666664
434 },
435 {
436 "word": "atmosphere",
437 "start_ms": 31645.666666666664,
438 "end_ms": 32205.666666666664
439 },
440 {
441 "word": "and",
442 "start_ms": 32325.666666666664,
443 "end_ms": 32425.666666666664
444 },
445 {
446 "word": "outer",
447 "start_ms": 32445.666666666664,
448 "end_ms": 32725.666666666664
449 },
450 {
451 "word": "space.",
452 "start_ms": 32785.666666666664,
453 "end_ms": 32905.666666666664
454 }
455 ],
456 "timestamps_mode": "full"
457 }
458 ],
459 "segment_count": 2
460}

Change #1312504 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[machinelearning/liftwing/inference-services@main] tts-section-generator: finalize Spike 1 and drop base64 after decode

https://gerrit.wikimedia.org/r/1312504

Change #1312504 merged by jenkins-bot:

[machinelearning/liftwing/inference-services@main] tts-section-generator: finalize Spike 1 and drop base64 after decode

https://gerrit.wikimedia.org/r/1312504

Change #1312511 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[operations/deployment-charts@master] ml-services: raise timeouts to 600s per Spike 1 measurement for tts-section-generator

https://gerrit.wikimedia.org/r/1312511

Change #1312511 merged by jenkins-bot:

[operations/deployment-charts@master] ml-services: raise timeouts to 600s per Spike 1 measurement for tts-section-generator

https://gerrit.wikimedia.org/r/1312511

Change #1312527 had a related patch set uploaded (by Kevin Bazira; author: Kevin Bazira):

[operations/deployment-charts@master] ml-services: Raise Knative revision timeout to 600s for TTS isvc

https://gerrit.wikimedia.org/r/1312527

Change #1312527 merged by jenkins-bot:

[operations/deployment-charts@master] ml-services: Raise Knative revision timeout to 600s for TTS isvc

https://gerrit.wikimedia.org/r/1312527

Change #1313099 had a related patch set uploaded (by Klausman; author: Klausman):

[operations/deployment-charts@master] ml/tts: Adjust staging/prod setup for TTS_ISVC_URL

https://gerrit.wikimedia.org/r/1313099

Change #1313099 abandoned by Klausman:

[operations/deployment-charts@master] ml/tts: Adjust egress IP setup for staging

Reason:

Brain-twist made me think the wrong away around. Change 1313155 supersedes this.

https://gerrit.wikimedia.org/r/1313099

Change #1311753 merged by Klausman:

[operations/puppet@production] tts-section-generator: Add envoy services-proxy listener

https://gerrit.wikimedia.org/r/1311753

Change #1311794 merged by Klausman:

[operations/puppet@production] service::catalog: Add tts-section-generator

https://gerrit.wikimedia.org/r/1311794