WIP: uncommitted work at pause (session limit)

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
Lucas WintherandClaude Opus 5.5 committed 2026-10-29 23:03:27 +01:00
1 parent f7d0525423
commit 826bcd2e49
2 files changed
+127 -3

No files matched your search

+111
View File
@@ -10,6 +10,13 @@ import {
type LiveTranscribeFn,
} from "../src/lib/live";
import { resolveDefaultDtype } from "../src/lib/models";
import {
buildPromptPrefix,
installPromptSupport,
PROMPT_PREFIX_KWARG,
type PromptableModel,
type PromptTokenizer,
} from "../src/worker/prompt";
import { wordErrorRate } from "./dsp";
/**
@@ -266,6 +273,10 @@ describe("live pipeline accuracy (real model, real recordings)", () => {
normalize((await transcribe(whole)).text),
);
// File-mode accuracy gate (spec 001): the upload flow is held to the
// same per-clip bar as live, so a file-mode regression fails here too.
expect(fileWer).toBeLessThanOrEqual(MAX_WER_BY_CLIP[clip.base] ?? MAX_WER);
// The live session, with the injected clock advancing with the audio
// fed so pass scheduling behaves as in real time.
let fedSamples = 0;
@@ -310,3 +321,103 @@ describe("live pipeline accuracy (real model, real recordings)", () => {
);
}
});
// ---------------------------------------------------------------------------
// Vocabulary hint (D-WS2a): the worker's real prompt code on real weights
// ---------------------------------------------------------------------------
const HINT = "Maxwell, Voices of the Void, Slenderman, RuneScape, UFO";
/** Decode the first `seconds` of a clip straight to 16 kHz (no mic path needed). */
async function decode16k(file: string, seconds?: number): Promise<Float32Array> {
const proc = Bun.spawn(
[
ffmpegPath as unknown as string,
...["-hide_banner", "-loglevel", "error", "-i", join(AUDIO_DIR, file)],
...(seconds ? ["-t", String(seconds)] : []),
...["-ac", "1", "-ar", String(RATE), "-f", "f32le", "-"],
],
{ stdout: "pipe", stderr: "pipe" },
);
const [bytes, exitCode] = await Promise.all([new Response(proc.stdout).arrayBuffer(), proc.exited]);
if (exitCode !== 0) throw new Error(`ffmpeg failed on ${file}`);
return new Float32Array(bytes);
}
/** One pass with the inference worker's options and (optionally) its prompt. */
async function transcribeWithHint(
audio: Float32Array,
hint: string,
language = "en",
): Promise<string> {
const asr = await loadAsr();
installPromptSupport(asr.model as unknown as PromptableModel);
const prefix = buildPromptPrefix(asr.tokenizer as unknown as PromptTokenizer, hint);
const output = await asr(audio, {
chunk_length_s: 30,
stride_length_s: 5,
return_timestamps: true,
max_new_tokens: 440,
language,
task: "transcribe",
...(prefix.length > 0 && { [PROMPT_PREFIX_KWARG]: prefix }),
} as Record<string, unknown>);
const single = (Array.isArray(output) ? output[0] : output) as { text?: string };
return single.text ?? "";
}
describe("vocabulary hint (real model, D-WS2a)", () => {
test.skipIf(!available)(
"a hint keeps the transcript intact, keeps the hinted names, and never leaks into the text",
async () => {
// The first 2 min of en_alien: four windows, "Voices of the Void" and
// several "Maxwell"s.
const audio = await decode16k("en_alien.opus", 120);
const plain = await transcribeWithHint(audio, "");
const hinted = await transcribeWithHint(audio, HINT);
const count = (text: string, term: string) => normalize(text).split(term).length - 1;
expect(count(hinted, "maxwell")).toBeGreaterThanOrEqual(count(plain, "maxwell"));
expect(count(hinted, "voices of the void")).toBeGreaterThanOrEqual(1);
// Not garbled: no repetition loop, no dropped windows (measured
// difference between the two runs ≈ punctuation-level).
expect(wordErrorRate(normalize(plain), normalize(hinted))).toBeLessThanOrEqual(0.15);
// The prompt itself is never part of the transcript.
expect(normalize(hinted).startsWith(normalize(HINT))).toBe(false);
expect(normalize(hinted)).not.toContain("slenderman runescape");
},
DOWNLOAD_BUDGET_MS + 120 * 2 * MS_PER_AUDIO_SECOND,
);
test.skipIf(!available)(
"a hint longer than Whisper's prompt budget is cut, not an error or a garbled window",
async () => {
const longHint = Array.from({ length: 200 }, (_, i) => `Project${i} Codename${i}`)
.join(", ")
.slice(0, 1000);
const reference = await Bun.file(join(AUDIO_DIR, "en_aiproblem.txt")).text();
const text = await transcribeWithHint(await decode16k("en_aiproblem.m4a"), longHint);
expect(wordErrorRate(normalize(reference), normalize(text))).toBeLessThanOrEqual(MAX_WER);
expect(text).not.toContain("Codename");
},
DOWNLOAD_BUDGET_MS + 60 * MS_PER_AUDIO_SECOND,
);
test.skipIf(!available)(
"a short, near-silent live window with a hint does not transcribe the hint",
async () => {
// 3 s of faint noise — what a live pass sees in a pause that the
// segmenter did not drop (its near-silence gate is far lower).
let seed = 1;
const noise = new Float32Array(3 * RATE).map(() => {
seed = (seed * 1103515245 + 12345) % 2 ** 31;
return (seed / 2 ** 31 - 0.5) * 0.01;
});
const text = normalize(await transcribeWithHint(noise, HINT));
for (const term of ["maxwell", "voices of the void", "slenderman", "runescape"]) {
expect(text).not.toContain(term);
}
},
DOWNLOAD_BUDGET_MS + 30 * MS_PER_AUDIO_SECOND,
);
});