Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,7 @@ agent = Agent(

**Realtime:** [OpenAI Realtime](https://visionagents.ai/integrations/openai) · [Gemini Live](https://visionagents.ai/integrations/gemini) · [AWS Nova Sonic](https://visionagents.ai/integrations/aws-bedrock) · [Qwen](https://visionagents.ai/integrations/qwen) · [Inworld](https://visionagents.ai/integrations/inworld)

**STT:** [Deepgram](https://visionagents.ai/integrations/deepgram) · [AssemblyAI](https://www.assemblyai.com/docs/streaming/universal-3-pro) · [Fast-Whisper](https://visionagents.ai/integrations/fast-whisper) · [Fish Audio](https://visionagents.ai/integrations/fish) · [Wizper](https://visionagents.ai/integrations/wizper) · [Mistral Voxtral](https://visionagents.ai/integrations/mistral)
**STT:** [Deepgram](https://visionagents.ai/integrations/deepgram) · [AssemblyAI](https://www.assemblyai.com/docs/streaming/universal-3-pro) · [Fast-Whisper](https://visionagents.ai/integrations/fast-whisper) · [FunASR](https://github.com/GetStream/Vision-Agents/tree/main/plugins/funasr) · [Fish Audio](https://visionagents.ai/integrations/fish) · [Wizper](https://visionagents.ai/integrations/wizper) · [Mistral Voxtral](https://visionagents.ai/integrations/mistral)

**TTS:** [ElevenLabs](https://visionagents.ai/integrations/elevenlabs) · [Cartesia](https://visionagents.ai/integrations/cartesia) · [Deepgram](https://visionagents.ai/integrations/deepgram) · [AWS Polly](https://visionagents.ai/integrations/aws-polly) · [Pocket](https://visionagents.ai/integrations/pocket) · [Kokoro](https://visionagents.ai/integrations/kokoro) · [Inworld](https://visionagents.ai/integrations/inworld) · [Fish Audio](https://visionagents.ai/integrations/fish)

Expand Down
1 change: 1 addition & 0 deletions agents-core/pyproject.toml
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ openrouter = ["vision-agents-plugins-openrouter"]
pocket = ["vision-agents-plugins-pocket"]
qwen = ["vision-agents-plugins-qwen"]
fish = ["vision-agents-plugins-fish"]
funasr = ["vision-agents-plugins-funasr"]
fast-whisper = ["vision-agents-plugins-fast-whisper"]
decart = ["vision-agents-plugins-decart"]
twilio = ["vision-agents-plugins-twilio"]
Expand Down
34 changes: 34 additions & 0 deletions plugins/funasr/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,34 @@
# Vision Agents — FunASR STT plugin

Local, self-hosted speech-to-text for [Vision Agents](https://visionagents.ai/),
powered by [FunASR](https://github.com/modelscope/FunASR) (SenseVoice / Fun-ASR-Nano /
Paraformer). Strong on Chinese, Cantonese, Japanese, Korean and more; runs locally on
CPU or CUDA with **no API key**.

## Install

```bash
uv add vision-agents-plugins-funasr
# or: pip install vision-agents-plugins-funasr
```

## Usage

```python
from vision_agents.plugins import funasr

# Default: SenseVoice-Small (fast, multilingual, CPU-friendly)
stt = funasr.STT(model="iic/SenseVoiceSmall", language="auto", device="cpu")

# On a GPU, use the flagship LLM-ASR model:
# stt = funasr.STT(model="FunAudioLLM/Fun-ASR-Nano-2512", device="cuda")
```

| Arg | Default | Description |
|---|---|---|
| `model` | `iic/SenseVoiceSmall` | FunASR model id. Use `FunAudioLLM/Fun-ASR-Nano-2512` (flagship) on GPU, or `paraformer-zh` for Chinese. |
| `language` | `auto` | Language hint, or `auto` to detect. |
| `device` | `cpu` | `cpu` or `cuda`. |
| `use_itn` | `true` | Apply inverse text normalization. |

The model downloads automatically on first run.
Empty file.
15 changes: 15 additions & 0 deletions plugins/funasr/example/funasr_example.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
"""Minimal example: construct the FunASR STT plugin for a Vision Agents pipeline.

The model downloads on first run; on a GPU swap to the flagship Fun-ASR-Nano.
"""

from vision_agents.plugins import funasr


def build_stt():
return funasr.STT(model="iic/SenseVoiceSmall", language="auto", device="cpu")


if __name__ == "__main__":
stt = build_stt()
print(f"FunASR STT ready: provider={stt.provider_name} model={stt.model_id}")
Empty file added plugins/funasr/py.typed
Empty file.
40 changes: 40 additions & 0 deletions plugins/funasr/pyproject.toml
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
[build-system]
requires = ["hatchling", "hatch-vcs"]
build-backend = "hatchling.build"

[project]
name = "vision-agents-plugins-funasr"
dynamic = ["version"]
description = "FunASR (SenseVoice / Fun-ASR-Nano / Paraformer) local STT integration for Vision Agents"
readme = "README.md"
keywords = ["funasr", "sensevoice", "STT", "speech-to-text", "AI", "voice agents", "agents", "chinese"]
requires-python = ">=3.10"
license = "MIT"
dependencies = [
"vision-agents",
"funasr>=1.1.0",
]

[project.urls]
Documentation = "https://visionagents.ai/"
Website = "https://visionagents.ai/"
Source = "https://github.com/GetStream/Vision-Agents"

[tool.hatch.version]
source = "vcs"
raw-options = { root = "..", search_parent_directories = true, fallback_version = "0.0.0" }

[tool.hatch.build.targets.wheel]
packages = ["vision_agents"]

[tool.hatch.build.targets.sdist]
include = ["vision_agents"]

[tool.uv.sources]
vision-agents = { workspace = true }

[dependency-groups]
dev = [
"pytest>=8.4.1",
"pytest-asyncio>=1.0.0",
]
86 changes: 86 additions & 0 deletions plugins/funasr/tests/test_funasr_stt.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
import asyncio

import numpy as np
import pytest
from getstream.video.rtc.track_util import AudioFormat, PcmData

from vision_agents.plugins import funasr


@pytest.fixture
def stt_factory():
instances = []

def create_stt(*args, **kwargs):
stt = funasr.STT(*args, **kwargs)
instances.append(stt)
return stt

yield create_stt

for stt in instances:
asyncio.run(stt.close())


class _FakeAutoModel:
pass


class _ExplodingAutoModel:
def generate(self, **kwargs):
raise AssertionError("unexpected transcription bug")


class _FakeSamples:
def __init__(self, size=0):
self.size = size


class _ExplodingPcmData:
samples = _FakeSamples(size=1)

def resample(self, sample_rate):
raise AssertionError("unexpected audio bug")


class TestSTT:
"""Unit tests for the FunASR STT plugin (no model loaded)."""

def test_construct(self, stt_factory):
"""The plugin constructs and exposes the expected defaults without loading a model."""
stt = stt_factory()
assert stt.provider_name == "funasr"
assert stt.model_id == "iic/SenseVoiceSmall"
assert stt.language == "auto"
assert stt.device == "cpu"
assert stt.use_itn is True

def test_custom_params(self, stt_factory):
stt = stt_factory(
model="FunAudioLLM/Fun-ASR-Nano-2512",
language="zh",
device="cuda",
use_itn=False,
)
assert stt.model_id == "FunAudioLLM/Fun-ASR-Nano-2512"
assert stt.language == "zh"
assert stt.device == "cuda"
assert stt.use_itn is False

def test_process_audio_raises_unexpected_buffer_errors(self, stt_factory):
stt = stt_factory(client=_FakeAutoModel())

with pytest.raises(AssertionError, match="unexpected audio bug"):
asyncio.run(stt.process_audio(_ExplodingPcmData(), participant=object()))

def test_process_buffer_raises_unexpected_transcription_errors(self, stt_factory):
stt = stt_factory(client=_ExplodingAutoModel())
pcm_data = PcmData(
samples=np.zeros(16000 * 8, dtype=np.float32),
sample_rate=16000,
channels=1,
format=AudioFormat.F32,
)

with pytest.raises(AssertionError, match="unexpected transcription bug"):
asyncio.run(stt.process_audio(pcm_data, participant=object()))
3 changes: 3 additions & 0 deletions plugins/funasr/vision_agents/plugins/funasr/__init__.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
from .stt import STT

__all__ = ["STT"]
Loading