Files
wehub-resource-sync c56bef871b
Sync docs with Docusaurus / sync (push) Waiting to run
Tests / Check if changed (push) Waiting to run
Tests / format (push) Blocked by required conditions
Tests / check-imports (push) Blocked by required conditions
Tests / Unit / macos-latest (push) Blocked by required conditions
Tests / Unit / ubuntu-latest (push) Blocked by required conditions
Tests / Unit / windows-latest (push) Blocked by required conditions
Tests / mypy (push) Blocked by required conditions
Tests / Integration / ubuntu-latest (push) Blocked by required conditions
Tests / Integration / macos-latest (push) Blocked by required conditions
Tests / Integration / windows-latest (push) Blocked by required conditions
Tests / notify-slack-on-failure (push) Blocked by required conditions
Tests / Mark tests as completed (push) Blocked by required conditions
Docker image release / Build base image (push) Waiting to run
CodeQL / Analyze (python) (push) Has been cancelled
Update Platform Components Table / update (push) Has been cancelled
chore: import upstream snapshot with attribution
2026-07-13 13:22:28 +08:00

157 lines
5.4 KiB
Python

# SPDX-FileCopyrightText: 2022-present deepset GmbH <info@deepset.ai>
#
# SPDX-License-Identifier: Apache-2.0
from dataclasses import asdict, dataclass, field
from typing import Any, Optional, Protocol, runtime_checkable
from haystack.core.serialization import default_from_dict, default_to_dict
from haystack.dataclasses import ChatMessage, Document
from haystack.utils.dataclasses import _warn_on_inplace_mutation
@runtime_checkable
@dataclass
class Answer(Protocol):
data: Any
query: str
meta: dict[str, Any]
def to_dict(self) -> dict[str, Any]: # noqa: D102
...
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "Answer": # noqa: D102
...
@_warn_on_inplace_mutation
@dataclass
class ExtractedAnswer:
"""
Holds an answer extracted by an extractive Reader (query, score, text, and optional document/context).
"""
query: str
score: float
data: str | None = None
document: Document | None = None
context: str | None = None
document_offset: Optional["Span"] = None
context_offset: Optional["Span"] = None
meta: dict[str, Any] = field(default_factory=dict)
@_warn_on_inplace_mutation
@dataclass
class Span:
start: int
end: int
def to_dict(self) -> dict[str, Any]:
"""
Serialize the object to a dictionary.
:returns:
Serialized dictionary representation of the object.
"""
document = self.document.to_dict(flatten=False) if self.document is not None else None
document_offset = asdict(self.document_offset) if self.document_offset is not None else None
context_offset = asdict(self.context_offset) if self.context_offset is not None else None
return default_to_dict(
self,
data=self.data,
query=self.query,
document=document,
context=self.context,
score=self.score,
document_offset=document_offset,
context_offset=context_offset,
meta=self.meta,
)
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "ExtractedAnswer":
"""
Deserialize the object from a dictionary.
:param data:
Dictionary representation of the object.
:returns:
Deserialized object.
"""
# Shallow-copy the init parameters so `from_dict` stays side-effect free: the nested
# replacements below otherwise mutate the caller's dict in place, corrupting it for reuse
# (a second deserialization of the same dict would then receive already-parsed objects).
init_params = data.get("init_parameters", {})
new_params = dict(init_params)
if (doc := init_params.get("document")) is not None:
new_params["document"] = Document.from_dict(doc)
if (offset := init_params.get("document_offset")) is not None:
new_params["document_offset"] = ExtractedAnswer.Span(**offset)
if (offset := init_params.get("context_offset")) is not None:
new_params["context_offset"] = ExtractedAnswer.Span(**offset)
return default_from_dict(cls, {**data, "init_parameters": new_params})
@_warn_on_inplace_mutation
@dataclass
class GeneratedAnswer:
"""
Holds a generated answer from a Generator (answer text, query, referenced documents, and metadata).
"""
data: str
query: str
documents: list[Document]
meta: dict[str, Any] = field(default_factory=dict)
def to_dict(self) -> dict[str, Any]:
"""
Serialize the object to a dictionary.
:returns:
Serialized dictionary representation of the object.
"""
documents = [doc.to_dict(flatten=False) for doc in self.documents]
# Serialize ChatMessage objects to dicts
meta = self.meta
all_messages = meta.get("all_messages")
# all_messages is either a list of ChatMessage objects or a list of strings
if all_messages and isinstance(all_messages[0], ChatMessage):
meta = {**meta, "all_messages": [msg.to_dict() for msg in all_messages]}
return default_to_dict(self, data=self.data, query=self.query, documents=documents, meta=meta)
@classmethod
def from_dict(cls, data: dict[str, Any]) -> "GeneratedAnswer":
"""
Deserialize the object from a dictionary.
:param data:
Dictionary representation of the object.
:returns:
Deserialized object.
"""
# Shallow-copy the init parameters so `from_dict` stays side-effect free: the nested
# replacements below otherwise mutate the caller's dict in place, corrupting it for reuse
# (a second deserialization of the same dict would then receive already-parsed objects).
init_params = data.get("init_parameters", {})
new_params = dict(init_params)
if (documents := init_params.get("documents")) is not None:
new_params["documents"] = [Document.from_dict(d) for d in documents]
# Shallow-copy `meta` before touching `all_messages` so the caller's nested dict is
# left untouched as well.
meta = dict(init_params.get("meta", {}))
if (all_messages := meta.get("all_messages")) and isinstance(all_messages[0], dict):
meta["all_messages"] = [ChatMessage.from_dict(m) for m in all_messages]
new_params["meta"] = meta
return default_from_dict(cls, {**data, "init_parameters": new_params})