Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ Once installed, just describe your task to your AI Agent — no need to assemble
| ------------------------ | --------------------------------------------------------------------------------- |
| Managed Agent | "Create a Managed Agent that can generate short-film storyboards and videos." |
| Image & video generation | "Generate an image of a cat in a spacesuit on Mars, then turn it into a video." |
| Speech recognition | "Transcribe this audio; if proper nouns are wrong, add hot words and try again." |
| Usage & quota | "Show my recent model usage, free-tier quota, and rate limits." |
| Model selection | "Recommend a model for image understanding and customer support." |
| About Bailian CLI | "Tell me what Bailian CLI can do for me, and suggest how to use it for my needs." |
Expand Down
1 change: 1 addition & 0 deletions README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ irm https://bailian.aliyun.com/cli/install.ps1 | iex
| ---------------- | ----------------------------------------------------------------------- |
| Managed Agent | “帮我创建一个能够生成短片分镜和视频的 Managed Agent。” |
| 图片和视频生成 | “生成一张穿着太空服的猫站在火星上的图片,再把它制作成一段视频。” |
| 语音识别 | “把这段音频转写成文字,专有名词识别不准的话帮我加上热词再试。” |
| 用量与额度 | “查看最近的模型用量、免费额度和限流情况。” |
| 模型选型 | “推荐一个适合图片理解和智能客服的模型。” |
| 了解 Bailian CLI | “介绍一下 Bailian CLI 能帮我完成哪些任务,并根据我的需求推荐使用方式。” |
Expand Down
1 change: 1 addition & 0 deletions packages/cli/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -115,6 +115,7 @@ Once installed, just describe your task to your AI Agent — no need to assemble
| ------------------------ | --------------------------------------------------------------------------------- |
| Managed Agent | "Create a Managed Agent that can generate short-film storyboards and videos." |
| Image & video generation | "Generate an image of a cat in a spacesuit on Mars, then turn it into a video." |
| Speech recognition | "Transcribe this audio; if proper nouns are wrong, add hot words and try again." |
| Usage & quota | "Show my recent model usage, free-tier quota, and rate limits." |
| Model selection | "Recommend a model for image understanding and customer support." |
| About Bailian CLI | "Tell me what Bailian CLI can do for me, and suggest how to use it for my needs." |
Expand Down
1 change: 1 addition & 0 deletions packages/cli/README.zh.md
Original file line number Diff line number Diff line change
Expand Up @@ -114,6 +114,7 @@ irm https://bailian.aliyun.com/cli/install.ps1 | iex
| ---------------- | ----------------------------------------------------------------------- |
| Managed Agent | “帮我创建一个能够生成短片分镜和视频的 Managed Agent。” |
| 图片和视频生成 | “生成一张穿着太空服的猫站在火星上的图片,再把它制作成一段视频。” |
| 语音识别 | “把这段音频转写成文字,专有名词识别不准的话帮我加上热词再试。” |
| 用量与额度 | “查看最近的模型用量、免费额度和限流情况。” |
| 模型选型 | “推荐一个适合图片理解和智能客服的模型。” |
| 了解 Bailian CLI | “介绍一下 Bailian CLI 能帮我完成哪些任务,并根据我的需求推荐使用方式。” |
Expand Down
10 changes: 10 additions & 0 deletions packages/cli/src/commands.ts
Original file line number Diff line number Diff line change
Expand Up @@ -70,6 +70,11 @@ import {
searchWeb,
speechSynthesize,
speechRecognize,
speechVocabularyCreate,
speechVocabularyList,
speechVocabularyGet,
speechVocabularyUpdate,
speechVocabularyDelete,
fileUpload,
consoleCall,
usageFree,
Expand Down Expand Up @@ -286,6 +291,11 @@ export const commands: Record<string, AnyCommand> = {
"search web": searchWeb,
"speech synthesize": speechSynthesize,
"speech recognize": speechRecognize,
"speech vocabulary create": speechVocabularyCreate,
"speech vocabulary list": speechVocabularyList,
"speech vocabulary get": speechVocabularyGet,
"speech vocabulary update": speechVocabularyUpdate,
"speech vocabulary delete": speechVocabularyDelete,
"file upload": fileUpload,
"console call": consoleCall,
"usage free": usageFree,
Expand Down
78 changes: 69 additions & 9 deletions packages/commands/src/commands/speech/recognize.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,9 +14,11 @@ import {
speechRecognizePath,
resolveAsrApi,
buildAsrFlashRequest,
buildAsrContextMessages,
buildAsyncAsrLanguageFields,
collectAsrTranscriptionItems,
extractAsrFlashText,
parseInstantVocabulary,
type AsrApiRoute,
type AsrFlashFamily,
type OutputFormat,
Expand Down Expand Up @@ -73,8 +75,30 @@ const RECOGNIZE_FLAGS = {
type: "string",
valueHint: "<id>",
description: {
"en-US": "Hot-word vocabulary ID for improved accuracy",
"zh-CN": "用于提升识别准确率的热词表 ID",
"en-US":
"Pre-built hot-word vocabulary ID (create it via `speech vocabulary create`). Its target_model must exactly match --model, otherwise it is silently ignored. Wider model support than --vocabulary, including Fun-ASR and Paraformer",
"zh-CN":
"预编译热词列表 ID(可用 `speech vocabulary create` 创建)。其 target_model 必须与 --model 完全一致,否则静默失效且无报错。支持模型比 --vocabulary 更广,含 Fun-ASR 与 Paraformer 系列",
},
},
vocabulary: {
type: "string",
valueHint: "<json>",
description: {
"en-US":
"Instant hot words as JSON object of word→weight, e.g. '{\"Fendouzhe\":4}'. Weight 1-5 (4 recommended; higher values can hurt other words), 50 for super hot word. No pre-built vocabulary needed. Takes effect only on Qwen-Audio-3.0-ASR-Flash models",
"zh-CN":
"即时热词,JSON 对象「热词→权重」,例如 '{\"奋斗者\":4}'。权重 1-5(推荐 4,过高会拖累其他词),50 表示超级热词。无需预先创建热词表。仅 Qwen-Audio-3.0-ASR-Flash 系列模型生效",
},
},
context: {
type: "string",
valueHint: "<text>",
description: {
"en-US":
"Context enhancement word list to improve accuracy on proper nouns; must contain the target words themselves (a topic description alone has little effect); max 400 chars. Takes effect only on Qwen-Audio-3.0-ASR-Flash and Fun-ASR-Flash models",
"zh-CN":
"上下文增强词表,提升专有名词准确率;须包含待识别的原词本身(只写主题描述效果有限),最长 400 字符。仅 Qwen-Audio-3.0-ASR-Flash 系列与 Fun-ASR-Flash 模型生效",
},
},
channelId: {
Expand Down Expand Up @@ -110,9 +134,11 @@ function assertSyncFlashFlagsAllowed(
const unsupported: string[] = [];
if (flags.diarization === true) unsupported.push("--diarization");
if (flags.speakerCount !== undefined) unsupported.push("--speaker-count");
// qwen3 sync Flash does not use vocabulary_id; input-audio Flash (fun-asr-flash* / qwen-audio-*-asr-flash) does
if (flashFamily === "qwen3" && flags.vocabularyId !== undefined) {
unsupported.push("--vocabulary-id");
// qwen3 sync Flash has no place for vocabulary_id / vocabulary / context in its body shape
if (flashFamily === "qwen3") {
if (flags.vocabularyId !== undefined) unsupported.push("--vocabulary-id");
if (flags.vocabulary !== undefined) unsupported.push("--vocabulary");
if (flags.context !== undefined) unsupported.push("--context");
}
if (flags.channelId !== undefined) unsupported.push("--channel-id");
if (flags.async === true) unsupported.push("--async");
Expand All @@ -121,12 +147,34 @@ function assertSyncFlashFlagsAllowed(
if (unsupported.length > 0) {
throw new BailianError(
`Model "${model}" uses sync Flash ASR and does not support: ${unsupported.join(", ")}.\n` +
`Hint: Use an async filetrans model (e.g. fun-asr, qwen3-asr-flash-filetrans) for those flags.`,
syncFlashUnsupportedHint(unsupported),
ExitCode.USAGE,
);
}
}

/** Pick a hint that matches the rejected flags (vocab/context vs diarization/async/…). */
function syncFlashUnsupportedHint(unsupported: string[]): string {
const vocabularyRelated = new Set(["--vocabulary", "--vocabulary-id", "--context"]);
const hasVocabularyRelated = unsupported.some((flag) => vocabularyRelated.has(flag));
const hasOtherFlags = unsupported.some((flag) => !vocabularyRelated.has(flag));

if (hasVocabularyRelated && !hasOtherFlags) {
return (
"Hint: Use qwen-audio-3.0-asr-flash (or an async filetrans model such as " +
"qwen-audio-3.0-asr-flash-filetrans) for vocabulary/context flags."
);
}
if (hasVocabularyRelated && hasOtherFlags) {
return (
"Hint: For vocabulary/context flags use qwen-audio-3.0-asr-flash or " +
"qwen-audio-3.0-asr-flash-filetrans; for the other flags use an async filetrans model " +
"(e.g. fun-asr)."
);
}
return "Hint: Use an async filetrans model (e.g. fun-asr, qwen3-asr-flash-filetrans) for those flags.";
}

export default defineCommand({
description: {
"en-US": "Recognize speech from audio files (FunAudio-ASR / Qwen-ASR Flash)",
Expand All @@ -141,6 +189,8 @@ export default defineCommand({
"--url https://example.com/meeting.wav --diarization --speaker-count 3",
"--url https://example.com/audio.mp3 --language zh",
"--url https://example.com/audio.mp3 --vocabulary-id vocab-abc123",
'--url https://example.com/audio.mp3 --model qwen-audio-3.0-asr-flash-filetrans --vocabulary \'{"奋斗者":4,"鲸落":4}\'',
'--url https://example.com/audio.mp3 --model qwen-audio-3.0-asr-flash-filetrans --context "奋斗者号 鲸落 深海勇士"',
"--url https://example.com/audio.mp3 --out result.json",
"--url https://example.com/audio.mp3 --async --quiet",
"--url https://example.com/audio.mp3 --model qwen-audio-3.0-asr-flash --language en",
Expand Down Expand Up @@ -198,6 +248,9 @@ export default defineCommand({

const format = detectOutputFormat(settings.output);

const vocabulary =
flags.vocabulary !== undefined ? parseInstantVocabulary(flags.vocabulary) : undefined;

// Auto-upload local files in parallel
const resolvedUrls = await Promise.all(rawUrls.map((url) => ctx.client.uploadFile(url, model)));

Expand All @@ -210,6 +263,7 @@ export default defineCommand({
model,
route,
resolvedUrls[0]!,
vocabulary,
);
return;
}
Expand All @@ -223,16 +277,19 @@ export default defineCommand({

const body: DashScopeASRRequest = {
model,
input:
route.asyncInputStyle === "file_url"
input: {
...(route.asyncInputStyle === "file_url"
? { file_url: resolvedUrls[0]! }
: { file_urls: resolvedUrls },
: { file_urls: resolvedUrls }),
...(flags.context !== undefined ? { context: buildAsrContextMessages(flags.context) } : {}),
},
parameters: {
channel_id: channelId !== undefined ? [channelId] : [0],
...languageFields,
diarization_enabled: diarization ? true : undefined,
speaker_count: speakerCount,
vocabulary_id: vocabularyId,
vocabulary,
},
};

Expand Down Expand Up @@ -260,13 +317,16 @@ async function handleSyncFlashMode(
model: string,
route: AsrApiRoute,
audioUrl: string,
vocabulary: Record<string, number> | undefined,
): Promise<void> {
const flashFamily = route.flashFamily as AsrFlashFamily;
const body = buildAsrFlashRequest({
model,
audioUrl,
language: flags.language,
vocabularyId: flags.vocabularyId,
vocabulary,
context: flags.context,
flashFamily,
});

Expand Down
117 changes: 117 additions & 0 deletions packages/commands/src/commands/speech/vocabulary/create.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,117 @@
import {
defineCommand,
detectOutputFormat,
speechVocabularyPath,
buildVocabularyRequest,
createVocabulary,
type FlagsDef,
type ParsedFlags,
} from "bailian-cli-core";
import { emitResult, emitBare } from "bailian-cli-runtime";
import {
VOCABULARY_BODY_FLAGS,
VOCABULARY_LIMIT_NOTES,
validateVocabularySource,
readVocabularyEntries,
} from "./shared.ts";

const CREATE_FLAGS = {
model: {
type: "string",
valueHint: "<model>",
description: {
"en-US":
"ASR model this vocabulary is built for (required). Must exactly match the --model passed to `speech recognize` later, otherwise the vocabulary is silently ignored",
"zh-CN":
"该热词表服务的 ASR 模型(必填)。必须与后续 `speech recognize` 的 --model 完全一致,否则热词表静默失效",
},
required: true,
},
prefix: {
type: "string",
valueHint: "<prefix>",
description: {
"en-US":
"Custom vocabulary prefix (required). Digits and lowercase letters only, max 10 chars",
"zh-CN": "热词表自定义前缀(必填)。仅允许数字和小写字母,最长 10 个字符",
},
required: true,
},
...VOCABULARY_BODY_FLAGS,
} satisfies FlagsDef;
type CreateFlags = ParsedFlags<typeof CREATE_FLAGS>;

export default defineCommand({
description: {
"en-US": "Create a precompiled hot-word vocabulary for ASR",
"zh-CN": "创建用于语音识别的预编译热词表",
},
auth: "apiKey",
usageArgs: "--model <model> --prefix <prefix> (--words <json> | --words-file <path>) [flags]",
flags: CREATE_FLAGS,
notes: [
{
"en-US":
"The --model must exactly match the --model used later with `speech recognize --vocabulary-id`; a mismatch causes silent failure with no error.",
"zh-CN":
"--model 必须与后续 `speech recognize --vocabulary-id` 使用的 --model 完全一致;不一致时热词表会静默失效且无报错。",
},
...VOCABULARY_LIMIT_NOTES,
],
exampleArgs: [
{
"en-US": '--model fun-asr --prefix demo --words \'{"Fendouzhe":4,"Jingluo":4}\'',
"zh-CN": '--model fun-asr --prefix demo --words \'{"奋斗者":4,"鲸落":4}\'',
},
{
"en-US":
'--model paraformer-v2 --prefix demo --words \'[{"text":"Fendouzhe","weight":4,"lang":"zh"}]\'',
"zh-CN":
'--model paraformer-v2 --prefix demo --words \'[{"text":"奋斗者","weight":4,"lang":"zh"}]\'',
},
{
"en-US": "--model fun-asr --prefix demo --words '{\"Fendouzhe\":4}' --lang zh",
"zh-CN": "--model fun-asr --prefix demo --words '{\"奋斗者\":4}' --lang zh",
},
"--model fun-asr --prefix demo --words-file ./hotwords.json",
{
"en-US": "--model fun-asr --prefix demo --words '{\"Fendouzhe\":4}' --quiet",
"zh-CN": "--model fun-asr --prefix demo --words '{\"奋斗者\":4}' --quiet",
},
],
validate: (flags: CreateFlags) => validateVocabularySource(flags),
async run(ctx) {
const { settings, flags } = ctx;
const vocabulary = readVocabularyEntries(flags);
const format = detectOutputFormat(settings.output);

const request = buildVocabularyRequest("create_vocabulary", {
target_model: flags.model,
prefix: flags.prefix,
vocabulary,
});

if (settings.dryRun) {
emitResult(
{
endpoint: ctx.client.url(speechVocabularyPath()),
request,
},
format,
);
return;
}

const response = await createVocabulary(ctx.client, {
targetModel: flags.model,
prefix: flags.prefix,
vocabulary,
});

if (settings.quiet || format === "text") {
emitBare(response.output?.vocabulary_id ?? "");
} else {
emitResult(response, format);
}
},
});
60 changes: 60 additions & 0 deletions packages/commands/src/commands/speech/vocabulary/delete.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import {
defineCommand,
detectOutputFormat,
speechVocabularyPath,
buildVocabularyRequest,
deleteVocabulary,
type FlagsDef,
type ParsedFlags,
} from "bailian-cli-core";
import { emitResult, emitBare } from "bailian-cli-runtime";
import { VOCABULARY_ID_FLAG } from "./shared.ts";

const DELETE_FLAGS = {
...VOCABULARY_ID_FLAG,
} satisfies FlagsDef;
type DeleteFlags = ParsedFlags<typeof DELETE_FLAGS>;

export default defineCommand({
description: {
"en-US": "Delete a precompiled hot-word vocabulary",
"zh-CN": "删除预编译热词表",
},
auth: "apiKey",
risk: {
level: "high",
message: {
"en-US": "This permanently deletes the specified hot-word vocabulary and cannot be undone.",
"zh-CN": "该操作会永久删除指定的热词表,且无法撤销。",
},
},
usageArgs: "--id <id>",
flags: DELETE_FLAGS,
exampleArgs: ["--id vocab-demo-xxx --dry-run", "--id vocab-demo-xxx --yes"],
async run(ctx) {
const { settings, flags } = ctx;
const vocabularyId = (flags as DeleteFlags).id;
const format = detectOutputFormat(settings.output);

if (settings.dryRun) {
emitResult(
{
endpoint: ctx.client.url(speechVocabularyPath()),
request: buildVocabularyRequest("delete_vocabulary", {
vocabulary_id: vocabularyId,
}),
},
format,
);
return;
}

const response = await deleteVocabulary(ctx.client, vocabularyId);

if (settings.quiet || format === "text") {
emitBare(vocabularyId);
} else {
emitResult(response, format);
}
},
});
Loading