Repository: deepset-ai/haystack

Issue:
SentenceWindowRetriever: support more document stores
The following document stores, need to have the prefix `meta` in the filter query in order to filter by value in the meta fields

- Astra
- PGVector
- Qdrant

Discussion:

Solution patch:
diff --git a/VERSION.txt b/VERSION.txt
index 3fef9b5648..841bd0ff55 100644
--- a/VERSION.txt
+++ b/VERSION.txt
@@ -1 +1 @@
-2.5.0-rc0
+2.6.0-rc0
diff --git a/haystack/components/builders/chat_prompt_builder.py b/haystack/components/builders/chat_prompt_builder.py
index 3a468066f3..07aabe3c5b 100644
--- a/haystack/components/builders/chat_prompt_builder.py
+++ b/haystack/components/builders/chat_prompt_builder.py
@@ -2,6 +2,7 @@
 #
 # SPDX-License-Identifier: Apache-2.0
 
+from copy import deepcopy
 from typing import Any, Dict, List, Optional, Set
 
 from jinja2 import meta
@@ -194,11 +195,9 @@ def run(
 
                 compiled_template = self._env.from_string(message.content)
                 rendered_content = compiled_template.render(template_variables_combined)
-                rendered_message = (
-                    ChatMessage.from_user(rendered_content)
-                    if message.is_from(ChatRole.USER)
-                    else ChatMessage.from_system(rendered_content)
-                )
+                # deep copy the message to avoid modifying the original message
+                rendered_message: ChatMessage = deepcopy(message)
+                rendered_message.content = rendered_content
                 processed_messages.append(rendered_message)
             else:
                 processed_messages.append(message)
diff --git a/haystack/components/caching/cache_checker.py b/haystack/components/caching/cache_checker.py
index 0ba1e43168..b67608b075 100644
--- a/haystack/components/caching/cache_checker.py
+++ b/haystack/components/caching/cache_checker.py
@@ -6,7 +6,7 @@
 
 from haystack import Document, component, default_from_dict, default_to_dict, logging
 from haystack.document_stores.types import DocumentStore
-from haystack.utils import deserialize_document_store_in_init_parameters
+from haystack.utils import deserialize_document_store_in_init_params_inplace
 
 logger = logging.getLogger(__name__)
 
@@ -73,7 +73,7 @@ def from_dict(cls, data: Dict[str, Any]) -> "CacheChecker":
             Deserialized component.
         """
         # deserialize the document store
-        data = deserialize_document_store_in_init_parameters(data)
+        deserialize_document_store_in_init_params_inplace(data)
 
         return default_from_dict(cls, data)
 
diff --git a/haystack/components/converters/output_adapter.py b/haystack/components/converters/output_adapter.py
index 64cac7996a..50ffd53919 100644
--- a/haystack/components/converters/output_adapter.py
+++ b/haystack/components/converters/output_adapter.py
@@ -5,9 +5,11 @@
 import ast
 import contextlib
 from typing import Any, Callable, Dict, Optional, Set
+from warnings import warn
 
 import jinja2.runtime
-from jinja2 import TemplateSyntaxError, meta
+from jinja2 import Environment, TemplateSyntaxError, meta
+from jinja2.nativetypes import NativeEnvironment
 from jinja2.sandbox import SandboxedEnvironment
 from typing_extensions import TypeAlias
 
@@ -37,7 +39,13 @@ class OutputAdapter:
     ```
     """
 
-    def __init__(self, template: str, output_type: TypeAlias, custom_filters: Optional[Dict[str, Callable]] = None):
+    def __init__(
+        self,
+        template: str,
+        output_type: TypeAlias,
+        custom_filters: Optional[Dict[str, Callable]] = None,
+        unsafe: bool = False,
+    ):
         """
         Create an OutputAdapter component.
 
@@ -54,13 +62,25 @@ def __init__(self, template: str, output_type: TypeAlias, custom_filters: Option
             The type of output this instance will return.
         :param custom_filters:
             A dictionary of custom Jinja filters used in the template.
+        :param unsafe:
+            Enable execution of arbitrary code in the Jinja template.
+            This should only be used if you trust the source of the template as it can be lead to remote code execution.
         """
         self.custom_filters = {**(custom_filters or {})}
         input_types: Set[str] = set()
 
-        # Create a Jinja native environment, we need it to:
-        # a) add custom filters to the environment for filter compilation stage
-        self._env = SandboxedEnvironment(undefined=jinja2.runtime.StrictUndefined)
+        self._unsafe = unsafe
+
+        if self._unsafe:
+            msg = (
+                "Unsafe mode is enabled. This allows execution of arbitrary code in the Jinja template. "
+                "Use this only if you trust the source of the template."
+            )
+            warn(msg)
+        self._env = (
+            NativeEnvironment() if self._unsafe else SandboxedEnvironment(undefined=jinja2.runtime.StrictUndefined)
+        )
+
         try:
             self._env.parse(template)  # Validate template syntax
             self.template = template
@@ -108,7 +128,8 @@ def run(self, **kwargs):
             # This must be done cause the output could be different literal structures.
             # This doesn't support any user types.
             with contextlib.suppress(Exception):
-                output_result = ast.literal_eval(output_result)
+                if not self._unsafe:
+                    output_result = ast.literal_eval(output_result)
 
             adapted_outputs["output"] = output_result
         except Exception as e:
@@ -124,7 +145,11 @@ def to_dict(self) -> Dict[str, Any]:
         """
         se_filters = {name: serialize_callable(filter_func) for name, filter_func in self.custom_filters.items()}
         return default_to_dict(
-            self, template=self.template, output_type=serialize_type(self.output_type), custom_filters=se_filters
+            self,
+            template=self.template,
+            output_type=serialize_type(self.output_type),
+            custom_filters=se_filters,
+            unsafe=self._unsafe,
         )
 
     @classmethod
@@ -148,11 +173,11 @@ def from_dict(cls, data: Dict[str, Any]) -> "OutputAdapter":
             }
         return default_from_dict(cls, data)
 
-    def _extract_variables(self, env: SandboxedEnvironment) -> Set[str]:
+    def _extract_variables(self, env: Environment) -> Set[str]:
         """
         Extracts all variables from a list of Jinja template strings.
 
-        :param env: A Jinja native environment.
+        :param env: A Jinja environment.
         :return: A set of variable names extracted from the template strings.
         """
         ast = env.parse(self.template)
diff --git a/haystack/components/retrievers/filter_retriever.py b/haystack/components/retrievers/filter_retriever.py
index 0f69770a31..6f9ad78b3d 100644
--- a/haystack/components/retrievers/filter_retriever.py
+++ b/haystack/components/retrievers/filter_retriever.py
@@ -6,7 +6,7 @@
 
 from haystack import Document, component, default_from_dict, default_to_dict, logging
 from haystack.document_stores.types import DocumentStore
-from haystack.utils import deserialize_document_store_in_init_parameters
+from haystack.utils import deserialize_document_store_in_init_params_inplace
 
 logger = logging.getLogger(__name__)
 
@@ -78,7 +78,7 @@ def from_dict(cls, data: Dict[str, Any]) -> "FilterRetriever":
             The deserialized component.
         """
         # deserialize the document store
-        data = deserialize_document_store_in_init_parameters(data)
+        deserialize_document_store_in_init_params_inplace(data)
 
         return default_from_dict(cls, data)
 
diff --git a/haystack/components/retrievers/sentence_window_retriever.py b/haystack/components/retrievers/sentence_window_retriever.py
index 7ec000f76e..7b7a7d859a 100644
--- a/haystack/components/retrievers/sentence_window_retriever.py
+++ b/haystack/components/retrievers/sentence_window_retriever.py
@@ -6,7 +6,7 @@
 
 from haystack import Document, component, default_from_dict, default_to_dict
 from haystack.document_stores.types import DocumentStore
-from haystack.utils import deserialize_document_store_in_init_parameters
+from haystack.utils import deserialize_document_store_in_init_params_inplace
 
 
 @component
@@ -24,6 +24,13 @@ class SentenceWindowRetriever:
     EmbeddingRetriever. First, use a Retriever to find documents based on a query and then use
     SentenceWindowRetriever to get the surrounding documents for context.
 
+    The SentenceWindowRetriever is compatible with the following DocumentStores:
+    - (Astra)[https://docs.haystack.deepset.ai/docs/astradocumentstore]
+    - (Elasticsearch)[https://docs.haystack.deepset.ai/docs/elasticsearch-document-store]
+    - (OpenSearch)[https://docs.haystack.deepset.ai/docs/opensearch-document-store]
+    - (Pgvector)[https://docs.haystack.deepset.ai/docs/pgvectordocumentstore]
+    - (Pinecone)[https://docs.haystack.deepset.ai/docs/pinecone-document-store]
+    - (Qdrant)[https://docs.haystack.deepset.ai/docs/qdrant-document-store]
 
     ### Usage example
 
@@ -131,7 +138,7 @@ def from_dict(cls, data: Dict[str, Any]) -> "SentenceWindowRetriever":
             Deserialized component.
         """
         # deserialize the document store
-        data = deserialize_document_store_in_init_parameters(data)
+        deserialize_document_store_in_init_params_inplace(data)
 
         # deserialize the component
         return default_from_dict(cls, data)
@@ -172,9 +179,9 @@ def run(self, retrieved_documents: List[Document]):
                 {
                     "operator": "AND",
                     "conditions": [
-                        {"field": "source_id", "operator": "==", "value": source_id},
-                        {"field": "split_id", "operator": ">=", "value": min_before},
-                        {"field": "split_id", "operator": "<=", "value": max_after},
+                        {"field": "meta.source_id", "operator": "==", "value": source_id},
+                        {"field": "meta.split_id", "operator": ">=", "value": min_before},
+                        {"field": "meta.split_id", "operator": "<=", "value": max_after},
                     ],
                 }
             )
diff --git a/haystack/components/routers/conditional_router.py b/haystack/components/routers/conditional_router.py
index a72bb4fc08..ccac555d3d 100644
--- a/haystack/components/routers/conditional_router.py
+++ b/haystack/components/routers/conditional_router.py
@@ -5,6 +5,7 @@
 import ast
 import contextlib
 from typing import Any, Callable, Dict, List, Optional, Set
+from warnings import warn
 
 from jinja2 import Environment, TemplateSyntaxError, meta
 from jinja2.nativetypes import NativeEnvironment
@@ -106,7 +107,7 @@ class ConditionalRouter:
     ```
     """
 
-    def __init__(self, routes: List[Dict], custom_filters: Optional[Dict[str, Callable]] = None):
+    def __init__(self, routes: List[Dict], custom_filters: Optional[Dict[str, Callable]] = None, unsafe: bool = False):
         """
         Initializes the `ConditionalRouter` with a list of routes detailing the conditions for routing.
 
@@ -123,15 +124,26 @@ def __init__(self, routes: List[Dict], custom_filters: Optional[Dict[str, Callab
             - `my_filter_fcn` is a callable that takes `my_var:str` and returns `my_var[:3]`.
               `{{ my_var|my_filter }}` can then be used inside a route condition expression:
                 `"condition": "{{ my_var|my_filter == 'foo' }}"`.
+        :param unsafe:
+            Enable execution of arbitrary code in the Jinja template.
+            This should only be used if you trust the source of the template as it can be lead to remote code execution.
         """
-        self._validate_routes(routes)
         self.routes: List[dict] = routes
         self.custom_filters = custom_filters or {}
+        self._unsafe = unsafe
+
+        # Create a Jinja environment to inspect variables in the condition templates
+        if self._unsafe:
+            msg = (
+                "Unsafe mode is enabled. This allows execution of arbitrary code in the Jinja template. "
+                "Use this only if you trust the source of the template."
+            )
+            warn(msg)
 
-        # Create a Jinja native environment to inspect variables in the condition templates
-        self._env = SandboxedEnvironment()
+        self._env = NativeEnvironment() if self._unsafe else SandboxedEnvironment()
         self._env.filters.update(self.custom_filters)
 
+        self._validate_routes(routes)
         # Inspect the routes to determine input and output types.
         input_types: Set[str] = set()  # let's just store the name, type will always be Any
         output_types: Dict[str, str] = {}
@@ -158,7 +170,7 @@ def to_dict(self) -> Dict[str, Any]:
             # output_type needs to be serialized to a string
             route["output_type"] = serialize_type(route["output_type"])
         se_filters = {name: serialize_callable(filter_func) for name, filter_func in self.custom_filters.items()}
-        return default_to_dict(self, routes=self.routes, custom_filters=se_filters)
+        return default_to_dict(self, routes=self.routes, custom_filters=se_filters, unsafe=self._unsafe)
 
     @classmethod
     def from_dict(cls, data: Dict[str, Any]) -> "ConditionalRouter":
@@ -202,7 +214,9 @@ def run(self, **kwargs):
             try:
                 t = self._env.from_string(route["condition"])
                 rendered = t.render(**kwargs)
-                if ast.literal_eval(rendered):
+                if not self._unsafe:
+                    rendered = ast.literal_eval(rendered)
+                if rendered:
                     # We now evaluate the `output` expression to determine the route output
                     t_output = self._env.from_string(route["output"])
                     output = t_output.render(**kwargs)
@@ -211,7 +225,8 @@ def run(self, **kwargs):
                     # This must be done cause the output could be different literal structures.
                     # This doesn't support any user types.
                     with contextlib.suppress(Exception):
-                        output = ast.literal_eval(output)
+                        if not self._unsafe:
+                            output = ast.literal_eval(output)
                     # and return the output as a dictionary under the output_name key
                     return {route["output_name"]: output}
             except Exception as e:
@@ -225,7 +240,6 @@ def _validate_routes(self, routes: List[Dict]):
 
         :param routes: A list of routes.
         """
-        env = NativeEnvironment()
         for route in routes:
             try:
                 keys = set(route.keys())
@@ -239,10 +253,10 @@ def _validate_routes(self, routes: List[Dict]):
                     f"Route must contain 'condition', 'output', 'output_type' and 'output_name' fields: {route}"
                 )
             for field in ["condition", "output"]:
-                if not self._validate_template(env, route[field]):
+                if not self._validate_template(self._env, route[field]):
                     raise ValueError(f"Invalid template for field '{field}': {route[field]}")
 
-    def _extract_variables(self, env: SandboxedEnvironment, templates: List[str]) -> Set[str]:
+    def _extract_variables(self, env: Environment, templates: List[str]) -> Set[str]:
         """
         Extracts all variables from a list of Jinja template strings.
 
diff --git a/haystack/components/writers/document_writer.py b/haystack/components/writers/document_writer.py
index bd1fa3138f..7e75759638 100644
--- a/haystack/components/writers/document_writer.py
+++ b/haystack/components/writers/document_writer.py
@@ -6,7 +6,7 @@
 
 from haystack import Document, component, default_from_dict, default_to_dict, logging
 from haystack.document_stores.types import DocumentStore, DuplicatePolicy
-from haystack.utils import deserialize_document_store_in_init_parameters
+from haystack.utils import deserialize_document_store_in_init_params_inplace
 
 logger = logging.getLogger(__name__)
 
@@ -74,7 +74,7 @@ def from_dict(cls, data: Dict[str, Any]) -> "DocumentWriter":
             If the document store is not properly specified in the serialization data or its type cannot be imported.
         """
         # deserialize the document store
-        data = deserialize_document_store_in_init_parameters(data)
+        deserialize_document_store_in_init_params_inplace(data)
 
         data["init_parameters"]["policy"] = DuplicatePolicy[data["init_parameters"]["policy"]]
 
diff --git a/haystack/utils/__init__.py b/haystack/utils/__init__.py
index baefe8fa06..dd89ad8264 100644
--- a/haystack/utils/__init__.py
+++ b/haystack/utils/__init__.py
@@ -5,7 +5,7 @@
 from .auth import Secret, deserialize_secrets_inplace
 from .callable_serialization import deserialize_callable, serialize_callable
 from .device import ComponentDevice, Device, DeviceMap, DeviceType
-from .docstore_deserialization import deserialize_document_store_in_init_parameters
+from .docstore_deserialization import deserialize_document_store_in_init_params_inplace
 from .expit import expit
 from .filters import document_matches_filter
 from .jupyter import is_in_jupyter
@@ -27,5 +27,5 @@
     "deserialize_callable",
     "serialize_type",
     "deserialize_type",
-    "deserialize_document_store_in_init_parameters",
+    "deserialize_document_store_in_init_params_inplace",
 ]
diff --git a/haystack/utils/docstore_deserialization.py b/haystack/utils/docstore_deserialization.py
index 419a2fa0cd..41d8b2714d 100644
--- a/haystack/utils/docstore_deserialization.py
+++ b/haystack/utils/docstore_deserialization.py
@@ -8,9 +8,9 @@
 from haystack.core.serialization import default_from_dict, import_class_by_name
 
 
-def deserialize_document_store_in_init_parameters(data: Dict[str, Any], key: str = "document_store") -> Dict[str, Any]:
+def deserialize_document_store_in_init_params_inplace(data: Dict[str, Any], key: str = "document_store"):
     """
-    Deserializes a generic document store from the init_parameters of a serialized component.
+    Deserializes a generic document store from the init_parameters of a serialized component in place.
 
     :param data:
         The dictionary to deserialize from.
@@ -37,5 +37,3 @@ def deserialize_document_store_in_init_parameters(data: Dict[str, Any], key: str
         data["init_parameters"][key] = doc_store_class.from_dict(doc_store_data)
     else:
         data["init_parameters"][key] = default_from_dict(doc_store_class, doc_store_data)
-
-    return data
diff --git a/releasenotes/notes/adapt-chat-promp-builder-message-copy-6aa4de39363ffbf2.yaml b/releasenotes/notes/adapt-chat-promp-builder-message-copy-6aa4de39363ffbf2.yaml
new file mode 100644
index 0000000000..c3a9e049d6
--- /dev/null
+++ b/releasenotes/notes/adapt-chat-promp-builder-message-copy-6aa4de39363ffbf2.yaml
@@ -0,0 +1,4 @@
+---
+enhancements:
+  - |
+    Adapts how ChatPromptBuilder creates ChatMessages. Messages are deep copied to ensure all meta fields are copied correctly.
diff --git a/releasenotes/notes/fix-sentence-window-retriever-filter-b7b136eedf6e8488.yaml b/releasenotes/notes/fix-sentence-window-retriever-filter-b7b136eedf6e8488.yaml
new file mode 100644
index 0000000000..3e29f416d2
--- /dev/null
+++ b/releasenotes/notes/fix-sentence-window-retriever-filter-b7b136eedf6e8488.yaml
@@ -0,0 +1,4 @@
+---
+fixes:
+  - |
+    Fixing the filters in the `SentenceWindowRetriever` allowing now support for 3 more DocumentStores: Astra, PGVector, Qdrant
diff --git a/releasenotes/notes/refactor_deserialize_document_store_in_init_parameters-b7f05173f0c56452.yaml b/releasenotes/notes/refactor_deserialize_document_store_in_init_parameters-b7f05173f0c56452.yaml
new file mode 100644
index 0000000000..500f0f4f60
--- /dev/null
+++ b/releasenotes/notes/refactor_deserialize_document_store_in_init_parameters-b7f05173f0c56452.yaml
@@ -0,0 +1,5 @@
+---
+enhancements:
+  - |
+    Refactor deserialize_document_store_in_init_parameters so that new function name
+   indicates that the operation occurs in place, with no return value. 
diff --git a/releasenotes/notes/unsafe-behaviour-e8b41d957113e0c3.yaml b/releasenotes/notes/unsafe-behaviour-e8b41d957113e0c3.yaml
new file mode 100644
index 0000000000..d6242432e3
--- /dev/null
+++ b/releasenotes/notes/unsafe-behaviour-e8b41d957113e0c3.yaml
@@ -0,0 +1,8 @@
+---
+features:
+  - |
+    Add `unsafe` argument to enable behaviour that could lead to remote code execution in `ConditionalRouter` and `OutputAdapter`.
+    By default unsafe behaviour is not enabled, the user must set it explicitly to `True`.
+    This means that user types like `ChatMessage`, `Document`, and `Answer` can be used as output types when `unsafe` is `True`.
+    We recommend using `unsafe` behaviour only when the Jinja templates source is trusted.
+    For more info see the documentation for [`ConditionalRouter`](https://docs.haystack.deepset.ai/docs/conditionalrouter#unsafe-behaviour) and [`OutputAdapter`](https://docs.haystack.deepset.ai/docs/outputadapter#unsafe-behaviour)


Test patch:
diff --git a/test/components/builders/test_chat_prompt_builder.py b/test/components/builders/test_chat_prompt_builder.py
index 4fa0870f62..e40b47b508 100644
--- a/test/components/builders/test_chat_prompt_builder.py
+++ b/test/components/builders/test_chat_prompt_builder.py
@@ -5,7 +5,7 @@
 from haystack.components.builders.chat_prompt_builder import ChatPromptBuilder
 from haystack import component
 from haystack.core.pipeline.pipeline import Pipeline
-from haystack.dataclasses.chat_message import ChatMessage
+from haystack.dataclasses.chat_message import ChatMessage, ChatRole
 from haystack.dataclasses.document import Document
 
 
@@ -194,6 +194,17 @@ def test_run_overwriting_default_template_with_variables(self):
 
         assert builder.run(template, name="John", var1="Big") == expected_result
 
+    def test_run_with_meta(self):
+        """
+        Test that the ChatPromptBuilder correctly handles meta data.
+        It should render the message and copy the meta data from the original message.
+        """
+        m = ChatMessage(content="This is a {{ variable }}", role=ChatRole.USER, name=None, meta={"test": "test"})
+        builder = ChatPromptBuilder(template=[m])
+        res = builder.run(variable="test")
+        res_msg = ChatMessage(content="This is a test", role=ChatRole.USER, name=None, meta={"test": "test"})
+        assert res == {"prompt": [res_msg]}
+
     def test_run_with_invalid_template(self):
         builder = ChatPromptBuilder()
 
diff --git a/test/components/converters/test_output_adapter.py b/test/components/converters/test_output_adapter.py
index 25b5eb7e07..547ce433e6 100644
--- a/test/components/converters/test_output_adapter.py
+++ b/test/components/converters/test_output_adapter.py
@@ -8,6 +8,7 @@
 import pytest
 
 from haystack import Pipeline, component
+from haystack.dataclasses import Document
 from haystack.components.converters import OutputAdapter
 from haystack.components.converters.output_adapter import OutputAdaptationException
 
@@ -150,6 +151,7 @@ def test_output_adapter_from_dict_custom_filters_none(self):
                     "template": "{{ documents[0].content}}",
                     "output_type": "str",
                     "custom_filters": None,
+                    "unsafe": False,
                 },
             }
         )
@@ -157,6 +159,7 @@ def test_output_adapter_from_dict_custom_filters_none(self):
         assert component.template == "{{ documents[0].content}}"
         assert component.output_type == str
         assert component.custom_filters == {}
+        assert not component._unsafe
 
     def test_output_adapter_in_pipeline(self):
         @component
@@ -179,3 +182,13 @@ def run(self):
         result = pipe.run(data={})
         assert result
         assert result["output_adapter"]["output"] == {"framework": "Haystack"}
+
+    def test_unsafe(self):
+        adapter = OutputAdapter(template="{{ documents[0] }}", output_type=Document, unsafe=True)
+        documents = [
+            Document(content="Test document"),
+            Document(content="Another test document"),
+            Document(content="Yet another test document"),
+        ]
+        res = adapter.run(documents=documents)
+        assert res["output"] == documents[0]
diff --git a/test/components/routers/test_conditional_router.py b/test/components/routers/test_conditional_router.py
index b324cf8067..47873e3d3f 100644
--- a/test/components/routers/test_conditional_router.py
+++ b/test/components/routers/test_conditional_router.py
@@ -307,3 +307,24 @@ def test_sede_with_custom_filter(self):
         assert deserialized_router.custom_filters == router.custom_filters
         assert deserialized_router.custom_filters["custom_filter_to_sede"]("123-456-789") == 123
         assert result == deserialized_router.run(**kwargs)
+
+    def test_unsafe(self):
+        routes = [
+            {
+                "condition": "{{streams|length < 2}}",
+                "output": "{{message}}",
+                "output_type": ChatMessage,
+                "output_name": "message",
+            },
+            {
+                "condition": "{{streams|length >= 2}}",
+                "output": "{{streams}}",
+                "output_type": List[int],
+                "output_name": "streams",
+            },
+        ]
+        router = ConditionalRouter(routes, unsafe=True)
+        streams = [1]
+        message = ChatMessage.from_user(content="This is a message")
+        res = router.run(streams=streams, message=message)
+        assert res == {"message": message}
diff --git a/test/utils/test_docstore_deserialization.py b/test/utils/test_docstore_deserialization.py
index 7a70ad37ee..ffce99dd2c 100644
--- a/test/utils/test_docstore_deserialization.py
+++ b/test/utils/test_docstore_deserialization.py
@@ -6,7 +6,7 @@
 import pytest
 
 from haystack.document_stores.in_memory.document_store import InMemoryDocumentStore
-from haystack.utils.docstore_deserialization import deserialize_document_store_in_init_parameters
+from haystack.utils.docstore_deserialization import deserialize_document_store_in_init_params_inplace
 from haystack.core.errors import DeserializationError
 
 
@@ -14,7 +14,7 @@ class FakeDocumentStore:
     pass
 
 
-def test_deserialize_document_store_in_init_parameters():
+def test_deserialize_document_store_in_init_params_inplace():
     data = {
         "type": "haystack.components.writers.document_writer.DocumentWriter",
         "init_parameters": {
@@ -25,8 +25,8 @@ def test_deserialize_document_store_in_init_parameters():
         },
     }
 
-    result = deserialize_document_store_in_init_parameters(data)
-    assert isinstance(result["init_parameters"]["document_store"], InMemoryDocumentStore)
+    deserialize_document_store_in_init_params_inplace(data)
+    assert isinstance(data["init_parameters"]["document_store"], InMemoryDocumentStore)
 
 
 def test_from_dict_is_called():
@@ -42,7 +42,7 @@ def test_from_dict_is_called():
     }
 
     with patch.object(InMemoryDocumentStore, "from_dict") as mock_from_dict:
-        deserialize_document_store_in_init_parameters(data)
+        deserialize_document_store_in_init_params_inplace(data)
 
         mock_from_dict.assert_called_once_with(
             {"type": "haystack.document_stores.in_memory.document_store.InMemoryDocumentStore", "init_parameters": {}}
@@ -59,7 +59,7 @@ def test_default_from_dict_is_called():
     }
 
     with patch("haystack.utils.docstore_deserialization.default_from_dict") as mock_default_from_dict:
-        deserialize_document_store_in_init_parameters(data)
+        deserialize_document_store_in_init_params_inplace(data)
 
         mock_default_from_dict.assert_called_once_with(
             FakeDocumentStore, {"type": "test_docstore_deserialization.FakeDocumentStore", "init_parameters": {}}
@@ -69,13 +69,13 @@ def test_default_from_dict_is_called():
 def test_missing_document_store_key():
     data = {"init_parameters": {"policy": "SKIP"}}
     with pytest.raises(DeserializationError):
-        deserialize_document_store_in_init_parameters(data)
+        deserialize_document_store_in_init_params_inplace(data)
 
 
 def test_missing_type_key_in_document_store():
     data = {"init_parameters": {"document_store": {"init_parameters": {}}, "policy": "SKIP"}}
     with pytest.raises(DeserializationError):
-        deserialize_document_store_in_init_parameters(data)
+        deserialize_document_store_in_init_params_inplace(data)
 
 
 def test_invalid_class_import():
@@ -86,4 +86,4 @@ def test_invalid_class_import():
         }
     }
     with pytest.raises(DeserializationError):
-        deserialize_document_store_in_init_parameters(data)
+        deserialize_document_store_in_init_params_inplace(data)