"""Helpers for selecting AI models and loading environment variables.
This module consolidates the common glue used by examples so they can remain
minimal. It provides:
- ``load_environment``: read ``.env`` (if present) into ``os.environ``.
- ``build_interface``: construct an ``AIModelInterface`` with available providers.
- ``select_model``: choose a supported model from a candidate list with an optional
override.
- ``getenv_str``: fetch and trim a string environment variable.
Examples import these helpers instead of duplicating logic.
"""
from __future__ import annotations
import logging
import os
from collections.abc import Sequence
from typing import TYPE_CHECKING
# Avoid importing heavy/optional dependencies at module import time.
# Import within functions to keep examples importable without provider SDKs.
if TYPE_CHECKING: # pragma: no cover - type checking only
from .ai_model_interface import AIModelInterface
# Candidate model lists used by examples. Order conveys preference.
[docs]
TEXT_MODEL_CANDIDATES: Sequence[str] = (
"o4",
"o4-mini",
"o3-mini",
"gpt-4.1",
"gpt-4.1-mini",
"gpt-4o",
"gpt-4o-mini",
"deepseek-r1",
"deepseek-reasoner",
"deepseek-chat-v3-0324",
"deepseek-chat",
"gemini-2.0-flash",
"claude-3-opus",
"claude-3-haiku",
"groq-llama-3.1-70b",
"groq-llama-3.1-8b",
"together-deepseek-r1",
"together-llama-3.1-70b",
"llama-3.3-70b-instruct",
"gpt-oss-120b",
"distilgpt2",
)
[docs]
IMAGE_MODEL_CANDIDATES: Sequence[str] = (
"claude-image",
"gpt-image-1",
"dall-e-3",
"dall-e-2",
"stabilityai/stable-diffusion-3.5-large",
"stabilityai/stable-diffusion-xl-base-1.0",
"stabilityai/stable-diffusion-2",
)
[docs]
def load_environment(env_path: str | None = None) -> None:
"""Load environment variables from configuration files.
Parameters
----------
env_path : str, optional
Override path to the ``.env`` file. Defaults to ``".env"`` if not provided.
"""
from .ai_model_interface import load_env_file
load_env_file(env_path or ".env")
[docs]
def build_interface(
*, enabled_providers: Sequence[str] | None = None
) -> AIModelInterface:
"""Initialise :class:`AIModelInterface` with available providers.
Parameters
----------
enabled_providers : sequence of str, optional
Explicit list of provider names to register. If omitted, all detected
providers are enabled.
Returns
-------
AIModelInterface
Interface capable of dispatching requests to the configured providers.
"""
load_environment()
providers = list(enabled_providers) if enabled_providers is not None else None
from .ai_model_interface import AIModelInterface
ai = AIModelInterface(enabled_providers=providers)
if not ai.list_available_models():
logging.warning(
"No AI providers registered. Ensure API keys are present in "
"the environment."
)
return ai
[docs]
def select_model(
ai: AIModelInterface,
candidates: Sequence[str],
*,
explicit: str | None = None,
kind: str = "text",
) -> str | None:
"""Return the first supported model, honouring an explicit override.
Parameters
----------
ai : AIModelInterface
Interface used to query available models.
candidates : sequence of str
Preferred model aliases evaluated in order.
explicit : str, optional
Explicit model request taking precedence if supported.
kind : str, optional
Human-friendly label for the capability being selected (e.g., ``"text"``).
Returns
-------
str or None
Supported model name or ``None`` when no candidate is available.
"""
if explicit:
request = explicit.strip()
if request:
if ai.supports(request):
logging.info("Using requested %s model: %s", kind, request)
return request
logging.warning(
"Requested %s model '%s' is not available. Falling back to candidates.",
kind,
request,
)
from .ai_model_interface import pick_first_supported_model
model = pick_first_supported_model(ai, candidates)
if model:
logging.info("Selected %s model: %s", kind, model)
else:
available = ", ".join(ai.list_available_models()) or "none"
logging.warning("No supported %s models. Available models: %s", kind, available)
return model
[docs]
def getenv_str(name: str) -> str | None:
"""Return the trimmed value of an environment variable.
Parameters
----------
name : str
Environment variable to read and sanitize.
Returns
-------
str or None
Trimmed string value with surrounding quotes removed, or ``None`` if
not set or empty.
"""
value = os.getenv(name)
if value is None:
return None
value = value.strip()
if len(value) >= 2 and value[0] == value[-1] and value[0] in {'"', "'"}:
value = value[1:-1].strip()
return value or None
__all__ = [
"TEXT_MODEL_CANDIDATES",
"IMAGE_MODEL_CANDIDATES",
"load_environment",
"build_interface",
"select_model",
"getenv_str",
]