move vad config to json
This commit is contained in:
35
README.md
35
README.md
@@ -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
|
carrying the full concatenated assistant text and an `interrupted` flag
|
||||||
(true when an `input.text` or barge-in cut the turn short).
|
(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
|
### Xfyun ASR
|
||||||
|
|
||||||
The STT provider can be switched to iFlytek/Xfyun's streaming voice dictation
|
The STT provider can be switched to iFlytek/Xfyun's streaming voice dictation
|
||||||
|
|||||||
@@ -12,6 +12,15 @@
|
|||||||
"session": {
|
"session": {
|
||||||
"inactivity_timeout_sec": 60
|
"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": {
|
"agent": {
|
||||||
"system_prompt": "You are a helpful, friendly voice assistant. Keep responses concise and natural for spoken conversation.",
|
"system_prompt": "You are a helpful, friendly voice assistant. Keep responses concise and natural for spoken conversation.",
|
||||||
"greeting": "Please introduce yourself briefly.",
|
"greeting": "Please introduce yourself briefly.",
|
||||||
|
|||||||
@@ -12,6 +12,15 @@
|
|||||||
"session": {
|
"session": {
|
||||||
"inactivity_timeout_sec": 60
|
"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": {
|
"agent": {
|
||||||
"system_prompt": "你是一个有用的语音对话助手名字叫小白",
|
"system_prompt": "你是一个有用的语音对话助手名字叫小白",
|
||||||
"greeting": "你好,我是小白,请问有什么可以帮你?",
|
"greeting": "你好,我是小白,请问有什么可以帮你?",
|
||||||
|
|||||||
@@ -12,6 +12,15 @@
|
|||||||
"session": {
|
"session": {
|
||||||
"inactivity_timeout_sec": 60
|
"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": {
|
"agent": {
|
||||||
"system_prompt": "你是一个有用的语音对话助手名字叫小白",
|
"system_prompt": "你是一个有用的语音对话助手名字叫小白",
|
||||||
"greeting": "你好,我是小白,请问有什么可以帮你?",
|
"greeting": "你好,我是小白,请问有什么可以帮你?",
|
||||||
|
|||||||
@@ -28,6 +28,39 @@ class SessionConfig:
|
|||||||
inactivity_timeout_sec: int = 60
|
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)
|
@dataclass(frozen=True)
|
||||||
class AgentConfig:
|
class AgentConfig:
|
||||||
system_prompt: str = "You are a helpful, friendly voice assistant."
|
system_prompt: str = "You are a helpful, friendly voice assistant."
|
||||||
@@ -90,6 +123,7 @@ class EngineConfig:
|
|||||||
server: ServerConfig = field(default_factory=ServerConfig)
|
server: ServerConfig = field(default_factory=ServerConfig)
|
||||||
audio: AudioConfig = field(default_factory=AudioConfig)
|
audio: AudioConfig = field(default_factory=AudioConfig)
|
||||||
session: SessionConfig = field(default_factory=SessionConfig)
|
session: SessionConfig = field(default_factory=SessionConfig)
|
||||||
|
turn: TurnConfig = field(default_factory=TurnConfig)
|
||||||
agent: AgentConfig = field(default_factory=AgentConfig)
|
agent: AgentConfig = field(default_factory=AgentConfig)
|
||||||
services: ServicesConfig = field(default_factory=ServicesConfig)
|
services: ServicesConfig = field(default_factory=ServicesConfig)
|
||||||
|
|
||||||
@@ -116,10 +150,19 @@ def config_from_dict(data: dict) -> EngineConfig:
|
|||||||
if stt.get("language") == "":
|
if stt.get("language") == "":
|
||||||
stt["language"] = None
|
stt["language"] = None
|
||||||
|
|
||||||
|
turn = _dict(data.get("turn"))
|
||||||
|
vad = _dict(turn.get("vad"))
|
||||||
|
|
||||||
return EngineConfig(
|
return EngineConfig(
|
||||||
server=ServerConfig(**_dict(data.get("server"))),
|
server=ServerConfig(**_dict(data.get("server"))),
|
||||||
audio=AudioConfig(**_dict(data.get("audio"))),
|
audio=AudioConfig(**_dict(data.get("audio"))),
|
||||||
session=SessionConfig(**_dict(data.get("session"))),
|
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),
|
agent=AgentConfig(**agent),
|
||||||
services=ServicesConfig(
|
services=ServicesConfig(
|
||||||
llm=LLMConfig(**_dict(services.get("llm"))),
|
llm=LLMConfig(**_dict(services.get("llm"))),
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from __future__ import annotations
|
|||||||
from loguru import logger
|
from loguru import logger
|
||||||
|
|
||||||
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
from pipecat.audio.vad.silero import SileroVADAnalyzer
|
||||||
|
from pipecat.audio.vad.vad_analyzer import VADParams
|
||||||
from pipecat.frames.frames import (
|
from pipecat.frames.frames import (
|
||||||
LLMRunFrame,
|
LLMRunFrame,
|
||||||
OutputTransportMessageUrgentFrame,
|
OutputTransportMessageUrgentFrame,
|
||||||
@@ -24,6 +25,10 @@ from pipecat.transports.websocket.fastapi import (
|
|||||||
FastAPIWebsocketParams,
|
FastAPIWebsocketParams,
|
||||||
FastAPIWebsocketTransport,
|
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 .config import EngineConfig
|
||||||
from .product_protocol import ProductWebsocketSerializer
|
from .product_protocol import ProductWebsocketSerializer
|
||||||
@@ -84,9 +89,33 @@ async def run_pipeline_with_serializer(
|
|||||||
messages.append({"role": "system", "content": config.agent.greeting})
|
messages.append({"role": "system", "content": config.agent.greeting})
|
||||||
|
|
||||||
context = LLMContext(messages)
|
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(
|
user_aggregator, assistant_aggregator = LLMContextAggregatorPair(
|
||||||
context,
|
context,
|
||||||
user_params=LLMUserAggregatorParams(vad_analyzer=SileroVADAnalyzer()),
|
user_params=LLMUserAggregatorParams(
|
||||||
|
vad_analyzer=SileroVADAnalyzer(params=vad_params),
|
||||||
|
user_turn_strategies=user_turn_strategies,
|
||||||
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
pipeline = Pipeline(
|
pipeline = Pipeline(
|
||||||
|
|||||||
Reference in New Issue
Block a user