mirror of
https://github.com/deepset-ai/haystack.git
synced 2025-12-29 07:59:27 +00:00
chore: Remove deprecated DynamicPromptBuilder and DynamicChatPromptBuilder components (#8085)
This commit is contained in:
parent
f372ca443c
commit
b2aef217da
@ -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
|
||||
|
||||
@ -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"]
|
||||
|
||||
@ -1,190 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
|
||||
#
|
||||
# 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("<your-api-key>"), 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=<ChatRole.ASSISTANT: 'assistant'>, 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=<ChatRole.ASSISTANT: 'assistant'>, 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
|
||||
@ -1,169 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
|
||||
#
|
||||
# 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("<your-api-key>"), 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
|
||||
@ -0,0 +1,4 @@
|
||||
---
|
||||
upgrade:
|
||||
- |
|
||||
Removed the deprecated `DynamicPromptBuilder` and `DynamicChatPromptBuilder` components. Use `PromptBuilder` and `ChatPromptBuilder` instead.
|
||||
@ -1,288 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
|
||||
#
|
||||
# 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"),
|
||||
]
|
||||
}
|
||||
}
|
||||
@ -1,113 +0,0 @@
|
||||
# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
|
||||
#
|
||||
# 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?"
|
||||
}
|
||||
}
|
||||
Loading…
x
Reference in New Issue
Block a user