From a0393b9af6df3e600d26d0669f1fe28208015c86 Mon Sep 17 00:00:00 2001 From: Paul Kompfner Date: Fri, 20 Mar 2026 15:25:39 -0400 Subject: [PATCH] Fix: warn on system_instruction conflict even with single system message When the only message in context was a system message, _extract_initial_system_or_developer would convert it to "user" (to prevent empty history) without warning about the conflict with system_instruction. Now warns inline before converting, with a message explaining both the conflict and the user-role conversion. --- src/pipecat/adapters/base_llm_adapter.py | 9 +++++++++ tests/test_get_llm_invocation_params.py | 17 +++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/src/pipecat/adapters/base_llm_adapter.py b/src/pipecat/adapters/base_llm_adapter.py index aad8664e7..6242d373e 100644 --- a/src/pipecat/adapters/base_llm_adapter.py +++ b/src/pipecat/adapters/base_llm_adapter.py @@ -188,6 +188,15 @@ class BaseLLMAdapter(ABC, Generic[TLLMInvocationParams]): # Would extracting empty the list? Convert to "user" instead. if len(messages) == 1: + if role == "system" and system_instruction: + if not self._warned_system_instruction: + self._warned_system_instruction = True + logger.warning( + "Both system_instruction and a system message in context are set." + " Using system_instruction. The system message in context is being" + " converted to a user message to avoid sending an empty conversation" + " history." + ) messages[0]["role"] = "user" return None, None diff --git a/tests/test_get_llm_invocation_params.py b/tests/test_get_llm_invocation_params.py index 22e950db9..b695cde59 100644 --- a/tests/test_get_llm_invocation_params.py +++ b/tests/test_get_llm_invocation_params.py @@ -2002,6 +2002,23 @@ class TestBaseLLMAdapterHelpers(unittest.TestCase): self.assertEqual(len(messages), 1) # not popped self.assertEqual(messages[0]["role"], "user") + def test_single_system_message_with_system_instruction_warns(self): + """Single system message + system_instruction still warns even though content isn't extracted.""" + messages = [ + {"role": "system", "content": "Be helpful."}, + ] + + adapter = OpenAILLMAdapter() + with patch("pipecat.adapters.base_llm_adapter.logger") as mock_logger: + content, role = adapter._extract_initial_system_or_developer( + messages, system_instruction="Be concise." + ) + mock_logger.warning.assert_called_once() + + self.assertIsNone(content) + self.assertIsNone(role) + self.assertEqual(messages[0]["role"], "user") + def test_non_system_message_ignored(self): """Non-system/developer first message is ignored.""" messages = [