"""Shared call termination timing for prompt tools and workflow end nodes.""" from __future__ import annotations from collections.abc import Awaitable, Callable from loguru import logger from pipecat.frames.frames import BotStartedSpeakingFrame, BotStoppedSpeakingFrame from pipecat.processors.frame_processor import FrameDirection, FrameProcessor class CallEndCoordinator: """End immediately or after the currently armed closing speech finishes.""" def __init__(self, queue_end: Callable[[str], Awaitable[None]]): self._queue_end = queue_end self._ending = False self._armed = False self._speaking = False self._response_speech_started = False self._finished = False self._reason = "completed" @property def ending(self) -> bool: return self._ending def begin(self, reason: str) -> None: self._ending = True self._reason = reason or "completed" def begin_response(self) -> None: """Start tracking speech produced by one LLM response.""" self._response_speech_started = False def arm_after_speech(self) -> None: """Wait for the next observed bot speech to finish.""" self._armed = True async def finish_after_current_speech(self, *, has_text: bool) -> None: """Finish now if speech is absent/done, otherwise wait for its stop.""" if not has_text: await self.finish() return if self._response_speech_started and not self._speaking: await self.finish() return self._armed = True async def finish(self) -> None: if self._finished: return self._finished = True await self._queue_end(self._reason) async def observe(self, frame) -> None: if isinstance(frame, BotStartedSpeakingFrame): self._speaking = True self._response_speech_started = True elif isinstance(frame, BotStoppedSpeakingFrame) and self._speaking: self._speaking = False if self._armed: logger.info("结束语播报完毕,挂断通话") await self.finish() class EndCallAfterSpeechProcessor(FrameProcessor): def __init__(self, coordinator: CallEndCoordinator): super().__init__() self._coordinator = coordinator async def process_frame(self, frame, direction: FrameDirection): await super().process_frame(frame, direction) await self.push_frame(frame, direction) await self._coordinator.observe(frame)