Repository: deepset-ai/haystack

Issue:
Add warning to `PromptBuilder` and `ChatPromptBuilder` if no `required_variables` is set
**Is your feature request related to a problem? Please describe.**
Most of our components require some (or all) inputs during runtime. For our components whose inputs are based on Jinja2 templates (e.g. `ConditionalRouter`, `OutputAdpater`, `PromptBuilder`, and `ChatPromptBuilder`) we differ on how we treat whether all Jinja2 variables are required or are optional by default. For example, the components `ConditionalRouter`, and `OutputAdpater` we require all Jinja2 variables defined in their templates to run. But for the `PromptBuilder`, and `ChatPromptBuilder` we set all Jinja2 variables as optional by default. 

This optionality has caused "intended" but usually unexpected behavior (from the perspective of the user) when running pipelines with multiple branches where each branch may contain a (Chat)PromptBuilder + (Chat)Generator. Specifically, if no required variables are set in the prompt builder then that component will always trigger even if it's along a branch that has been turned "off" by a previous `ConditionalRouter`. 

**Describe the solution you'd like**
To help make users aware of this behavior @julian-risch and I agreed that adding a warning to the init method of the `PromptBuilder` and `ChatPromptBuilder` when 1) Jinja2 variables are present in the template and 2) `required_variables=None` makes sense. 

This way users can be better aware that these components can still trigger even if they receive no input.

**Additional context**
@ju-gu and I have run into this multiple times when building pipelines for clients. 



Discussion:
Mathis described here that the new pipeline run logic changes the previous behavior: https://github.com/deepset-ai/haystack/issues/8903#issuecomment-2675585679
Could still be worth warning the user, but the warning message may need to be worded differently based on Mathis' comment. 
I also would still see a warning as useful, because it seems components can still run if not given all inputs (when not required), which probably not every user is aware. 



Patch:
diff --git a/haystack/components/builders/chat_prompt_builder.py b/haystack/components/builders/chat_prompt_builder.py
index 33e2feda2d..a097c93194 100644
--- a/haystack/components/builders/chat_prompt_builder.py
+++ b/haystack/components/builders/chat_prompt_builder.py
@@ -136,6 +136,15 @@ def __init__(
                     variables += list(template_variables)
         self.variables = variables
 
+        if len(self.variables) > 0 and required_variables is None:
+            logger.warning(
+                "ChatPromptBuilder has {length} prompt variables, but `required_variables` is not set. "
+                "By default, all prompt variables are treated as optional, which may lead to unintended behavior in "
+                "multi-branch pipelines. To avoid unexpected execution, ensure that variables intended to be required "
+                "are explicitly set in `required_variables`.",
+                length=len(self.variables),
+            )
+
         # setup inputs
         for var in self.variables:
             if self.required_variables == "*" or var in self.required_variables:
diff --git a/haystack/components/builders/prompt_builder.py b/haystack/components/builders/prompt_builder.py
index d5b52cd42a..5f6fd245df 100644
--- a/haystack/components/builders/prompt_builder.py
+++ b/haystack/components/builders/prompt_builder.py
@@ -7,9 +7,11 @@
 from jinja2 import meta
 from jinja2.sandbox import SandboxedEnvironment
 
-from haystack import component, default_to_dict
+from haystack import component, default_to_dict, logging
 from haystack.utils import Jinja2TimeExtension
 
+logger = logging.getLogger(__name__)
+
 
 @component
 class PromptBuilder:
@@ -180,6 +182,15 @@ def __init__(
         variables = variables or []
         self.variables = variables
 
+        if len(self.variables) > 0 and required_variables is None:
+            logger.warning(
+                "PromptBuilder has {length} prompt variables, but `required_variables` is not set. "
+                "By default, all prompt variables are treated as optional, which may lead to unintended behavior in "
+                "multi-branch pipelines. To avoid unexpected execution, ensure that variables intended to be required "
+                "are explicitly set in `required_variables`.",
+                length=len(self.variables),
+            )
+
         # setup inputs
         for var in self.variables:
             if self.required_variables == "*" or var in self.required_variables:
diff --git a/releasenotes/notes/warning-chat-and-prompt-builder-ca42295df9c1b770.yaml b/releasenotes/notes/warning-chat-and-prompt-builder-ca42295df9c1b770.yaml
new file mode 100644
index 0000000000..c788f8a2ad
--- /dev/null
+++ b/releasenotes/notes/warning-chat-and-prompt-builder-ca42295df9c1b770.yaml
@@ -0,0 +1,4 @@
+---
+enhancements:
+  - |
+    Added a warning to ChatPromptBuilder and PromptBuilder when prompt variables are present and `required_variables` is unset to help users avoid unexpected execution in multi-branch pipelines. The warning recommends users to set `required_variables`.


Test patch:
diff --git a/test/components/builders/test_chat_prompt_builder.py b/test/components/builders/test_chat_prompt_builder.py
index 1c39922df4..fcee86b173 100644
--- a/test/components/builders/test_chat_prompt_builder.py
+++ b/test/components/builders/test_chat_prompt_builder.py
@@ -1,5 +1,6 @@
 from typing import Any, Dict, List, Optional
 from jinja2 import TemplateSyntaxError
+import logging
 import pytest
 
 from haystack.components.builders.chat_prompt_builder import ChatPromptBuilder
@@ -335,6 +336,16 @@ def test_example_in_pipeline_simple(self):
         }
         assert result == expected_dynamic
 
+    def test_warning_no_required_variables(self, caplog):
+        with caplog.at_level(logging.WARNING):
+            _ = ChatPromptBuilder(
+                template=[
+                    ChatMessage.from_system("Write your response in this language:{{language}}"),
+                    ChatMessage.from_user("Tell me about {{location}}"),
+                ]
+            )
+            assert "ChatPromptBuilder has 2 prompt variables, but `required_variables` is not set. " in caplog.text
+
 
 class TestChatPromptBuilderDynamic:
     def test_multiple_templated_chat_messages(self):
@@ -521,13 +532,13 @@ def run(self):
         }
 
     def test_to_dict(self):
