Source code for mllm_shap.backends.mock.backend
"""Deterministic mock backend for CI-safe E2E tests.
@domain backends
@see [[knowledge/architecture/backends]]
@tested-by [[tests/backends/test_mock.py]]
"""
import hashlib
import struct
from typing import TYPE_CHECKING, ClassVar
from ...domain.config import GenerationConfig
from ...domain.conversation import ConversationSnapshot
from ...domain.generation import GenerationRecord
from ...protocols.plugin import PluginOptions
from ...value.generation_records import generation_record_from_text
from .._chat import message_chat_text
if TYPE_CHECKING:
from ...plugins.context import PluginContext
[docs]
class MockBackend:
"""Hash-based generative backend with no external model dependencies."""
options_model: ClassVar[type[PluginOptions]] = PluginOptions
"""Accepts no ``explanation.plugin_options``."""
def __init__(
self,
context: "PluginContext | None" = None,
*,
model_id: str = "mock",
) -> None:
"""Use ``context.config.backend.model_id`` when a context is given."""
self._model_id = (
context.config.backend.model_id if context is not None else model_id
)
@property
def model_id(self) -> str:
"""Return the backend model identifier."""
return self._model_id
[docs]
async def generate(
self,
snapshot: ConversationSnapshot,
generation: GenerationConfig,
) -> GenerationRecord:
"""Return deterministic generation output for ``snapshot``."""
text = _deterministic_text(snapshot, self._model_id, generation)
return generation_record_from_text(text)
def _deterministic_text(
snapshot: ConversationSnapshot,
model_id: str,
generation: GenerationConfig,
) -> str:
hasher = hashlib.sha256()
hasher.update(snapshot.snapshot_id.encode("utf-8"))
for turn in snapshot.turns:
for message in turn.messages:
hasher.update(message_chat_text(message).encode("utf-8"))
hasher.update(model_id.encode("utf-8"))
hasher.update(
struct.pack(
"<ifi", generation.max_new_tokens, generation.temperature, generation.top_k
),
)
digest = hasher.hexdigest()
token_count = max(1, min(generation.max_new_tokens, 16))
return " ".join(digest[index : index + 4] for index in range(0, token_count * 4, 4))