feat(stt): add FunASR / SenseVoice provider (#16473)

### Summary

Adds FunASR as a self-hosted speech-to-text provider through its
OpenAI-compatible `/v1/audio/transcriptions` endpoint.

This is a focused replacement for #15526 by @Rene0422 and relates to
#15448. The unrelated Markdown parser changes from the previous branch
are intentionally removed so this PR contains only the FunASR provider
integration.

- register FunASR as a `SPEECH2TEXT` factory;
- add `FunASRSeq2txt` with `sensevoice` and `http://localhost:8000/v1`
defaults, an optional API key, URL normalization, and inherited
transcription handling;
- wire FunASR into the current local-provider schema with a prefilled
local URL and official documentation link;
- discover the server's `/v1/models` dynamically and expose every
returned model as speech-to-text in the model picker;
- use RAGFlow's existing default provider icon fallback instead of
referencing a missing `funasr` asset;
- list FunASR in the supported-provider documentation;
- add focused backend and frontend regression tests.

### Validation

- focused backend pytest suite -> `7 passed`
- real CPU `funasr-server` + RAGFlow provider smoke test -> discovered
`fun-asr-nano`, `sensevoice`, and `paraformer`; transcribed a real WAV
as `我现在在录一段测试音频` (`10` tokens, `0.504s`)
- `ruff check` and `ruff format --check` on the changed Python files
- `python3 -m py_compile` on the provider and its test
- JSON parse and a semantic assertion for exactly one enabled FunASR
`SPEECH2TEXT` factory
- focused frontend Jest test -> `2 passed`
- ESLint and Prettier on all changed TypeScript files
- `npm run build` -> production build succeeded (`14,181` modules
transformed)
- `git diff --check`

### Deployment

Run FunASR separately and point the RAGFlow provider at it:

```bash
pip install funasr
funasr-server --device cuda --model sensevoice
```

The API key remains optional because the stock local server does not
require authentication. A key can still be supplied when the endpoint is
protected by a gateway.

---------

Signed-off-by: LauraGPT <LauraGPT@users.noreply.github.com>
Co-authored-by: LauraGPT <LauraGPT@users.noreply.github.com>
This commit is contained in:
zhifu gao
2026-07-15 19:02:05 +08:00
committed by GitHub
parent 2223a514de
commit 06e36d24f4
11 changed files with 233 additions and 0 deletions

View File

@@ -74,6 +74,7 @@ export enum LLMFactory {
RAGcon = 'RAGcon',
Perplexity = 'Perplexity',
NewAPI = 'New API',
FunASR = 'FunASR',
}
// Please lowercase the file name

View File

@@ -40,6 +40,7 @@ export const LocalLlmFactories = [
LLMFactory.ModelScope,
LLMFactory.VLLM,
LLMFactory.RAGcon,
LLMFactory.FunASR,
];
export enum TenantRole {

View File

@@ -38,6 +38,7 @@ export const LIST_MODEL_PROVIDERS = new Set<string>([
LLMFactory.VolcEngine,
LLMFactory.Xinference,
LLMFactory.LocalAI,
LLMFactory.FunASR,
LLMFactory.BaiduYiYan,
LLMFactory.NewAPI,

View File

@@ -0,0 +1,38 @@
import { LLMFactory } from '@/constants/llm';
import { LIST_MODEL_PROVIDERS } from '../constants';
import { LocalLlmConfigs } from './local-llm-configs';
jest.mock('@/components/dynamic-form', () => ({
FormFieldType: {
Password: 'password',
Switch: 'switch',
Text: 'text',
},
}));
describe('FunASR local provider configuration', () => {
it('uses the model picker backed by the FunASR models endpoint', () => {
expect(LIST_MODEL_PROVIDERS.has(LLMFactory.FunASR)).toBe(true);
});
it('registers an optional-key local endpoint with the FunASR default URL', () => {
const config = LocalLlmConfigs[LLMFactory.FunASR];
expect(config).toMatchObject({
llmFactory: LLMFactory.FunASR,
title: 'FunASR',
docLink: 'https://github.com/modelscope/FunASR',
});
expect(
config.fields.find((field) => field.name === 'base_url'),
).toMatchObject({
required: true,
defaultValue: 'http://localhost:8000/v1',
});
expect(
config.fields.find((field) => field.name === 'api_key'),
).toMatchObject({
required: false,
});
});
});

View File

@@ -42,6 +42,25 @@ export const LocalLlmConfigs: Record<string, ProviderConfig> = {
undefined,
'https://inference.readthedocs.io/en/latest/user_guide',
),
[LLMFactory.FunASR]: buildLocalConfig(
LLMFactory.FunASR,
'FunASR',
['speech2text'],
undefined,
false,
[
{
name: 'base_url',
label: 'addLlmBaseUrl',
type: 'inputSelect',
required: true,
defaultValue: 'http://localhost:8000/v1',
placeholder: 'baseUrlNameMessage',
shouldRender: 'hideWhenInstanceExists',
},
],
'https://github.com/modelscope/FunASR',
),
[LLMFactory.ModelScope]: buildLocalConfig(
LLMFactory.ModelScope,
'ModelScope',