move vad config to json

This commit is contained in:
Xin Wang
2026-05-21 17:23:04 +08:00
parent 6a59df3dbd
commit 872b8ac64a
6 changed files with 135 additions and 1 deletions

View File

@@ -132,6 +132,41 @@ the TTS in the pipeline. `response.text.final` fires when the turn ends,
carrying the full concatenated assistant text and an `interrupted` flag
(true when an `input.text` or barge-in cut the turn short).
### Turn detection
User-turn segmentation (VAD thresholds + how long to wait after silence
before declaring the turn done) is configurable per environment:
```json
"turn": {
"vad": {
"confidence": 0.7,
"start_secs": 0.2,
"stop_secs": 0.6,
"min_volume": 0.6
},
"user_speech_timeout_sec": 1.0
}
```
- `vad.*` maps directly to `pipecat.audio.vad.vad_analyzer.VADParams` and
controls the Silero VAD. `stop_secs` is the duration of silence required
before VAD reports the user stopped speaking; raise it if VAD is
cutting users off mid-clause, lower it for snappier turn-taking.
- `user_speech_timeout_sec` is the additional grace window (used by
`SpeechTimeoutUserTurnStopStrategy`) during which the user may resume
speaking before the aggregator finalizes the turn. The timer is
re-armed every time the user resumes, so brief mid-sentence pauses do
not split one utterance into multiple LLM turns.
The total "user pause before turn ends" budget is approximately
`vad.stop_secs + user_speech_timeout_sec`. The repo defaults are tuned
slightly more conservatively than upstream pipecat to avoid streaming
ASRs (xfyun in particular) producing many short fragments per logical
utterance. Setting this stop strategy explicitly also replaces pipecat's
default Smart Turn v3 analyzer, so the engine no longer loads the
`smart-turn-v3.*-cpu.onnx` model at startup.
### Xfyun ASR
The STT provider can be switched to iFlytek/Xfyun's streaming voice dictation

View File

