mirror of
https://github.com/modelstudioai/cli.git
synced 2026-09-14 19:49:23 +08:00
feat(speech): 支持同步Flash ASR模型和异步文件转录模型
- 新增同步Flash ASR模型请求流程,支持单音频文件识别 - 实现了对同步Flash模型不支持异步标志及参数的限制校验 - 异步文件转录模型支持单文件URL上传和语言参数细化 - 优化异步和同步鉴权域显示,丰富根帮助和分组帮助提示 - speech recognize增加dry-run测试覆盖多种识别场景 - free-tier自动停用功能优化,改用统一轮询函数处理批量请求 - 统一轮询逻辑,支持console和telemetry接口的异步任务完成判定 - 规范输出格式和错误提示,增强用户调试体验 - 版本升级到1.14.3,更新示例参数和模型ID引用
This commit is contained in:
@@ -1,6 +1,6 @@
|
||||
{
|
||||
"name": "bailian-cli-core",
|
||||
"version": "1.14.2",
|
||||
"version": "1.14.3",
|
||||
"description": "Core SDK for bailian-cli. See https://www.npmjs.com/package/bailian-cli for usage.",
|
||||
"homepage": "https://bailian.console.aliyun.com/cli",
|
||||
"bugs": {
|
||||
|
||||
@@ -0,0 +1,327 @@
|
||||
import { imageSyncPath, speechRecognizePath } from "./endpoints.ts";
|
||||
|
||||
/**
|
||||
* DashScope ASR APIs differ by model family:
|
||||
*
|
||||
* - async file transcription (`.../audio/asr/transcription`):
|
||||
* fun-asr*, paraformer* (non-realtime), *-filetrans, sensevoice*
|
||||
* language via `parameters.language_hints`
|
||||
* - sync multimodal (`.../aigc/multimodal-generation/generation`):
|
||||
* - qwen3: `{ content: [{ audio }] }` + optional `asr_options.language`
|
||||
* (qwen3-asr-flash*)
|
||||
* - input-audio: `{ type: input_audio, input_audio.data }` +
|
||||
* `format`/`sample_rate` + optional `language_hints`
|
||||
* (fun-asr-flash*, qwen-audio-*-asr-flash*)
|
||||
* - realtime / streaming: WebSocket — not supported by `speech recognize`
|
||||
*/
|
||||
|
||||
export type AsrApiKind = "async-filetrans" | "sync-flash" | "unsupported";
|
||||
|
||||
/** Sync-flash request body shape differs by Flash protocol family. */
|
||||
export type AsrFlashFamily = "qwen3" | "input-audio";
|
||||
|
||||
export interface AsrApiRoute {
|
||||
kind: AsrApiKind;
|
||||
path: string;
|
||||
/** True when the call is synchronous (no X-DashScope-Async / task poll). */
|
||||
useSync: boolean;
|
||||
/**
|
||||
* Async transcription request input style.
|
||||
* - `file_urls`: classic async models (fun-asr / paraformer / qwen-audio filetrans...)
|
||||
* - `file_url`: qwen3-asr-flash-filetrans family
|
||||
*/
|
||||
asyncInputStyle?: "file_urls" | "file_url";
|
||||
/**
|
||||
* Async transcription language field style.
|
||||
* - `language_hints`: fun-asr / paraformer / qwen-audio filetrans...
|
||||
* - `language`: qwen3-asr-flash-filetrans*
|
||||
*/
|
||||
asyncLanguageStyle?: "language_hints" | "language";
|
||||
flashFamily?: AsrFlashFamily;
|
||||
/** Human-readable reason when kind is unsupported. */
|
||||
unsupportedReason?: string;
|
||||
}
|
||||
|
||||
function isRealtimeOrStreaming(model: string): boolean {
|
||||
return /realtime|streaming/i.test(model);
|
||||
}
|
||||
|
||||
function isFiletransModel(model: string): boolean {
|
||||
return /filetrans/i.test(model);
|
||||
}
|
||||
|
||||
function isQwen3FiletransModel(model: string): boolean {
|
||||
return /^qwen3-asr-flash-filetrans(?:-|$)/i.test(model);
|
||||
}
|
||||
|
||||
const INPUT_AUDIO_FLASH_PREFIXES = ["fun-asr-flash", "qwen-audio"] as const;
|
||||
|
||||
/**
|
||||
* Fun-ASR-Flash / Qwen-Audio-*-ASR-Flash share the input_audio + format protocol.
|
||||
* Examples: fun-asr-flash-2026-06-15, qwen-audio-3.0-asr-flash
|
||||
*/
|
||||
function isInputAudioFlashModel(model: string): boolean {
|
||||
if (isRealtimeOrStreaming(model) || isFiletransModel(model)) return false;
|
||||
if (model.startsWith(INPUT_AUDIO_FLASH_PREFIXES[0])) return true;
|
||||
if (model.startsWith(INPUT_AUDIO_FLASH_PREFIXES[1]) && /asr-flash/i.test(model)) return true;
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Qwen3-ASR-Flash sync models use content.audio + asr_options.
|
||||
* Examples: qwen3-asr-flash, qwen3-asr-flash-2025-09-08, qwen3-asr-flash-us
|
||||
*/
|
||||
function isQwen3AsrFlashModel(model: string): boolean {
|
||||
if (!/^qwen3-asr-flash(?:-|$)/i.test(model)) return false;
|
||||
if (isFiletransModel(model) || isRealtimeOrStreaming(model)) return false;
|
||||
if (isInputAudioFlashModel(model)) return false;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Resolve which DashScope ASR API a model should use for file recognition.
|
||||
* Unknown models default to async-filetrans (preserves existing CLI behavior).
|
||||
*/
|
||||
export function resolveAsrApi(model: string): AsrApiRoute {
|
||||
if (isRealtimeOrStreaming(model)) {
|
||||
return {
|
||||
kind: "unsupported",
|
||||
path: "",
|
||||
useSync: false,
|
||||
unsupportedReason:
|
||||
`Model "${model}" is a realtime/streaming ASR model and requires a WebSocket API. ` +
|
||||
`Use an async filetrans model (e.g. fun-asr, qwen3-asr-flash-filetrans) or a sync flash model ` +
|
||||
`(e.g. qwen3-asr-flash, qwen-audio-3.0-asr-flash) with this command.`,
|
||||
};
|
||||
}
|
||||
|
||||
if (isFiletransModel(model)) {
|
||||
const isQwen3Filetrans = isQwen3FiletransModel(model);
|
||||
return {
|
||||
kind: "async-filetrans",
|
||||
path: speechRecognizePath(),
|
||||
useSync: false,
|
||||
asyncInputStyle: isQwen3Filetrans ? "file_url" : "file_urls",
|
||||
asyncLanguageStyle: isQwen3Filetrans ? "language" : "language_hints",
|
||||
};
|
||||
}
|
||||
|
||||
if (isInputAudioFlashModel(model)) {
|
||||
return {
|
||||
kind: "sync-flash",
|
||||
path: imageSyncPath(),
|
||||
useSync: true,
|
||||
flashFamily: "input-audio",
|
||||
};
|
||||
}
|
||||
|
||||
if (isQwen3AsrFlashModel(model)) {
|
||||
return {
|
||||
kind: "sync-flash",
|
||||
path: imageSyncPath(),
|
||||
useSync: true,
|
||||
flashFamily: "qwen3",
|
||||
};
|
||||
}
|
||||
|
||||
// fun-asr / paraformer / sensevoice / unknown → keep legacy async path
|
||||
return {
|
||||
kind: "async-filetrans",
|
||||
path: speechRecognizePath(),
|
||||
useSync: false,
|
||||
asyncInputStyle: "file_urls",
|
||||
asyncLanguageStyle: "language_hints",
|
||||
};
|
||||
}
|
||||
|
||||
/** Infer audio container hint for input-audio Flash `parameters.format`. */
|
||||
export function inferAudioFormatHint(audioUrl: string): string {
|
||||
// data URI: data:audio/mpeg;base64,... → mp3; data:audio/x-wav;... → wav
|
||||
const dataType = /^data:audio\/([^;,]+)/i.exec(audioUrl)?.[1]?.toLowerCase();
|
||||
if (dataType) {
|
||||
if (dataType === "mpeg") return "mp3";
|
||||
if (dataType === "x-wav" || dataType === "wave") return "wav";
|
||||
return dataType;
|
||||
}
|
||||
|
||||
const pathPart = audioUrl.split(/[?#]/, 1)[0] ?? audioUrl;
|
||||
const match = pathPart.match(/\.([a-zA-Z0-9]+)$/);
|
||||
const extension = match?.[1]?.toLowerCase();
|
||||
if (!extension) return "wav";
|
||||
if (extension === "mpeg") return "mp3";
|
||||
return extension;
|
||||
}
|
||||
|
||||
export interface BuildAsrFlashRequestOpts {
|
||||
model: string;
|
||||
audioUrl: string;
|
||||
language?: string;
|
||||
/** Precompiled hotword vocabulary ID; supported for input-audio Flash (fun-asr-flash* / qwen-audio-*-asr-flash). */
|
||||
vocabularyId?: string;
|
||||
flashFamily: AsrFlashFamily;
|
||||
}
|
||||
|
||||
/**
|
||||
* Build language fields for async ASR routes.
|
||||
* qwen3-asr-flash-filetrans* → `language`; other async models → `language_hints`.
|
||||
*/
|
||||
export function buildAsyncAsrLanguageFields(
|
||||
languageStyle: "language_hints" | "language",
|
||||
language?: string,
|
||||
): { language_hints?: string[]; language?: string } {
|
||||
if (!language) return {};
|
||||
if (languageStyle === "language") {
|
||||
return { language };
|
||||
}
|
||||
return { language_hints: [language] };
|
||||
}
|
||||
|
||||
/** Build a sync multimodal ASR request body for Flash models. */
|
||||
export function buildAsrFlashRequest(opts: BuildAsrFlashRequestOpts): Record<string, unknown> {
|
||||
const { model, audioUrl, language, vocabularyId, flashFamily } = opts;
|
||||
|
||||
if (flashFamily === "input-audio") {
|
||||
// Match official Qwen-Audio / Fun-ASR-Flash docs: language_hints + vocabulary_id
|
||||
const parameters: Record<string, unknown> = {
|
||||
format: inferAudioFormatHint(audioUrl),
|
||||
sample_rate: "16000",
|
||||
};
|
||||
if (language) {
|
||||
parameters.language_hints = [language];
|
||||
}
|
||||
if (vocabularyId) {
|
||||
parameters.vocabulary_id = vocabularyId;
|
||||
}
|
||||
return {
|
||||
model,
|
||||
input: {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [
|
||||
{
|
||||
type: "input_audio",
|
||||
input_audio: { data: audioUrl },
|
||||
},
|
||||
],
|
||||
},
|
||||
],
|
||||
},
|
||||
parameters,
|
||||
};
|
||||
}
|
||||
|
||||
const asrOptions: Record<string, unknown> = {};
|
||||
if (language) {
|
||||
asrOptions.language = language;
|
||||
}
|
||||
|
||||
const parameters: Record<string, unknown> = {};
|
||||
if (Object.keys(asrOptions).length > 0) {
|
||||
parameters.asr_options = asrOptions;
|
||||
}
|
||||
|
||||
const body: Record<string, unknown> = {
|
||||
model,
|
||||
input: {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [{ audio: audioUrl }],
|
||||
},
|
||||
],
|
||||
},
|
||||
};
|
||||
if (Object.keys(parameters).length > 0) {
|
||||
body.parameters = parameters;
|
||||
}
|
||||
return body;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extract recognition text from a sync Flash ASR response.
|
||||
* Qwen3 uses choices[].message.content; input-audio Flash uses output.text /
|
||||
* output.sentence.text / output.output.sentence.text.
|
||||
*/
|
||||
export function extractAsrFlashText(
|
||||
response: Record<string, unknown>,
|
||||
flashFamily: AsrFlashFamily,
|
||||
): string {
|
||||
const output = response.output as Record<string, unknown> | undefined;
|
||||
if (!output) return "";
|
||||
|
||||
if (flashFamily === "input-audio") {
|
||||
if (typeof output.text === "string" && output.text.length > 0) {
|
||||
return output.text;
|
||||
}
|
||||
const topSentence = output.sentence as Record<string, unknown> | undefined;
|
||||
if (typeof topSentence?.text === "string" && topSentence.text.length > 0) {
|
||||
return topSentence.text;
|
||||
}
|
||||
const nested = output.output as Record<string, unknown> | undefined;
|
||||
const nestedSentence = nested?.sentence as Record<string, unknown> | undefined;
|
||||
if (typeof nestedSentence?.text === "string") {
|
||||
return nestedSentence.text;
|
||||
}
|
||||
return "";
|
||||
}
|
||||
|
||||
const choices = output.choices as Array<Record<string, unknown>> | undefined;
|
||||
if (!choices?.length) return "";
|
||||
|
||||
const texts: string[] = [];
|
||||
for (const choice of choices) {
|
||||
const message = choice.message as Record<string, unknown> | undefined;
|
||||
if (!message) continue;
|
||||
const content = message.content;
|
||||
if (typeof content === "string") {
|
||||
texts.push(content);
|
||||
continue;
|
||||
}
|
||||
if (!Array.isArray(content)) continue;
|
||||
for (const item of content) {
|
||||
if (typeof item === "string") {
|
||||
texts.push(item);
|
||||
continue;
|
||||
}
|
||||
if (item && typeof item === "object") {
|
||||
const record = item as Record<string, unknown>;
|
||||
if (typeof record.text === "string") {
|
||||
texts.push(record.text);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return texts.join("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalize async ASR task transcription items:
|
||||
* - classic models: `output.results[]`
|
||||
* - qwen3-asr-flash-filetrans*: `output.result.transcription_url`
|
||||
*/
|
||||
export function collectAsrTranscriptionItems(output: {
|
||||
results?: Array<{
|
||||
file_url?: string;
|
||||
transcription_url?: string;
|
||||
subtask_status?: string;
|
||||
code?: string;
|
||||
message?: string;
|
||||
}>;
|
||||
result?: { transcription_url?: string };
|
||||
}): Array<{
|
||||
file_url?: string;
|
||||
transcription_url?: string;
|
||||
subtask_status?: string;
|
||||
code?: string;
|
||||
message?: string;
|
||||
}> {
|
||||
if (output.results && output.results.length > 0) {
|
||||
return output.results;
|
||||
}
|
||||
const transcriptionUrl = output.result?.transcription_url;
|
||||
if (typeof transcriptionUrl === "string" && transcriptionUrl.length > 0) {
|
||||
return [{ transcription_url: transcriptionUrl, subtask_status: "SUCCEEDED" }];
|
||||
}
|
||||
return [];
|
||||
}
|
||||
@@ -36,6 +36,18 @@ export {
|
||||
type ImageInputStyle,
|
||||
type ImageSizeProfile,
|
||||
} from "./image-routes.ts";
|
||||
export {
|
||||
buildAsrFlashRequest,
|
||||
buildAsyncAsrLanguageFields,
|
||||
collectAsrTranscriptionItems,
|
||||
extractAsrFlashText,
|
||||
inferAudioFormatHint,
|
||||
resolveAsrApi,
|
||||
type AsrApiKind,
|
||||
type AsrApiRoute,
|
||||
type AsrFlashFamily,
|
||||
type BuildAsrFlashRequestOpts,
|
||||
} from "./asr-routes.ts";
|
||||
export {
|
||||
CHANNEL,
|
||||
OPEN_API_SOURCE,
|
||||
|
||||
@@ -58,7 +58,7 @@ export function effectiveConsoleGatewayConfig(
|
||||
}
|
||||
|
||||
export interface ConsoleGatewayRequest {
|
||||
/** Console API name, e.g. zeldaEasy.broadscope-bailian.freeTrial.queryFreeTierQuota */
|
||||
/** Console API name, e.g. zeldaEasy.bailian-commerce.freeTrial.queryFreeTierQuota */
|
||||
api: string;
|
||||
data: Record<string, unknown>;
|
||||
}
|
||||
|
||||
@@ -536,33 +536,46 @@ export interface DashScopeTTSStreamChunk {
|
||||
export interface DashScopeASRRequest {
|
||||
model: string;
|
||||
input: {
|
||||
file_urls: string[];
|
||||
file_urls?: string[];
|
||||
file_url?: string;
|
||||
};
|
||||
parameters?: {
|
||||
channel_id?: number[];
|
||||
/** Classic async models (fun-asr / paraformer / qwen-audio filetrans, etc.) */
|
||||
language_hints?: string[];
|
||||
/** qwen3-asr-flash-filetrans* uses singular `language` */
|
||||
language?: string;
|
||||
diarization_enabled?: boolean;
|
||||
speaker_count?: number;
|
||||
vocabulary_id?: string;
|
||||
};
|
||||
}
|
||||
|
||||
export interface DashScopeASRTranscriptionItem {
|
||||
file_url?: string;
|
||||
transcription_url?: string;
|
||||
subtask_status?: string;
|
||||
code?: string;
|
||||
message?: string;
|
||||
}
|
||||
|
||||
export interface DashScopeASRTaskResult {
|
||||
output: {
|
||||
task_id: string;
|
||||
task_status: "PENDING" | "RUNNING" | "SUCCEEDED" | "FAILED" | "UNKNOWN";
|
||||
results?: Array<{
|
||||
file_url?: string;
|
||||
/** Multi-file async results (fun-asr / paraformer / qwen-audio filetrans, etc.) */
|
||||
results?: DashScopeASRTranscriptionItem[];
|
||||
/** Singular result returned by qwen3-asr-flash-filetrans* on success */
|
||||
result?: {
|
||||
transcription_url?: string;
|
||||
subtask_status?: string;
|
||||
code?: string;
|
||||
message?: string;
|
||||
}>;
|
||||
};
|
||||
task_metrics?: {
|
||||
TOTAL: number;
|
||||
SUCCEEDED: number;
|
||||
FAILED: number;
|
||||
};
|
||||
code?: string;
|
||||
message?: string;
|
||||
};
|
||||
usage?: Record<string, unknown>;
|
||||
request_id: string;
|
||||
|
||||
@@ -48,6 +48,7 @@ export type {
|
||||
ChatTool,
|
||||
DashScopeASRRequest,
|
||||
DashScopeASRTaskResult,
|
||||
DashScopeASRTranscriptionItem,
|
||||
DashScopeAsyncResponse,
|
||||
DashScopeImageRequest,
|
||||
DashScopeImageSyncResponse,
|
||||
|
||||
@@ -0,0 +1,213 @@
|
||||
import { expect, test } from "vite-plus/test";
|
||||
import {
|
||||
buildAsrFlashRequest,
|
||||
buildAsyncAsrLanguageFields,
|
||||
collectAsrTranscriptionItems,
|
||||
extractAsrFlashText,
|
||||
inferAudioFormatHint,
|
||||
resolveAsrApi,
|
||||
} from "../src/client/asr-routes.ts";
|
||||
|
||||
test("resolveAsrApi routes model families correctly", () => {
|
||||
const cases = [
|
||||
{
|
||||
model: "fun-asr",
|
||||
expected: {
|
||||
kind: "async-filetrans",
|
||||
useSync: false,
|
||||
path: "/api/v1/services/audio/asr/transcription",
|
||||
asyncInputStyle: "file_urls",
|
||||
},
|
||||
},
|
||||
{
|
||||
model: "qwen3-asr-flash-filetrans-2025-11-17",
|
||||
expected: {
|
||||
kind: "async-filetrans",
|
||||
useSync: false,
|
||||
path: "/api/v1/services/audio/asr/transcription",
|
||||
asyncInputStyle: "file_url",
|
||||
asyncLanguageStyle: "language",
|
||||
},
|
||||
},
|
||||
{
|
||||
model: "qwen-audio-3.0-asr-flash-filetrans",
|
||||
expected: {
|
||||
kind: "async-filetrans",
|
||||
useSync: false,
|
||||
asyncInputStyle: "file_urls",
|
||||
asyncLanguageStyle: "language_hints",
|
||||
},
|
||||
},
|
||||
{
|
||||
model: "qwen3-asr-flash-us",
|
||||
expected: {
|
||||
kind: "sync-flash",
|
||||
useSync: true,
|
||||
flashFamily: "qwen3",
|
||||
path: "/api/v1/services/aigc/multimodal-generation/generation",
|
||||
},
|
||||
},
|
||||
{
|
||||
model: "qwen-audio-3.0-asr-flash",
|
||||
expected: {
|
||||
kind: "sync-flash",
|
||||
useSync: true,
|
||||
flashFamily: "input-audio",
|
||||
},
|
||||
},
|
||||
{
|
||||
model: "qwen3-asr-flash-realtime",
|
||||
expected: {
|
||||
kind: "unsupported",
|
||||
},
|
||||
},
|
||||
{
|
||||
model: "foo-asr-flash",
|
||||
expected: {
|
||||
kind: "async-filetrans",
|
||||
useSync: false,
|
||||
path: "/api/v1/services/audio/asr/transcription",
|
||||
asyncInputStyle: "file_urls",
|
||||
},
|
||||
},
|
||||
] as const;
|
||||
|
||||
for (const { model, expected } of cases) {
|
||||
const route = resolveAsrApi(model);
|
||||
expect(route, model).toMatchObject(expected);
|
||||
if (expected.kind === "unsupported") {
|
||||
expect(route.unsupportedReason, model).toMatch(/realtime|streaming|WebSocket/i);
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
test("unknown models default to async-filetrans for backward compatibility", () => {
|
||||
expect(resolveAsrApi("custom-asr-model")).toMatchObject({
|
||||
kind: "async-filetrans",
|
||||
useSync: false,
|
||||
});
|
||||
});
|
||||
|
||||
test("inferAudioFormatHint reads extension from url", () => {
|
||||
expect(inferAudioFormatHint("https://example.com/a.mp3")).toBe("mp3");
|
||||
expect(inferAudioFormatHint("oss://bucket/path/file.WAV")).toBe("wav");
|
||||
expect(inferAudioFormatHint("https://example.com/a.mpeg?x=1")).toBe("mp3");
|
||||
expect(inferAudioFormatHint("https://example.com/noext")).toBe("wav");
|
||||
expect(inferAudioFormatHint("data:audio/mpeg;base64,AAA")).toBe("mp3");
|
||||
expect(inferAudioFormatHint("data:audio/x-wav;base64,AAA")).toBe("wav");
|
||||
expect(inferAudioFormatHint("data:audio/ogg;codecs=opus;base64,AAA")).toBe("ogg");
|
||||
});
|
||||
|
||||
test("buildAsrFlashRequest shapes qwen3 and input-audio bodies", () => {
|
||||
expect(
|
||||
buildAsrFlashRequest({
|
||||
model: "qwen3-asr-flash",
|
||||
audioUrl: "https://example.com/a.mp3",
|
||||
language: "en",
|
||||
flashFamily: "qwen3",
|
||||
}),
|
||||
).toEqual({
|
||||
model: "qwen3-asr-flash",
|
||||
input: {
|
||||
messages: [{ role: "user", content: [{ audio: "https://example.com/a.mp3" }] }],
|
||||
},
|
||||
parameters: { asr_options: { language: "en" } },
|
||||
});
|
||||
|
||||
expect(
|
||||
buildAsrFlashRequest({
|
||||
model: "qwen-audio-3.0-asr-flash",
|
||||
audioUrl: "https://example.com/a.wav",
|
||||
language: "en",
|
||||
vocabularyId: "vocab-abc",
|
||||
flashFamily: "input-audio",
|
||||
}),
|
||||
).toEqual({
|
||||
model: "qwen-audio-3.0-asr-flash",
|
||||
input: {
|
||||
messages: [
|
||||
{
|
||||
role: "user",
|
||||
content: [{ type: "input_audio", input_audio: { data: "https://example.com/a.wav" } }],
|
||||
},
|
||||
],
|
||||
},
|
||||
parameters: {
|
||||
format: "wav",
|
||||
sample_rate: "16000",
|
||||
language_hints: ["en"],
|
||||
vocabulary_id: "vocab-abc",
|
||||
},
|
||||
});
|
||||
});
|
||||
|
||||
test("buildAsyncAsrLanguageFields maps language by async style", () => {
|
||||
expect(buildAsyncAsrLanguageFields("language_hints", "zh")).toEqual({
|
||||
language_hints: ["zh"],
|
||||
});
|
||||
expect(buildAsyncAsrLanguageFields("language", "zh")).toEqual({ language: "zh" });
|
||||
expect(buildAsyncAsrLanguageFields("language", undefined)).toEqual({});
|
||||
});
|
||||
|
||||
test("extractAsrFlashText reads qwen3 choices and input-audio text fields", () => {
|
||||
expect(
|
||||
extractAsrFlashText(
|
||||
{
|
||||
output: {
|
||||
choices: [{ message: { content: [{ text: "你好" }] } }],
|
||||
},
|
||||
},
|
||||
"qwen3",
|
||||
),
|
||||
).toBe("你好");
|
||||
|
||||
expect(
|
||||
extractAsrFlashText(
|
||||
{
|
||||
output: {
|
||||
text: "Hello World",
|
||||
output: { sentence: { text: "ignored when text present" } },
|
||||
},
|
||||
},
|
||||
"input-audio",
|
||||
),
|
||||
).toBe("Hello World");
|
||||
|
||||
expect(
|
||||
extractAsrFlashText(
|
||||
{
|
||||
output: {
|
||||
sentence: { text: "top-level sentence" },
|
||||
},
|
||||
},
|
||||
"input-audio",
|
||||
),
|
||||
).toBe("top-level sentence");
|
||||
|
||||
expect(
|
||||
extractAsrFlashText(
|
||||
{
|
||||
output: {
|
||||
output: { sentence: { text: "nested sentence" } },
|
||||
},
|
||||
},
|
||||
"input-audio",
|
||||
),
|
||||
).toBe("nested sentence");
|
||||
});
|
||||
|
||||
test("collectAsrTranscriptionItems prefers results[] then singular result", () => {
|
||||
expect(
|
||||
collectAsrTranscriptionItems({
|
||||
results: [{ transcription_url: "https://example.com/a.json", file_url: "https://a.wav" }],
|
||||
}),
|
||||
).toEqual([{ transcription_url: "https://example.com/a.json", file_url: "https://a.wav" }]);
|
||||
|
||||
expect(
|
||||
collectAsrTranscriptionItems({
|
||||
result: { transcription_url: "https://example.com/qwen3.json" },
|
||||
}),
|
||||
).toEqual([{ transcription_url: "https://example.com/qwen3.json", subtask_status: "SUCCEEDED" }]);
|
||||
|
||||
expect(collectAsrTranscriptionItems({})).toEqual([]);
|
||||
});
|
||||
Reference in New Issue
Block a user