feat: add postprocess hotword correction for AutoModel.generate() (#2959)#3108
feat: add postprocess hotword correction for AutoModel.generate() (#2959)#3108lixuanqun wants to merge 2 commits into
Conversation
…delscope#2959) Implement text-level hotword replacement after ASR decoding so large vocabularies can be corrected without model-level hotword injection. Refs modelscope#2959 Co-authored-by: Cursor <cursoragent@cursor.com>
There was a problem hiding this comment.
Code Review
This pull request introduces a text-level postprocess hotword correction feature to FunASR, allowing users to perform explicit or fuzzy-match text replacements on ASR decoding results. It adds documentation, integrates the correction logic into the generate pipeline, and includes comprehensive unit tests. The review feedback suggests caching the optional dependencies (pypinyin and rapidfuzz) globally to avoid repeated import overhead during fuzzy matching, and validating that the threshold parameter is within the valid range of [0.0, 1.0].
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
| def _require_pypinyin(): | ||
| try: | ||
| from pypinyin import Style, lazy_pinyin | ||
| except ImportError as exc: | ||
| raise ImportError( | ||
| "postprocess hotword fuzzy matching requires pypinyin. " | ||
| "Install it with: pip install pypinyin" | ||
| ) from exc | ||
| return lazy_pinyin, Style |
There was a problem hiding this comment.
In Python, performing local imports and handling exceptions repeatedly inside a nested loop (such as the sliding window in _apply_fuzzy) can introduce significant overhead. Caching the imported lazy_pinyin and Style globally after the first successful import avoids this overhead and improves performance.
_LAZY_PINYIN = None
_PINYIN_STYLE = None
def _require_pypinyin():
global _LAZY_PINYIN, _PINYIN_STYLE
if _LAZY_PINYIN is None:
try:
from pypinyin import Style, lazy_pinyin
_LAZY_PINYIN = lazy_pinyin
_PINYIN_STYLE = Style
except ImportError as exc:
raise ImportError(
"postprocess hotword fuzzy matching requires pypinyin. "
"Install it with: pip install pypinyin"
) from exc
return _LAZY_PINYIN, _PINYIN_STYLE| def _require_rapidfuzz(): | ||
| try: | ||
| from rapidfuzz import fuzz | ||
| except ImportError as exc: | ||
| raise ImportError( | ||
| "postprocess hotword fuzzy matching requires rapidfuzz. " | ||
| "Install it with: pip install rapidfuzz" | ||
| ) from exc | ||
| return fuzz |
There was a problem hiding this comment.
Similarly to _require_pypinyin, caching the imported fuzz module globally avoids repeated local imports and try-except checks, ensuring consistent performance.
| def _require_rapidfuzz(): | |
| try: | |
| from rapidfuzz import fuzz | |
| except ImportError as exc: | |
| raise ImportError( | |
| "postprocess hotword fuzzy matching requires rapidfuzz. " | |
| "Install it with: pip install rapidfuzz" | |
| ) from exc | |
| return fuzz | |
| _RAPIDFUZZ_FUZZ = None | |
| def _require_rapidfuzz(): | |
| global _RAPIDFUZZ_FUZZ | |
| if _RAPIDFUZZ_FUZZ is None: | |
| try: | |
| from rapidfuzz import fuzz | |
| _RAPIDFUZZ_FUZZ = fuzz | |
| except ImportError as exc: | |
| raise ImportError( | |
| "postprocess hotword fuzzy matching requires rapidfuzz. " | |
| "Install it with: pip install rapidfuzz" | |
| ) from exc | |
| return _RAPIDFUZZ_FUZZ |
| enable_fuzzy: bool = True, | ||
| ): | ||
| self.explicit_map = dict(explicit_map or {}) | ||
| self.threshold = float(threshold) |
There was a problem hiding this comment.
To prevent unexpected behavior or silent failures, it is recommended to validate that the postprocess_hotword_threshold is within the valid range of [0.0, 1.0].
| self.threshold = float(threshold) | |
| self.threshold = float(threshold) | |
| if not (0.0 <= self.threshold <= 1.0): | |
| raise ValueError(f"postprocess_hotword_threshold must be between 0.0 and 1.0, got {threshold}") |
Cache optional pypinyin/rapidfuzz imports and validate threshold range. Co-authored-by: Cursor <cursoragent@cursor.com>
|
Addressed the review feedback: cached optional \pypinyin/ |
Summary
Add text-level postprocess hotword correction to
AutoModel.generate()for issue #2959.Model-level
hotword/hotwordsonly works well for a small prompt set. This change addspostprocess_hotwordsandpostprocess_hotword_fileso large vocabularies (for example thousands of stock names) can be corrected after decoding on any ASR model.v1 scope follows the maintainer checklist:
funasr/utils/postprocess_hotwords.pygenerate()call after ASR / punctuation / ITN outputtextand sentence-leveltext/sentenceonlyreturn_postprocess_hotword_matchespypinyin,rapidfuzzType of change
Validation
python -m compileall funasr examples testspython -m unittest tests.test_postprocess_hotwords -v(10 tests)docs/tutorial/README.mdanddocs/tutorial/README_zh.md)User impact
ASR users with large domain vocabularies can correct recognition output without changing the underlying model or runtime services. Explicit mappings work with no extra dependencies; fuzzy correction is opt-in.
Notes for reviewers
asr-hotwordphoneme pipeline if needed.Made with Cursor