@@ -12,6 +12,15 @@
"session": {
"inactivity_timeout_sec": 60
},
"turn": {
"vad": {
"confidence": 0.7,
"start_secs": 0.2,
"stop_secs": 0.4,
"min_volume": 0.6
},
"user_speech_timeout_sec": 0.8
},
"agent": {
"system_prompt": "You are a helpful, friendly voice assistant. Keep responses concise and natural for spoken conversation.",
"greeting": "Please introduce yourself briefly.",

View File

@@ -12,6 +12,15 @@
"session": {
"inactivity_timeout_sec": 60
},
"turn": {
"vad": {
"confidence": 0.7,
"start_secs": 0.2,
"stop_secs": 0.6,
"min_volume": 0.6
},
"user_speech_timeout_sec": 1.0
},
"agent": {
"system_prompt": "你是一个有用的语音对话助手名字叫小白",
"greeting": "你好,我是小白,请问有什么可以帮你?",

View File

@@ -12,6 +12,15 @@
"session": {
"inactivity_timeout_sec": 60
},
"turn": {
"vad": {
"confidence": 0.7,
"start_secs": 0.2,
"stop_secs": 0.6,
"min_volume": 0.6
},
"user_speech_timeout_sec": 1.0
},
"agent": {
"system_prompt": "你是一个有用的语音对话助手名字叫小白",
"greeting": "你好,我是小白,请问有什么可以帮你?",

View File

@@ -28,6 +28,39 @@ class SessionConfig:
inactivity_timeout_sec: int = 60
@dataclass(frozen=True)
class VADConfig:
"""Voice Activity Detection thresholds for the Silero analyzer.
These map directly to ``pipecat.audio.vad.vad_analyzer.VADParams``.
Defaults are tuned a touch more conservative than upstream pipecat so
short pauses in continuous speech don't end the user turn prematurely.
"""
confidence: float = 0.7
start_secs: float = 0.2
stop_secs: float = 0.6
min_volume: float = 0.6
@dataclass(frozen=True)
class TurnConfig:
"""User-turn segmentation policy.
``user_speech_timeout_sec`` is the grace window (in seconds) after VAD
has confirmed silence during which the user is allowed to resume
speaking before the aggregator finalizes the turn. Used by
``SpeechTimeoutUserTurnStopStrategy``. Higher = more tolerant of
natural mid-sentence pauses; lower = snappier turn-taking.
The combined "user pause before turn ends" budget is roughly
``vad.stop_secs + user_speech_timeout_sec``.
"""
vad: VADConfig = field(default_factory=VADConfig)
user_speech_timeout_sec: float = 1.0
@dataclass(frozen=True)
class AgentConfig:
system_prompt: str = "You are a helpful, friendly voice assistant."
@@ -90,6 +123,7 @@ class EngineConfig:
server: ServerConfig = field(default_factory=ServerConfig)
audio: AudioConfig = field(default_factory=AudioConfig)
session: SessionConfig = field(default_factory=SessionConfig)
turn: TurnConfig = field(default_factory=TurnConfig)
agent: AgentConfig = field(default_factory=AgentConfig)
services: ServicesConfig = field(default_factory=ServicesConfig)
@@ -116,10 +150,19 @@ def config_from_dict(data: dict) -> EngineConfig:
if stt.get("language") == "":
stt["language"] = None
turn = _dict(data.get("turn"))
vad = _dict(turn.get("vad"))
return EngineConfig(
server=ServerConfig(**_dict(data.get("server"))),
audio=AudioConfig(**_dict(data.get("audio"))),
session=SessionConfig(**_dict(data.get("session"))),
turn=TurnConfig(
vad=VADConfig(**vad),
user_speech_timeout_sec=float(
turn.get("user_speech_timeout_sec", TurnConfig().user_speech_timeout_sec)
),
),
agent=AgentConfig(**agent),
services=ServicesConfig(
llm=LLMConfig(**_dict(services.get("llm"))),

View File

@@ -3,6 +3,7 @@ from __future__ import annotations
from loguru import logger
from pipecat.audio.vad.silero import SileroVADAnalyzer
from pipecat.audio.vad.vad_analyzer import VADParams
from pipecat.frames.frames import (
LLMRunFrame,
OutputTransportMessageUrgentFrame,
@@ -24,6 +25,10 @@ from pipecat.transports.websocket.fastapi import (
FastAPIWebsocketParams,
FastAPIWebsocketTransport,
)
from pipecat.turns.user_stop.speech_timeout_user_turn_stop_strategy import (
SpeechTimeoutUserTurnStopStrategy,
)
from pipecat.turns.user_turn_strategies import UserTurnStrategies
from .config import EngineConfig
from .product_protocol import ProductWebsocketSerializer
@@ -84,9 +89,33 @@ async def run_pipeline_with_serializer(
messages.append({"role": "system", "content": config.agent.greeting})
context = LLMContext(messages)
vad_params = VADParams(
confidence=config.turn.vad.confidence,
start_secs=config.turn.vad.start_secs,
stop_secs=config.turn.vad.stop_secs,
min_volume=config.turn.vad.min_volume,
)
# Replace pipecat's default stop strategy (Smart Turn v3) with a simple
# silence-timeout strategy. Smart Turn v3 was finalizing every short
# Chinese phrase as a complete turn, which caused one logical utterance
# to become several LLM calls and several user bubbles in the UI. The
# timeout strategy waits for `user_speech_timeout_sec` of silence
# (re-armed every time the user resumes speaking) before declaring the
# turn finished — which is what we actually want for streaming ASRs.
user_turn_strategies = UserTurnStrategies(
stop=[
SpeechTimeoutUserTurnStopStrategy(
user_speech_timeout=config.turn.user_speech_timeout_sec,
),
],
)
user_aggregator, assistant_aggregator = LLMContextAggregatorPair(
context,
user_params=LLMUserAggregatorParams(vad_analyzer=SileroVADAnalyzer()),
user_params=LLMUserAggregatorParams(
vad_analyzer=SileroVADAnalyzer(params=vad_params),
user_turn_strategies=user_turn_strategies,
),
)
pipeline = Pipeline(