Skip to content

Commit 200cb45

Browse files
fix: replace in-place dataclass mutations with dataclasses.replace() (#3112)
* fix: replace in-place dataclass mutations with dataclasses.replace() Fixes DeprecationWarnings triggered by direct attribute assignment on Haystack dataclass instances (Document, StreamingChunk, ChatMessage). Affected components: - fastembed ranker: doc.score - google_vertex document embedder: doc.embedding - nvidia ranker: doc.score - ollama document embedder: doc.embedding (sync + async) - ollama chat generator: chunk.start (sync + async), chat_msg._meta Part of deepset-ai/haystack#10956 * fix(nvidia): sort imports in ranker.py
1 parent 87fba25 commit 200cb45

5 files changed

Lines changed: 22 additions & 26 deletions

File tree

integrations/fastembed/src/haystack_integrations/components/rankers/fastembed/ranker.py

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

5+
from dataclasses import replace
56
from typing import Any
67

78
from haystack import Document, component, default_from_dict, default_to_dict, logging
@@ -198,10 +199,6 @@ def run(self, query: str, documents: list[Document], top_k: int | None = None) -
198199
# Sort the list of tuples by the score in descending order
199200
sorted_doc_scores = sorted(doc_scores, key=lambda x: x[1], reverse=True)
200201

201-
# Get the top_k documents
202-
top_k_documents = []
203-
for doc, score in sorted_doc_scores[:top_k]:
204-
doc.score = score
205-
top_k_documents.append(doc)
202+
top_k_documents = [replace(doc, score=score) for doc, score in sorted_doc_scores[:top_k]]
206203

207204
return {"documents": top_k_documents}

integrations/google_vertex/src/haystack_integrations/components/embedders/google_vertex/document_embedder.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import math
22
import time
3+
from dataclasses import replace
34
from typing import Any, Literal, Optional
45

56
import vertexai
@@ -271,8 +272,7 @@ def run(self, documents: list[Document]):
271272
i += batch_size
272273
batch_number += 1
273274

274-
for doc, embeddings in zip(documents, all_embeddings):
275-
doc.embedding = embeddings
275+
documents = [replace(doc, embedding=emb) for doc, emb in zip(documents, all_embeddings)]
276276

277277
return {"documents": documents}
278278

integrations/nvidia/src/haystack_integrations/components/rankers/nvidia/ranker.py

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44

55
import os
66
import warnings
7+
from dataclasses import replace
78
from typing import Any
89

910
from haystack import Document, component, default_from_dict, default_to_dict, logging
@@ -236,11 +237,8 @@ def run(self, query: str, documents: list[Document], top_k: int | None = None) -
236237

237238
# rank result is list[{index: int, logit: float}] sorted by logit
238239
sorted_indexes_and_scores = self.backend.rank(query_text=query_text, document_texts=document_texts)
239-
sorted_documents = []
240-
for item in sorted_indexes_and_scores[:top_k]:
241-
# mutate (don't copy) the document because we're only updating the score
242-
doc = documents[item["index"]]
243-
doc.score = item["logit"]
244-
sorted_documents.append(doc)
240+
sorted_documents = [
241+
replace(documents[item["index"]], score=item["logit"]) for item in sorted_indexes_and_scores[:top_k]
242+
]
245243

246244
return {"documents": sorted_documents}

integrations/ollama/src/haystack_integrations/components/embedders/ollama/document_embedder.py

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import asyncio
2+
from dataclasses import replace
23
from typing import Any
34

45
from haystack import Document, component
@@ -209,8 +210,7 @@ def run(
209210
texts_to_embed=texts_to_embed, batch_size=self.batch_size, generation_kwargs=generation_kwargs
210211
)
211212

212-
for doc, emb in zip(documents, embeddings, strict=True):
213-
doc.embedding = emb
213+
documents = [replace(doc, embedding=emb) for doc, emb in zip(documents, embeddings, strict=True)]
214214

215215
return {"documents": documents, "meta": {"model": self.model}}
216216

@@ -245,7 +245,6 @@ async def run_async(
245245
texts_to_embed=texts_to_embed, batch_size=self.batch_size, generation_kwargs=generation_kwargs
246246
)
247247

248-
for doc, emb in zip(documents, embeddings, strict=True):
249-
doc.embedding = emb
248+
documents = [replace(doc, embedding=emb) for doc, emb in zip(documents, embeddings, strict=True)]
250249

251250
return {"documents": documents, "meta": {"model": self.model}}

integrations/ollama/src/haystack_integrations/components/generators/ollama/chat/chat_generator.py

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
import json
22
from collections.abc import AsyncIterator, Callable, Iterator
3+
from dataclasses import replace
34
from typing import Any, Literal
45

56
from haystack import component, default_from_dict, default_to_dict
@@ -174,9 +175,12 @@ def _convert_ollama_response_to_chatmessage(ollama_response: ChatResponse) -> Ch
174175

175176
reasoning = ollama_message.get("thinking", None)
176177

177-
chat_msg = ChatMessage.from_assistant(text=text or None, tool_calls=tool_calls, reasoning=reasoning)
178-
179-
chat_msg._meta = _convert_ollama_meta_to_openai_format(response_dict)
178+
chat_msg = ChatMessage.from_assistant(
179+
text=text or None,
180+
tool_calls=tool_calls,
181+
reasoning=reasoning,
182+
meta=_convert_ollama_meta_to_openai_format(response_dict),
183+
)
180184

181185
return chat_msg
182186

@@ -379,10 +383,9 @@ def _handle_streaming_response(
379383
chunk = _build_chunk(
380384
chunk_response=raw, component_info=component_info, index=index, tool_call_index=tool_call_index
381385
)
382-
chunks.append(chunk)
383-
384386
start = index == 0 or bool(chunk.tool_calls)
385-
chunk.start = start
387+
chunk = replace(chunk, start=start)
388+
chunks.append(chunk)
386389

387390
if chunk.tool_calls:
388391
for tool_call in chunk.tool_calls:
@@ -463,10 +466,9 @@ async def _handle_streaming_response_async(
463466
chunk = _build_chunk(
464467
chunk_response=raw, component_info=component_info, index=index, tool_call_index=tool_call_index
465468
)
466-
chunks.append(chunk)
467-
468469
start = index == 0 or bool(chunk.tool_calls)
469-
chunk.start = start
470+
chunk = replace(chunk, start=start)
471+
chunks.append(chunk)
470472

471473
if chunk.tool_calls:
472474
for tool_call in chunk.tool_calls:

0 commit comments

Comments
 (0)