-        component = ChatPromptBuilder(
+        comp = ChatPromptBuilder(
             template=[ChatMessage.from_user("text and {var}"), ChatMessage.from_assistant("content {required_var}")],
             variables=["var", "required_var"],
             required_variables=["required_var"],
         )
 
-        assert component.to_dict() == {
+        assert comp.to_dict() == {
             "type": "haystack.components.builders.chat_prompt_builder.ChatPromptBuilder",
             "init_parameters": {
                 "template": [
@@ -545,7 +556,7 @@ def test_to_dict(self):
         }
 
     def test_from_dict(self):
-        component = ChatPromptBuilder.from_dict(
+        comp = ChatPromptBuilder.from_dict(
             data={
                 "type": "haystack.components.builders.chat_prompt_builder.ChatPromptBuilder",
                 "init_parameters": {
@@ -564,21 +575,21 @@ def test_from_dict(self):
             }
         )
 
-        assert component.template == [
+        assert comp.template == [
             ChatMessage.from_user("text and {var}"),
             ChatMessage.from_assistant("content {required_var}"),
         ]
-        assert component._variables == ["var", "required_var"]
-        assert component._required_variables == ["required_var"]
+        assert comp._variables == ["var", "required_var"]
+        assert comp._required_variables == ["required_var"]
 
     def test_from_dict_template_none(self):
-        component = ChatPromptBuilder.from_dict(
+        comp = ChatPromptBuilder.from_dict(
             data={
                 "type": "haystack.components.builders.chat_prompt_builder.ChatPromptBuilder",
                 "init_parameters": {"template": None},
             }
         )
 
-        assert component.template is None
-        assert component._variables is None
-        assert component._required_variables is None
+        assert comp.template is None
+        assert comp._variables is None
+        assert comp._required_variables is None
diff --git a/test/components/builders/test_prompt_builder.py b/test/components/builders/test_prompt_builder.py
index 39c23735a5..88b33a97ee 100644
--- a/test/components/builders/test_prompt_builder.py
+++ b/test/components/builders/test_prompt_builder.py
@@ -5,6 +5,7 @@
 from unittest.mock import patch
 
 import arrow
+import logging
 import pytest
 from jinja2 import TemplateSyntaxError
 
@@ -330,3 +331,8 @@ def test_invalid_offset(self) -> None:
         # Expect ValueError for invalid offset
         with pytest.raises(ValueError, match="Invalid offset or operator"):
             builder.run()
+
+    def test_warning_no_required_variables(self, caplog):
+        with caplog.at_level(logging.WARNING):
+            _ = PromptBuilder(template="This is a {{ variable }}")
+            assert "but `required_variables` is not set." in caplog.text