turn on/off openai vad
This commit is contained in:
@@ -20,13 +20,12 @@ from pipecat.processors.aggregators.openai_llm_context import (
|
|||||||
OpenAILLMContext,
|
OpenAILLMContext,
|
||||||
)
|
)
|
||||||
from pipecat.services.openai_realtime_beta import (
|
from pipecat.services.openai_realtime_beta import (
|
||||||
InputAudioTranscription,
|
|
||||||
OpenAILLMServiceRealtimeBeta,
|
OpenAILLMServiceRealtimeBeta,
|
||||||
SessionProperties,
|
SessionProperties,
|
||||||
TurnDetection,
|
|
||||||
)
|
)
|
||||||
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
from pipecat.transports.services.daily import DailyParams, DailyTransport
|
||||||
from pipecat.vad.silero import SileroVADAnalyzer
|
from pipecat.vad.silero import SileroVADAnalyzer
|
||||||
|
from pipecat.vad.vad_analyzer import VADParams
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
@@ -76,15 +75,19 @@ async def main():
|
|||||||
audio_out_enabled=True,
|
audio_out_enabled=True,
|
||||||
audio_out_sample_rate=24000,
|
audio_out_sample_rate=24000,
|
||||||
transcription_enabled=False,
|
transcription_enabled=False,
|
||||||
vad_enabled=False,
|
vad_enabled=True,
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
vad_analyzer=SileroVADAnalyzer(params=VADParams(stop_secs=0.8)),
|
||||||
vad_audio_passthrough=True,
|
vad_audio_passthrough=True,
|
||||||
),
|
),
|
||||||
)
|
)
|
||||||
|
|
||||||
session_properties = SessionProperties(
|
session_properties = SessionProperties(
|
||||||
input_audio_transcription=InputAudioTranscription(),
|
# input_audio_transcription=InputAudioTranscription(),
|
||||||
turn_detection=TurnDetection(silence_duration_ms=1000),
|
# Set openai TurnDetection parameters. Not setting this at all will turn it
|
||||||
|
# on by default
|
||||||
|
# turn_detection=TurnDetection(silence_duration_ms=1000),
|
||||||
|
# Or set to False to disable openai turn detection and use transport VAD
|
||||||
|
turn_detection=False,
|
||||||
tools=tools,
|
tools=tools,
|
||||||
instructions="""
|
instructions="""
|
||||||
Your knowledge cutoff is 2023-10. You are a helpful and friendly AI.
|
Your knowledge cutoff is 2023-10. You are a helpful and friendly AI.
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ import json
|
|||||||
import uuid
|
import uuid
|
||||||
from typing import Any, Dict, List, Literal, Optional, Union
|
from typing import Any, Dict, List, Literal, Optional, Union
|
||||||
|
|
||||||
|
from loguru import logger
|
||||||
from pydantic import BaseModel, Field
|
from pydantic import BaseModel, Field
|
||||||
|
|
||||||
#
|
#
|
||||||
@@ -27,7 +28,8 @@ class SessionProperties(BaseModel):
|
|||||||
input_audio_format: Optional[Literal["pcm16", "g711_ulaw", "g711_alaw"]] = None
|
input_audio_format: Optional[Literal["pcm16", "g711_ulaw", "g711_alaw"]] = None
|
||||||
output_audio_format: Optional[Literal["pcm16", "g711_ulaw", "g711_alaw"]] = None
|
output_audio_format: Optional[Literal["pcm16", "g711_ulaw", "g711_alaw"]] = None
|
||||||
input_audio_transcription: Optional[InputAudioTranscription] = None
|
input_audio_transcription: Optional[InputAudioTranscription] = None
|
||||||
turn_detection: Optional[TurnDetection] = None
|
# set turn_detection to False to disable turn detection
|
||||||
|
turn_detection: Optional[Union[TurnDetection, bool]] = Field(default=None)
|
||||||
tools: Optional[List[Dict]] = None
|
tools: Optional[List[Dict]] = None
|
||||||
tool_choice: Optional[Literal["auto", "none", "required"]] = None
|
tool_choice: Optional[Literal["auto", "none", "required"]] = None
|
||||||
temperature: Optional[float] = None
|
temperature: Optional[float] = None
|
||||||
@@ -100,6 +102,17 @@ class SessionUpdateEvent(ClientEvent):
|
|||||||
type: Literal["session.update"] = "session.update"
|
type: Literal["session.update"] = "session.update"
|
||||||
session: SessionProperties
|
session: SessionProperties
|
||||||
|
|
||||||
|
def model_dump(self, *args, **kwargs) -> Dict[str, Any]:
|
||||||
|
logger.debug(f"!!! SessionUpdateEvent.model_dump: {self}")
|
||||||
|
dump = super().model_dump(*args, **kwargs)
|
||||||
|
|
||||||
|
# Handle turn_detection so that False is serialized as null
|
||||||
|
if "turn_detection" in dump["session"]:
|
||||||
|
if dump["session"]["turn_detection"] is False:
|
||||||
|
dump["session"]["turn_detection"] = None
|
||||||
|
|
||||||
|
return dump
|
||||||
|
|
||||||
|
|
||||||
class InputAudioBufferAppendEvent(ClientEvent):
|
class InputAudioBufferAppendEvent(ClientEvent):
|
||||||
type: Literal["input_audio_buffer.append"] = "input_audio_buffer.append"
|
type: Literal["input_audio_buffer.append"] = "input_audio_buffer.append"
|
||||||
|
|||||||
@@ -123,7 +123,7 @@ class OpenAILLMServiceRealtimeBeta(LLMService):
|
|||||||
session_properties: events.SessionProperties = events.SessionProperties(),
|
session_properties: events.SessionProperties = events.SessionProperties(),
|
||||||
start_audio_paused: bool = False,
|
start_audio_paused: bool = False,
|
||||||
send_transcription_frames: bool = True,
|
send_transcription_frames: bool = True,
|
||||||
send_user_started_speaking_frames: bool = True,
|
send_user_started_speaking_frames: bool = False,
|
||||||
**kwargs,
|
**kwargs,
|
||||||
):
|
):
|
||||||
super().__init__(base_url=base_url, **kwargs)
|
super().__init__(base_url=base_url, **kwargs)
|
||||||
@@ -133,6 +133,7 @@ class OpenAILLMServiceRealtimeBeta(LLMService):
|
|||||||
self._session_properties = session_properties
|
self._session_properties = session_properties
|
||||||
self._audio_input_paused = start_audio_paused
|
self._audio_input_paused = start_audio_paused
|
||||||
self._send_transcription_frames = send_transcription_frames
|
self._send_transcription_frames = send_transcription_frames
|
||||||
|
# todo: wire _send_user_started_speaking_frames up correctly
|
||||||
self._send_user_started_speaking_frames = send_user_started_speaking_frames
|
self._send_user_started_speaking_frames = send_user_started_speaking_frames
|
||||||
self._websocket = None
|
self._websocket = None
|
||||||
self._receive_task = None
|
self._receive_task = None
|
||||||
@@ -158,7 +159,7 @@ class OpenAILLMServiceRealtimeBeta(LLMService):
|
|||||||
await self._disconnect()
|
await self._disconnect()
|
||||||
|
|
||||||
async def send_client_event(self, event: events.ClientEvent):
|
async def send_client_event(self, event: events.ClientEvent):
|
||||||
await self._ws_send(event.dict(exclude_none=True))
|
await self._ws_send(event.model_dump(exclude_none=True))
|
||||||
|
|
||||||
async def _ws_send(self, realtime_message):
|
async def _ws_send(self, realtime_message):
|
||||||
try:
|
try:
|
||||||
@@ -359,9 +360,6 @@ class OpenAILLMServiceRealtimeBeta(LLMService):
|
|||||||
context = self._context
|
context = self._context
|
||||||
messages = context.get_unsent_messages()
|
messages = context.get_unsent_messages()
|
||||||
context.update_all_messages_sent()
|
context.update_all_messages_sent()
|
||||||
logger.debug(
|
|
||||||
f"Sending message context updates: {context._marker} {context.get_messages_for_logging()}"
|
|
||||||
)
|
|
||||||
|
|
||||||
items = []
|
items = []
|
||||||
for m in messages:
|
for m in messages:
|
||||||
@@ -401,10 +399,20 @@ class OpenAILLMServiceRealtimeBeta(LLMService):
|
|||||||
await self.send_client_event(events.InputAudioBufferAppendEvent(audio=payload))
|
await self.send_client_event(events.InputAudioBufferAppendEvent(audio=payload))
|
||||||
|
|
||||||
async def _handle_interruption(self, frame):
|
async def _handle_interruption(self, frame):
|
||||||
|
await self.send_client_event(events.InputAudioBufferClearEvent())
|
||||||
|
await self.send_client_event(events.ResponseCancelEvent())
|
||||||
await self.stop_all_metrics()
|
await self.stop_all_metrics()
|
||||||
await self.push_frame(LLMFullResponseEndFrame())
|
await self.push_frame(LLMFullResponseEndFrame())
|
||||||
await self.push_frame(TTSStoppedFrame())
|
await self.push_frame(TTSStoppedFrame())
|
||||||
# todo: track whether a response is in progress and cancel it with a response.cancela nd input_audio_buffer.clear (?)
|
|
||||||
|
async def _handle_user_started_speaking(self, frame):
|
||||||
|
pass
|
||||||
|
|
||||||
|
async def _handle_user_stopped_speaking(self, frame):
|
||||||
|
if self._session_properties.turn_detection is None:
|
||||||
|
await self.send_client_event(events.InputAudioBufferCommitEvent())
|
||||||
|
await self.send_client_event(events.ResponseCreateEvent())
|
||||||
|
pass
|
||||||
|
|
||||||
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
async def process_frame(self, frame: Frame, direction: FrameDirection):
|
||||||
await super().process_frame(frame, direction)
|
await super().process_frame(frame, direction)
|
||||||
@@ -422,6 +430,10 @@ class OpenAILLMServiceRealtimeBeta(LLMService):
|
|||||||
await self._send_user_audio(frame)
|
await self._send_user_audio(frame)
|
||||||
elif isinstance(frame, StartInterruptionFrame):
|
elif isinstance(frame, StartInterruptionFrame):
|
||||||
await self._handle_interruption(frame)
|
await self._handle_interruption(frame)
|
||||||
|
elif isinstance(frame, UserStartedSpeakingFrame):
|
||||||
|
await self._handle_user_started_speaking(frame)
|
||||||
|
elif isinstance(frame, UserStoppedSpeakingFrame):
|
||||||
|
await self._handle_user_stopped_speaking(frame)
|
||||||
elif isinstance(frame, _InternalMessagesUpdateFrame):
|
elif isinstance(frame, _InternalMessagesUpdateFrame):
|
||||||
self._context = frame.context
|
self._context = frame.context
|
||||||
await self._send_messages_context_update()
|
await self._send_messages_context_update()
|
||||||
|
|||||||
Reference in New Issue
Block a user