diff --git a/changelog/3888.fixed.md b/changelog/3888.fixed.md new file mode 100644 index 000000000..99e9ad0e0 --- /dev/null +++ b/changelog/3888.fixed.md @@ -0,0 +1 @@ +- Fixed turn completion instructions being lost when `LLMMessagesUpdateFrame` replaces the LLM context. When `filter_incomplete_user_turns` is enabled, the turn completion system message is now re-injected after context replacement. diff --git a/src/pipecat/processors/aggregators/llm_response_universal.py b/src/pipecat/processors/aggregators/llm_response_universal.py index c43cc279d..96f3702be 100644 --- a/src/pipecat/processors/aggregators/llm_response_universal.py +++ b/src/pipecat/processors/aggregators/llm_response_universal.py @@ -642,6 +642,9 @@ class LLMUserAggregator(LLMContextAggregator): async def _handle_llm_messages_update(self, frame: LLMMessagesUpdateFrame): self.set_messages(frame.messages) + if self._params.filter_incomplete_user_turns: + config = self._params.user_turn_completion_config or UserTurnCompletionConfig() + self._context.add_message({"role": "system", "content": config.completion_instructions}) if frame.run_llm: await self.push_context_frame() diff --git a/tests/test_context_aggregators_universal.py b/tests/test_context_aggregators_universal.py index e86905e1c..b22abf6c6 100644 --- a/tests/test_context_aggregators_universal.py +++ b/tests/test_context_aggregators_universal.py @@ -50,6 +50,7 @@ from pipecat.turns.user_mute import ( MuteUntilFirstBotCompleteUserMuteStrategy, ) from pipecat.turns.user_stop import SpeechTimeoutUserTurnStopStrategy +from pipecat.turns.user_turn_completion_mixin import UserTurnCompletionConfig from pipecat.turns.user_turn_strategies import UserTurnStrategies USER_TURN_STOP_TIMEOUT = 0.2 @@ -155,6 +156,28 @@ class TestLLMUserAggregator(unittest.IsolatedAsyncioTestCase): ) assert context.messages[0]["content"] == "Hi there!" + async def test_llm_messages_update_reinjects_turn_completion_instructions(self): + context = LLMContext() + params = LLMUserAggregatorParams(filter_incomplete_user_turns=True) + pipeline = Pipeline([LLMUserAggregator(context, params=params)]) + + new_messages = [ + {"role": "system", "content": "You are a helpful assistant."}, + {"role": "user", "content": "Hello!"}, + ] + frames_to_send = [LLMMessagesUpdateFrame(messages=new_messages)] + await run_test( + pipeline, + frames_to_send=frames_to_send, + ) + config = UserTurnCompletionConfig() + # The context should contain the new messages plus the re-injected instructions + assert len(context.messages) == 3 + assert context.messages[0]["content"] == "You are a helpful assistant." + assert context.messages[1]["content"] == "Hello!" + assert context.messages[2]["role"] == "system" + assert context.messages[2]["content"] == config.completion_instructions + async def test_default_user_turn_strategies(self): context = LLMContext() user_aggregator = LLMUserAggregator(