Make on_voicemail_detected callback required, cleanup logging
This commit is contained in:
@@ -59,7 +59,7 @@ async def handle_voicemail(processor):
|
|||||||
processor: The VoicemailProcessor instance. processor.push_frame() is
|
processor: The VoicemailProcessor instance. processor.push_frame() is
|
||||||
available to push frames.
|
available to push frames.
|
||||||
"""
|
"""
|
||||||
logger.info("Voicemail detected! Playing greeting...")
|
logger.info("Voicemail detected! Leaving a message...")
|
||||||
|
|
||||||
# Push frames using standard Pipecat pattern
|
# Push frames using standard Pipecat pattern
|
||||||
await processor.push_frame(
|
await processor.push_frame(
|
||||||
|
|||||||
@@ -120,7 +120,6 @@ class ClassifierGate(FrameProcessor):
|
|||||||
|
|
||||||
if self._gate_opened:
|
if self._gate_opened:
|
||||||
self._gate_opened = False
|
self._gate_opened = False
|
||||||
logger.debug(f"{self}: Gate closed - classification complete")
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
logger.debug(f"{self}: Gate task was cancelled")
|
logger.debug(f"{self}: Gate task was cancelled")
|
||||||
raise
|
raise
|
||||||
@@ -200,7 +199,6 @@ class ConversationGate(FrameProcessor):
|
|||||||
|
|
||||||
if self._gate_opened:
|
if self._gate_opened:
|
||||||
self._gate_opened = False
|
self._gate_opened = False
|
||||||
logger.debug(f"{self}: Conversation gate closed - voicemail detected")
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
logger.debug(f"{self}: Conversation gate task was cancelled")
|
logger.debug(f"{self}: Conversation gate task was cancelled")
|
||||||
raise
|
raise
|
||||||
@@ -288,7 +286,6 @@ class ClassificationProcessor(FrameProcessor):
|
|||||||
# Begin aggregating a new LLM response
|
# Begin aggregating a new LLM response
|
||||||
self._processing_response = True
|
self._processing_response = True
|
||||||
self._response_buffer = ""
|
self._response_buffer = ""
|
||||||
logger.debug(f"{self}: Starting LLM response aggregation")
|
|
||||||
|
|
||||||
elif isinstance(frame, LLMFullResponseEndFrame):
|
elif isinstance(frame, LLMFullResponseEndFrame):
|
||||||
# Complete response received - make classification decision
|
# Complete response received - make classification decision
|
||||||
@@ -300,21 +297,16 @@ class ClassificationProcessor(FrameProcessor):
|
|||||||
elif isinstance(frame, LLMTextFrame) and self._processing_response:
|
elif isinstance(frame, LLMTextFrame) and self._processing_response:
|
||||||
# Accumulate text tokens from the streaming LLM response
|
# Accumulate text tokens from the streaming LLM response
|
||||||
self._response_buffer += frame.text
|
self._response_buffer += frame.text
|
||||||
logger.trace(f"{self}: Buffer: '{self._response_buffer}'")
|
|
||||||
|
|
||||||
elif isinstance(frame, UserStartedSpeakingFrame):
|
elif isinstance(frame, UserStartedSpeakingFrame):
|
||||||
# User started speaking - cancel voicemail callback timer
|
# User started speaking - cancel voicemail callback timer
|
||||||
if self._voicemail_callback_task:
|
if self._voicemail_callback_task:
|
||||||
logger.debug(f"{self}: User started speaking, cancelling voicemail callback")
|
|
||||||
await self.cancel_task(self._voicemail_callback_task)
|
await self.cancel_task(self._voicemail_callback_task)
|
||||||
self._voicemail_callback_task = None
|
self._voicemail_callback_task = None
|
||||||
|
|
||||||
elif isinstance(frame, UserStoppedSpeakingFrame):
|
elif isinstance(frame, UserStoppedSpeakingFrame):
|
||||||
# User stopped speaking - restart voicemail callback timer if voicemail detected
|
# User stopped speaking - restart voicemail callback timer if voicemail detected
|
||||||
if self._voicemail_detected and not self._voicemail_callback_task:
|
if self._voicemail_detected and not self._voicemail_callback_task:
|
||||||
logger.debug(
|
|
||||||
f"{self}: User stopped speaking, restarting voicemail callback timer ({self._voicemail_response_delay}s)"
|
|
||||||
)
|
|
||||||
self._voicemail_callback_task = self.create_task(self._delayed_voicemail_callback())
|
self._voicemail_callback_task = self.create_task(self._delayed_voicemail_callback())
|
||||||
|
|
||||||
else:
|
else:
|
||||||
@@ -333,11 +325,10 @@ class ClassificationProcessor(FrameProcessor):
|
|||||||
full_response: The complete aggregated response text from the LLM.
|
full_response: The complete aggregated response text from the LLM.
|
||||||
"""
|
"""
|
||||||
if self._decision_made:
|
if self._decision_made:
|
||||||
logger.debug(f"{self}: Decision already made, ignoring response")
|
|
||||||
return
|
return
|
||||||
|
|
||||||
response = full_response.upper()
|
response = full_response.upper()
|
||||||
logger.info(f"{self}: Classifying response: '{full_response}'")
|
logger.debug(f"{self}: Classifying response: '{full_response}'")
|
||||||
|
|
||||||
if "CONVERSATION" in response:
|
if "CONVERSATION" in response:
|
||||||
# Human answered - continue normal conversation flow
|
# Human answered - continue normal conversation flow
|
||||||
@@ -360,14 +351,11 @@ class ClassificationProcessor(FrameProcessor):
|
|||||||
# Always start the callback timer immediately
|
# Always start the callback timer immediately
|
||||||
# It will be cancelled and restarted if user starts/stops speaking
|
# It will be cancelled and restarted if user starts/stops speaking
|
||||||
if not self._voicemail_callback_task:
|
if not self._voicemail_callback_task:
|
||||||
logger.debug(
|
|
||||||
f"{self}: Starting voicemail callback timer ({self._voicemail_response_delay}s)"
|
|
||||||
)
|
|
||||||
self._voicemail_callback_task = self.create_task(self._delayed_voicemail_callback())
|
self._voicemail_callback_task = self.create_task(self._delayed_voicemail_callback())
|
||||||
|
|
||||||
else:
|
else:
|
||||||
# Unexpected response - log warning but don't crash
|
# This can happen if the LLM is interrupted before completing the response
|
||||||
logger.warning(f"{self}: Unexpected classification response: '{full_response}'")
|
logger.debug(f"{self}: No classification found: '{full_response}'")
|
||||||
|
|
||||||
async def _delayed_voicemail_callback(self):
|
async def _delayed_voicemail_callback(self):
|
||||||
"""Execute the voicemail callback after the configured delay.
|
"""Execute the voicemail callback after the configured delay.
|
||||||
@@ -377,20 +365,16 @@ class ClassificationProcessor(FrameProcessor):
|
|||||||
based on user speech patterns to ensure proper timing.
|
based on user speech patterns to ensure proper timing.
|
||||||
"""
|
"""
|
||||||
try:
|
try:
|
||||||
logger.debug(
|
|
||||||
f"{self}: Waiting {self._voicemail_response_delay}s before voicemail callback"
|
|
||||||
)
|
|
||||||
await asyncio.sleep(self._voicemail_response_delay)
|
await asyncio.sleep(self._voicemail_response_delay)
|
||||||
|
|
||||||
logger.info(f"{self}: Executing voicemail callback")
|
|
||||||
if self._on_voicemail_detected:
|
if self._on_voicemail_detected:
|
||||||
try:
|
try:
|
||||||
|
logger.debug(f"{self}: Executing voicemail callback")
|
||||||
await self._on_voicemail_detected(self)
|
await self._on_voicemail_detected(self)
|
||||||
except Exception as e:
|
except Exception as e:
|
||||||
logger.exception(f"{self}: Error in voicemail callback: {e}")
|
logger.exception(f"{self}: Error in voicemail callback: {e}")
|
||||||
|
|
||||||
except asyncio.CancelledError:
|
except asyncio.CancelledError:
|
||||||
logger.debug(f"{self}: Voicemail callback timer was cancelled")
|
|
||||||
raise
|
raise
|
||||||
finally:
|
finally:
|
||||||
self._voicemail_callback_task = None
|
self._voicemail_callback_task = None
|
||||||
@@ -589,21 +573,15 @@ Respond with ONLY "CONVERSATION" if a person answered, or "VOICEMAIL" if it's vo
|
|||||||
self,
|
self,
|
||||||
*,
|
*,
|
||||||
llm: LLMService,
|
llm: LLMService,
|
||||||
|
on_voicemail_detected: Callable[["ClassificationProcessor"], Awaitable[None]],
|
||||||
|
voicemail_response_delay: float = 2.0,
|
||||||
system_prompt: Optional[str] = None,
|
system_prompt: Optional[str] = None,
|
||||||
on_voicemail_detected: Optional[
|
|
||||||
Callable[["ClassificationProcessor"], Awaitable[None]]
|
|
||||||
] = None,
|
|
||||||
voicemail_response_delay: Optional[float] = 2.0,
|
|
||||||
):
|
):
|
||||||
"""Initialize the voicemail detector with classification and buffering components.
|
"""Initialize the voicemail detector with classification and buffering components.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
llm: LLM service used for voicemail vs conversation classification.
|
llm: LLM service used for voicemail vs conversation classification.
|
||||||
Should be fast and reliable for real-time classification.
|
Should be fast and reliable for real-time classification.
|
||||||
system_prompt: Optional custom system prompt for classification. If None,
|
|
||||||
uses the default prompt optimized for outbound calling scenarios.
|
|
||||||
Custom prompts should instruct the LLM to respond with exactly
|
|
||||||
"CONVERSATION" or "VOICEMAIL" for proper detection functionality.
|
|
||||||
on_voicemail_detected: Optional callback function invoked when voicemail
|
on_voicemail_detected: Optional callback function invoked when voicemail
|
||||||
is detected. Receives the ClassificationProcessor instance which can be
|
is detected. Receives the ClassificationProcessor instance which can be
|
||||||
used to push frames (like custom voicemail greetings).
|
used to push frames (like custom voicemail greetings).
|
||||||
@@ -611,6 +589,10 @@ Respond with ONLY "CONVERSATION" if a person answered, or "VOICEMAIL" if it's vo
|
|||||||
before triggering the voicemail callback. This allows voicemail
|
before triggering the voicemail callback. This allows voicemail
|
||||||
responses to be played back after a short delay to ensure the response
|
responses to be played back after a short delay to ensure the response
|
||||||
occurs during the voicemail recording. Default is 2.0 seconds.
|
occurs during the voicemail recording. Default is 2.0 seconds.
|
||||||
|
system_prompt: Optional custom system prompt for classification. If None,
|
||||||
|
uses the default prompt optimized for outbound calling scenarios.
|
||||||
|
Custom prompts should instruct the LLM to respond with exactly
|
||||||
|
"CONVERSATION" or "VOICEMAIL" for proper detection functionality.
|
||||||
"""
|
"""
|
||||||
self._classifier_llm = llm
|
self._classifier_llm = llm
|
||||||
self._prompt = system_prompt if system_prompt is not None else self.DEFAULT_SYSTEM_PROMPT
|
self._prompt = system_prompt if system_prompt is not None else self.DEFAULT_SYSTEM_PROMPT
|
||||||
@@ -640,7 +622,7 @@ Respond with ONLY "CONVERSATION" if a person answered, or "VOICEMAIL" if it's vo
|
|||||||
# Create the processor components
|
# Create the processor components
|
||||||
self._classifier_gate = ClassifierGate(self._gate_notifier)
|
self._classifier_gate = ClassifierGate(self._gate_notifier)
|
||||||
self._conversation_gate = ConversationGate(self._voicemail_notifier)
|
self._conversation_gate = ConversationGate(self._voicemail_notifier)
|
||||||
self._voicemail_processor = ClassificationProcessor(
|
self._classification_processor = ClassificationProcessor(
|
||||||
gate_notifier=self._gate_notifier,
|
gate_notifier=self._gate_notifier,
|
||||||
conversation_notifier=self._conversation_notifier,
|
conversation_notifier=self._conversation_notifier,
|
||||||
voicemail_notifier=self._voicemail_notifier,
|
voicemail_notifier=self._voicemail_notifier,
|
||||||
@@ -658,7 +640,7 @@ Respond with ONLY "CONVERSATION" if a person answered, or "VOICEMAIL" if it's vo
|
|||||||
self._classifier_gate,
|
self._classifier_gate,
|
||||||
self._context_aggregator.user(),
|
self._context_aggregator.user(),
|
||||||
self._classifier_llm,
|
self._classifier_llm,
|
||||||
self._voicemail_processor,
|
self._classification_processor,
|
||||||
self._context_aggregator.assistant(),
|
self._context_aggregator.assistant(),
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
|
|||||||
Reference in New Issue
Block a user