Skip to content

Commit 1d6a9f6

Browse files
authored
fix: serialization of nested ChatMessage in GeneratedAnswerdataclass (#9497)
* Fix serialization * small fix * fix the erros * Fix tests * PR comments
1 parent 12665ad commit 1d6a9f6

4 files changed

Lines changed: 94 additions & 8 deletions

File tree

haystack/dataclasses/answer.py

Lines changed: 18 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@
66
from typing import Any, Dict, List, Optional, Protocol, runtime_checkable
77

88
from haystack.core.serialization import default_from_dict, default_to_dict
9-
from haystack.dataclasses.document import Document
9+
from haystack.dataclasses import ChatMessage, Document
1010

1111

1212
@runtime_checkable
@@ -99,7 +99,16 @@ def to_dict(self) -> Dict[str, Any]:
9999
Serialized dictionary representation of the object.
100100
"""
101101
documents = [doc.to_dict(flatten=False) for doc in self.documents]
102-
return default_to_dict(self, data=self.data, query=self.query, documents=documents, meta=self.meta)
102+
103+
# Serialize ChatMessage objects to dicts
104+
meta = self.meta
105+
all_messages = meta.get("all_messages")
106+
107+
# all_messages is either a list of ChatMessage objects or a list of strings
108+
if all_messages and isinstance(all_messages[0], ChatMessage):
109+
meta = {**meta, "all_messages": [msg.to_dict() for msg in all_messages]}
110+
111+
return default_to_dict(self, data=self.data, query=self.query, documents=documents, meta=meta)
103112

104113
@classmethod
105114
def from_dict(cls, data: Dict[str, Any]) -> "GeneratedAnswer":
@@ -113,7 +122,13 @@ def from_dict(cls, data: Dict[str, Any]) -> "GeneratedAnswer":
113122
Deserialized object.
114123
"""
115124
init_params = data.get("init_parameters", {})
125+
116126
if (documents := init_params.get("documents")) is not None:
117-
data["init_parameters"]["documents"] = [Document.from_dict(d) for d in documents]
127+
init_params["documents"] = [Document.from_dict(d) for d in documents]
128+
129+
meta = init_params.get("meta", {})
130+
if (all_messages := meta.get("all_messages")) is not None and isinstance(all_messages[0], dict):
131+
meta["all_messages"] = [ChatMessage.from_dict(m) for m in all_messages]
132+
init_params["meta"] = meta
118133

119134
return default_from_dict(cls, data)
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
---
2+
fixes:
3+
- Fix serialization of `GeneratedAnswer` when `ChatMessage` objects are nested in `meta`.

test/components/builders/test_answer_builder.py

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@ def test_run_with_meta(self):
5252
assert answers[0].data == "reply1"
5353
_check_metadata_excluding_all_messages(answers[0].meta, {"test": "meta"})
5454
assert "all_messages" in answers[0].meta
55+
assert answers[0].meta["all_messages"] == ["reply1"]
5556
assert answers[0].query == "query"
5657
assert answers[0].documents == []
5758
assert isinstance(answers[0], GeneratedAnswer)
@@ -254,6 +255,9 @@ def test_run_with_chat_message_replies_with_documents(self):
254255
assert answers[0].query == "test query"
255256
assert len(answers[0].documents) == 1
256257
assert answers[0].documents[0].content == "test doc 2"
258+
assert answers[0].meta["all_messages"] == [
259+
ChatMessage.from_assistant("Answer: AnswerString[2]", meta=message_meta)
260+
]
257261

258262
def test_run_with_chat_message_replies_with_pattern_set_at_runtime(self):
259263
component = AnswerBuilder(pattern="unused pattern")

test/dataclasses/test_answer.py

Lines changed: 69 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
# SPDX-License-Identifier: Apache-2.0
44

55
from haystack.dataclasses.answer import Answer, ExtractedAnswer, GeneratedAnswer
6-
from haystack.dataclasses.document import Document
6+
from haystack.dataclasses import Document, ChatMessage
77

88

99
class TestExtractedAnswer:
@@ -133,25 +133,85 @@ def test_protocol(self):
133133
assert isinstance(answer, Answer)
134134

135135
def test_to_dict(self):
136+
answer = GeneratedAnswer(data="42", query="What is the answer?", documents=[])
137+
assert answer.to_dict() == {
138+
"type": "haystack.dataclasses.answer.GeneratedAnswer",
139+
"init_parameters": {"data": "42", "query": "What is the answer?", "documents": [], "meta": {}},
140+
}
141+
142+
def test_to_dict_with_meta(self):
143+
answer = GeneratedAnswer(
144+
data="42",
145+
query="What is the answer?",
146+
documents=[],
147+
meta={"meta_key": "meta_value", "all_messages": ["What is the answer?"]},
148+
)
149+
assert answer.to_dict() == {
150+
"type": "haystack.dataclasses.answer.GeneratedAnswer",
151+
"init_parameters": {
152+
"data": "42",
153+
"query": "What is the answer?",
154+
"documents": [],
155+
"meta": {"meta_key": "meta_value", "all_messages": ["What is the answer?"]},
156+
},
157+
}
158+
159+
def test_to_dict_with_chat_message_in_meta(self):
136160
documents = [
137161
Document(id="1", content="The answer is 42."),
138162
Document(id="2", content="I believe the answer is 42."),
139163
Document(id="3", content="42 is definitely the answer."),
140164
]
141165
answer = GeneratedAnswer(
142-
data="42", query="What is the answer?", documents=documents, meta={"meta_key": "meta_value"}
166+
data="42",
167+
query="What is the answer?",
168+
documents=documents,
169+
meta={"meta_key": "meta_value", "all_messages": [ChatMessage.from_user("What is the answer?")]},
143170
)
144171
assert answer.to_dict() == {
145172
"type": "haystack.dataclasses.answer.GeneratedAnswer",
146173
"init_parameters": {
147174
"data": "42",
148175
"query": "What is the answer?",
149176
"documents": [d.to_dict(flatten=False) for d in documents],
150-
"meta": {"meta_key": "meta_value"},
177+
"meta": {
178+
"meta_key": "meta_value",
179+
"all_messages": [ChatMessage.from_user("What is the answer?").to_dict()],
180+
},
151181
},
152182
}
153183

154184
def test_from_dict(self):
185+
answer = GeneratedAnswer.from_dict(
186+
{
187+
"type": "haystack.dataclasses.answer.GeneratedAnswer",
188+
"init_parameters": {"data": "42", "query": "What is the answer?", "documents": [], "meta": {}},
189+
}
190+
)
191+
assert answer.data == "42"
192+
assert answer.query == "What is the answer?"
193+
assert answer.documents == []
194+
assert answer.meta == {}
195+
196+
def test_from_dict_with_meta(self):
197+
answer = GeneratedAnswer.from_dict(
198+
{
199+
"type": "haystack.dataclasses.answer.GeneratedAnswer",
200+
"init_parameters": {
201+
"data": "42",
202+
"query": "What is the answer?",
203+
"documents": [],
204+
"meta": {"meta_key": "meta_value", "all_messages": ["What is the answer?"]},
205+
},
206+
}
207+
)
208+
assert answer.data == "42"
209+
assert answer.query == "What is the answer?"
210+
assert answer.documents == []
211+
assert answer.meta["meta_key"] == "meta_value"
212+
assert answer.meta["all_messages"] == ["What is the answer?"]
213+
214+
def test_from_dict_with_chat_message_in_meta(self):
155215
answer = GeneratedAnswer.from_dict(
156216
{
157217
"type": "haystack.dataclasses.answer.GeneratedAnswer",
@@ -163,7 +223,10 @@ def test_from_dict(self):
163223
{"id": "2", "content": "I believe the answer is 42."},
164224
{"id": "3", "content": "42 is definitely the answer."},
165225
],
166-
"meta": {"meta_key": "meta_value"},
226+
"meta": {
227+
"meta_key": "meta_value",
228+
"all_messages": [ChatMessage.from_user("What is the answer?").to_dict()],
229+
},
167230
},
168231
}
169232
)
@@ -174,4 +237,5 @@ def test_from_dict(self):
174237
Document(id="2", content="I believe the answer is 42."),
175238
Document(id="3", content="42 is definitely the answer."),
176239
]
177-
assert answer.meta == {"meta_key": "meta_value"}
240+
assert answer.meta["meta_key"] == "meta_value"
241+
assert answer.meta["all_messages"] == [ChatMessage.from_user("What is the answer?")]

0 commit comments

Comments
 (0)