From fb1baf49210ee7391caea02d051ff6f314a8c862 Mon Sep 17 00:00:00 2001 From: Stefano Fiorucci Date: Thu, 28 Nov 2024 10:53:02 +0100 Subject: [PATCH] refactor: `ChatMessage` - introduce `text` property and deprecate `content` (#8588) * introduce text property and deprecate content * release note * minor test refactoring --------- Co-authored-by: Michele Pangrazzi --- haystack/dataclasses/chat_message.py | 21 ++++++++++++ .../chatmessage-text-5bd06f6ac70ac649.yaml | 7 ++++ test/dataclasses/test_chat_message.py | 32 +++++++++++++++++-- 3 files changed, 57 insertions(+), 3 deletions(-) create mode 100644 releasenotes/notes/chatmessage-text-5bd06f6ac70ac649.yaml diff --git a/haystack/dataclasses/chat_message.py b/haystack/dataclasses/chat_message.py index 4bfbe55820..fb15ee6f5e 100644 --- a/haystack/dataclasses/chat_message.py +++ b/haystack/dataclasses/chat_message.py @@ -2,6 +2,7 @@ # # SPDX-License-Identifier: Apache-2.0 +import warnings from dataclasses import asdict, dataclass, field from enum import Enum from typing import Any, Dict, Optional @@ -32,6 +33,26 @@ class ChatMessage: name: Optional[str] meta: Dict[str, Any] = field(default_factory=dict, hash=False) + @property + def text(self) -> Optional[str]: + """ + Returns the textual content of the message. + """ + # Currently, this property mirrors the `content` attribute. This will change in 2.9.0. + # The current actual return type is str. We are using Optional[str] to be ready for 2.9.0, + # when None will be a valid value for `text`. + return object.__getattribute__(self, "content") + + def __getattribute__(self, name): + # this method is reimplemented to warn about the deprecation of the `content` attribute + if name == "content": + msg = ( + "The `content` attribute of `ChatMessage` will be removed in Haystack 2.9.0. " + "Use the `text` property to access the textual value." + ) + warnings.warn(msg, DeprecationWarning) + return object.__getattribute__(self, name) + def is_from(self, role: ChatRole) -> bool: """ Check if the message is from a specific role. diff --git a/releasenotes/notes/chatmessage-text-5bd06f6ac70ac649.yaml b/releasenotes/notes/chatmessage-text-5bd06f6ac70ac649.yaml new file mode 100644 index 0000000000..9d994bbab4 --- /dev/null +++ b/releasenotes/notes/chatmessage-text-5bd06f6ac70ac649.yaml @@ -0,0 +1,7 @@ +--- +deprecations: + - | + In Haystack 2.9.0, the `ChatMessage` dataclass will be refactored to make it more flexible and future-proof. + As part of this change, the `content` attribute will be removed. + A new `text` property has been introduced to provide access to the textual value of the `ChatMessage`. + To ensure a smooth transition, start using the `text` property now in place of `content`. diff --git a/test/dataclasses/test_chat_message.py b/test/dataclasses/test_chat_message.py index 165e403301..cffd4c94da 100644 --- a/test/dataclasses/test_chat_message.py +++ b/test/dataclasses/test_chat_message.py @@ -12,6 +12,7 @@ def test_from_assistant_with_valid_content(): content = "Hello, how can I assist you?" message = ChatMessage.from_assistant(content) assert message.content == content + assert message.text == content assert message.role == ChatRole.ASSISTANT @@ -19,6 +20,7 @@ def test_from_user_with_valid_content(): content = "I have a question." message = ChatMessage.from_user(content) assert message.content == content + assert message.text == content assert message.role == ChatRole.USER @@ -26,19 +28,24 @@ def test_from_system_with_valid_content(): content = "System message." message = ChatMessage.from_system(content) assert message.content == content + assert message.text == content assert message.role == ChatRole.SYSTEM def test_with_empty_content(): message = ChatMessage.from_user("") assert message.content == "" + assert message.text == "" + assert message.role == ChatRole.USER def test_from_function_with_empty_name(): content = "Function call" message = ChatMessage.from_function(content, "") assert message.content == content + assert message.text == content assert message.name == "" + assert message.role == ChatRole.FUNCTION def test_to_openai_format(): @@ -89,10 +96,15 @@ def test_apply_custom_chat_templating_on_chat_message(): def test_to_dict(): - message = ChatMessage.from_user("content") - message.meta["some"] = "some" + content = "content" + role = "user" + meta = {"some": "some"} + + message = ChatMessage.from_user(content) + message.meta.update(meta) - assert message.to_dict() == {"content": "content", "role": "user", "name": None, "meta": {"some": "some"}} + assert message.text == content + assert message.to_dict() == {"content": content, "role": role, "name": None, "meta": meta} def test_from_dict(): @@ -105,3 +117,17 @@ def test_from_dict_with_meta(): assert ChatMessage.from_dict( data={"content": "text", "role": "assistant", "name": None, "meta": {"something": "something"}} ) == ChatMessage.from_assistant("text", meta={"something": "something"}) + + +def test_content_deprecation_warning(recwarn): + message = ChatMessage.from_user("my message") + + # accessing the content attribute triggers the deprecation warning + _ = message.content + assert len(recwarn) == 1 + wrn = recwarn.pop(DeprecationWarning) + assert "`content` attribute" in wrn.message.args[0] + + # accessing the text property does not trigger a warning + assert message.text == "my message" + assert len(recwarn) == 0