mirror of
https://github.com/deepset-ai/haystack.git
synced 2026-10-12 02:12:50 +00:00
fix: render every content part of ChatPromptBuilder message templates (#12907)
Co-authored-by: anakin87 <[email protected]>
This commit is contained in:
@@ -176,12 +176,13 @@ class ChatPromptBuilder:
|
||||
# infer variables from template
|
||||
if message.text is None:
|
||||
raise ValueError(NO_TEXT_ERROR_MESSAGE.format(role=message.role.value, message=message))
|
||||
if message.text and "templatize_part" in message.text:
|
||||
raise ValueError(FILTER_NOT_ALLOWED_ERROR_MESSAGE)
|
||||
assigned_variables, template_variables = _extract_template_variables_and_assignments(
|
||||
env=self._env, template=message.text
|
||||
)
|
||||
extracted_variables += list(template_variables - assigned_variables)
|
||||
for text in message.texts:
|
||||
if "templatize_part" in text:
|
||||
raise ValueError(FILTER_NOT_ALLOWED_ERROR_MESSAGE)
|
||||
assigned_variables, template_variables = _extract_template_variables_and_assignments(
|
||||
env=self._env, template=text
|
||||
)
|
||||
extracted_variables += list(template_variables - assigned_variables)
|
||||
elif isinstance(template, str):
|
||||
assigned_variables, template_variables = _extract_template_variables_and_assignments(
|
||||
env=self._env, template=template
|
||||
@@ -263,12 +264,15 @@ class ChatPromptBuilder:
|
||||
self._validate_variables(set(template_variables_combined.keys()))
|
||||
if message.text is None:
|
||||
raise ValueError(NO_TEXT_ERROR_MESSAGE.format(role=message.role.value, message=message))
|
||||
if message.text and "templatize_part" in message.text:
|
||||
raise ValueError(FILTER_NOT_ALLOWED_ERROR_MESSAGE)
|
||||
compiled_template = self._env.from_string(message.text)
|
||||
rendered_text = compiled_template.render(template_variables_combined)
|
||||
rendered_content = list(message._content)
|
||||
for index, part in enumerate(rendered_content):
|
||||
if isinstance(part, TextContent):
|
||||
if "templatize_part" in part.text:
|
||||
raise ValueError(FILTER_NOT_ALLOWED_ERROR_MESSAGE)
|
||||
rendered_text = self._env.from_string(part.text).render(template_variables_combined)
|
||||
rendered_content[index] = TextContent(text=rendered_text)
|
||||
# use dataclasses.replace to avoid in-place mutation of the original message
|
||||
rendered_message: ChatMessage = replace(message, _content=[TextContent(text=rendered_text)])
|
||||
rendered_message: ChatMessage = replace(message, _content=rendered_content)
|
||||
processed_messages.append(rendered_message)
|
||||
else:
|
||||
processed_messages.append(message)
|
||||
|
||||
@@ -0,0 +1,8 @@
|
||||
---
|
||||
fixes:
|
||||
- |
|
||||
Fixed ``ChatPromptBuilder`` dropping content parts when the template is a list of ``ChatMessage`` objects.
|
||||
Only the first ``TextContent`` part was rendered, and every other part - additional texts, images, files -
|
||||
was removed from the rendered prompt without a warning. Template variables used in those dropped parts
|
||||
were not detected either, so they were not exposed as inputs. Now all ``TextContent`` parts are
|
||||
rendered and the remaining content parts are passed through unchanged.
|
||||
@@ -15,7 +15,7 @@ from haystack import component
|
||||
from haystack.components.builders.chat_prompt_builder import ChatPromptBuilder
|
||||
from haystack.components.retrievers.in_memory import InMemoryBM25Retriever
|
||||
from haystack.core.pipeline.pipeline import Pipeline
|
||||
from haystack.dataclasses.chat_message import ChatMessage, FileContent, ImageContent, ReasoningContent
|
||||
from haystack.dataclasses.chat_message import ChatMessage, FileContent, ImageContent, ReasoningContent, TextContent
|
||||
from haystack.dataclasses.document import Document
|
||||
|
||||
|
||||
@@ -119,6 +119,36 @@ class TestChatPromptBuilder:
|
||||
res = builder.run(variable="test")
|
||||
assert res == {"prompt": [ChatMessage.from_user("This is a test")]}
|
||||
|
||||
def test_run_with_multiple_text_parts(self):
|
||||
template = [
|
||||
ChatMessage.from_user(
|
||||
content_parts=[TextContent(text="Hello, {{ name }}!"), TextContent(text="Goodbye, {{ other }}!")]
|
||||
)
|
||||
]
|
||||
builder = ChatPromptBuilder(template=template)
|
||||
|
||||
assert set(builder.variables) == {"name", "other"}
|
||||
assert builder.run(name="John", other="Jane")["prompt"][0].texts == ["Hello, John!", "Goodbye, Jane!"]
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"content_part",
|
||||
[
|
||||
ImageContent(base64_image="cHJldGVuZC1wbmctYnl0ZXM=", mime_type="image/png"),
|
||||
FileContent(base64_data="dGVzdA==", mime_type="application/pdf", filename="document.pdf"),
|
||||
],
|
||||
ids=["image", "file"],
|
||||
)
|
||||
def test_run_preserves_non_text_content_parts(self, content_part):
|
||||
template = [ChatMessage.from_user(content_parts=["Describe {{ thing }}", content_part, "Also {{ other }}"])]
|
||||
builder = ChatPromptBuilder(template=template)
|
||||
|
||||
assert builder.run(thing="this", other="that") == {
|
||||
"prompt": [ChatMessage.from_user(content_parts=["Describe this", content_part, "Also that"])]
|
||||
}
|
||||
assert template == [
|
||||
ChatMessage.from_user(content_parts=["Describe {{ thing }}", content_part, "Also {{ other }}"])
|
||||
]
|
||||
|
||||
def test_run_template_variable(self):
|
||||
builder = ChatPromptBuilder(template=[ChatMessage.from_user("This is a {{ variable }}")])
|
||||
res = builder.run(template_variables={"variable": "test"})
|
||||
@@ -720,14 +750,24 @@ class TestChatPromptBuilderDynamic:
|
||||
assert comp._variables is None
|
||||
assert comp._required_variables == "*"
|
||||
|
||||
def test_chat_message_list_with_templatize_part_init_raises_error(self):
|
||||
template = [ChatMessage.from_user("This is a {{ variable | templatize_part }}")]
|
||||
@pytest.mark.parametrize(
|
||||
"content_parts",
|
||||
[["This is a {{ variable | templatize_part }}"], ["First text", "This is a {{ variable | templatize_part }}"]],
|
||||
ids=["first_text", "second_text"],
|
||||
)
|
||||
def test_chat_message_list_with_templatize_part_init_raises_error(self, content_parts):
|
||||
template = [ChatMessage.from_user(content_parts=content_parts)]
|
||||
with pytest.raises(ValueError, match="templatize_part filter cannot be used"):
|
||||
ChatPromptBuilder(template=template)
|
||||
|
||||
def test_chat_message_list_with_templatize_part_run_raises_error(self):
|
||||
@pytest.mark.parametrize(
|
||||
"content_parts",
|
||||
[["This is a {{ variable | templatize_part }}"], ["First text", "This is a {{ variable | templatize_part }}"]],
|
||||
ids=["first_text", "second_text"],
|
||||
)
|
||||
def test_chat_message_list_with_templatize_part_run_raises_error(self, content_parts):
|
||||
builder = ChatPromptBuilder()
|
||||
template = [ChatMessage.from_user("This is a {{ variable | templatize_part }}")]
|
||||
template = [ChatMessage.from_user(content_parts=content_parts)]
|
||||
with pytest.raises(ValueError, match="templatize_part filter cannot be used"):
|
||||
builder.run(template=template, variable="test")
|
||||
|
||||
|
||||
Reference in New Issue
Block a user