diff --git a/haystack/components/generators/utils.py b/haystack/components/generators/utils.py index 4504a05708..25bf764c15 100644 --- a/haystack/components/generators/utils.py +++ b/haystack/components/generators/utils.py @@ -160,7 +160,13 @@ 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 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() if hasattr(obj, "__dict__"): diff --git a/haystack/core/serialization.py b/haystack/core/serialization.py index 85f7d60940..344a6bac95 100644 --- a/haystack/core/serialization.py +++ b/haystack/core/serialization.py @@ -193,6 +193,11 @@ 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._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: ```python @@ -257,7 +262,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._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/tracing/utils.py b/haystack/tracing/utils.py index 0f3b18ff50..10ff4ce43a 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 fe1929e1cd..64e61d250d 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.