Skip to content

feat(asr): FunASR (SenseVoice) as an opt-in alternative ASR backend (#182) - #191

Merged
debpalash merged 1 commit into
mainfrom
feat/asr-funasr
May 30, 2026
Merged

feat(asr): FunASR (SenseVoice) as an opt-in alternative ASR backend (#182)#191
debpalash merged 1 commit into
mainfrom
feat/asr-funasr

Conversation

@debpalash

@debpalash debpalash commented May 30, 2026

Copy link
Copy Markdown
Owner

Addresses #182 — adds FunASR (SenseVoiceSmall + FSMN-VAD: 50+ languages, punctuation, optional cam++ diarization) as an opt-in alternative to WhisperX (which stays the default).

ASR is already pluggable (ASRBackend ABC + _REGISTRY + /system/asr-backends picker), so this is one new backend:

  • FunASRBackend (id='funasr'): is_available() does a deferred import funasr and reports an install hint when absent (opt-in, not a hard dep — won't bloat the default install); _ensure_model loads AutoModel(SenseVoiceSmall, vad=fsmn-vad); transcribe() normalises output; unload().
  • _normalize_funasr() — pure, defensive normaliser → the {chunks, segments, language} shape the segmenter already consumes (VAD sentence_info w/ ms→s timestamps + optional speaker; single-utterance fallback; strips SenseVoice <|…|> rich tokens). Unit-tested without funasr installed.
  • Registered in _REGISTRYauto-appears in the Settings ASR picker with availability/hint. No frontend change needed.
  • Phase 2 (future): feed FunASR's cam++ speaker ids into dub diarization.

5 tests (normaliser cases, install-hint, registry); router smoke confirms boot; CJK guard ✓.

🤖 Generated with Claude Code

Summary by CodeRabbit

  • New Features
    • Added FunASR ASR backend for speech recognition, supporting SenseVoice/FSMN-VAD models
    • Automatic normalization of transcription output format, including timestamp conversion and speaker metadata handling

Review Change Stack

…182)

FunASR is an all-in-one multilingual ASR (50+ languages, punctuation, optional
cam++ speaker diarization). ASR is already pluggable, so this is a new
ASRBackend:

- FunASRBackend (id 'funasr'): deferred funasr import in is_available() (reports
  an install hint when absent — opt-in, NOT a hard dep); _ensure_model loads
  AutoModel(SenseVoiceSmall + fsmn-vad); transcribe() normalises output.
- _normalize_funasr(): pure, defensive normaliser → OmniVoice's
  {chunks, segments, language} shape (handles VAD sentence_info with ms
  timestamps + optional speaker, single-utterance fallback, strips SenseVoice
  rich tokens). Unit-tested without funasr installed.
- Registered in _REGISTRY → auto-appears in /system/asr-backends → the Settings
  ASR picker, with availability/install hint. WhisperX stays the default.

Phase 2 (future): wire FunASR's cam++ speaker ids into dub diarization.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@coderabbitai

coderabbitai Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor
📝 Walkthrough

Walkthrough

This PR adds FunASRBackend, a new optional ASR backend integrating FunASR (SenseVoice/FSMN-VAD) for transcription. The backend normalizes FunASR output—stripping SenseVoice tags, converting millisecond timestamps to seconds, and mapping speaker metadata—into the project's standard {chunks, segments, language} shape. The backend is registered under id "funasr" and includes comprehensive tests.

Changes

FunASR Backend Integration

Layer / File(s) Summary
FunASR output normalization utilities
backend/services/asr_backend.py
Adds re import and pure helper functions: regex-based tag stripping, millisecond-to-second conversion, segment/chunk extraction with optional speaker labeling, and fallback handling for empty outputs.
FunASRBackend class, registration, and tests
backend/services/asr_backend.py, tests/test_funasr_backend.py
Implements FunASRBackend with model loading, transcribe() method calling FunASR and applying normalization, availability checks, and resource cleanup. Registered under "funasr" and validated by tests covering timestamp conversion, token stripping, speaker mapping, edge cases, and backend wiring.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~10 minutes

Possibly related issues

🚥 Pre-merge checks | ✅ 5
✅ Passed checks (5 passed)
Check name Status Explanation
Title check ✅ Passed The pull request title clearly and specifically describes the main change: adding FunASR as an opt-in alternative ASR backend, directly matching the changeset which adds FunASRBackend and related normalization logic.
Description check ✅ Passed The pull request description is comprehensive and mostly complete, covering the summary, detailed changes, type (new feature), testing approach, and checklist items. However, the description does not explicitly check the required template sections like explicit testing steps, documentation updates, or release cadence confirmation.
Docstring Coverage ✅ Passed No functions found in the changed files to evaluate docstring coverage. Skipping docstring coverage check.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch feat/asr-funasr

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

import torch
if torch.cuda.is_available():
torch.cuda.empty_cache()
except Exception:
@greptile-apps

greptile-apps Bot commented May 30, 2026

Copy link
Copy Markdown
Contributor

Greptile Summary

This PR adds FunASRBackend (SenseVoiceSmall + FSMN-VAD) as an opt-in alternative ASR backend. The implementation is well-structured: the pure _normalize_funasr normalizer is fully unit-tested without requiring funasr installed, the backend plugs cleanly into the existing _REGISTRY / get_active_asr_backend() fallback path, and is_available() correctly reports an install hint when the optional dep is absent.

  • The FunASR helper functions and class are placed between the # ── Registry ── section banner and the _REGISTRY dict, breaking the visual grouping where all backend classes precede the Registry section; the block should move above the banner.
  • The single-utterance timestamp fallback accesses ts[0][0] / ts[-1][1] without an IndexError guard, leaving a narrow crash path if FunASR returns malformed inner timestamp lists; the rest of the normalizer is otherwise robustly defensive.

Confidence Score: 4/5

Safe to merge; FunASR is strictly opt-in and the default WhisperX path is untouched.

The new backend is well isolated: it only activates when a user explicitly sets OMNIVOICE_ASR_BACKEND=funasr, the normalizer is pure and tested, and registry integration uses the existing fallback path. The two items worth a second look are the code placement (FunASR class inside the Registry section rather than before it) and the unguarded ts[0][0]/ts[-1][1] indexing in the single-utterance fallback, which would crash on malformed FunASR output rather than returning an empty result.

backend/services/asr_backend.py — code placement and the timestamp indexing in _normalize_funasr's fallback branch.

Important Files Changed

Filename Overview
backend/services/asr_backend.py Adds FunASR/SenseVoice backend with module-level normalizer helpers; code is placed between the Registry section banner and the _REGISTRY dict rather than alongside the other backends; minor robustness gap in the single-utterance timestamp path.
tests/test_funasr_backend.py Five pure-normalizer and registration tests; correctly skippable without funasr installed; doesn't cover the malformed timestamp edge case or unload().

Sequence Diagram

sequenceDiagram
    participant Caller as Caller (segmentation)
    participant GAB as get_active_asr_backend()
    participant FunASR as FunASRBackend
    participant AM as AutoModel (funasr)
    participant Norm as _normalize_funasr()

    Caller->>GAB: "bid = "funasr""
    GAB->>GAB: not matched by hardcoded ids
    GAB->>GAB: _REGISTRY["funasr"]()
    GAB-->>Caller: FunASRBackend instance

    Caller->>FunASR: transcribe(audio_path)
    FunASR->>FunASR: _ensure_model()
    FunASR->>AM: "AutoModel(SenseVoiceSmall, vad=fsmn-vad)"
    AM-->>FunASR: self._model

    FunASR->>AM: "generate(input, cache={}, language="auto", use_itn=True)"
    AM-->>FunASR: raw result (list with sentence_info / text)

    FunASR->>Norm: _normalize_funasr(res)
    Note over Norm: Prefers sentence_info (VAD segments)<br/>Falls back to single-utterance text
    Norm-->>FunASR: "{chunks, segments, language}"

    FunASR-->>Caller: "{chunks, segments, language}"
Loading

Fix All in Claude Code

Reviews (1): Last reviewed commit: "feat(asr): add FunASR (SenseVoice) as an..." | Re-trigger Greptile

Comment on lines 785 to +788
# ── Registry ────────────────────────────────────────────────────────────────


# ── FunASR (SenseVoice — all-in-one multilingual, opt-in alternative, #182) ──

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The FunASR implementation (module helpers + class) is inserted after the # ── Registry ── section banner, making it visually part of the Registry section rather than grouped with the other backend classes. Every other backend lives above the banner; the FunASR block should follow the same layout so contributors scanning the file for backends see them all in one place.

Suggested change
# ── Registry ────────────────────────────────────────────────────────────────
# ── FunASR (SenseVoice — all-in-one multilingual, opt-in alternative, #182) ──
# ── FunASR (SenseVoice — all-in-one multilingual, opt-in alternative, #182) ──

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

Comment on lines +836 to +838
ts = item.get("timestamp") or [] # [[start_ms, end_ms], ...]
start = _ms_to_s(ts[0][0]) if ts else 0.0
end = _ms_to_s(ts[-1][1]) if ts else None

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 The single-utterance fallback accesses ts[0][0] and ts[-1][1] directly. If FunASR ever returns a timestamp list with empty inner lists (e.g. [[]]), both accesses raise IndexError which is not caught by _ms_to_s — the exception propagates out of transcribe(). Wrapping in a try/except (IndexError, TypeError) keeps the same defensive posture as the rest of the normalizer.

Suggested change
ts = item.get("timestamp") or [] # [[start_ms, end_ms], ...]
start = _ms_to_s(ts[0][0]) if ts else 0.0
end = _ms_to_s(ts[-1][1]) if ts else None
ts = item.get("timestamp") or [] # [[start_ms, end_ms], ...]
try:
start = _ms_to_s(ts[0][0]) if ts else 0.0
end = _ms_to_s(ts[-1][1]) if ts else None
except (IndexError, TypeError):
start, end = 0.0, None

Fix in Claude Code

Comment on lines +882 to +883
import gc
gc.collect()

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P2 import gc deferred inside unload()gc is a zero-cost stdlib module with no install footprint, so deferring it is unnecessary. The convention throughout this file is to place stdlib imports at the top (os, re, logging). Move import gc there and drop the inline import.

Note: If this suggestion doesn't match your team's coding style, reply to this and let me know. I'll remember it for next time!

Fix in Claude Code

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@backend/services/asr_backend.py`:
- Around line 855-872: Update the FunASR model loading to use the Hugging Face
hub and HF repo IDs: change default values for _model_name and _vad_model to the
HF mirrors "FunAudioLLM/SenseVoiceSmall" and "funasr/fsmn-vad" (respectively)
and, inside _ensure_model, call funasr.AutoModel with hub="hf" in addition to
model=self._model_name and vad_model=self._vad_model (keep disable_update=True).
This ensures AutoModel uses HF mirrors; update any env var docs if present to
reflect the new default repo IDs.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro Plus

Run ID: d74b804c-9747-452a-b8f6-ac2b2bfc1c6f

📥 Commits

Reviewing files that changed from the base of the PR and between 7d04200 and 71c46a6.

📒 Files selected for processing (2)
  • backend/services/asr_backend.py
  • tests/test_funasr_backend.py

Comment on lines +855 to +872
self._model_name = os.environ.get("ASR_MODEL_FUNASR", "iic/SenseVoiceSmall")
self._vad_model = os.environ.get("ASR_FUNASR_VAD", "fsmn-vad")
self._model = None

@classmethod
def is_available(cls) -> tuple[bool, str]:
try:
import funasr # noqa: F401
return True, "ready"
except ImportError:
return False, "funasr not installed. Install with: uv pip install funasr"

def _ensure_model(self):
if self._model is not None:
return
from funasr import AutoModel
logger.info("FunASR loading %s (vad=%s)", self._model_name, self._vad_model)
self._model = AutoModel(model=self._model_name, vad_model=self._vad_model, disable_update=True)

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

Does funasr AutoModel support a hub="hf" parameter to download models from HuggingFace instead of ModelScope, and what are the HuggingFace repo ids for SenseVoiceSmall and fsmn-vad?

💡 Result:

Yes, the FunASR AutoModel explicitly supports the hub="hf" parameter, which allows you to download models from the Hugging Face Hub instead of the default ModelScope [1][2][3]. When using AutoModel, setting hub="hf" tells the loader to fetch the specified model repository from Hugging Face [4][5]. The Hugging Face repository IDs for the requested models are: - SenseVoiceSmall: FunAudioLLM/SenseVoiceSmall [6][7] - fsmn-vad: funasr/fsmn-vad [5] Example usage for loading a model from Hugging Face: from funasr import AutoModel model = AutoModel( model="FunAudioLLM/SenseVoiceSmall", hub="hf", vad_model="funasr/fsmn-vad", trust_remote_code=True, device="cuda:0")

Citations:


Pin FunASR downloads to HuggingFace (and switch to HF mirror repo IDs)

funasr.AutoModel supports hub="hf", which routes model downloads to Hugging Face instead of the default ModelScope behavior. If you set hub="hf" in backend/services/asr_backend.py (_ensure_model), also update the repo IDs to the HF mirrors—FunAudioLLM/SenseVoiceSmall and funasr/fsmn-vad—since the current defaults (iic/SenseVoiceSmall, fsmn-vad) are ModelScope-style and may not resolve on HF.

♻️ Pin downloads to HuggingFace
-        self._model = AutoModel(model=self._model_name, vad_model=self._vad_model, disable_update=True)
+        self._model = AutoModel(
+            model=self._model_name, vad_model=self._vad_model,
+            hub="hf", disable_update=True,
+        )
🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@backend/services/asr_backend.py` around lines 855 - 872, Update the FunASR
model loading to use the Hugging Face hub and HF repo IDs: change default values
for _model_name and _vad_model to the HF mirrors "FunAudioLLM/SenseVoiceSmall"
and "funasr/fsmn-vad" (respectively) and, inside _ensure_model, call
funasr.AutoModel with hub="hf" in addition to model=self._model_name and
vad_model=self._vad_model (keep disable_update=True). This ensures AutoModel
uses HF mirrors; update any env var docs if present to reflect the new default
repo IDs.

@debpalash
debpalash merged commit e9bb451 into main May 30, 2026
15 checks passed
@debpalash
debpalash deleted the feat/asr-funasr branch May 30, 2026 23:05
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants