fix(workflow): prevent untracked BotStoppedSpeakingFrame from consuming end-node tracked-speech counter
The CallEndCoordinator.observe() decremented _tracked_speeches on every BotStoppedSpeakingFrame, not just the one corresponding to tracked (end-node) speech. When a preceding LLM utterance finished between track_speech() and the actual fixed-speech playback, the counter was consumed prematurely, ending the call before the end-node message played. Gate the decrement behind a _current_speech_is_tracked flag that is set only when BotStartedSpeakingFrame follows a pending tracked-speech start. Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
@@ -27,6 +27,12 @@ class CallEndCoordinator:
|
|||||||
self._finish_after_tracked_speech = False
|
self._finish_after_tracked_speech = False
|
||||||
self._finished = False
|
self._finished = False
|
||||||
self._reason = "completed"
|
self._reason = "completed"
|
||||||
|
# Only BotStoppedSpeakingFrame that follows a tracked BotStartedSpeakingFrame
|
||||||
|
# decrements the tracked-speech counter. This prevents a stale or cross-talk
|
||||||
|
# stop frame (e.g. from a preceding LLM utterance) from consuming the counter
|
||||||
|
# meant for a fixed end-node message.
|
||||||
|
self._pending_tracked_starts = 0
|
||||||
|
self._current_speech_is_tracked = False
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def ending(self) -> bool:
|
def ending(self) -> bool:
|
||||||
@@ -58,6 +64,7 @@ class CallEndCoordinator:
|
|||||||
completion = asyncio.get_running_loop().create_future()
|
completion = asyncio.get_running_loop().create_future()
|
||||||
self._tracked_speech_completions.append(completion)
|
self._tracked_speech_completions.append(completion)
|
||||||
self._tracked_speeches += 1
|
self._tracked_speeches += 1
|
||||||
|
self._pending_tracked_starts += 1
|
||||||
return completion
|
return completion
|
||||||
|
|
||||||
async def arm_after_tracked_speech(self) -> None:
|
async def arm_after_tracked_speech(self) -> None:
|
||||||
@@ -87,14 +94,19 @@ class CallEndCoordinator:
|
|||||||
self._speaking = True
|
self._speaking = True
|
||||||
self._speech_stopped.clear()
|
self._speech_stopped.clear()
|
||||||
self._response_speech_started = True
|
self._response_speech_started = True
|
||||||
|
if self._pending_tracked_starts > 0:
|
||||||
|
self._pending_tracked_starts -= 1
|
||||||
|
self._current_speech_is_tracked = True
|
||||||
elif isinstance(frame, BotStoppedSpeakingFrame) and self._speaking:
|
elif isinstance(frame, BotStoppedSpeakingFrame) and self._speaking:
|
||||||
self._speaking = False
|
self._speaking = False
|
||||||
self._speech_stopped.set()
|
self._speech_stopped.set()
|
||||||
if self._tracked_speeches > 0:
|
if self._current_speech_is_tracked:
|
||||||
self._tracked_speeches -= 1
|
self._current_speech_is_tracked = False
|
||||||
completion = self._tracked_speech_completions.popleft()
|
if self._tracked_speeches > 0:
|
||||||
if not completion.done():
|
self._tracked_speeches -= 1
|
||||||
completion.set_result(None)
|
completion = self._tracked_speech_completions.popleft()
|
||||||
|
if not completion.done():
|
||||||
|
completion.set_result(None)
|
||||||
if (
|
if (
|
||||||
self._finish_after_tracked_speech
|
self._finish_after_tracked_speech
|
||||||
and self._tracked_speeches == 0
|
and self._tracked_speeches == 0
|
||||||
|
|||||||
Reference in New Issue
Block a user