From 444cbb64991c0bb67a42df46320b0919dee16fd6 Mon Sep 17 00:00:00 2001 From: Paul Kompfner Date: Fri, 13 Feb 2026 10:31:25 -0500 Subject: [PATCH] Add turn-completion fields to LLMSettings and handle them in the typed-service-settings path. `filter_incomplete_user_turns` and `user_turn_completion_config` were only handled in the legacy dict-based `_update_settings` code path. This adds them to `LLMSettings` and introduces `LLMService._update_settings_from_typed` so the typed path handles them too. --- src/pipecat/services/llm_service.py | 25 ++++++++++++++++++++++++- src/pipecat/services/settings.py | 16 +++++++++++++++- 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/src/pipecat/services/llm_service.py b/src/pipecat/services/llm_service.py index 77af50f15..3a6e64def 100644 --- a/src/pipecat/services/llm_service.py +++ b/src/pipecat/services/llm_service.py @@ -59,7 +59,7 @@ from pipecat.processors.aggregators.llm_response import ( from pipecat.processors.aggregators.openai_llm_context import OpenAILLMContext from pipecat.processors.frame_processor import FrameDirection from pipecat.services.ai_service import AIService -from pipecat.services.settings import ServiceSettings +from pipecat.services.settings import LLMSettings, ServiceSettings, is_given from pipecat.turns.user_turn_completion_mixin import UserTurnCompletionLLMServiceMixin from pipecat.utils.context.llm_context_summarization import ( LLMContextSummarizationUtil, @@ -311,6 +311,29 @@ class LLMService(UserTurnCompletionLLMServiceMixin, AIService): await self._cancel_sequential_runner_task() await self._cancel_summary_task() + async def _update_settings_from_typed(self, update: LLMSettings) -> set[str]: + """Apply a typed settings update, handling turn-completion fields. + + Args: + update: A typed LLM settings delta. + + Returns: + Set of field names whose values actually changed. + """ + changed = await super()._update_settings_from_typed(update) + + if "filter_incomplete_user_turns" in changed: + self._filter_incomplete_user_turns = self._settings.filter_incomplete_user_turns + logger.info( + f"{self}: Incomplete turn filtering " + f"{'enabled' if self._filter_incomplete_user_turns else 'disabled'}" + ) + + if "user_turn_completion_config" in changed and self._filter_incomplete_user_turns: + self.set_user_turn_completion_config(self._settings.user_turn_completion_config) + + return changed + async def _update_settings(self, settings: Mapping[str, Any]): """Update LLM service settings. diff --git a/src/pipecat/services/settings.py b/src/pipecat/services/settings.py index fbec5cdf8..718996984 100644 --- a/src/pipecat/services/settings.py +++ b/src/pipecat/services/settings.py @@ -31,10 +31,13 @@ from __future__ import annotations import copy from dataclasses import dataclass, field, fields -from typing import Any, ClassVar, Dict, Mapping, Optional, Set, Type, TypeVar +from typing import TYPE_CHECKING, Any, ClassVar, Dict, Mapping, Optional, Set, Type, TypeVar from loguru import logger +if TYPE_CHECKING: + from pipecat.turns.user_turn_completion_mixin import UserTurnCompletionConfig + # --------------------------------------------------------------------------- # NOT_GIVEN sentinel # --------------------------------------------------------------------------- @@ -258,6 +261,13 @@ class LLMSettings(ServiceSettings): frequency_penalty: Frequency penalty. presence_penalty: Presence penalty. seed: Random seed for reproducibility. + filter_incomplete_user_turns: Enable LLM-based turn completion detection + to suppress bot responses when the user was cut off mid-thought. + See ``examples/foundational/22-filter-incomplete-turns.py`` and + ``UserTurnCompletionLLMServiceMixin``. + user_turn_completion_config: Configuration for turn completion behavior + when ``filter_incomplete_user_turns`` is enabled. Controls timeouts + and prompts for incomplete turns. """ temperature: Any = field(default_factory=lambda: NOT_GIVEN) @@ -267,6 +277,10 @@ class LLMSettings(ServiceSettings): frequency_penalty: Any = field(default_factory=lambda: NOT_GIVEN) presence_penalty: Any = field(default_factory=lambda: NOT_GIVEN) seed: Any = field(default_factory=lambda: NOT_GIVEN) + filter_incomplete_user_turns: bool | _NotGiven = field(default_factory=lambda: NOT_GIVEN) + user_turn_completion_config: UserTurnCompletionConfig | _NotGiven = field( + default_factory=lambda: NOT_GIVEN + ) @dataclass