Replace llm_completion_user_turn_stop_strategies() with FilterIncompleteUserTurnStrategies

Wrap the detector chain with `deferred(...)` and append the LLM
completion gate via a `UserTurnStrategies` specialization rather than
a free-standing helper, mirroring the existing
`ExternalUserTurnStrategies` pattern. The class lives next to other
strategy containers in `pipecat.turns.user_turn_strategies`, so users
discover it where they're already configuring `user_turn_strategies`.

The deprecated `filter_incomplete_user_turns` flag now rewires
through `FilterIncompleteUserTurnStrategies` under the hood, keeping
the migration path identical to before. `deferred(...)` stays public
as the explicit escape hatch for non-default compositions.
This commit is contained in:
Aleix Conchillo Flaqué
2026-05-06 11:12:32 -07:00
parent e3e90d38aa
commit 952dddca8b
6 changed files with 94 additions and 90 deletions

View File

@@ -85,7 +85,10 @@ from pipecat.turns.user_start import BaseUserTurnStartStrategy, UserTurnStartedP
from pipecat.turns.user_stop import BaseUserTurnStopStrategy, UserTurnStoppedParams
from pipecat.turns.user_turn_completion_mixin import UserTurnCompletionConfig
from pipecat.turns.user_turn_controller import UserTurnController
from pipecat.turns.user_turn_strategies import UserTurnStrategies
from pipecat.turns.user_turn_strategies import (
FilterIncompleteUserTurnStrategies,
UserTurnStrategies,
)
from pipecat.utils.context.llm_context_summarization import (
LLMAutoContextSummarizationConfig,
LLMContextSummarizationConfig,
@@ -123,18 +126,18 @@ class LLMUserAggregatorParams:
has been idle (not speaking) for this duration. Set to 0 to disable
idle detection.
vad_analyzer: Voice Activity Detection analyzer instance.
filter_incomplete_user_turns: [DEPRECATED] Add
:class:`~pipecat.turns.user_stop.LLMTurnCompletionUserTurnStopStrategy`
to ``user_turn_strategies.stop`` instead. When enabled, the LLM
outputs a turn-completion marker at the start of each response:
✓ (complete), ○ (incomplete short), or ◐ (incomplete long).
Incomplete responses are suppressed and timeouts trigger
re-prompting.
filter_incomplete_user_turns: [DEPRECATED] Use
``user_turn_strategies=FilterIncompleteUserTurnStrategies()``
instead. When enabled, the LLM outputs a turn-completion
marker at the start of each response: ✓ (complete), ○
(incomplete short), or ◐ (incomplete long). Incomplete
responses are suppressed and timeouts trigger re-prompting.
user_turn_completion_config: [DEPRECATED] Configuration for turn
completion behavior including custom instructions, timeouts, and
prompts. Only used when filter_incomplete_user_turns is True
(deprecated path) — for the new strategy-based API, pass the config
directly to ``LLMTurnCompletionUserTurnStopStrategy(config=...)``.
directly to ``FilterIncompleteUserTurnStrategies(config=...)``.
"""
add_tool_change_messages: bool = False
@@ -151,16 +154,14 @@ class LLMUserAggregatorParams:
if self.filter_incomplete_user_turns:
warnings.warn(
"LLMUserAggregatorParams.filter_incomplete_user_turns is deprecated. "
"Add LLMTurnCompletionUserTurnStopStrategy to "
"user_turn_strategies.stop instead.",
"Use user_turn_strategies=FilterIncompleteUserTurnStrategies() instead.",
DeprecationWarning,
stacklevel=2,
)
if self.user_turn_completion_config:
warnings.warn(
"LLMUserAggregatorParams.user_turn_completion_config is deprecated. "
"Add LLMTurnCompletionUserTurnStopStrategy to "
"user_turn_strategies.stop instead.",
"Use user_turn_strategies=FilterIncompleteUserTurnStrategies() instead.",
DeprecationWarning,
stacklevel=2,
)
@@ -570,21 +571,15 @@ class LLMUserAggregator(LLMContextAggregator):
user_turn_strategies = self._params.user_turn_strategies or UserTurnStrategies()
# Deprecated path: translate filter_incomplete_user_turns into
# wrapping pre-existing stop strategies with deferred() and
# appending LLMTurnCompletionUserTurnStopStrategy. The
# the equivalent FilterIncompleteUserTurnStrategies wiring. The
# DeprecationWarning is emitted in LLMUserAggregatorParams.__post_init__.
if self._params.filter_incomplete_user_turns:
from pipecat.turns.user_stop import (
LLMTurnCompletionUserTurnStopStrategy,
deferred,
user_turn_strategies = FilterIncompleteUserTurnStrategies(
start=user_turn_strategies.start,
stop=user_turn_strategies.stop,
config=self._params.user_turn_completion_config,
)
existing_stop = list(user_turn_strategies.stop or [])
user_turn_strategies.stop = [deferred(s) for s in existing_stop] + [
LLMTurnCompletionUserTurnStopStrategy(
config=self._params.user_turn_completion_config
)
]
self._params.user_turn_strategies = user_turn_strategies
self._user_is_muted = False
self._user_turn_start_timestamp = ""

View File

@@ -51,42 +51,6 @@ def default_user_turn_stop_strategies() -> list[BaseUserTurnStopStrategy]:
return [TurnAnalyzerUserTurnStopStrategy(turn_analyzer=LocalSmartTurnAnalyzerV3())]
def llm_completion_user_turn_stop_strategies(
strategies: list[BaseUserTurnStopStrategy] | None = None,
*,
config: UserTurnCompletionConfig | None = None,
) -> list[BaseUserTurnStopStrategy]:
"""Build a stop-strategy list gated on the LLM's turn-completion verdict.
Wraps ``strategies`` with :func:`deferred` so they trigger inference
but don't fire ``on_user_turn_stopped`` themselves, then appends
:class:`~pipecat.turns.user_stop.LLMTurnCompletionUserTurnStopStrategy`
as the finalizer. Use as the ``stop`` field of a
:class:`UserTurnStrategies`::
UserTurnStrategies(
stop=llm_completion_user_turn_stop_strategies(),
)
Args:
strategies: Stop strategies that should drive inference. If
None, uses :func:`default_user_turn_stop_strategies`.
config: Optional configuration applied to the LLM via the
``filter_incomplete_user_turns`` setting. Customizes the
turn-completion instructions, incomplete-turn timeouts, and
re-prompts.
Returns:
``[deferred(s) for s in strategies] +
[LLMTurnCompletionUserTurnStopStrategy(config=config)]``.
"""
strategies = strategies if strategies is not None else default_user_turn_stop_strategies()
return [
*(deferred(s) for s in strategies),
LLMTurnCompletionUserTurnStopStrategy(config=config),
]
@dataclass
class UserTurnStrategies:
"""Container for user turn start and stop strategies.
@@ -134,3 +98,57 @@ class ExternalUserTurnStrategies(UserTurnStrategies):
def __post_init__(self):
self.start = [ExternalUserTurnStartStrategy()]
self.stop = [ExternalUserTurnStopStrategy()]
@dataclass
class FilterIncompleteUserTurnStrategies(UserTurnStrategies):
"""Stop strategies gated on the LLM's turn-completion verdict.
The LLM is asked to begin every response with one of three markers:
✓ (complete), ○ (incomplete short), or ◐ (incomplete long). Only ✓
finalizes the user turn; ○ / ◐ keep the turn open so the user can
continue speaking and the LLM can re-evaluate later.
Configuring strategies this way preserves the existing detector
chain (defaults or user-supplied) for inference triggering and
appends :class:`~pipecat.turns.user_stop.LLMTurnCompletionUserTurnStopStrategy`
as the finalizer. The detector strategies are wrapped with
:func:`~pipecat.turns.user_stop.deferred` automatically so they fire
only ``on_user_turn_inference_triggered`` and leave finalization to
the LLM gate.
Parameters:
config: Optional configuration applied to the LLM via the
``filter_incomplete_user_turns`` setting. Customizes the
turn-completion instructions, incomplete-turn timeouts, and
re-prompts. If None, defaults from
:class:`~pipecat.turns.user_turn_completion_mixin.UserTurnCompletionConfig`
are used.
Example::
user_turn_strategies=FilterIncompleteUserTurnStrategies()
# Custom detector chain:
user_turn_strategies=FilterIncompleteUserTurnStrategies(
stop=[SpeechTimeoutUserTurnStopStrategy(...)],
)
# Custom completion config:
user_turn_strategies=FilterIncompleteUserTurnStrategies(
config=UserTurnCompletionConfig(
incomplete_short_timeout=5.0,
incomplete_long_timeout=15.0,
),
)
"""
config: UserTurnCompletionConfig | None = None
def __post_init__(self):
super().__post_init__()
# Defer the detector chain so it only fires inference-triggered,
# then append the LLM gate as the sole finalizer.
gated: list[BaseUserTurnStopStrategy] = [deferred(s) for s in self.stop or []]
gated.append(LLMTurnCompletionUserTurnStopStrategy(config=self.config))
self.stop = gated