Record the batched-VAD measurements and check heartbeat, bursts and cancel against the real sidecar
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
1 parent
1c1806840e
commit
104c1c4e4d
5 files changed
+143
-2
No files matched your search
@@ -31,6 +31,9 @@ faster-whisper isn't installed for `python3` itself.
|
||||
Without faster-whisper at all, `bun test tests/transcribe.test.ts` still runs
|
||||
the real `server-py/sidecar.py` against a stub (`tests/fixtures/stub_fw`) to
|
||||
check the mode routing, the one-model cache and cancel between segments.
|
||||
With `FW_INTEGRATION=1` it also pushes the whole 15-min `en_alien` clip
|
||||
(`tiny.en`) through `POST /api/transcribe` — heartbeat lines before the first
|
||||
burst, segments in bursts — and cancels a batched run after its first burst.
|
||||
|
||||
- [ ] The `real faster-whisper sidecar` describe block passes.
|
||||
|
||||
@@ -52,7 +55,8 @@ check the mode routing, the one-model cache and cancel between segments.
|
||||
batched pipeline (D-WS5a) they arrive in bursts — one burst per 8
|
||||
speech chunks (≈ up to 4 min of audio) — with blank heartbeat lines
|
||||
every 5 s in between; on a clip under ~30 s of speech, everything
|
||||
arrives as a single segment at the end.
|
||||
arrives as a single segment at the end. Each segment covers one VAD
|
||||
speech chunk (up to 30 s), not one sentence — expected (D-WS5a).
|
||||
- [ ] Cancelling a long server transcription (abort / page refresh) stops
|
||||
the sidecar within one burst; the next request then starts normally.
|
||||
- [ ] Both transcripts agree in content (minor punctuation/casing drift
|
||||
|
||||
@@ -624,4 +624,91 @@ describe.skipIf(!process.env.FW_INTEGRATION)("real faster-whisper sidecar", () =
|
||||
},
|
||||
{ timeout: 300_000 },
|
||||
);
|
||||
|
||||
// The whole 15-min en_alien clip (≈ 17 VAD chunks ⇒ 3 batches) as uploaded PCM.
|
||||
async function wholeAlienPcm(): Promise<string> {
|
||||
const clip = join(import.meta.dir, "audio_files/en_alien.opus");
|
||||
const pcm = await Bun.$`ffmpeg -v error -i ${clip} -f f32le -ac 1 -ar 16000 -`.arrayBuffer();
|
||||
const audioPath = join(await makeTempDir(), "alien-full.f32");
|
||||
await Bun.write(audioPath, pcm);
|
||||
return audioPath;
|
||||
}
|
||||
|
||||
test(
|
||||
"a long file streams in bursts behind heartbeats over HTTP (D-021, D-WS5a)",
|
||||
async () => {
|
||||
const handlers = createTranscribeHandlers({
|
||||
sidecar: createSidecar(),
|
||||
getSessionUser: sessionFromCookie,
|
||||
tempDir: await makeTempDir(),
|
||||
heartbeatIntervalMs: 100,
|
||||
});
|
||||
const body = await Bun.file(await wholeAlienPcm()).arrayBuffer();
|
||||
const response = await handlers.post(
|
||||
postRequest("?model=onnx-community/whisper-tiny.en&language=en", body, {
|
||||
cookie: "sid=ok",
|
||||
}),
|
||||
);
|
||||
expect(response.status).toBe(200);
|
||||
|
||||
// Timestamp every line as it arrives.
|
||||
const started = performance.now();
|
||||
const lines: { at: number; line: string }[] = [];
|
||||
const decoder = new TextDecoder();
|
||||
let buffered = "";
|
||||
for await (const chunk of response.body!) {
|
||||
buffered += decoder.decode(chunk, { stream: true });
|
||||
let newline: number;
|
||||
while ((newline = buffered.indexOf("\n")) >= 0) {
|
||||
lines.push({ at: performance.now() - started, line: buffered.slice(0, newline) });
|
||||
buffered = buffered.slice(newline + 1);
|
||||
}
|
||||
}
|
||||
const segmentLines = lines.filter(({ line }) => line.includes('"type":"segment"'));
|
||||
const firstSegment = lines.findIndex(({ line }) => line.includes('"type":"segment"'));
|
||||
// VAD over 15 min of audio + the first batch: heartbeats fill the gap.
|
||||
expect(lines.slice(0, firstSegment).filter(({ line }) => line === "").length).toBeGreaterThan(0);
|
||||
expect(segmentLines.length).toBeGreaterThan(3);
|
||||
// Streamed, not one blob at the end: later bursts arrive measurably later.
|
||||
expect(segmentLines.at(-1)!.at - segmentLines[0]!.at).toBeGreaterThan(100);
|
||||
expect(lines.at(-1)!.line).toContain('"type":"result"');
|
||||
console.log(
|
||||
"[D-WS5a] segment arrival (ms):",
|
||||
segmentLines.map(({ at }) => Math.round(at)).join(" "),
|
||||
);
|
||||
},
|
||||
{ timeout: 600_000 },
|
||||
);
|
||||
|
||||
test(
|
||||
"cancelling a batched file run stops it early and the next request still works",
|
||||
async () => {
|
||||
const sidecar = createSidecar();
|
||||
const audioPath = await wholeAlienPcm();
|
||||
const controller = new AbortController();
|
||||
const cancelledRun: SidecarSegment[] = [];
|
||||
const outcome = await sidecar.transcribe(
|
||||
{ model: "tiny.en", audioPath, language: "en" },
|
||||
(segment) => {
|
||||
cancelledRun.push(segment);
|
||||
controller.abort();
|
||||
},
|
||||
controller.signal,
|
||||
);
|
||||
expect(outcome.cancelled).toBe(true);
|
||||
// At most the first burst (8 chunks) got out; the full clip is ≈ 17 chunks.
|
||||
expect(cancelledRun.length).toBeGreaterThan(0);
|
||||
expect(cancelledRun.length).toBeLessThanOrEqual(8);
|
||||
expect(cancelledRun.at(-1)!.end).toBeLessThan(800);
|
||||
|
||||
const again: SidecarSegment[] = [];
|
||||
const next = await sidecar.transcribe(
|
||||
{ model: "tiny.en", audioPath, language: "en" },
|
||||
(segment) => again.push(segment),
|
||||
);
|
||||
expect(next.cancelled).toBe(false);
|
||||
expect(again.length).toBeGreaterThan(cancelledRun.length);
|
||||
},
|
||||
{ timeout: 600_000 },
|
||||
);
|
||||
});
|
||||
Reference in new issue
Block a user