Skip to content

Commit 2b3f2ea

Browse files
LauraGPTLauraGPT
andauthored
feat(asr): add local FunASR provider (#1044)
Signed-off-by: LauraGPT <199134975+LauraGPT@users.noreply.github.com> Co-authored-by: LauraGPT <199134975+LauraGPT@users.noreply.github.com>
1 parent 378579e commit 2b3f2ea

37 files changed

Lines changed: 330 additions & 15 deletions

.env.example

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -138,6 +138,9 @@ ASR_QWEN_BASE_URL=
138138
ASR_AZURE_API_KEY=
139139
ASR_AZURE_BASE_URL=https://{region}.api.cognitive.microsoft.com
140140

141+
# FunASR (local, WAV input only, no API key required)
142+
# ASR_FUNASR_BASE_URL=http://localhost:8000/v1
143+
141144
# Lemonade ASR (local, WAV input only, no API key required)
142145
# ASR_LEMONADE_BASE_URL=http://localhost:13305/v1
143146

README-zh.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
<p align="center">
3434
<a href="./README.md">English</a> | <a href="./README-zh.md">简体中文</a>
3535
<br/>
36-
<a href="https://open.maic.chat/">在线体验</a> · <a href="#-快速开始">快速开始</a> · <a href="#lemonade-local-ai">Lemonade</a> · <a href="#-功能特性">功能特性</a> · <a href="#-使用场景">使用场景</a> · <a href="#-openclaw-集成">OpenClaw</a>
36+
<a href="https://open.maic.chat/">在线体验</a> · <a href="#-快速开始">快速开始</a> · <a href="#lemonade-local-ai">Lemonade</a> · <a href="#funasr-local-asr">FunASR</a> · <a href="#-功能特性">功能特性</a> · <a href="#-使用场景">使用场景</a> · <a href="#-openclaw-集成">OpenClaw</a>
3737
</p>
3838

3939

@@ -137,7 +137,7 @@ providers:
137137
- us.anthropic.claude-opus-4-8
138138
```
139139
140-
支持的服务商:**OpenAI**、**Azure OpenAI**、**Anthropic**、**Amazon Bedrock**、**Google Gemini**、**DeepSeek**、**通义千问 Qwen**、**Kimi**、**MiniMax**、**Grok (xAI)**、**OpenRouter**、**豆包**、**腾讯混元 / TokenHub**、**小米 MiMo**、**智谱 GLM**、**Ollama**(本地)、**Lemonade**(本地 LLM / 图像 / TTS / ASR)以及任何兼容 OpenAI API 的服务。
140+
支持的服务商:**OpenAI**、**Azure OpenAI**、**Anthropic**、**Amazon Bedrock**、**Google Gemini**、**DeepSeek**、**通义千问 Qwen**、**Kimi**、**MiniMax**、**Grok (xAI)**、**OpenRouter**、**豆包**、**腾讯混元 / TokenHub**、**小米 MiMo**、**智谱 GLM**、**Ollama**(本地)、**Lemonade**(本地 LLM / 图像 / TTS / ASR)、**FunASR**(本地 ASR)以及任何兼容 OpenAI API 的服务。
141141
142142
Amazon Bedrock 快速示例:
143143
@@ -164,6 +164,28 @@ ASR_LEMONADE_BASE_URL=http://localhost:13305/v1
164164
IMAGE_LEMONADE_BASE_URL=http://localhost:13305/v1
165165
```
166166

167+
<a id="funasr-local-asr"></a>
168+
169+
### 可选:FunASR(本地语音识别)
170+
171+
OpenMAIC 可以通过 FunASR 的 OpenAI 兼容服务完成本地转写。内置 provider 支持 SenseVoiceSmall、Paraformer 和 Fun-ASR-Nano,无需 API Key。
172+
173+
```bash
174+
python -m pip install torch torchaudio
175+
python -m pip install "funasr==1.4.0" fastapi uvicorn python-multipart
176+
# NVIDIA GPU 上运行 Fun-ASR-Nano 时再安装 vLLM
177+
python -m pip install vllm
178+
funasr-server --device cuda --model fun-asr-nano
179+
```
180+
181+
将 OpenMAIC 指向该服务:
182+
183+
```env
184+
ASR_FUNASR_BASE_URL=http://localhost:8000/v1
185+
```
186+
187+
纯 CPU 环境可运行 `funasr-server --device cpu --model sensevoice`。生产部署方式参见 [FunASR 部署指南](https://github.com/modelscope/FunASR#deploy)
188+
167189
OpenAI 快速示例:
168190

169191
```env

README.md

Lines changed: 24 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@
3333
<p align="center">
3434
<a href="./README.md">English</a> | <a href="./README-zh.md">简体中文</a>
3535
<br/>
36-
<a href="https://open.maic.chat/">Live Demo</a> · <a href="#-quick-start">Quick Start</a> · <a href="#lemonade-local-ai">Lemonade</a> · <a href="#-features">Features</a> · <a href="#-use-cases">Use Cases</a> · <a href="#-openclaw-integration">OpenClaw</a>
36+
<a href="https://open.maic.chat/">Live Demo</a> · <a href="#-quick-start">Quick Start</a> · <a href="#lemonade-local-ai">Lemonade</a> · <a href="#funasr-local-asr">FunASR</a> · <a href="#-features">Features</a> · <a href="#-use-cases">Use Cases</a> · <a href="#-openclaw-integration">OpenClaw</a>
3737
</p>
3838

3939

@@ -137,7 +137,7 @@ providers:
137137
- us.anthropic.claude-opus-4-8
138138
```
139139
140-
Supported providers: **OpenAI**, **Azure OpenAI**, **Anthropic**, **Amazon Bedrock**, **Google Gemini**, **DeepSeek**, **Qwen**, **Kimi**, **MiniMax**, **Grok (xAI)**, **OpenRouter**, **Doubao**, **Tencent Hunyuan/TokenHub**, **Xiaomi MiMo**, **GLM (Zhipu)**, **Ollama** (local), **Lemonade** (local LLM / image / TTS / ASR), and any OpenAI-compatible API.
140+
Supported providers: **OpenAI**, **Azure OpenAI**, **Anthropic**, **Amazon Bedrock**, **Google Gemini**, **DeepSeek**, **Qwen**, **Kimi**, **MiniMax**, **Grok (xAI)**, **OpenRouter**, **Doubao**, **Tencent Hunyuan/TokenHub**, **Xiaomi MiMo**, **GLM (Zhipu)**, **Ollama** (local), **Lemonade** (local LLM / image / TTS / ASR), **FunASR** (local ASR), and any OpenAI-compatible API.
141141
142142
Amazon Bedrock quick example:
143143
@@ -164,6 +164,28 @@ ASR_LEMONADE_BASE_URL=http://localhost:13305/v1
164164
IMAGE_LEMONADE_BASE_URL=http://localhost:13305/v1
165165
```
166166

167+
<a id="funasr-local-asr"></a>
168+
169+
### Optional: FunASR (Local Speech Recognition)
170+
171+
OpenMAIC can transcribe locally through FunASR's OpenAI-compatible server. The built-in provider supports SenseVoiceSmall, Paraformer, and Fun-ASR-Nano and requires no API key.
172+
173+
```bash
174+
python -m pip install torch torchaudio
175+
python -m pip install "funasr==1.4.0" fastapi uvicorn python-multipart
176+
# Add vLLM for Fun-ASR-Nano on NVIDIA GPUs
177+
python -m pip install vllm
178+
funasr-server --device cuda --model fun-asr-nano
179+
```
180+
181+
Point OpenMAIC at the server:
182+
183+
```env
184+
ASR_FUNASR_BASE_URL=http://localhost:8000/v1
185+
```
186+
187+
Use `funasr-server --device cpu --model sensevoice` for a CPU-only setup. See the [FunASR deployment guide](https://github.com/modelscope/FunASR#deploy) for production options.
188+
167189
OpenAI quick example:
168190

169191
```env

components/settings/asr-settings.tsx

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -281,6 +281,7 @@ export function ASRSettings({ selectedProviderId }: ASRSettingsProps) {
281281
} else {
282282
switch (selectedProviderId) {
283283
case 'openai-whisper':
284+
case 'funasr-asr':
284285
case 'lemonade-asr':
285286
endpointPath = '/audio/transcriptions';
286287
break;

lib/audio/asr-providers.ts

Lines changed: 18 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -185,8 +185,16 @@ export async function transcribeAudio(
185185
case 'azure-asr':
186186
return await transcribeAzureASR(config, audioBuffer);
187187

188+
case 'funasr-asr':
189+
return await transcribeWavOpenAICompatibleASR(config, audioBuffer, 'funasr-asr', 'FunASR');
190+
188191
case 'lemonade-asr':
189-
return await transcribeLemonadeASR(config, audioBuffer);
192+
return await transcribeWavOpenAICompatibleASR(
193+
config,
194+
audioBuffer,
195+
'lemonade-asr',
196+
'Lemonade',
197+
);
190198

191199
default:
192200
if (isCustomASRProvider(config.providerId)) {
@@ -197,29 +205,31 @@ export async function transcribeAudio(
197205
}
198206

199207
/**
200-
* Lemonade ASR implementation (OpenAI-compatible multipart transcription).
208+
* WAV-only OpenAI-compatible multipart transcription.
201209
*
202-
* Lemonade currently supports WAV input and JSON response format.
210+
* Used by local providers whose transcription endpoint accepts WAV and JSON.
203211
*/
204-
async function transcribeLemonadeASR(
212+
async function transcribeWavOpenAICompatibleASR(
205213
config: ASRModelConfig,
206214
audioBuffer: Buffer | Blob,
215+
providerId: 'funasr-asr' | 'lemonade-asr',
216+
providerName: string,
207217
): Promise<ASRTranscriptionResult> {
208-
const baseUrl = (config.baseUrl || ASR_PROVIDERS['lemonade-asr'].defaultBaseUrl || '').replace(
218+
const baseUrl = (config.baseUrl || ASR_PROVIDERS[providerId].defaultBaseUrl || '').replace(
209219
/\/$/,
210220
'',
211221
);
212222

213223
const audioBlob = await toAudioBlob(audioBuffer);
214224
if (!(await isWavAudio(audioBlob))) {
215225
throw new Error(
216-
'Lemonade ASR currently supports WAV input only. Recordings should be converted to WAV before upload.',
226+
`${providerName} ASR currently supports WAV input only. Recordings should be converted to WAV before upload.`,
217227
);
218228
}
219229

220230
const formData = new FormData();
221231
formData.set('file', audioBlob, 'audio.wav');
222-
formData.set('model', config.modelId || ASR_PROVIDERS['lemonade-asr'].defaultModelId);
232+
formData.set('model', config.modelId || ASR_PROVIDERS[providerId].defaultModelId);
223233
formData.set('response_format', 'json');
224234
if (config.language && config.language !== 'auto') {
225235
formData.set('language', config.language);
@@ -236,7 +246,7 @@ async function transcribeLemonadeASR(
236246
if (errorText.includes('audio is empty') || errorText.includes('too short')) {
237247
return { text: '' };
238248
}
239-
throw new Error(`Lemonade ASR API error: ${errorText || response.statusText}`);
249+
throw new Error(`${providerName} ASR API error: ${errorText || response.statusText}`);
240250
}
241251

242252
const data = await response.json();

lib/audio/constants.ts

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1250,6 +1250,22 @@ export const ASR_PROVIDERS: Record<BuiltInASRProviderId, ASRProviderConfig> = {
12501250
supportedFormats: ['webm'], // MediaRecorder format
12511251
},
12521252

1253+
'funasr-asr': {
1254+
id: 'funasr-asr',
1255+
name: 'FunASR',
1256+
requiresApiKey: false,
1257+
defaultBaseUrl: 'http://localhost:8000/v1',
1258+
icon: '/logos/funasr.png',
1259+
models: [
1260+
{ id: 'sensevoice', name: 'SenseVoiceSmall' },
1261+
{ id: 'paraformer', name: 'Paraformer' },
1262+
{ id: 'fun-asr-nano', name: 'Fun-ASR-Nano' },
1263+
],
1264+
defaultModelId: 'sensevoice',
1265+
supportedLanguages: ['auto', 'zh', 'en', 'ja', 'ko', 'yue'],
1266+
supportedFormats: ['wav'],
1267+
},
1268+
12531269
'lemonade-asr': {
12541270
id: 'lemonade-asr',
12551271
name: 'Lemonade ASR',

lib/audio/provider-display.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,7 @@ const ASR_PROVIDER_NAME_KEYS: Record<string, string> = {
1818
'browser-native': 'settings.providerBrowserNative',
1919
'qwen-asr': 'settings.providerQwenASR',
2020
'azure-asr': 'settings.providerAzureASR',
21+
'funasr-asr': 'settings.providerFunASRASR',
2122
'lemonade-asr': 'settings.providerLemonadeASR',
2223
};
2324

lib/audio/types.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -157,6 +157,7 @@ export type BuiltInASRProviderId =
157157
| 'openai-whisper'
158158
| 'browser-native'
159159
| 'qwen-asr'
160+
| 'funasr-asr'
160161
| 'lemonade-asr'
161162
| 'azure-asr';
162163

lib/audio/wav-utils.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ export async function normalizeASRUploadAudio(
7777
providerId: string,
7878
audioBlob: Blob,
7979
): Promise<{ blob: Blob; fileName: string }> {
80-
if (providerId !== 'lemonade-asr') {
80+
if (providerId !== 'lemonade-asr' && providerId !== 'funasr-asr') {
8181
return { blob: audioBlob, fileName: 'recording.webm' };
8282
}
8383
return { blob: await audioBlobToWav(audioBlob), fileName: 'recording.wav' };

lib/i18n/locales/ar-SA.json

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1317,6 +1317,7 @@
13171317
"providerOpenAIWhisper": "OpenAI ASR (gpt-4o-mini-transcribe)",
13181318
"providerBrowserNative": "التعرّف على الكلام المدمج في المتصفح",
13191319
"providerQwenASR": "Qwen ASR (سحابة علي بابا بايليان)",
1320+
"providerFunASRASR": "FunASR (محلي)",
13201321
"providerLemonadeASR": "Lemonade ASR (محلي)",
13211322
"providerAzureASR": "Azure STT",
13221323
"providerUnpdf": "unpdf (مُدمج)",

0 commit comments

Comments
 (0)