From b2aef217dade564380d053f2a5e97925c9e71879 Mon Sep 17 00:00:00 2001 From: Madeesh Kannan Date: Fri, 26 Jul 2024 10:00:59 +0200 Subject: [PATCH] chore: Remove deprecated `DynamicPromptBuilder` and `DynamicChatPromptBuilder` components (#8085) --- docs/pydoc/config/builders_api.yml | 9 +- haystack/components/builders/__init__.py | 4 +- .../builders/dynamic_chat_prompt_builder.py | 190 ------------ .../builders/dynamic_prompt_builder.py | 169 ---------- ...namic-promptbuilders-14de431d98ae3e39.yaml | 4 + .../test_dynamic_chat_prompt_builder.py | 288 ------------------ .../builders/test_dynamic_prompt_builder.py | 113 ------- 7 files changed, 6 insertions(+), 771 deletions(-) delete mode 100644 haystack/components/builders/dynamic_chat_prompt_builder.py delete mode 100644 haystack/components/builders/dynamic_prompt_builder.py create mode 100644 releasenotes/notes/deprecate-dynamic-promptbuilders-14de431d98ae3e39.yaml delete mode 100644 test/components/builders/test_dynamic_chat_prompt_builder.py delete mode 100644 test/components/builders/test_dynamic_prompt_builder.py diff --git a/docs/pydoc/config/builders_api.yml b/docs/pydoc/config/builders_api.yml index db9c2707b8..d16085b0c4 100644 --- a/docs/pydoc/config/builders_api.yml +++ b/docs/pydoc/config/builders_api.yml @@ -1,14 +1,7 @@ loaders: - type: haystack_pydoc_tools.loaders.CustomPythonLoader search_path: [../../../haystack/components/builders] - modules: - [ - "answer_builder", - "prompt_builder", - "dynamic_prompt_builder", - "dynamic_chat_prompt_builder", - "chat_prompt_builder", - ] + modules: ["answer_builder", "prompt_builder", "chat_prompt_builder"] ignore_when_discovered: ["__init__"] processors: - type: filter diff --git a/haystack/components/builders/__init__.py b/haystack/components/builders/__init__.py index ca75bbf3e2..5e1ebf6d11 100644 --- a/haystack/components/builders/__init__.py +++ b/haystack/components/builders/__init__.py @@ -4,8 +4,6 @@ from haystack.components.builders.answer_builder import AnswerBuilder from haystack.components.builders.chat_prompt_builder import ChatPromptBuilder -from haystack.components.builders.dynamic_chat_prompt_builder import DynamicChatPromptBuilder -from haystack.components.builders.dynamic_prompt_builder import DynamicPromptBuilder from haystack.components.builders.prompt_builder import PromptBuilder -__all__ = ["AnswerBuilder", "PromptBuilder", "DynamicPromptBuilder", "DynamicChatPromptBuilder", "ChatPromptBuilder"] +__all__ = ["AnswerBuilder", "PromptBuilder", "ChatPromptBuilder"] diff --git a/haystack/components/builders/dynamic_chat_prompt_builder.py b/haystack/components/builders/dynamic_chat_prompt_builder.py deleted file mode 100644 index feabfd98af..0000000000 --- a/haystack/components/builders/dynamic_chat_prompt_builder.py +++ /dev/null @@ -1,190 +0,0 @@ -# SPDX-FileCopyrightText: 2022-present deepset GmbH -# -# SPDX-License-Identifier: Apache-2.0 - -import warnings -from typing import Any, Dict, List, Optional, Set - -from jinja2 import Template, meta - -from haystack import component, logging -from haystack.dataclasses.chat_message import ChatMessage, ChatRole - -logger = logging.getLogger(__name__) - - -@component -class DynamicChatPromptBuilder: - """ - DynamicChatPromptBuilder is designed to construct dynamic prompts from a list of `ChatMessage` instances. - - It integrates with Jinja2 templating for dynamic prompt generation. It considers any user or system message in the - list potentially containing a template and renders it with variables provided to the constructor. Additional - template variables can be feed into the component/pipeline `run` method and will be merged before rendering the - template. - - Usage example: - ```python - from haystack.components.builders import DynamicChatPromptBuilder - from haystack.components.generators.chat import OpenAIChatGenerator - from haystack.dataclasses import ChatMessage - from haystack import Pipeline - from haystack.utils import Secret - - # no parameter init, we don't use any runtime template variables - prompt_builder = DynamicChatPromptBuilder() - llm = OpenAIChatGenerator(api_key=Secret.from_token(""), model="gpt-3.5-turbo") - - pipe = Pipeline() - pipe.add_component("prompt_builder", prompt_builder) - pipe.add_component("llm", llm) - pipe.connect("prompt_builder.prompt", "llm.messages") - - location = "Berlin" - language = "English" - system_message = ChatMessage.from_system("You are an assistant giving information to tourists in {{language}}") - messages = [system_message, ChatMessage.from_user("Tell me about {{location}}")] - - res = pipe.run(data={"prompt_builder": {"template_variables": {"location": location, "language": language}, - "prompt_source": messages}}) - print(res) - - >> {'llm': {'replies': [ChatMessage(content="Berlin is the capital city of Germany and one of the most vibrant - and diverse cities in Europe. Here are some key things to know...Enjoy your time exploring the vibrant and dynamic - capital of Germany!", role=, name=None, meta={'model': 'gpt-3.5-turbo-0613', - 'index': 0, 'finish_reason': 'stop', 'usage': {'prompt_tokens': 27, 'completion_tokens': 681, 'total_tokens': - 708}})]}} - - - messages = [system_message, ChatMessage.from_user("What's the weather forecast for {{location}} in the next - {{day_count}} days?")] - - res = pipe.run(data={"prompt_builder": {"template_variables": {"location": location, "day_count": "5"}, - "prompt_source": messages}}) - - print(res) - >> {'llm': {'replies': [ChatMessage(content="Here is the weather forecast for Berlin in the next 5 - days:\n\nDay 1: Mostly cloudy with a high of 22°C (72°F) and...so it's always a good idea to check for updates - closer to your visit.", role=, name=None, meta={'model': 'gpt-3.5-turbo-0613', - 'index': 0, 'finish_reason': 'stop', 'usage': {'prompt_tokens': 37, 'completion_tokens': 201, - 'total_tokens': 238}})]}} - ``` - - Note that the weather forecast in the example above is fictional, but it can be easily connected to a weather - API to provide real weather forecasts. - """ - - def __init__(self, runtime_variables: Optional[List[str]] = None): - """ - Constructs a DynamicChatPromptBuilder component. - - :param runtime_variables: - A list of template variable names you can use in chat prompt construction. For example, - if `runtime_variables` contains the string `documents`, the component will create an input called - `documents` of type `Any`. These variable names are used to resolve variables and their values during - pipeline execution. The values associated with variables from the pipeline runtime are then injected into - template placeholders of a ChatMessage that is provided to the `run` method. - """ - warnings.warn( - "`DynamicChatPromptBuilder` is deprecated and will be removed in Haystack 2.4.0." - "Use `ChatPromptBuilder` instead.", - DeprecationWarning, - ) - runtime_variables = runtime_variables or [] - - # setup inputs - default_inputs = {"prompt_source": List[ChatMessage], "template_variables": Optional[Dict[str, Any]]} - additional_input_slots = {var: Optional[Any] for var in runtime_variables} - component.set_input_types(self, **default_inputs, **additional_input_slots) - - # setup outputs - component.set_output_types(self, prompt=List[ChatMessage]) - self.runtime_variables = runtime_variables - - def run(self, prompt_source: List[ChatMessage], template_variables: Optional[Dict[str, Any]] = None, **kwargs): - """ - Executes the dynamic prompt building process by processing a list of `ChatMessage` instances. - - Any user message or system message is inspected for templates and rendered with the variables provided to the - constructor. You can provide additional template variables directly to this method, which are then merged with - the variables provided to the constructor. - - :param prompt_source: - A list of `ChatMessage` instances. All user and system messages are treated as potentially having templates - and are rendered with the provided template variables - if templates are found. - :param template_variables: - A dictionary of template variables. Template variables provided at initialization are required - to resolve pipeline variables, and these are additional variables users can provide directly to this method. - :param kwargs: - Additional keyword arguments, typically resolved from a pipeline, which are merged with the provided - template variables. - - :returns: A dictionary with the following keys: - - `prompt`: The updated list of `ChatMessage` instances after rendering the found templates. - :raises ValueError: - If `chat_messages` is empty or contains elements that are not instances of `ChatMessage`. - :raises ValueError: - If the last message in `chat_messages` is not from a user. - """ - if not prompt_source: - raise ValueError( - f"The {self.__class__.__name__} requires a non-empty list of ChatMessage instances. " - f"Please provide a valid list of ChatMessage instances to render the prompt." - ) - if not all(isinstance(message, ChatMessage) for message in prompt_source): - raise ValueError( - f"The {self.__class__.__name__} expects a list containing only ChatMessage instances. " - f"The provided list contains other types. Please ensure that all elements in the list " - f"are ChatMessage instances." - ) - - kwargs = kwargs or {} - template_variables = template_variables or {} - template_variables = {**kwargs, **template_variables} - if not template_variables: - logger.warning( - "The DynamicChatPromptBuilder run method requires template variables, but none were provided. " - "Please provide an appropriate template variable to enable correct prompt generation." - ) - processed_messages = [] - for message in prompt_source: - if message.is_from(ChatRole.USER) or message.is_from(ChatRole.SYSTEM): - template = self._validate_template(message.content, set(template_variables.keys())) - rendered_content = template.render(template_variables) - rendered_message = ( - ChatMessage.from_user(rendered_content) - if message.is_from(ChatRole.USER) - else ChatMessage.from_system(rendered_content) - ) - processed_messages.append(rendered_message) - else: - processed_messages.append(message) - return {"prompt": processed_messages} - - def _validate_template(self, template_text: str, provided_variables: Set[str]): - """ - Checks if all the required template variables are provided to the pipeline `run` method. - - If all the required template variables are provided, returns a Jinja2 template object. - Otherwise, raises a ValueError. - - :param template_text: - A Jinja2 template as a string. - :param provided_variables: - A set of provided template variables. - :returns: - A Jinja2 template object if all the required template variables are provided. - :raises ValueError: - If all the required template variables are not provided. - """ - template = Template(template_text) - ast = template.environment.parse(template_text) - required_template_variables = meta.find_undeclared_variables(ast) - filled_template_vars = required_template_variables.intersection(provided_variables) - if len(filled_template_vars) != len(required_template_variables): - raise ValueError( - f"The {self.__class__.__name__} requires specific template variables that are missing. " - f"Required variables: {required_template_variables}. Only the following variables were " - f"provided: {provided_variables}. Please provide all the required template variables." - ) - return template diff --git a/haystack/components/builders/dynamic_prompt_builder.py b/haystack/components/builders/dynamic_prompt_builder.py deleted file mode 100644 index a01b8d17d2..0000000000 --- a/haystack/components/builders/dynamic_prompt_builder.py +++ /dev/null @@ -1,169 +0,0 @@ -# SPDX-FileCopyrightText: 2022-present deepset GmbH -# -# SPDX-License-Identifier: Apache-2.0 - -import warnings -from typing import Any, Dict, List, Optional, Set - -from jinja2 import Template, meta - -from haystack import component, logging - -logger = logging.getLogger(__name__) - - -@component -class DynamicPromptBuilder: - """ - DynamicPromptBuilder is designed to construct dynamic prompts for the pipeline. - - Users can change the prompt template at runtime by providing a new template for each pipeline run invocation - if needed. - - Usage example: - ```python - from typing import List - from haystack.components.builders import DynamicPromptBuilder - from haystack.components.generators import OpenAIGenerator - from haystack import Pipeline, component, Document - from haystack.utils import Secret - - prompt_builder = DynamicPromptBuilder(runtime_variables=["documents"]) - llm = OpenAIGenerator(api_key=Secret.from_token(""), model="gpt-3.5-turbo") - - - @component - class DocumentProducer: - - @component.output_types(documents=List[Document]) - def run(self, doc_input: str): - return {"documents": [Document(content=doc_input)]} - - - pipe = Pipeline() - pipe.add_component("doc_producer", DocumentProducer()) - pipe.add_component("prompt_builder", prompt_builder) - pipe.add_component("llm", llm) - pipe.connect("doc_producer.documents", "prompt_builder.documents") - pipe.connect("prompt_builder.prompt", "llm.prompt") - - template = "Here is the document: {{documents[0].content}} \\n Answer: {{query}}" - result = pipe.run( - data={ - "doc_producer": {"doc_input": "Hello world, I live in Berlin"}, - "prompt_builder": { - "prompt_source": template, - "template_variables": {"query": "Where does the speaker live?"}, - }, - } - ) - print(result) - - >> {'llm': {'replies': ['The speaker lives in Berlin.'], - >> 'meta': [{'model': 'gpt-3.5-turbo-0613', - >> 'index': 0, - >> 'finish_reason': 'stop', - >> 'usage': {'prompt_tokens': 28, - >> 'completion_tokens': 6, - >> 'total_tokens': 34}}]}} - - Note how in the example above, we can dynamically change the prompt template by providing a new template to the - run method of the pipeline. This dynamic prompt generation is in contrast to the static prompt generation - using `PromptBuilder`, where the prompt template is fixed for the pipeline's lifetime and cannot be changed - for each pipeline run invocation. - - """ - - def __init__(self, runtime_variables: Optional[List[str]] = None): - """ - Constructs a DynamicPromptBuilder component. - - :param runtime_variables: - A list of template variable names you can use in prompt construction. For example, - if `runtime_variables` contains the string `documents`, the component will create an input called - `documents` of type `Any`. These variable names are used to resolve variables and their values during - pipeline execution. The values associated with variables from the pipeline runtime are then injected into - template placeholders of a prompt text template that is provided to the `run` method. - """ - warnings.warn( - "`DynamicPromptBuilder` is deprecated and will be removed in Haystack 2.4.0." - "Use `PromptBuilder` instead.", - DeprecationWarning, - ) - - runtime_variables = runtime_variables or [] - - # setup inputs - run_input_slots = {"prompt_source": str, "template_variables": Optional[Dict[str, Any]]} - kwargs_input_slots = {var: Optional[Any] for var in runtime_variables} - component.set_input_types(self, **run_input_slots, **kwargs_input_slots) - - # setup outputs - component.set_output_types(self, prompt=str) - - self.runtime_variables = runtime_variables - - def run(self, prompt_source: str, template_variables: Optional[Dict[str, Any]] = None, **kwargs): - """ - Executes the dynamic prompt building process. - - Depending on the provided type of `prompt_source`, this method either processes a list of `ChatMessage` - instances or a string template. In the case of `ChatMessage` instances, the last user message is treated as a - template and rendered with the resolved pipeline variables and any additional template variables provided. - - For a string template, it directly applies the template variables to render the final prompt. You can provide - additional template variables directly to this method, that are then merged with the variables resolved from - the pipeline runtime. - - :param prompt_source: - A string template. - :param template_variables: - An optional dictionary of template variables. Template variables provided at initialization are required - to resolve pipeline variables, and these are additional variables users can provide directly to this method. - :param kwargs: - Additional keyword arguments, typically resolved from a pipeline, which are merged with the provided - template variables. - - :returns: A dictionary with the following keys: - - `prompt`: The updated prompt text after rendering the string template. - """ - kwargs = kwargs or {} - template_variables = template_variables or {} - template_variables_combined = {**kwargs, **template_variables} - if not template_variables_combined: - raise ValueError( - "The DynamicPromptBuilder run method requires template variables, but none were provided. " - "Please provide an appropriate template variable to enable prompt generation." - ) - - template = self._validate_template(prompt_source, set(template_variables_combined.keys())) - result = template.render(template_variables_combined) - return {"prompt": result} - - def _validate_template(self, template_text: str, provided_variables: Set[str]): - """ - Checks if all the required template variables are provided to the pipeline `run` method. - - If all the required template variables are provided, returns a Jinja2 template object. - Otherwise, raises a ValueError. - - :param template_text: - A Jinja2 template as a string. - :param provided_variables: - A set of provided template variables. - :returns: - A Jinja2 template object if all the required template variables are provided. - :raises ValueError: - If all the required template variables are not provided. - """ - template = Template(template_text) - ast = template.environment.parse(template_text) - required_template_variables = meta.find_undeclared_variables(ast) - filled_template_vars = required_template_variables.intersection(provided_variables) - if len(filled_template_vars) != len(required_template_variables): - raise ValueError( - f"The {self.__class__.__name__} requires specific template variables that are missing. " - f"Required variables: {required_template_variables}. Only the following variables were " - f"provided: {provided_variables}. Please provide all the required template variables." - ) - return template diff --git a/releasenotes/notes/deprecate-dynamic-promptbuilders-14de431d98ae3e39.yaml b/releasenotes/notes/deprecate-dynamic-promptbuilders-14de431d98ae3e39.yaml new file mode 100644 index 0000000000..aed37439b0 --- /dev/null +++ b/releasenotes/notes/deprecate-dynamic-promptbuilders-14de431d98ae3e39.yaml @@ -0,0 +1,4 @@ +--- +upgrade: + - | + Removed the deprecated `DynamicPromptBuilder` and `DynamicChatPromptBuilder` components. Use `PromptBuilder` and `ChatPromptBuilder` instead. diff --git a/test/components/builders/test_dynamic_chat_prompt_builder.py b/test/components/builders/test_dynamic_chat_prompt_builder.py deleted file mode 100644 index 35b3d2bf1f..0000000000 --- a/test/components/builders/test_dynamic_chat_prompt_builder.py +++ /dev/null @@ -1,288 +0,0 @@ -# SPDX-FileCopyrightText: 2022-present deepset GmbH -# -# SPDX-License-Identifier: Apache-2.0 -from typing import List - -import pytest - -from haystack import Pipeline, component -from haystack.components.builders import DynamicChatPromptBuilder -from haystack.dataclasses import ChatMessage - - -class TestDynamicChatPromptBuilder: - def test_initialization(self): - runtime_variables = ["var1", "var2", "var3"] - builder = DynamicChatPromptBuilder(runtime_variables) - assert builder.runtime_variables == runtime_variables - - # we have inputs that contain: prompt_source, template_variables + runtime_variables - expected_keys = set(runtime_variables + ["prompt_source", "template_variables"]) - assert set(builder.__haystack_input__._sockets_dict.keys()) == expected_keys - - # response is always prompt regardless of chat mode - assert set(builder.__haystack_output__._sockets_dict.keys()) == {"prompt"} - - # prompt_source is a list of ChatMessage - assert builder.__haystack_input__._sockets_dict["prompt_source"].type == List[ChatMessage] - - # output is always prompt, but the type is different depending on the chat mode - assert builder.__haystack_output__._sockets_dict["prompt"].type == List[ChatMessage] - - def test_non_empty_chat_messages(self): - prompt_builder = DynamicChatPromptBuilder(runtime_variables=["documents"]) - prompt_source = [ChatMessage.from_system(content="Hello"), ChatMessage.from_user(content="Hello, {{ who }}!")] - template_variables = {"who": "World"} - - result = prompt_builder.run(prompt_source, template_variables) - - assert result == { - "prompt": [ChatMessage.from_system(content="Hello"), ChatMessage.from_user(content="Hello, World!")] - } - - def test_single_chat_message(self): - prompt_builder = DynamicChatPromptBuilder(runtime_variables=["documents"]) - prompt_source = [ChatMessage.from_user(content="Hello, {{ who }}!")] - template_variables = {"who": "World"} - - result = prompt_builder.run(prompt_source, template_variables) - - assert result == {"prompt": [ChatMessage.from_user(content="Hello, World!")]} - - def test_empty_chat_message_list(self): - prompt_builder = DynamicChatPromptBuilder(runtime_variables=["documents"]) - - with pytest.raises(ValueError): - prompt_builder.run(prompt_source=[], template_variables={}) - - def test_chat_message_list_with_mixed_object_list(self): - prompt_builder = DynamicChatPromptBuilder(runtime_variables=["documents"]) - - with pytest.raises(ValueError): - prompt_builder.run(prompt_source=[ChatMessage.from_user("Hello"), "there world"], template_variables={}) - - def test_chat_message_list_with_missing_variables(self): - prompt_builder = DynamicChatPromptBuilder(runtime_variables=["documents"]) - prompt_source = [ChatMessage.from_user(content="Hello, {{ who }}!")] - - # Call the _process_chat_messages method and expect a ValueError - with pytest.raises(ValueError): - prompt_builder.run(prompt_source, template_variables={}) - - def test_missing_template_variables(self): - prompt_builder = DynamicChatPromptBuilder(runtime_variables=["documents"]) - - # missing template variable city - with pytest.raises(ValueError): - prompt_builder._validate_template("Hello, I'm {{ name }}, and I live in {{ city }}.", {"name"}) - - # missing template variable name - with pytest.raises(ValueError): - prompt_builder._validate_template("Hello, I'm {{ name }}, and I live in {{ city }}.", {"city"}) - - # completely unknown template variable - with pytest.raises(ValueError): - prompt_builder._validate_template("Hello, I'm {{ name }}, and I live in {{ city }}.", {"age"}) - - def test_provided_template_variables(self): - prompt_builder = DynamicChatPromptBuilder(runtime_variables=["documents"]) - - # both variables are provided - prompt_builder._validate_template("Hello, I'm {{ name }}, and I live in {{ city }}.", {"name", "city"}) - - # provided variables are a superset of the required variables - prompt_builder._validate_template("Hello, I'm {{ name }}, and I live in {{ city }}.", {"name", "city", "age"}) - - def test_multiple_templated_chat_messages(self): - prompt_builder = DynamicChatPromptBuilder() - language = "French" - location = "Berlin" - messages = [ - ChatMessage.from_system("Write your response in this language:{{language}}"), - ChatMessage.from_user("Tell me about {{location}}"), - ] - - result = prompt_builder.run( - template_variables={"language": language, "location": location}, prompt_source=messages - ) - assert result["prompt"] == [ - ChatMessage.from_system("Write your response in this language:French"), - ChatMessage.from_user("Tell me about Berlin"), - ], "The templated messages should match the expected output." - - def test_multiple_templated_chat_messages_in_place(self): - prompt_builder = DynamicChatPromptBuilder() - language = "French" - location = "Berlin" - messages = [ - ChatMessage.from_system("Write your response ins this language:{{language}}"), - ChatMessage.from_user("Tell me about {{location}}"), - ] - - res = prompt_builder.run( - template_variables={"language": language, "location": location}, prompt_source=messages - ) - assert res == { - "prompt": [ - ChatMessage.from_system("Write your response ins this language:French"), - ChatMessage.from_user("Tell me about Berlin"), - ] - }, "The templated messages should match the expected output." - - def test_some_templated_chat_messages(self): - prompt_builder = DynamicChatPromptBuilder() - language = "English" - location = "Paris" - messages = [ - ChatMessage.from_system("Please, respond in the following language: {{language}}."), - ChatMessage.from_user("I would like to learn more about {{location}}."), - ChatMessage.from_assistant("Yes, I can help you with that {{subject}}"), - ChatMessage.from_user("Ok so do so please, be elaborate."), - ] - - result = prompt_builder.run( - template_variables={"language": language, "location": location}, prompt_source=messages - ) - - expected_messages = [ - ChatMessage.from_system("Please, respond in the following language: English."), - ChatMessage.from_user("I would like to learn more about Paris."), - ChatMessage.from_assistant( - "Yes, I can help you with that {{subject}}" - ), # assistant message should not be templated - ChatMessage.from_user("Ok so do so please, be elaborate."), - ] - - assert result["prompt"] == expected_messages, "The templated messages should match the expected output." - - def test_example_in_pipeline(self): - prompt_builder = DynamicChatPromptBuilder() - - pipe = Pipeline() - pipe.add_component("prompt_builder", prompt_builder) - - location = "Berlin" - system_message = ChatMessage.from_system( - "You are a helpful assistant giving out valuable information to tourists." - ) - messages = [system_message, ChatMessage.from_user("Tell me about {{location}}")] - - res = pipe.run( - data={"prompt_builder": {"template_variables": {"location": location}, "prompt_source": messages}} - ) - assert res == { - "prompt_builder": { - "prompt": [ - ChatMessage.from_system("You are a helpful assistant giving out valuable information to tourists."), - ChatMessage.from_user("Tell me about Berlin"), - ] - } - } - - messages = [ - system_message, - ChatMessage.from_user("What's the weather forecast for {{location}} in the next {{day_count}} days?"), - ] - - res = pipe.run( - data={ - "prompt_builder": { - "template_variables": {"location": location, "day_count": "5"}, - "prompt_source": messages, - } - } - ) - assert res == { - "prompt_builder": { - "prompt": [ - ChatMessage.from_system("You are a helpful assistant giving out valuable information to tourists."), - ChatMessage.from_user("What's the weather forecast for Berlin in the next 5 days?"), - ] - } - } - - def test_example_in_pipeline_with_multiple_templated_messages(self): - # no parameter init, we don't use any runtime template variables - prompt_builder = DynamicChatPromptBuilder() - - pipe = Pipeline() - pipe.add_component("prompt_builder", prompt_builder) - - location = "Berlin" - system_message = ChatMessage.from_system( - "You are a helpful assistant giving out valuable information to tourists in {{language}}." - ) - messages = [system_message, ChatMessage.from_user("Tell me about {{location}}")] - - res = pipe.run( - data={ - "prompt_builder": { - "template_variables": {"location": location, "language": "German"}, - "prompt_source": messages, - } - } - ) - assert res == { - "prompt_builder": { - "prompt": [ - ChatMessage.from_system( - "You are a helpful assistant giving out valuable information to tourists in German." - ), - ChatMessage.from_user("Tell me about Berlin"), - ] - } - } - - messages = [ - system_message, - ChatMessage.from_user("What's the weather forecast for {{location}} in the next {{day_count}} days?"), - ] - - res = pipe.run( - data={ - "prompt_builder": { - "template_variables": {"location": location, "day_count": "5", "language": "English"}, - "prompt_source": messages, - } - } - ) - assert res == { - "prompt_builder": { - "prompt": [ - ChatMessage.from_system( - "You are a helpful assistant giving out valuable information to tourists in English." - ), - ChatMessage.from_user("What's the weather forecast for Berlin in the next 5 days?"), - ] - } - } - - def test_pipeline_complex(self): - @component - class ValueProducer: - def __init__(self, value_to_produce: str): - self.value_to_produce = value_to_produce - - @component.output_types(value_output=str) - def run(self): - return {"value_output": self.value_to_produce} - - pipe = Pipeline() - pipe.add_component("prompt_builder", DynamicChatPromptBuilder(runtime_variables=["value_output"])) - pipe.add_component("value_producer", ValueProducer(value_to_produce="Berlin")) - pipe.connect("value_producer.value_output", "prompt_builder") - - messages = [ - ChatMessage.from_system("You give valuable information to tourists."), - ChatMessage.from_user("Tell me about {{value_output}}"), - ] - - res = pipe.run(data={"prompt_source": messages}) - assert res == { - "prompt_builder": { - "prompt": [ - ChatMessage.from_system("You give valuable information to tourists."), - ChatMessage.from_user("Tell me about Berlin"), - ] - } - } diff --git a/test/components/builders/test_dynamic_prompt_builder.py b/test/components/builders/test_dynamic_prompt_builder.py deleted file mode 100644 index cf28e6eeee..0000000000 --- a/test/components/builders/test_dynamic_prompt_builder.py +++ /dev/null @@ -1,113 +0,0 @@ -# SPDX-FileCopyrightText: 2022-present deepset GmbH -# -# SPDX-License-Identifier: Apache-2.0 -from typing import List - -import pytest -from jinja2 import TemplateSyntaxError - -from haystack import Document, Pipeline, component -from haystack.components.builders import DynamicPromptBuilder - - -class TestDynamicPromptBuilder: - def test_initialization(self): - runtime_variables = ["var1", "var2"] - builder = DynamicPromptBuilder(runtime_variables) - assert builder.runtime_variables == runtime_variables - - # regardless of the chat mode - # we have inputs that contain: prompt_source, template_variables + runtime_variables - expected_keys = set(runtime_variables + ["prompt_source", "template_variables"]) - assert set(builder.__haystack_input__._sockets_dict.keys()) == expected_keys - - # response is always prompt regardless of chat mode - assert set(builder.__haystack_output__._sockets_dict.keys()) == {"prompt"} - - # prompt_source is a list of ChatMessage or a string - assert builder.__haystack_input__._sockets_dict["prompt_source"].type == str - - # output is always prompt, but the type is different depending on the chat mode - assert builder.__haystack_output__._sockets_dict["prompt"].type == str - - def test_processing_a_simple_template_with_provided_variables(self): - runtime_variables = ["var1", "var2", "var3"] - - builder = DynamicPromptBuilder(runtime_variables) - - template = "Hello, {{ name }}!" - template_variables = {"name": "John"} - expected_result = {"prompt": "Hello, John!"} - - assert builder.run(template, template_variables) == expected_result - - def test_processing_a_simple_template_with_invalid_template(self): - runtime_variables = ["var1", "var2", "var3"] - builder = DynamicPromptBuilder(runtime_variables) - - template = "Hello, {{ name }!" - template_variables = {"name": "John"} - with pytest.raises(TemplateSyntaxError): - builder.run(template, template_variables) - - def test_processing_a_simple_template_with_missing_variables(self): - runtime_variables = ["var1", "var2", "var3"] - builder = DynamicPromptBuilder(runtime_variables) - - with pytest.raises(ValueError): - builder.run("Hello, {{ name }}!", {}) - - def test_missing_template_variables(self): - prompt_builder = DynamicPromptBuilder(runtime_variables=["documents"]) - - # missing template variable city - with pytest.raises(ValueError): - prompt_builder._validate_template("Hello, I'm {{ name }}, and I live in {{ city }}.", {"name"}) - - # missing template variable name - with pytest.raises(ValueError): - prompt_builder._validate_template("Hello, I'm {{ name }}, and I live in {{ city }}.", {"city"}) - - # completely unknown template variable - with pytest.raises(ValueError): - prompt_builder._validate_template("Hello, I'm {{ name }}, and I live in {{ city }}.", {"age"}) - - def test_provided_template_variables(self): - prompt_builder = DynamicPromptBuilder(runtime_variables=["documents"]) - - # both variables are provided - prompt_builder._validate_template("Hello, I'm {{ name }}, and I live in {{ city }}.", {"name", "city"}) - - # provided variables are a superset of the required variables - prompt_builder._validate_template("Hello, I'm {{ name }}, and I live in {{ city }}.", {"name", "city", "age"}) - - def test_example_in_pipeline(self): - prompt_builder = DynamicPromptBuilder(runtime_variables=["documents"]) - - @component - class DocumentProducer: - @component.output_types(documents=List[Document]) - def run(self, doc_input: str): - return {"documents": [Document(content=doc_input)]} - - pipe = Pipeline() - pipe.add_component("doc_producer", DocumentProducer()) - pipe.add_component("prompt_builder", prompt_builder) - pipe.connect("doc_producer.documents", "prompt_builder.documents") - - template = "Here is the document: {{documents[0].content}} \\n Answer: {{query}}" - result = pipe.run( - data={ - "doc_producer": {"doc_input": "Hello world, I live in Berlin"}, - "prompt_builder": { - "prompt_source": template, - "template_variables": {"query": "Where does the speaker live?"}, - }, - } - ) - - assert result == { - "prompt_builder": { - "prompt": "Here is the document: Hello world, I live in Berlin \\n Answer: Where does the speaker live?" - } - }