From e66ff272881195443487e83f9d89ab7c678b2f70 Mon Sep 17 00:00:00 2001 From: XiaoYang Date: Tue, 10 Mar 2026 21:05:44 +0800 Subject: [PATCH 1/3] docs(api): add audio transcription API documentation --- docs/api.md | 125 ++++++++++++++++++++++++++++++++++++++++++++++ docs/api.zh-CN.md | 125 ++++++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 250 insertions(+) diff --git a/docs/api.md b/docs/api.md index 5cc234e..db68889 100644 --- a/docs/api.md +++ b/docs/api.md @@ -277,6 +277,9 @@ curl http://127.0.0.1:10100/chat/completions \ }' ``` +> **Note:** For OpenAI-standard audio transcription with `multipart/form-data` file upload, +> see the [Audio Transcriptions](#audio-transcriptions) endpoint. + **Streaming Response:** ```bash @@ -376,6 +379,128 @@ Returns audio data in base64 WAV format. - `voxcpm`, `voxcpm1.5` +### Audio Transcriptions + +Transcribe audio files to text (Automatic Speech Recognition). + +This endpoint provides OpenAI-compatible audio transcription using `multipart/form-data` format. + +#### Endpoints + +``` +POST /audio/transcriptions +POST /v1/audio/transcriptions +``` + +Both endpoints use the same handler and return identical responses. The `/v1/audio/transcriptions` path follows OpenAI's standard API convention. + +#### Request Body + +| Parameter | Type | Required | Description | +|-----------|------|----------|-------------| +| `file` | file | Yes | The audio file to transcribe (wav, mp3, m4a, etc.) | +| `model` | string | No | Model identifier (optional, ignored - uses loaded model) | +| `language` | string | No | Language code (e.g., "zh", "en", "yue") | +| `prompt` | string | No | Optional text to guide transcription (not implemented, ignored) | +| `response_format` | string | No | Response format, only "json" or "text" supported (default: "json") | +| `temperature` | number | No | Sampling temperature (0.0 to 1.0, default: 0.0) | + +#### Supported Languages + +| Code | Language | Code | Language | +|------|----------|------|----------| +| `zh` | Chinese | `en` | English | +| `yue` | Cantonese | `ar` | Arabic | +| `de` | German | `fr` | French | +| `es` | Spanish | `pt` | Portuguese | +| `id` | Indonesian | `it` | Italian | +| `ko` | Korean | `ru` | Russian | +| `th` | Thai | `vi` | Vietnamese | +| `ja` | Japanese | `tr` | Turkish | +| `hi` | Hindi | `ms` | Malay | +| `nl` | Dutch | `sv` | Swedish | +| `da` | Danish | `fi` | Finnish | +| `pl` | Polish | `cs` | Czech | +| `fil` | Filipino | `fa` | Persian | +| `el` | Greek | `ro` | Romanian | +| `hu` | Hungarian | `mk` | Macedonian | + +#### Examples + +**Basic transcription:** + +```bash +curl -X POST http://127.0.0.1:10100/audio/transcriptions \ + -H "Authorization: Bearer NO_NEED" \ + -F file="@./audio.wav" \ + -F model="qwen3asr-0.6b" +``` + +**With language specification:** + +```bash +curl -X POST http://127.0.0.1:10100/v1/audio/transcriptions \ + -H "Authorization: Bearer NO_NEED" \ + -F file="@./chinese_audio.wav" \ + -F model="qwen3asr-0.6b" \ + -F language="zh" +``` + +**With temperature:** + +```bash +curl -X POST http://127.0.0.1:10100/v1/audio/transcriptions \ + -H "Authorization: Bearer NO_NEED" \ + -F file="@./audio.wav" \ + -F model="qwen3asr-0.6b" \ + -F temperature="0.0" +``` + +#### Response + +**Success (HTTP 200):** + +```json +{ + "text": "Transcribed text from the audio file" +} +``` + +**Error (HTTP 400):** + +```json +{ + "error": { + "message": "Audio file is required", + "type": "invalid_request_error", + "code": "missing_file" + } +} +``` + +**Error (HTTP 503):** + +```json +{ + "error": { + "message": "Model not initialized", + "type": "service_unavailable", + "code": "model_not_loaded" + } +} +``` + +#### Supported Models + +- `qwen3asr-0.6b` +- `qwen3asr-1.7b` +- `glm-asr-nano-2512` +- `fun-asr-nano-2512` + +#### File Upload Limit + +Maximum audio file size: 100 MB + ### Images Remove Background Remove background from images. diff --git a/docs/api.zh-CN.md b/docs/api.zh-CN.md index 8663927..e43196f 100644 --- a/docs/api.zh-CN.md +++ b/docs/api.zh-CN.md @@ -277,6 +277,9 @@ curl http://127.0.0.1:10100/chat/completions \ }' ``` +> **提示**:支持 OpenAI 标准的音频转录接口,使用 `multipart/form-data` 上传文件。 +> 详见 [语音转写](#语音转写) 章节。 + **流式响应:** ```bash @@ -376,6 +379,128 @@ curl http://127.0.0.1:10100/audio/speech \ - `voxcpm`、`voxcpm1.5` +### 语音转写 + +将音频文件转录为文本(自动语音识别)。 + +此接口提供符合 OpenAI 标准的音频转录功能,使用 `multipart/form-data` 格式。 + +#### 端点 + +``` +POST /audio/transcriptions +POST /v1/audio/transcriptions +``` + +两个端点使用相同的处理函数并返回相同的响应。`/v1/audio/transcriptions` 路径遵循 OpenAI 的标准 API 约定。 + +#### 请求参数 + +| 参数 | 类型 | 必需 | 描述 | +|------|------|------|------| +| `file` | file | 是 | 要转录的音频文件 (wav, mp3, m4a 等) | +| `model` | string | 否 | 模型标识符(可选,被忽略 - 使用已加载的模型) | +| `language` | string | 否 | 语言代码(如 "zh"、"en"、"yue") | +| `prompt` | string | 否 | 引导转录的可选文本(未实现,被忽略) | +| `response_format` | string | 否 | 响应格式,仅支持 "json" 或 "text"(默认:"json") | +| `temperature` | number | 否 | 采样温度 (0.0 到 1.0,默认:0.0) | + +#### 支持的语言 + +| 代码 | 语言 | 代码 | 语言 | +|------|------|------|------| +| `zh` | 中文 | `en` | 英语 | +| `yue` | 粤语 | `ar` | 阿拉伯语 | +| `de` | 德语 | `fr` | 法语 | +| `es` | 西班牙语 | `pt` | 葡萄牙语 | +| `id` | 印尼语 | `it` | 意大利语 | +| `ko` | 韩语 | `ru` | 俄语 | +| `th` | 泰语 | `vi` | 越南语 | +| `ja` | 日语 | `tr` | 土耳其语 | +| `hi` | 印地语 | `ms` | 马来语 | +| `nl` | 荷兰语 | `sv` | 瑞典语 | +| `da` | 丹麦语 | `fi` | 芬兰语 | +| `pl` | 波兰语 | `cs` | 捷克语 | +| `fil` | 菲律宾语 | `fa` | 波斯语 | +| `el` | 希腊语 | `ro` | 罗马尼亚语 | +| `hu` | 匈牙利语 | `mk` | 马其顿语 | + +#### 示例 + +**基本转录:** + +```bash +curl -X POST http://127.0.0.1:10100/audio/transcriptions \ + -H "Authorization: Bearer NO_NEED" \ + -F file="@./audio.wav" \ + -F model="qwen3asr-0.6b" +``` + +**指定语言:** + +```bash +curl -X POST http://127.0.0.1:10100/v1/audio/transcriptions \ + -H "Authorization: Bearer NO_NEED" \ + -F file="@./chinese_audio.wav" \ + -F model="qwen3asr-0.6b" \ + -F language="zh" +``` + +**设置温度参数:** + +```bash +curl -X POST http://127.0.0.1:10100/v1/audio/transcriptions \ + -H "Authorization: Bearer NO_NEED" \ + -F file="@./audio.wav" \ + -F model="qwen3asr-0.6b" \ + -F temperature="0.0" +``` + +#### 响应 + +**成功 (HTTP 200):** + +```json +{ + "text": "从音频文件转录的文本" +} +``` + +**错误 (HTTP 400):** + +```json +{ + "error": { + "message": "Audio file is required", + "type": "invalid_request_error", + "code": "missing_file" + } +} +``` + +**错误 (HTTP 503):** + +```json +{ + "error": { + "message": "Model not initialized", + "type": "service_unavailable", + "code": "model_not_loaded" + } +} +``` + +#### 支持的模型 + +- `qwen3asr-0.6b` +- `qwen3asr-1.7b` +- `glm-asr-nano-2512` +- `fun-asr-nano-2512` + +#### 文件上传限制 + +最大音频文件大小:100 MB + ### 图像背景移除 从图像中移除背景。 From 2f452ab925c55149412ebaac51c5b6e8108475d7 Mon Sep 17 00:00:00 2001 From: XiaoYang Date: Tue, 10 Mar 2026 21:24:23 +0800 Subject: [PATCH 2/3] feat(cli): add model download verification and update model listing output(cli/json) --- src/main.rs | 19 +++++++++++++++++-- 1 file changed, 17 insertions(+), 2 deletions(-) diff --git a/src/main.rs b/src/main.rs index f408503..0dad55a 100644 --- a/src/main.rs +++ b/src/main.rs @@ -185,6 +185,18 @@ fn get_default_weight_path(model: WhichModel) -> String { format!("{}/{}", save_dir, model_id) } +/// Check if a model is downloaded by verifying the model directory exists +/// Returns true if ~/.aha/{model_id} directory exists, false otherwise +fn is_model_downloaded(model: WhichModel) -> bool { + let model_id = model.model_id(); + let save_dir = match get_default_save_dir() { + Some(dir) => dir, + None => return false, + }; + let model_path = format!("{}/{}", save_dir, model_id); + std::path::Path::new(&model_path).exists() +} + /// Model information for JSON output #[derive(Serialize)] struct ModelInfo { @@ -192,6 +204,7 @@ struct ModelInfo { model_id: String, #[serde(rename = "type")] model_type: String, + downloaded: bool, } /// List all supported models @@ -232,6 +245,7 @@ fn run_list(args: ListArgs) -> anyhow::Result<()> { name: possible_value.get_name().to_string(), model_id: model.model_id().to_string(), model_type: model.model_type().to_string(), + downloaded: is_model_downloaded(*model), } }) .collect(); @@ -240,13 +254,14 @@ fn run_list(args: ListArgs) -> anyhow::Result<()> { // Table output (default) println!("Available models:"); println!(); - println!("{:<30} ModelScope ID", "Model Name"); + println!("{:<30} {:<40} {:<10}", "Model Name", "ModelScope ID", "Download"); println!("{}", "-".repeat(80)); for model in models { let possible_value = model.to_possible_value().unwrap(); let name = possible_value.get_name(); let id = model.model_id(); - println!("{:<30} {}", name, id); + let download_status = if is_model_downloaded(model) { " ✔" } else { "" }; + println!("{:<30} {:<40} {:<10}", name, id, download_status); } } From 8ec2ef6ad31ad35fe2f55ae6c99de09541c1b213 Mon Sep 17 00:00:00 2001 From: XiaoYang Date: Tue, 10 Mar 2026 21:45:04 +0800 Subject: [PATCH 3/3] fmt: format code according to rustfmt standards --- .gitignore | 4 ++++ src/main.rs | 11 +++++++++-- 2 files changed, 13 insertions(+), 2 deletions(-) diff --git a/.gitignore b/.gitignore index c628483..090eae4 100644 --- a/.gitignore +++ b/.gitignore @@ -17,3 +17,7 @@ docs/MODEL_DEVELOPMENT_GUIDE.md bak .gitignore + + +demos +benchmarks diff --git a/src/main.rs b/src/main.rs index 0dad55a..5668640 100644 --- a/src/main.rs +++ b/src/main.rs @@ -254,13 +254,20 @@ fn run_list(args: ListArgs) -> anyhow::Result<()> { // Table output (default) println!("Available models:"); println!(); - println!("{:<30} {:<40} {:<10}", "Model Name", "ModelScope ID", "Download"); + println!( + "{:<30} {:<40} {:<10}", + "Model Name", "ModelScope ID", "Download" + ); println!("{}", "-".repeat(80)); for model in models { let possible_value = model.to_possible_value().unwrap(); let name = possible_value.get_name(); let id = model.model_id(); - let download_status = if is_model_downloaded(model) { " ✔" } else { "" }; + let download_status = if is_model_downloaded(model) { + " ✔" + } else { + "" + }; println!("{:<30} {:<40} {:<10}", name, id, download_status); } }