Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat: time extension for ChatPromptBuilder #9001

Merged
merged 4 commits into from
Mar 19, 2025
Merged
Changes from 2 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
9 changes: 8 additions & 1 deletion haystack/components/builders/chat_prompt_builder.py
Original file line number Diff line number Diff line change
@@ -10,6 +10,7 @@

from haystack import component, default_from_dict, default_to_dict, logging
from haystack.dataclasses.chat_message import ChatMessage, ChatRole, TextContent
from haystack.utils import Jinja2TimeExtension

logger = logging.getLogger(__name__)

@@ -124,7 +125,13 @@ def __init__(
self.required_variables = required_variables or []
self.template = template
variables = variables or []
self._env = SandboxedEnvironment()
try:
# The Jinja2TimeExtension needs an optional dependency to be installed.
# If it's not available we can do without it and use the PromptBuilder as is.
self._env = SandboxedEnvironment(extensions=[Jinja2TimeExtension])
except ImportError:
self._env = SandboxedEnvironment()

if template and not variables:
for message in template:
if message.is_from(ChatRole.USER) or message.is_from(ChatRole.SYSTEM):
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
features:
- |
Users can now work with date and time in the ChatPromptBuilder.
In the same way as the PromptBuilder, the ChatPromptBuilder now supports arrow to work with datetime.
83 changes: 83 additions & 0 deletions test/components/builders/test_chat_prompt_builder.py
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
from typing import Any, Dict, List, Optional
from jinja2 import TemplateSyntaxError

import arrow
import pytest

from haystack.components.builders.chat_prompt_builder import ChatPromptBuilder
@@ -335,6 +337,87 @@ def test_example_in_pipeline_simple(self):
}
assert result == expected_dynamic

def test_with_custom_dateformat(self) -> None:
template = [ChatMessage.from_user("Formatted date: {% now 'UTC', '%Y-%m-%d' %}")]
builder = ChatPromptBuilder(template=template)

result = builder.run()["prompt"]

now_formatted = f"Formatted date: {arrow.now('UTC').strftime('%Y-%m-%d')}"
assert len(result) == 1
assert result[0].role == "user"
assert result[0].text == now_formatted

def test_with_different_timezone(self) -> None:
template = [ChatMessage.from_user("Current time in New York is: {% now 'America/New_York' %}")]
builder = ChatPromptBuilder(template=template)

result = builder.run()["prompt"]

now_ny = f"Current time in New York is: {arrow.now('America/New_York').strftime('%Y-%m-%d %H:%M:%S')}"
assert len(result) == 1
assert result[0].role == "user"
assert result[0].text == now_ny

def test_date_with_addition_offset(self) -> None:
template = [ChatMessage.from_user("Time after 2 hours is: {% now 'UTC' + 'hours=2' %}")]
builder = ChatPromptBuilder(template=template)

result = builder.run()["prompt"]

now_plus_2 = f"Time after 2 hours is: {(arrow.now('UTC').shift(hours=+2)).strftime('%Y-%m-%d %H:%M:%S')}"
assert len(result) == 1
assert result[0].role == "user"
assert result[0].text == now_plus_2

def test_date_with_subtraction_offset(self) -> None:
template = [ChatMessage.from_user("Time after 12 days is: {% now 'UTC' - 'days=12' %}")]
builder = ChatPromptBuilder(template=template)

result = builder.run()["prompt"]

now_minus_12 = f"Time after 12 days is: {(arrow.now('UTC').shift(days=-12)).strftime('%Y-%m-%d %H:%M:%S')}"
assert len(result) == 1
assert result[0].role == "user"
assert result[0].text == now_minus_12

def test_invalid_timezone(self) -> None:
template = [ChatMessage.from_user("Current time is: {% now 'Invalid/Timezone' %}")]
builder = ChatPromptBuilder(template=template)

# Expect ValueError for invalid timezone
with pytest.raises(ValueError, match="Invalid timezone"):
builder.run()

def test_invalid_offset(self) -> None:
template = [ChatMessage.from_user("Time after invalid offset is: {% now 'UTC' + 'invalid_offset' %}")]
builder = ChatPromptBuilder(template=template)

# Expect ValueError for invalid offset
with pytest.raises(ValueError, match="Invalid offset or operator"):
builder.run()

def test_multiple_messages_with_date_template(self) -> None:
template = [
ChatMessage.from_user("Current date is: {% now 'UTC' %}"),
ChatMessage.from_assistant("Thank you for providing the date"),
ChatMessage.from_user("Yesterday was: {% now 'UTC' - 'days=1' %}"),
]
builder = ChatPromptBuilder(template=template)

result = builder.run()["prompt"]

now = f"Current date is: {arrow.now('UTC').strftime('%Y-%m-%d %H:%M:%S')}"
yesterday = f"Yesterday was: {(arrow.now('UTC').shift(days=-1)).strftime('%Y-%m-%d %H:%M:%S')}"

assert len(result) == 3
assert result[0].role == "user"
assert result[0].text == now
assert result[1].role == "assistant"
assert result[1].text == "Thank you for providing the date"
assert result[2].role == "user"
assert result[2].text == yesterday


class TestChatPromptBuilderDynamic:
def test_multiple_templated_chat_messages(self):
Loading