diff --git a/CHANGELOG.md b/CHANGELOG.md index 534142f5a..2fe67b9be 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,6 +14,8 @@ metadata and the backend fallback mirror it. ### Fixed +- Call replies use sentence boundaries after their first short utterance (#2628) — thanks @rudycelekli! + - MCP speech tools wait through model loading and progress-extended CPU renders instead of timing out before the backend (#2609) ## [0.5.7] — 2026-10-05 diff --git a/backend/services/sentence_chunker.py b/backend/services/sentence_chunker.py index 10e9c1181..cd2325d2b 100644 --- a/backend/services/sentence_chunker.py +++ b/backend/services/sentence_chunker.py @@ -478,6 +478,7 @@ def _maybe_short_flush(self) -> list[str]: return [] self._buffer = "" + self._is_first_flush = False return [stripped] def _maybe_aggressive_first_flush(self) -> str | None: diff --git a/docs/integrations/calls.md b/docs/integrations/calls.md index e593ae868..f227a72b9 100644 --- a/docs/integrations/calls.md +++ b/docs/integrations/calls.md @@ -196,3 +196,5 @@ turn the other person talked over. resampled to 8 kHz μ-law and sent as 20 ms frames. On barge-in (about 200 ms of the other person's speech while the agent talks), VoiceStudio sends Twilio a `clear` to drop buffered audio. + +Call speech may start at a clause boundary for the first utterance of each turn. After a complete short reply has played, later speech waits for normal sentence boundaries. diff --git a/tests/test_sentence_chunker.py b/tests/test_sentence_chunker.py index b1a92e70e..34f482df9 100644 --- a/tests/test_sentence_chunker.py +++ b/tests/test_sentence_chunker.py @@ -69,3 +69,19 @@ def test_single_sentence_request_stays_whole(): def test_italian_language_disables_aggressive_flush(): chunker = SentenceChunker(language="it", aggressive_first_flush=True) assert chunker._aggressive_first_flush is False + + +def test_short_flush_retires_first_clause_window_until_next_turn(): + chunker = SentenceChunker(aggressive_first_flush=True) + assert chunker.push("Hello!") == ["Hello!"] + second = "Here is a longer follow-up with more information, " + assert chunker.push(second) == [] + assert chunker.flush() == [second.strip()] + assert chunker.push(second) == [second.strip()] + + +def test_short_flush_disabled_threshold_keeps_first_clause_window(): + chunker = SentenceChunker(aggressive_first_flush=True, min_words_for_short_flush=2) + assert chunker.push("Hello!") == [] + rest = " Here is a longer follow-up with more information, " + assert chunker.push(rest) == [("Hello!" + rest).strip()]