Implement response handling and speech tracking in CallEnd functionality

- Add begin_response and finish_after_current_speech methods to CallEndCoordinator for better management of speech events.
- Update PromptBrain to utilize new methods, ensuring proper handling of generated closing speech and tool-only calls.
- Enhance tests to verify the correct behavior of speech tracking and response handling in various scenarios, including waiting for audio to finish before ending calls.
- Introduce a new test suite for CallEndCoordinator to validate the interaction with speech frames.
This commit is contained in:
Xin Wang
2026-07-14 13:43:42 +08:00
parent d069e5282e
commit e78dc4088a
5 changed files with 151 additions and 12 deletions

View File

@@ -17,6 +17,7 @@ class CallEndCoordinator:
self._ending = False
self._armed = False
self._speaking = False
self._response_speech_started = False
self._finished = False
self._reason = "completed"
@@ -28,7 +29,22 @@ class CallEndCoordinator:
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:
@@ -38,15 +54,14 @@ class CallEndCoordinator:
await self._queue_end(self._reason)
async def observe(self, frame) -> None:
if isinstance(frame, BotStartedSpeakingFrame) and self._armed:
if isinstance(frame, BotStartedSpeakingFrame):
self._speaking = True
elif (
isinstance(frame, BotStoppedSpeakingFrame)
and self._armed
and self._speaking
):
logger.info("结束语播报完毕,挂断通话")
await self.finish()
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):