Conversation
Co-authored-by: devin-ai-integration[bot] <158243242+devin-ai-integration[bot]@users.noreply.github.com>
| def __repr__(self) -> str: | ||
| verdict = self._verdict.result() if self._verdict.done() else None | ||
| return ( | ||
| f"AMD(llm={self._llm.label}, model={self._llm.model}, " | ||
| f"started={self._started}, closed={self._closed}, " | ||
| f"speech_duration={self.speech_duration:.2f}, verdict={verdict!r})" | ||
| ) | ||
|
|
||
| def __str__(self) -> str: | ||
| if self._verdict.done(): | ||
| result = self._verdict.result() | ||
| return f"AMD({result.category}, reason={result.reason})" | ||
| if self._closed: | ||
| return "AMD(closed)" | ||
| if not self._started: | ||
| return "AMD(pending)" | ||
| return "AMD(listening)" |
There was a problem hiding this comment.
🟡 __repr__ and __str__ raise CancelledError after close() cancels the verdict future
AMD.close() at livekit-agents/livekit/agents/voice/amd/base.py:276-277 cancels the _verdict future when it's not done. After cancellation, future.done() returns True but future.result() raises CancelledError. Both __repr__ (line 314) and __str__ (line 323) check self._verdict.done() and then call self._verdict.result(), which will crash with CancelledError. In __str__, the if self._closed branch (line 325) that would return "AMD(closed)" is never reached because the _verdict.done() check comes first and raises before getting there.
| def __repr__(self) -> str: | |
| verdict = self._verdict.result() if self._verdict.done() else None | |
| return ( | |
| f"AMD(llm={self._llm.label}, model={self._llm.model}, " | |
| f"started={self._started}, closed={self._closed}, " | |
| f"speech_duration={self.speech_duration:.2f}, verdict={verdict!r})" | |
| ) | |
| def __str__(self) -> str: | |
| if self._verdict.done(): | |
| result = self._verdict.result() | |
| return f"AMD({result.category}, reason={result.reason})" | |
| if self._closed: | |
| return "AMD(closed)" | |
| if not self._started: | |
| return "AMD(pending)" | |
| return "AMD(listening)" | |
| def __repr__(self) -> str: | |
| verdict = None | |
| if self._verdict.done() and not self._verdict.cancelled(): | |
| verdict = self._verdict.result() | |
| return ( | |
| f"AMD(llm={self._llm.label}, model={self._llm.model}, " | |
| f"started={self._started}, closed={self._closed}, " | |
| f"speech_duration={self.speech_duration:.2f}, verdict={verdict!r})" | |
| ) | |
| def __str__(self) -> str: | |
| if self._verdict.done() and not self._verdict.cancelled(): | |
| result = self._verdict.result() | |
| return f"AMD({result.category}, reason={result.reason})" | |
| if self._closed: | |
| return "AMD(closed)" | |
| if not self._started: | |
| return "AMD(pending)" | |
| return "AMD(listening)" |
Was this helpful? React with 👍 or 👎 to provide feedback.
| if speech_duration > self._human_speech_threshold: | ||
| if self._classify_task is None: | ||
| self._classify_task = asyncio.create_task(self._classify_user_speech()) | ||
|
|
||
| if self._silence_timer is not None: | ||
| self._silence_timer.cancel() | ||
| self._silence_timer = None | ||
| self._silence_timer = asyncio.get_running_loop().call_later( | ||
| max(0, self._machine_silence_threshold - silence_duration), | ||
| partial(self._silence_timer_callback, speech_duration=speech_duration), | ||
| ) |
There was a problem hiding this comment.
🔴 _silence_timer_callback for machine-silence case can leave AMD hanging without emitting a result if _input_ch has no text
When on_user_speech_ended fires with speech_duration > HUMAN_SPEECH_THRESHOLD at livekit-agents/livekit/agents/voice/amd/base.py:167-177, the _classify_task is created (which reads from _input_ch) and a silence timer is scheduled without category/reason. If no transcript text is ever pushed to _input_ch (e.g., STT is slow or unavailable), the _classify_task blocks forever on the empty channel, and the silence timer callback at line 199 sets _machine_silence_reached = True but cannot emit because _verdict is not done. The only resolution is the 20-second _amd_timeout_timer. During those 20 seconds, authorization is paused (_authorization_allowed is cleared), making the agent completely unresponsive — it cannot play any speech. Consider closing the _input_ch or setting a verdict with a fallback category (like "uncertain") in the machine-silence timer callback when the LLM hasn't classified yet, rather than relying solely on the 20s global timeout.
Prompt for agents
In livekit-agents/livekit/agents/voice/amd/base.py, the _silence_timer_callback at line 174-177 for the machine-silence case (long speech) schedules a callback with no category/reason. When the LLM classification hasn't completed by the time this timer fires, the AMD enters a limbo state: _machine_silence_reached is True, but no verdict is set and no event is emitted. The agent's authorization remains paused for up to 20s (AMD_TIMEOUT). Fix this by having the machine-silence timer callback set a fallback verdict (e.g., category='uncertain', reason='machine_silence_no_classification') when the LLM hasn't provided a result yet. This ensures the AMD always emits promptly when the silence threshold is reached, rather than blocking the agent for the full timeout.
Was this helpful? React with 👍 or 👎 to provide feedback.
AMDResultwith categories:human,machine-dtmf,machine-vm,machine-nvm, anduncertain(reserved for time out)examples/dtmf/amd.pySession Interface
Usage