Add official FunASR runtime websocket service

This commit is contained in:
Xulilong 2026-07-16 09:41:24 +08:00
parent f3d207a6fb
commit 4ec8bfb93b
4 changed files with 81 additions and 25 deletions

View File

@ -43,7 +43,8 @@ docker compose down
| --- | --- | --- | --- |
| Speakr | `voice-speakr` | `8899` | 页面和后端 |
| ASR | `asr-test` | `59805` | Paraformer HTTP / WebSocket 识别 |
| 官方实时 ASR | `funasr-online` | `10096` | FunASR 官方实时 WebSocket |
| Python 实时 ASR | `funasr-online` | `10096` | FunASR Python WebSocket保留用于回退 |
| 官方 Runtime 实时 ASR | `funasr-runtime-2pass` | `10097` | FunASR 官方 C++/ONNX 2pass WebSocket |
| 声纹提取 | `voice-dected` | `18000` | `/extract_embedding` 声纹向量服务 |
| 实时桥接 | `fastapi-wss` | `10095` | 页面实时 ASR WebSocket 桥接 |
| Redis | `voice-redis` | `16380` | 实时桥接的缓存/会话存储 |
@ -86,7 +87,9 @@ docker compose up -d
```env
ASR_BASE_URL=http://asr-test:59805
FUNASR_WEBSOCKET_IP=ws://fastapi-wss:10095/websocket_offline
TARGET_WS_URL=ws://funasr-online:10096
TARGET_WS_URL=ws://funasr-runtime-2pass:10095
# 如需回退到 Python 版实时 ASR可改为
# TARGET_WS_URL=ws://funasr-online:10096
VOICE_DETECTED_URL=http://voice-dected:8000
SPEAKER_DET_URL=http://voice-dected:8000/extract_embedding
REDIS_URL=redis://redis:6379/0

View File

@ -14,8 +14,10 @@ TEXT_MODEL_API_KEY=none
# Realtime WebSocket bridge
FUNASR_WEBSOCKET_IP=ws://fastapi-wss:10095/websocket_offline
TARGET_WS_URL=ws://funasr-online:10096
REDIS_URL=redis://redis:6379/0
TARGET_WS_URL=ws://funasr-runtime-2pass:10095
# Optional A/B target for the official C++/ONNX FunASR runtime service:
# TARGET_WS_URL=ws://funasr-online:10096
REDIS_URL=redis://redis:6379/0
LLM_API_KEY=none
LLM_BASE_URL=http://192.168.0.46:59800/v1
LLM_MODEL=46-qwen3.5-35B

View File

@ -94,9 +94,9 @@ services:
- "1"
funasr-online:
image: paraformer-asr:latest
container_name: funasr-online
funasr-online:
image: paraformer-asr:latest
container_name: funasr-online
restart: unless-stopped
runtime: nvidia
gpus:
@ -153,23 +153,53 @@ services:
- --keyfile
- ""
funasr-runtime-2pass:
image: registry.cn-hangzhou.aliyuncs.com/funasr_repo/funasr:funasr-runtime-sdk-online-cpu-0.1.13
container_name: funasr-runtime-2pass
restart: unless-stopped
working_dir: /workspace/FunASR/runtime
volumes:
- ./deploy/data/funasr-runtime-models:/workspace/models
ports:
- "10097:10095"
command:
- bash
- -lc
- >
touch /workspace/models/hotwords.txt &&
exec /workspace/FunASR/runtime/websocket/build/bin/funasr-wss-server-2pass
--download-model-dir /workspace/models
--vad-dir damo/speech_fsmn_vad_zh-cn-16k-common-onnx
--model-dir damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-onnx
--online-model-dir damo/speech_paraformer-large_asr_nat-zh-cn-16k-common-vocab8404-online-onnx
--punc-dir damo/punc_ct-transformer_zh-cn-common-vad_realtime-vocab272727-onnx
--lm-dir damo/speech_ngram_lm_zh-cn-ai-wesp-fst
--itn-dir thuduj12/fst_itn_zh
--decoder-thread-num 4
--model-thread-num 1
--io-thread-num 1
--port 10095
--hotword /workspace/models/hotwords.txt
--certfile ""
--keyfile ""
fastapi-wss:
image: fastapi-wss:runtime
container_name: fastapi-wss
image: fastapi-wss:runtime
container_name: fastapi-wss
restart: unless-stopped
working_dir: /app/src
env_file:
- ./config.env
environment:
TARGET_WS_URL: ws://funasr-online:10096
REDIS_URL: redis://redis:6379/0
env_file:
- ./config.env
environment:
TARGET_WS_URL: ws://funasr-runtime-2pass:10095
REDIS_URL: redis://redis:6379/0
volumes:
- ./fastapi_wss:/app
ports:
- "10095:10095"
depends_on:
- funasr-online
- redis
depends_on:
- funasr-runtime-2pass
- redis
command:
- python
- a2a_wss.py

View File

@ -19,6 +19,7 @@ import os
import websockets
import re
import logging
from contextlib import asynccontextmanager
from pathlib import Path
from typing import List, Optional, Dict, Set
from dataclasses import dataclass
@ -789,6 +790,33 @@ def build_role_enriched_message(speaker_msg: Dict, role_result: Dict) -> Dict:
# 5. WebSocket 处理与任务追踪
# ========================
@asynccontextmanager
async def connect_target_asr():
connect_options = {
"close_timeout": 10,
"ping_interval": None,
"ping_timeout": None,
"max_queue": 1024,
}
try:
target_ws = await websockets.connect(
TARGET_WS_URL,
**connect_options,
subprotocols=["binary"],
)
except websockets.exceptions.InvalidMessage as exc:
logger.warning(
"ASR WebSocket binary subprotocol handshake failed, retrying without subprotocol: %s",
exc,
)
target_ws = await websockets.connect(TARGET_WS_URL, **connect_options)
try:
yield target_ws
finally:
await target_ws.close()
async def forward_audio(websocket):
"""
WebSocket 连接处理函数核心入口
@ -824,14 +852,7 @@ async def forward_audio(websocket):
try:
# 连接到目标 ASR 服务
# 添加连接参数以提高鲁棒性
async with websockets.connect(
TARGET_WS_URL,
close_timeout=10, # 关闭超时
ping_interval=None,
ping_timeout=None,
max_queue=1024, # 消息队列大小
subprotocols=["binary"],
) as target_ws:
async with connect_target_asr() as target_ws:
logger.info(f"已连接到 ASR 服务: {TARGET_WS_URL}")
# 发送 ASR 配置双通道模式PCM 音频格式)