-
Notifications
You must be signed in to change notification settings - Fork 2.8k
feat: dynamic endpointing delay #4720
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| from ..utils import is_given | ||
|
|
||
|
|
||
| class ExponentialMovingAverage: |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We have one here:
| class ExpFilter: |
fa7cffb to
ff0c7b1
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
| if is_given(min_delay): | ||
| self._min_delay = min_delay | ||
| self._utterance_pause.reset(initial=self._min_delay, min_val=self._min_delay) | ||
|
|
||
| if is_given(max_delay): | ||
| self._max_delay = max_delay | ||
| self._turn_pause.reset(initial=self._max_delay, max_val=self._max_delay) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 update_options leaves cross-filter clamp bounds stale
When update_options changes min_delay or max_delay, it only updates the bounds on one of the two ExpFilter instances, leaving the other filter's bounds stale. Both _utterance_pause and _turn_pause are initialized with min_val=min_delay, max_val=max_delay, but update_options only resets one filter per parameter.
Root Cause and Impact
At initialization (endpointing.py:39-44), both filters share the same bounds:
self._utterance_pause = ExpFilter(alpha=alpha, initial=min_delay, min_val=min_delay, max_val=max_delay)
self._turn_pause = ExpFilter(alpha=alpha, initial=max_delay, min_val=min_delay, max_val=max_delay)But update_options only partially updates:
update_options(max_delay=2.0)resets_turn_pausewithmax_val=2.0, but_utterance_pause._max_valstays at the oldmax_delay(e.g.1.0). This meansmin_delay(read from_utterance_pause.value) can never exceed the oldmax_delay, even though the configuredmax_delayhas increased.update_options(min_delay=0.5)resets_utterance_pausewithmin_val=0.5, but_turn_pause._min_valstays at the oldmin_delay(e.g.0.3). This meansmax_delay(read from_turn_pause.value) could drop below the newmin_delay, violating the invariantmax_delay >= min_delay.
Impact: After dynamically updating endpointing delay bounds, the EMA-filtered values can be clamped to incorrect/stale limits, potentially making the dynamic adjustment ineffective or violating the min ≤ max invariant.
| if is_given(min_delay): | |
| self._min_delay = min_delay | |
| self._utterance_pause.reset(initial=self._min_delay, min_val=self._min_delay) | |
| if is_given(max_delay): | |
| self._max_delay = max_delay | |
| self._turn_pause.reset(initial=self._max_delay, max_val=self._max_delay) | |
| if is_given(min_delay): | |
| self._min_delay = min_delay | |
| self._utterance_pause.reset(initial=self._min_delay, min_val=self._min_delay) | |
| self._turn_pause.reset(min_val=self._min_delay) | |
| if is_given(max_delay): | |
| self._max_delay = max_delay | |
| self._turn_pause.reset(initial=self._max_delay, max_val=self._max_delay) | |
| self._utterance_pause.reset(max_val=self._max_delay) | |
Was this helpful? React with 👍 or 👎 to provide feedback.
| if is_given(alpha): | ||
| self._alpha = alpha | ||
| self._filtered = -1.0 | ||
| self._filtered = initial |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🟡 ExpFilter.reset unconditionally clears filtered value even when only updating bounds
The reset method always executes self._filtered = initial (line 30), even when initial is not provided (NOT_GIVEN). This means calling reset(min_val=0.5) to only update the min bound will also discard the accumulated EMA state.
Root Cause and Impact
At exp_filter.py:30, self._filtered = initial runs unconditionally. Since initial defaults to NOT_GIVEN, any call to reset() that doesn't explicitly pass initial will wipe the filtered value:
def reset(self, alpha=NOT_GIVEN, initial=NOT_GIVEN, min_val=NOT_GIVEN, max_val=NOT_GIVEN):
if is_given(alpha):
self._alpha = alpha
self._filtered = initial # always runs — clears EMA state if initial not providedThis is particularly relevant because the suggested fix for BUG-0001 would call self._turn_pause.reset(min_val=self._min_delay) (without initial), which would unintentionally clear the _turn_pause EMA state. Without fixing this, any caller that wants to update only min_val or max_val via reset() will lose the accumulated filter state.
Impact: Accumulated EMA state is lost when reset is called to update only bounds, causing the filter to restart from scratch.
| self._filtered = initial | |
| if is_given(initial): | |
| self._filtered = initial | |
Was this helpful? React with 👍 or 👎 to provide feedback.
Adjust the endpointing delays based on the pause statistics of a running session:
EMA for both
with values capped to the original min and max.