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:
@@ -1 +1 @@
|
|||||||
- Added `llm_completion_user_turn_stop_strategies()` in `pipecat.turns.user_turn_strategies` — a one-call helper that pairs default (or user-supplied) stop strategies, each wrapped with `deferred(...)`, with `LLMTurnCompletionUserTurnStopStrategy`. Common case: `stop=llm_completion_user_turn_stop_strategies()`.
|
- Added `FilterIncompleteUserTurnStrategies` in `pipecat.turns.user_turn_strategies` — a `UserTurnStrategies` specialization that wraps the detector chain with `deferred(...)` and appends `LLMTurnCompletionUserTurnStopStrategy` as the finalizer. Common case: `user_turn_strategies=FilterIncompleteUserTurnStrategies()`. Pass `config=UserTurnCompletionConfig(...)` to customize timeouts and prompts.
|
||||||
|
|||||||
@@ -1 +1 @@
|
|||||||
- Deprecated `LLMUserAggregatorParams.filter_incomplete_user_turns`. Add `LLMTurnCompletionUserTurnStopStrategy` to `user_turn_strategies.stop` instead (typically via `llm_completion_user_turn_stop_strategies()`). Setting the legacy flag still works for one release: the aggregator emits a `DeprecationWarning`, wraps existing stop strategies with `deferred(...)`, and appends the new finalizer strategy.
|
- Deprecated `LLMUserAggregatorParams.filter_incomplete_user_turns`. Use `user_turn_strategies=FilterIncompleteUserTurnStrategies()` (or add `LLMTurnCompletionUserTurnStopStrategy` to a custom `user_turn_strategies.stop`) instead. Setting the legacy flag still works for one release: the aggregator emits a `DeprecationWarning` and rewires the strategies as if you had passed `FilterIncompleteUserTurnStrategies` directly.
|
||||||
|
|||||||
@@ -41,10 +41,7 @@ from pipecat.services.openai.llm import OpenAILLMService
|
|||||||
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
from pipecat.transports.base_transport import BaseTransport, TransportParams
|
||||||
from pipecat.transports.daily.transport import DailyParams
|
from pipecat.transports.daily.transport import DailyParams
|
||||||
from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams
|
from pipecat.transports.websocket.fastapi import FastAPIWebsocketParams
|
||||||
from pipecat.turns.user_turn_strategies import (
|
from pipecat.turns.user_turn_strategies import FilterIncompleteUserTurnStrategies
|
||||||
UserTurnStrategies,
|
|
||||||
llm_completion_user_turn_stop_strategies,
|
|
||||||
)
|
|
||||||
|
|
||||||
load_dotenv(override=True)
|
load_dotenv(override=True)
|
||||||
|
|
||||||
@@ -87,11 +84,11 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
|
|||||||
)
|
)
|
||||||
|
|
||||||
context = LLMContext()
|
context = LLMContext()
|
||||||
# `llm_completion_user_turn_stop_strategies()` pairs the default
|
# `FilterIncompleteUserTurnStrategies` pairs the default detector
|
||||||
# stop strategies with `LLMTurnCompletionUserTurnStopStrategy`:
|
# chain with `LLMTurnCompletionUserTurnStopStrategy`: detectors
|
||||||
# those strategies trigger LLM inference but the public
|
# trigger LLM inference but the public `on_user_turn_stopped` event
|
||||||
# `on_user_turn_stopped` event fires only when the LLM confirms ✓.
|
# fires only when the LLM confirms ✓. The LLM marks each response
|
||||||
# The LLM marks each response with one of:
|
# with one of:
|
||||||
# ✓ = complete (respond normally)
|
# ✓ = complete (respond normally)
|
||||||
# ○ = incomplete short (wait 5s, then prompt)
|
# ○ = incomplete short (wait 5s, then prompt)
|
||||||
# ◐ = incomplete long (wait 15s, then prompt)
|
# ◐ = incomplete long (wait 15s, then prompt)
|
||||||
@@ -99,17 +96,14 @@ async def run_bot(transport: BaseTransport, runner_args: RunnerArguments):
|
|||||||
context,
|
context,
|
||||||
user_params=LLMUserAggregatorParams(
|
user_params=LLMUserAggregatorParams(
|
||||||
vad_analyzer=SileroVADAnalyzer(),
|
vad_analyzer=SileroVADAnalyzer(),
|
||||||
user_turn_strategies=UserTurnStrategies(
|
user_turn_strategies=FilterIncompleteUserTurnStrategies(
|
||||||
stop=llm_completion_user_turn_stop_strategies(),
|
|
||||||
# Optional: customize turn completion behavior
|
# Optional: customize turn completion behavior
|
||||||
# stop=llm_completion_user_turn_stop_strategies(
|
# config=UserTurnCompletionConfig(
|
||||||
# config=UserTurnCompletionConfig(
|
# incomplete_short_timeout=5.0,
|
||||||
# incomplete_short_timeout=5.0,
|
# incomplete_long_timeout=15.0,
|
||||||
# incomplete_long_timeout=15.0,
|
# incomplete_short_prompt="Custom prompt...",
|
||||||
# incomplete_short_prompt="Custom prompt...",
|
# incomplete_long_prompt="Custom prompt...",
|
||||||
# incomplete_long_prompt="Custom prompt...",
|
# instructions="Custom turn completion instructions...",
|
||||||
# instructions="Custom turn completion instructions...",
|
|
||||||
# ),
|
|
||||||
# ),
|
# ),
|
||||||
),
|
),
|
||||||
),
|
),
|
||||||
|
|||||||
@@ -85,7 +85,10 @@ from pipecat.turns.user_start import BaseUserTurnStartStrategy, UserTurnStartedP
|
|||||||
from pipecat.turns.user_stop import BaseUserTurnStopStrategy, UserTurnStoppedParams
|
from pipecat.turns.user_stop import BaseUserTurnStopStrategy, UserTurnStoppedParams
|
||||||
from pipecat.turns.user_turn_completion_mixin import UserTurnCompletionConfig
|
from pipecat.turns.user_turn_completion_mixin import UserTurnCompletionConfig
|
||||||
from pipecat.turns.user_turn_controller import UserTurnController
|
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 (
|
from pipecat.utils.context.llm_context_summarization import (
|
||||||
LLMAutoContextSummarizationConfig,
|
LLMAutoContextSummarizationConfig,
|
||||||
LLMContextSummarizationConfig,
|
LLMContextSummarizationConfig,
|
||||||
@@ -123,18 +126,18 @@ class LLMUserAggregatorParams:
|
|||||||
has been idle (not speaking) for this duration. Set to 0 to disable
|
has been idle (not speaking) for this duration. Set to 0 to disable
|
||||||
idle detection.
|
idle detection.
|
||||||
vad_analyzer: Voice Activity Detection analyzer instance.
|
vad_analyzer: Voice Activity Detection analyzer instance.
|
||||||
filter_incomplete_user_turns: [DEPRECATED] Add
|
filter_incomplete_user_turns: [DEPRECATED] Use
|
||||||
:class:`~pipecat.turns.user_stop.LLMTurnCompletionUserTurnStopStrategy`
|
``user_turn_strategies=FilterIncompleteUserTurnStrategies()``
|
||||||
to ``user_turn_strategies.stop`` instead. When enabled, the LLM
|
instead. When enabled, the LLM outputs a turn-completion
|
||||||
outputs a turn-completion marker at the start of each response:
|
marker at the start of each response: ✓ (complete), ○
|
||||||
✓ (complete), ○ (incomplete short), or ◐ (incomplete long).
|
(incomplete short), or ◐ (incomplete long). Incomplete
|
||||||
Incomplete responses are suppressed and timeouts trigger
|
responses are suppressed and timeouts trigger re-prompting.
|
||||||
re-prompting.
|
|
||||||
user_turn_completion_config: [DEPRECATED] Configuration for turn
|
user_turn_completion_config: [DEPRECATED] Configuration for turn
|
||||||
completion behavior including custom instructions, timeouts, and
|
completion behavior including custom instructions, timeouts, and
|
||||||
prompts. Only used when filter_incomplete_user_turns is True
|
prompts. Only used when filter_incomplete_user_turns is True
|
||||||
(deprecated path) — for the new strategy-based API, pass the config
|
(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
|
add_tool_change_messages: bool = False
|
||||||
@@ -151,16 +154,14 @@ class LLMUserAggregatorParams:
|
|||||||
if self.filter_incomplete_user_turns:
|
if self.filter_incomplete_user_turns:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"LLMUserAggregatorParams.filter_incomplete_user_turns is deprecated. "
|
"LLMUserAggregatorParams.filter_incomplete_user_turns is deprecated. "
|
||||||
"Add LLMTurnCompletionUserTurnStopStrategy to "
|
"Use user_turn_strategies=FilterIncompleteUserTurnStrategies() instead.",
|
||||||
"user_turn_strategies.stop instead.",
|
|
||||||
DeprecationWarning,
|
DeprecationWarning,
|
||||||
stacklevel=2,
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
if self.user_turn_completion_config:
|
if self.user_turn_completion_config:
|
||||||
warnings.warn(
|
warnings.warn(
|
||||||
"LLMUserAggregatorParams.user_turn_completion_config is deprecated. "
|
"LLMUserAggregatorParams.user_turn_completion_config is deprecated. "
|
||||||
"Add LLMTurnCompletionUserTurnStopStrategy to "
|
"Use user_turn_strategies=FilterIncompleteUserTurnStrategies() instead.",
|
||||||
"user_turn_strategies.stop instead.",
|
|
||||||
DeprecationWarning,
|
DeprecationWarning,
|
||||||
stacklevel=2,
|
stacklevel=2,
|
||||||
)
|
)
|
||||||
@@ -570,21 +571,15 @@ class LLMUserAggregator(LLMContextAggregator):
|
|||||||
user_turn_strategies = self._params.user_turn_strategies or UserTurnStrategies()
|
user_turn_strategies = self._params.user_turn_strategies or UserTurnStrategies()
|
||||||
|
|
||||||
# Deprecated path: translate filter_incomplete_user_turns into
|
# Deprecated path: translate filter_incomplete_user_turns into
|
||||||
# wrapping pre-existing stop strategies with deferred() and
|
# the equivalent FilterIncompleteUserTurnStrategies wiring. The
|
||||||
# appending LLMTurnCompletionUserTurnStopStrategy. The
|
|
||||||
# DeprecationWarning is emitted in LLMUserAggregatorParams.__post_init__.
|
# DeprecationWarning is emitted in LLMUserAggregatorParams.__post_init__.
|
||||||
if self._params.filter_incomplete_user_turns:
|
if self._params.filter_incomplete_user_turns:
|
||||||
from pipecat.turns.user_stop import (
|
user_turn_strategies = FilterIncompleteUserTurnStrategies(
|
||||||
LLMTurnCompletionUserTurnStopStrategy,
|
start=user_turn_strategies.start,
|
||||||
deferred,
|
stop=user_turn_strategies.stop,
|
||||||
|
config=self._params.user_turn_completion_config,
|
||||||
)
|
)
|
||||||
|
self._params.user_turn_strategies = user_turn_strategies
|
||||||
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._user_is_muted = False
|
self._user_is_muted = False
|
||||||
self._user_turn_start_timestamp = ""
|
self._user_turn_start_timestamp = ""
|
||||||
|
|||||||
@@ -51,42 +51,6 @@ def default_user_turn_stop_strategies() -> list[BaseUserTurnStopStrategy]:
|
|||||||
return [TurnAnalyzerUserTurnStopStrategy(turn_analyzer=LocalSmartTurnAnalyzerV3())]
|
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
|
@dataclass
|
||||||
class UserTurnStrategies:
|
class UserTurnStrategies:
|
||||||
"""Container for user turn start and stop strategies.
|
"""Container for user turn start and stop strategies.
|
||||||
@@ -134,3 +98,57 @@ class ExternalUserTurnStrategies(UserTurnStrategies):
|
|||||||
def __post_init__(self):
|
def __post_init__(self):
|
||||||
self.start = [ExternalUserTurnStartStrategy()]
|
self.start = [ExternalUserTurnStartStrategy()]
|
||||||
self.stop = [ExternalUserTurnStopStrategy()]
|
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
|
||||||
|
|||||||
@@ -64,7 +64,10 @@ from pipecat.turns.user_mute import (
|
|||||||
MuteUntilFirstBotCompleteUserMuteStrategy,
|
MuteUntilFirstBotCompleteUserMuteStrategy,
|
||||||
)
|
)
|
||||||
from pipecat.turns.user_stop import SpeechTimeoutUserTurnStopStrategy
|
from pipecat.turns.user_stop import SpeechTimeoutUserTurnStopStrategy
|
||||||
from pipecat.turns.user_turn_strategies import UserTurnStrategies
|
from pipecat.turns.user_turn_strategies import (
|
||||||
|
FilterIncompleteUserTurnStrategies,
|
||||||
|
UserTurnStrategies,
|
||||||
|
)
|
||||||
from pipecat.utils.text.base_text_aggregator import AggregationType
|
from pipecat.utils.text.base_text_aggregator import AggregationType
|
||||||
|
|
||||||
USER_TURN_STOP_TIMEOUT = 0.2
|
USER_TURN_STOP_TIMEOUT = 0.2
|
||||||
@@ -179,15 +182,9 @@ class TestLLMUserAggregator(unittest.IsolatedAsyncioTestCase):
|
|||||||
assert context.messages[0]["content"] == "Hi there!"
|
assert context.messages[0]["content"] == "Hi there!"
|
||||||
|
|
||||||
async def test_llm_messages_update_does_not_inject_turn_completion_into_context(self):
|
async def test_llm_messages_update_does_not_inject_turn_completion_into_context(self):
|
||||||
from pipecat.turns.user_turn_strategies import (
|
|
||||||
llm_completion_user_turn_stop_strategies,
|
|
||||||
)
|
|
||||||
|
|
||||||
context = LLMContext()
|
context = LLMContext()
|
||||||
params = LLMUserAggregatorParams(
|
params = LLMUserAggregatorParams(
|
||||||
user_turn_strategies=UserTurnStrategies(
|
user_turn_strategies=FilterIncompleteUserTurnStrategies(),
|
||||||
stop=llm_completion_user_turn_stop_strategies(),
|
|
||||||
),
|
|
||||||
)
|
)
|
||||||
pipeline = Pipeline([LLMUserAggregator(context, params=params)])
|
pipeline = Pipeline([LLMUserAggregator(context, params=params)])
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user