Source code for mllm_shap.value.generation_records
"""Build generation records from generated text for value-function scoring.
@domain value
@see [[knowledge/architecture/value]]
@tested-by [[tests/value/test_generation_records.py]]
"""
import hashlib
import struct
import warnings
from typing import Final
from ..domain.generation import GenerationRecord
EMBED_DIM: Final[int] = 8
"""Dimension of the deterministic hash embedding used by mock and archive paths."""
[docs]
def generation_record_from_text(
text: str, context_id: str | None = None
) -> GenerationRecord:
"""Build a record whose token rows and embeddings derive from ``text`` only.
``explain`` and ``reanalyze`` both score records built here, so a value
function sees the same inputs whether the text came from a backend or an
archive. ``contextual_embedding`` hashes ``text`` with a separator so it
differs from ``embedding``; it takes no prompt or snapshot id, which a
backend sees for the rendered coalition and ``reanalyze`` never sees.
Args:
text: Generated text.
context_id: Deprecated and ignored; passing it emits a
``DeprecationWarning``. It salted ``contextual_embedding`` until
0.1.18, which made backend records differ from ``reanalyze``.
"""
if context_id is not None:
warnings.warn(
"generation_record_from_text(context_id=...) is ignored and will be "
"removed in 0.2.0",
DeprecationWarning,
stacklevel=2,
)
rows = tuple((stable_token_id(token),) for token in text.split())
return GenerationRecord(
text=text,
text_token_rows=rows,
embedding=hash_embedding(text),
contextual_embedding=hash_embedding(f"{text}\0"),
)
[docs]
def stable_token_id(token: str) -> int:
"""Return a process-independent 56-bit id for ``token``."""
digest = hashlib.blake2b(token.encode("utf-8"), digest_size=7).digest()
return int.from_bytes(digest, byteorder="big", signed=False)
[docs]
def hash_embedding(payload: str) -> tuple[float, ...]:
"""Map ``payload`` to a fixed-size float vector in [-1, 1] via SHA-256."""
digest = hashlib.sha256(payload.encode("utf-8")).digest()
return tuple(
(struct.unpack_from("<I", digest, index * 4)[0] / 0xFFFFFFFF) * 2.0 - 1.0
for index in range(EMBED_DIM)
)