Source code for mud_server.translation.config

"""Translation layer configuration.

``TranslationLayerConfig`` is a frozen dataclass that mirrors the
``translation_layer`` block inside each world's ``world.json``.  It is
loaded once when the World is initialised and never mutated at runtime.

Configuration precedence (locked)
----------------------------------
1. If server-level ``ollama_translation.enabled = false``
   → translation is OFF globally (enforced in World._init_translation_service).
2. Else if world.json ``translation_layer.enabled = true``
   → translation is ON for that world.
3. Otherwise → OFF.

World config may override individual field values (model, timeout, etc.),
but it cannot override the server master switch.

Deterministic mode
------------------
When ``deterministic = true`` the renderer will clamp temperature to 0.0
and use an integer seed derived from the IPC hash.

IPC hash sourcing (FUTURE — axis engine integration)
----------------------------------------------------
Currently ``translate()`` accepts an optional ``ipc_hash: str | None``
parameter.  When it is ``None`` (the default) deterministic mode is
skipped even if ``deterministic = true`` in config, because there is no
seed available yet.

Once the axis engine is built and integrated, it will call::

    ipc_hash = axis_engine.compute_ipc(world_id, entity_a, entity_b, turn)

and pass that hash to ``service.translate(..., ipc_hash=ipc_hash)``.  At
that point deterministic mode will activate automatically when
``deterministic = true`` and the hash is provided.

See ``_working/translation_layer/ooc_ic_translator_design_principles.md``
section 3 (Determinism Mode) for the full specification.
"""

from __future__ import annotations

from dataclasses import dataclass
from pathlib import Path


[docs] @dataclass(frozen=True) class TranslationLayerConfig: """Immutable, world-scoped configuration for the OOC→IC translation layer. Loaded from the ``translation_layer`` block in a world's ``world.json`` and frozen after construction so that no runtime code can mutate it. Attributes: enabled: Master switch. If ``False`` the service will not be instantiated and the layer is inactive. model: Ollama model tag (e.g. ``"gemma2:2b"``). ollama_base_url: Base URL of the running Ollama instance. The ``/api/chat`` path is appended automatically. timeout_seconds: HTTP request timeout for Ollama calls. On expiry the service returns ``None`` and the caller falls back to the OOC message. keep_alive: Ollama ``keep_alive`` duration string. Controls how long the model stays loaded in GPU/CPU memory after each request (e.g. ``"5m"``). Prevents cold-start reloads between consecutive requests. strict_mode: When ``True``, any non-compliant LLM output (multi-line, forbidden patterns, over-length) triggers an immediate fallback rather than a best-effort cleanup. max_output_chars: Hard ceiling on IC output length. Responses exceeding this are rejected (strict) or truncated (non-strict). prompt_policy_id: Canonical prompt policy id selector used for runtime resolution (for example ``prompt:translation.prompts.ic:default``). active_axes: Subset of axis names to include in the character profile sent to the LLM. An empty list means "all axes that exist for this character". deterministic: When ``True`` and a non-``None`` ``ipc_hash`` is provided to ``translate()``, the renderer will use ``temperature=0.0`` and a seed derived from the IPC hash. See module docstring for IPC sourcing status. """ enabled: bool model: str ollama_base_url: str timeout_seconds: float keep_alive: str strict_mode: bool max_output_chars: int prompt_policy_id: str | None active_axes: list[str] deterministic: bool @property def api_endpoint(self) -> str: """Full Ollama ``/api/chat`` URL constructed from ``ollama_base_url``.""" return f"{self.ollama_base_url.rstrip('/')}/api/chat"
[docs] @classmethod def from_dict(cls, data: dict, *, world_root: Path) -> TranslationLayerConfig: # noqa: ARG002 """Parse a ``translation_layer`` config block from ``world.json``. Missing optional fields fall back to safe defaults so that a minimal ``{"enabled": true}`` block is sufficient for basic operation. Args: data: The ``translation_layer`` dict from ``world.json``. world_root: Passed for future use (e.g. resolving relative paths at parse time). Currently unused at construction but kept in the signature to avoid a breaking change later. Returns: A fully-populated, frozen ``TranslationLayerConfig``. """ raw_prompt_policy_id = data.get("prompt_policy_id") prompt_policy_id = ( str(raw_prompt_policy_id).strip() if raw_prompt_policy_id is not None else "" ) # ``prompt_template_path`` is intentionally ignored in canonical # runtime mode. Prompt selection is policy-id based only. return cls( enabled=bool(data.get("enabled", False)), model=str(data.get("model", "gemma2:2b")), ollama_base_url=str(data.get("ollama_base_url", "http://localhost:11434")), timeout_seconds=float(data.get("timeout_seconds", 10.0)), keep_alive=str(data.get("keep_alive", "5m")), strict_mode=bool(data.get("strict_mode", True)), max_output_chars=int(data.get("max_output_chars", 280)), prompt_policy_id=prompt_policy_id or "prompt:translation.prompts.ic:default", active_axes=list(data.get("active_axes", [])), deterministic=bool(data.get("deterministic", False)), )
[docs] @classmethod def disabled(cls) -> TranslationLayerConfig: """Return a config object that represents a disabled translation layer. Used as the default when a world has no ``translation_layer`` block, or when the server master switch is off. """ return cls( enabled=False, model="gemma2:2b", ollama_base_url="http://localhost:11434", timeout_seconds=10.0, keep_alive="5m", strict_mode=True, max_output_chars=280, prompt_policy_id="prompt:translation.prompts.ic:default", active_axes=[], deterministic=False, )