Skip to content

sensai

SensAIDeps dataclass

Python
SensAIDeps(
    job_status: JobStatus | None,
    column_manifest: ColumnManifest,
    current_plot_config: PlotConfig | None,
)

Runtime context available to the SensAI chat agent and every domain agent it delegates to.

job_status instance-attribute

Python
job_status: JobStatus | None

The current job, or None if no job is loaded.

column_manifest instance-attribute

Python
column_manifest: ColumnManifest

Signal column names available in the current trial's telemetry data.

current_plot_config instance-attribute

Python
current_plot_config: PlotConfig | None

The plot config currently active in the trial viewer, or None if not set.

build_fallback_model

Python
build_fallback_model() -> FallbackModel

Builds the FallbackModel shared by every SensAI agent from SensAISettings.models, in the priority order the user configured there - the first (provider, model name) pair is tried first, and each later one is only used if every earlier one errors.

Each entry's Model comes from ModelProvider.build_model, which builds that entry's provider exactly as Agent('provider:model') would - reading whatever environment variable that provider looks for by default (see ModelProvider.env_var_name) - except for an OLLAMA entry, which gets SensAISettings.base_url explicitly: unlike most providers, pydantic_ai's OllamaProvider has no built-in localhost default and raises without one. Only OLLAMA gets this - passing it to a provider that also happens to accept base_url (e.g. Google) would silently redirect that provider's real endpoint to Ollama's URL instead.

Raises:

Type Description
ValueError

If SensAISettings.models is empty - FallbackModel needs at least one model to try.

Source code in src/mujoco_mojo/utils/layers/dojo/sensai/model.py
Python
def build_fallback_model() -> FallbackModel:
    """
    Builds the `FallbackModel` shared by every SensAI agent from `SensAISettings.models`, in the priority order the user configured there - the first `(provider, model name)` pair is tried first, and each later one is only used if every earlier one errors.

    Each entry's `Model` comes from `ModelProvider.build_model`, which builds that entry's provider exactly as `Agent('provider:model')` would - reading whatever environment variable that provider looks for by default (see `ModelProvider.env_var_name`) - except for an `OLLAMA` entry, which gets `SensAISettings.base_url` explicitly: unlike most providers, `pydantic_ai`'s `OllamaProvider` has no built-in `localhost` default and raises without one. Only `OLLAMA` gets this - passing it to a provider that also happens to accept `base_url` (e.g. Google) would silently redirect that provider's real endpoint to Ollama's URL instead.

    Raises:
        ValueError: If `SensAISettings.models` is empty - `FallbackModel` needs at least one model to try.

    """
    settings = MujocoMojoSettings().dojo.sensai
    if not settings.models:
        msg = "dojo.sensai.models is empty; configure at least one (provider, model name) pair."
        raise ValueError(msg)

    models = [
        entry.provider.build_model(
            entry.model_name,
            base_url=settings.base_url
            if entry.provider is ModelProvider.OLLAMA
            else None,
        )
        for entry in settings.models
    ]

    return FallbackModel(
        models[0],
        *models[1:],
        # pydantic-ai's Google model only wraps errors.APIError as ModelAPIError when
        # opening the connection, not when the first streamed chunk raises it (the actual
        # 503 case). Include the raw SDK error type too so streaming failures still fall back.
        # This only helps entries that are actually Google models - a non-Google-only
        # fallback chain gets no equivalent coverage for its own provider's raw SDK errors.
        fallback_on=(ModelAPIError, google_errors.APIError),
    )