From 3aabcb07c8a3e2ff067fa82c8326e1121929ab1d Mon Sep 17 00:00:00 2001 From: "David S. Batista" Date: Tue, 7 Jul 2026 16:10:01 +0200 Subject: [PATCH 1/2] detailing in docstrings the scope and context of each serialisation function --- haystack/components/generators/utils.py | 7 ++++++- haystack/core/serialization.py | 9 ++++++++- haystack/dataclasses/chat_message.py | 3 +++ haystack/tracing/utils.py | 4 ++++ haystack/utils/base_serialization.py | 4 ++++ 5 files changed, 25 insertions(+), 2 deletions(-) diff --git a/haystack/components/generators/utils.py b/haystack/components/generators/utils.py index 4504a057085..47083eebe73 100644 --- a/haystack/components/generators/utils.py +++ b/haystack/components/generators/utils.py @@ -160,7 +160,12 @@ def _convert_streaming_chunks_to_chat_message(chunks: list[StreamingChunk]) -> C def _serialize_object(obj: Any) -> Any: - """Convert an object to a serializable dict recursively""" + """ + Convert an object to a serializable dict recursively. + + Used for OpenAI SDK response objects, so it skips any attribute starting with "_" (SDK-internal + fields). `base_serialization`'s serializer doesn't skip those, so don't swap this out for it. + """ if hasattr(obj, "model_dump"): return obj.model_dump() if hasattr(obj, "__dict__"): diff --git a/haystack/core/serialization.py b/haystack/core/serialization.py index 85f7d609401..e9d1ac8a445 100644 --- a/haystack/core/serialization.py +++ b/haystack/core/serialization.py @@ -193,6 +193,10 @@ def default_to_dict(obj: Any, **init_parameters: Any) -> dict[str, Any]: Objects in `init_parameters` that have a `to_dict()` method are automatically serialized by calling that method. + This is the format used for saved pipeline files (`Pipeline.dump`/`Pipeline.load`). Don't merge + it with `base_serialization`'s serializer — that one uses a different envelope for a different + job (arbitrary runtime values, not Components) and changing either would break saved files. + An example usage: ```python @@ -257,7 +261,10 @@ def default_from_dict(cls: type[T], data: dict[str, Any]) -> T: """ Utility function to deserialize a dictionary to an object. - This is mostly necessary for components but can be used by any object. + This is mostly necessary for components but can be used by any object. Reverses the + `{"type": ..., "init_parameters": ...}` envelope produced by `default_to_dict` — see that + function's docstring for why this envelope is not interchangeable with + `haystack.utils.base_serialization`'s. The function will raise a `DeserializationError` if the `type` field in `data` is missing or it doesn't match the type of `cls`. diff --git a/haystack/dataclasses/chat_message.py b/haystack/dataclasses/chat_message.py index 39da893f665..c9480d2e383 100644 --- a/haystack/dataclasses/chat_message.py +++ b/haystack/dataclasses/chat_message.py @@ -249,6 +249,9 @@ def _serialize_content_part(part: ChatMessageContentT) -> dict[str, Any]: """ Serialize a single content part of a ChatMessage. + Output looks like `{"text": "hello"}` or `{"image": {...}}` — short, hand-writable keys, not a + full class name. People write these by hand (e.g. in chat templates), so keep it this way. + :param part: A ChatMessageContentT object. :returns: diff --git a/haystack/tracing/utils.py b/haystack/tracing/utils.py index 0f3b18ff50b..10ff4ce43ad 100644 --- a/haystack/tracing/utils.py +++ b/haystack/tracing/utils.py @@ -43,6 +43,10 @@ def _serializable_value(value: Any, use_placeholders: bool = True) -> Any: """ Serializes a value into a format suitable for tracing. + One-way only: this is never deserialized, so it's allowed to lose data (e.g. replace large + objects with a placeholder). Don't swap it for `base_serialization`'s schema serializer, which + is built to round-trip exactly. + :param value: The value to serialize. :param use_placeholders: diff --git a/haystack/utils/base_serialization.py b/haystack/utils/base_serialization.py index fe1929e1cd5..64e61d250d7 100644 --- a/haystack/utils/base_serialization.py +++ b/haystack/utils/base_serialization.py @@ -77,6 +77,10 @@ def _serialize_value_with_schema(payload: Any) -> dict[str, Any]: # noqa: PLR09 - Lists, tuples, and sets. Lists with mixed types are not supported. - Primitive types (str, int, float, bool, None) + This is for runtime values (Agent/State data, pipeline inputs/outputs at a breakpoint), not + Component definitions — see `default_to_dict` in `core/serialization.py` for that other format. + Don't merge the two; they're not interchangeable. + :param payload: The value to serialize (can be any type) :returns: The serialized dict representation of the given value. Contains two keys: - "serialization_schema": Contains type information for each field. From 6a68f5c501e5c55792c79b7da632e202f13c3556 Mon Sep 17 00:00:00 2001 From: "David S. Batista" Date: Tue, 7 Jul 2026 17:50:42 +0200 Subject: [PATCH 2/2] attending PR comments --- haystack/components/generators/utils.py | 5 +++-- haystack/core/serialization.py | 7 ++++--- haystack/dataclasses/chat_message.py | 3 --- 3 files changed, 7 insertions(+), 8 deletions(-) diff --git a/haystack/components/generators/utils.py b/haystack/components/generators/utils.py index 47083eebe73..25bf764c15e 100644 --- a/haystack/components/generators/utils.py +++ b/haystack/components/generators/utils.py @@ -163,8 +163,9 @@ def _serialize_object(obj: Any) -> Any: """ Convert an object to a serializable dict recursively. - Used for OpenAI SDK response objects, so it skips any attribute starting with "_" (SDK-internal - fields). `base_serialization`'s serializer doesn't skip those, so don't swap this out for it. + Used to serialize `logprobs` and `usage` from OpenAI SDK response objects, so it skips any + attribute starting with "_" (SDK-internal fields). `base_serialization._serialize_value_with_schema` + doesn't skip those, so don't swap this out for it. """ if hasattr(obj, "model_dump"): return obj.model_dump() diff --git a/haystack/core/serialization.py b/haystack/core/serialization.py index e9d1ac8a445..344a6bac957 100644 --- a/haystack/core/serialization.py +++ b/haystack/core/serialization.py @@ -194,8 +194,9 @@ def default_to_dict(obj: Any, **init_parameters: Any) -> dict[str, Any]: serialized by calling that method. This is the format used for saved pipeline files (`Pipeline.dump`/`Pipeline.load`). Don't merge - it with `base_serialization`'s serializer — that one uses a different envelope for a different - job (arbitrary runtime values, not Components) and changing either would break saved files. + it with `base_serialization._serialize_value_with_schema` — that one uses a different envelope + for a different job (arbitrary runtime values, not Components) and changing either would break + saved files. An example usage: @@ -264,7 +265,7 @@ def default_from_dict(cls: type[T], data: dict[str, Any]) -> T: This is mostly necessary for components but can be used by any object. Reverses the `{"type": ..., "init_parameters": ...}` envelope produced by `default_to_dict` — see that function's docstring for why this envelope is not interchangeable with - `haystack.utils.base_serialization`'s. + `haystack.utils.base_serialization._serialize_value_with_schema`'s. The function will raise a `DeserializationError` if the `type` field in `data` is missing or it doesn't match the type of `cls`. diff --git a/haystack/dataclasses/chat_message.py b/haystack/dataclasses/chat_message.py index c9480d2e383..39da893f665 100644 --- a/haystack/dataclasses/chat_message.py +++ b/haystack/dataclasses/chat_message.py @@ -249,9 +249,6 @@ def _serialize_content_part(part: ChatMessageContentT) -> dict[str, Any]: """ Serialize a single content part of a ChatMessage. - Output looks like `{"text": "hello"}` or `{"image": {...}}` — short, hand-writable keys, not a - full class name. People write these by hand (e.g. in chat templates), so keep it this way. - :param part: A ChatMessageContentT object. :returns: