Reject task=translate on the server for models that cannot translate (D-044)
The UI already hid it; a direct API call to POST /api/transcribe or the live WebSocket could still get the source language back labelled as a translation. Co-Authored-By: Claude Opus 5.5 <[email protected]>
This commit is contained in:
co-authored by
Claude Opus 5.5
parent
96d100bd45
commit
3583d60847
@@ -427,8 +427,9 @@ Found while building transcript translation (D-045), 2026-10-29:
|
||||
every row of a padded batch, so translation runs one sentence per call
|
||||
(~4× slower than batch 8 would be). Re-check after the 4.3 upgrade with
|
||||
`bun scripts/bench-translate.ts` ("output identical" across batch sizes).
|
||||
- [ ] The server API still accepts `task: "translate"` with large-v3-turbo
|
||||
(only the UI hides it, D-044).
|
||||
- [x] The server API still accepts `task: "translate"` with large-v3-turbo
|
||||
(only the UI hides it, D-044). — Fixed: `POST /api/transcribe` and the
|
||||
live WebSocket answer 400 via `translateNotSupported` (src/server/transcribe.ts).
|
||||
|
||||
Found in spec 001 workstream 6 (D-046), open:
|
||||
- [ ] Transformers.js 4.2 has no beam search: `num_beams > 1` in a model's
|
||||
|
||||
+9
-1
@@ -7,7 +7,13 @@ import { ortAliasPlugin } from "../scripts/ortAlias.ts";
|
||||
import { injectPrecacheManifest } from "./pwa/precache.ts";
|
||||
import { auth } from "./server/auth.ts";
|
||||
import { AUTH_ENABLED } from "./server/flags.ts";
|
||||
import { MAX_UPLOAD_BYTES, parseLanguageAndTask, sidecar, transcribe } from "./server/transcribe.ts";
|
||||
import {
|
||||
MAX_UPLOAD_BYTES,
|
||||
parseLanguageAndTask,
|
||||
sidecar,
|
||||
transcribe,
|
||||
translateNotSupported,
|
||||
} from "./server/transcribe.ts";
|
||||
import { liveWebSocket } from "./server/live.ts";
|
||||
import { getFasterWhisperModel } from "./lib/models.ts";
|
||||
|
||||
@@ -140,6 +146,8 @@ async function handleApiRequest(request: Request, server: any): Promise<Response
|
||||
// because this entry point is a WebSocket, not JSON over HTTP).
|
||||
const options = parseLanguageAndTask(url);
|
||||
if (typeof options === "string") return new Response(options, { status: 400 });
|
||||
const translateError = translateNotSupported(modelId, options.task);
|
||||
if (translateError) return new Response(translateError, { status: 400 });
|
||||
// Fail fast like the HTTP route (D-013 "degrade gracefully") instead of
|
||||
// opening a socket that can only report unavailability once the first
|
||||
// segment reaches the sidecar.
|
||||
|
||||
@@ -27,7 +27,7 @@ import { tmpdir } from "node:os";
|
||||
import { join } from "node:path";
|
||||
import type { TranscribeStreamLine } from "../lib/engine";
|
||||
import { HINT_MAX_CHARS, normalizeHint } from "../lib/hint";
|
||||
import { getFasterWhisperModel } from "../lib/models";
|
||||
import { getFasterWhisperModel, getModel } from "../lib/models";
|
||||
import type { TranscriptChunk, WhisperTask } from "../worker/protocol";
|
||||
import { getSessionUser as appGetSessionUser, type SessionUser } from "./auth.ts";
|
||||
|
||||
@@ -377,6 +377,17 @@ export function parseLanguageAndTask(
|
||||
return { language, task: rawTask, ...(prompt && { prompt }) };
|
||||
}
|
||||
|
||||
/**
|
||||
* Whisper's translate task only on models trained for it (D-044): the UI
|
||||
* hides it elsewhere, and the server rejects it the same way so a direct
|
||||
* API call can't get the source language back labelled as a translation.
|
||||
* Returns the 400 error text, or undefined when the request is fine.
|
||||
*/
|
||||
export function translateNotSupported(modelId: string, task: WhisperTask): string | undefined {
|
||||
if (task !== "translate" || getModel(modelId)?.canTranslate) return undefined;
|
||||
return `model ${modelId} cannot translate; use task=transcribe`;
|
||||
}
|
||||
|
||||
/** Parse + validate query params; a string is the 400 error text. */
|
||||
function parseParams(url: URL): TranscribeParams | string {
|
||||
const modelId = url.searchParams.get("model");
|
||||
@@ -387,6 +398,8 @@ function parseParams(url: URL): TranscribeParams | string {
|
||||
|
||||
const options = parseLanguageAndTask(url);
|
||||
if (typeof options === "string") return options;
|
||||
const translateError = translateNotSupported(modelId, options.task);
|
||||
if (translateError) return translateError;
|
||||
|
||||
return { modelId, fasterWhisperModel, ...options };
|
||||
}
|
||||
|
||||
@@ -6,6 +6,7 @@ import {
|
||||
createSidecar,
|
||||
createTranscribeHandlers,
|
||||
parseLanguageAndTask,
|
||||
translateNotSupported,
|
||||
type Sidecar,
|
||||
type SidecarSegment,
|
||||
type SidecarTranscribeRequest,
|
||||
@@ -262,6 +263,41 @@ describe("POST /api/transcribe", () => {
|
||||
}
|
||||
});
|
||||
|
||||
test("400 for translate on a model not trained for it; translate still works where it is (D-044)", async () => {
|
||||
const sidecar = fakeSidecar();
|
||||
const handlers = createTranscribeHandlers({
|
||||
sidecar,
|
||||
getSessionUser: sessionFromCookie,
|
||||
tempDir: await makeTempDir(),
|
||||
});
|
||||
for (const model of [
|
||||
"onnx-community/whisper-large-v3-turbo",
|
||||
"onnx-community/whisper-small.en",
|
||||
]) {
|
||||
const response = await handlers.post(
|
||||
postRequest(`?model=${model}&task=translate`, pcmBody([0.1]), { cookie: "sid=ok" }),
|
||||
);
|
||||
expect(response.status).toBe(400);
|
||||
expect(await response.text()).toContain("cannot translate");
|
||||
}
|
||||
expect(sidecar.requests).toHaveLength(0);
|
||||
|
||||
const ok = await handlers.post(
|
||||
postRequest("?model=onnx-community/whisper-small&task=translate", pcmBody([0.1]), {
|
||||
cookie: "sid=ok",
|
||||
}),
|
||||
);
|
||||
expect(ok.status).toBe(200);
|
||||
});
|
||||
|
||||
test("translateNotSupported: the shared check for the HTTP and live routes", () => {
|
||||
expect(translateNotSupported("onnx-community/whisper-large-v3-turbo", "translate")).toContain(
|
||||
"cannot translate",
|
||||
);
|
||||
expect(translateNotSupported("onnx-community/whisper-large-v3-turbo", "transcribe")).toBeUndefined();
|
||||
expect(translateNotSupported("onnx-community/whisper-small", "translate")).toBeUndefined();
|
||||
});
|
||||
|
||||
test("503 with the reason when the sidecar is unavailable", async () => {
|
||||
const sidecar = fakeSidecar({ available: false, reason: "faster-whisper is not installed" });
|
||||
const handlers = createTranscribeHandlers({
|
||||
|
||||
Reference in New Issue
Block a user