Download code/moth/backends/__init__.py from darioooooo0o/moth-112m: direct link, hf CLI and curl.
- Browser
- Download file 8.06 kB
-
https://huggingface.co/darioooooo0o/moth-112m/resolve/main/code/moth/backends/__init__.py
- Command line
-
hf download hf://darioooooo0o/moth-112m/code/moth/backends/__init__.py
-
curl -L -o __init__.py https://huggingface.co/darioooooo0o/moth-112m/resolve/main/code/moth/backends/__init__.py
8.06 kB
| """Swappable model backends: what runs behind moth's pet, hooks and chat page. | |
| tiny-agent moth-112m and its kin: tiny_agent's own architecture, KV cache kept across turns, device graphs, | |
| int8 on the CPU, the phrase ban, home tools. The default; a folder without moth.json is this. | |
| transformers any causal LM transformers can load (a folder or an HF repo id) with a chat template, CPU or GPU. | |
| Optional: pip install "moth-companion[transformers]". | |
| openai any OpenAI-compatible /v1/chat/completions server (llama.cpp server, Ollama, vLLM, LM Studio...), | |
| on this machine by default. | |
| Pick one with a model spec (serve --model, `moth config model ...`, `moth setup --model ...`): | |
| a folder its moth.json (moth/backends/manifest.py), else tiny-agent defaults; a plain | |
| transformers folder (config.json with model_type) without moth.json: transformers | |
| an HF repo id (owner/name) its moth.json if it has one (fetched alone, a few hundred bytes), else transformers | |
| openai:<base_url>#<model> e.g. openai:http://127.0.0.1:11434/v1#qwen3:0.6b | |
| Stdlib only at import: torch / transformers load inside the backend that needs them. The format and examples: | |
| moth/docs/MODEL_BACKENDS.md. | |
| """ | |
| from __future__ import annotations | |
| import json | |
| import os | |
| import re | |
| import sys | |
| from urllib.parse import urlsplit | |
| from moth.backends.manifest import FILE, Manifest, ManifestError, load, parse, with_updates # noqa: F401 | |
| HUB_ID = re.compile(r"[A-Za-z0-9][A-Za-z0-9._-]*/[A-Za-z0-9][A-Za-z0-9._-]*") | |
| OPENAI = "openai:" | |
| LOCAL_HOSTS = ("localhost", "127.0.0.1", "::1", "0.0.0.0") | |
| def is_local_url(url: str) -> bool: | |
| """The endpoint is on this machine (loopback). A LAN address is not: moth's text would leave the machine.""" | |
| try: | |
| host = (urlsplit(url).hostname or "").lower() | |
| except ValueError: | |
| return False | |
| return host in LOCAL_HOSTS or host.startswith("127.") or host.endswith(".localhost") | |
| def parse_spec(spec: str) -> tuple[str, object]: | |
| """("openai", (base_url, model)) | ("folder", abspath) | ("hub", repo_id).""" | |
| spec = (spec or "").strip() | |
| if spec.startswith(OPENAI): | |
| rest = spec[len(OPENAI):] | |
| url, sep, model = rest.rpartition("#") | |
| if not sep or not url or not model: | |
| raise ManifestError(f"model {spec!r}: write openai:<base_url>#<model>, " | |
| "e.g. openai:http://127.0.0.1:11434/v1#qwen3:0.6b") | |
| if not url.startswith(("http://", "https://")): | |
| raise ManifestError(f"model {spec!r}: the base url must start with http:// or https://") | |
| return "openai", (url, model) | |
| path = os.path.expanduser(spec) | |
| if os.path.exists(path) or spec.startswith((".", "/", "~", "\\")) or "\\" in spec or not HUB_ID.fullmatch(spec): | |
| return "folder", os.path.abspath(path) | |
| return "hub", spec | |
| def configured() -> str | None: | |
| """The `model` setting of ~/.moth/config.json, or None (the install's own moth).""" | |
| try: | |
| from moth import config | |
| return config.model() | |
| except Exception: # noqa: BLE001 an unreadable config is the default | |
| return None | |
| def _transformers_folder(folder: str) -> bool: | |
| """A plain transformers save (config.json with model_type), not a tiny-agent checkpoint (whose config has none).""" | |
| try: | |
| with open(os.path.join(folder, "config.json"), encoding="utf-8") as f: | |
| cfg = json.load(f) | |
| except (OSError, ValueError): | |
| return False | |
| return isinstance(cfg, dict) and bool(cfg.get("model_type")) | |
| def resolve(spec: str | None, network: bool = True) -> Manifest: | |
| """The Manifest for a model spec (None or "": the install's default folder is the caller's business). network: | |
| an HF repo id moth hasn't seen yet may fetch its moth.json (only that file); False (doctor, the pet): never.""" | |
| kind, val = parse_spec(spec or "") | |
| if kind == "openai": | |
| url, model = val | |
| m = parse({"backend": "openai", "base_url": url, "model": model}, None, f"model {spec}") | |
| return with_updates(m, source="spec") | |
| if kind == "folder": | |
| m = load(val) | |
| if m.source == "default" and _transformers_folder(val): | |
| return with_updates(parse({"backend": "transformers", "model": val}, val), source="inferred") | |
| return m | |
| return _hub(val, network) | |
| def _hub(repo: str, network: bool) -> Manifest: | |
| from moth import weights # stdlib only | |
| folder = weights.cache_dir(repo) | |
| if repo == weights.REPO_ID: # moth-112m itself: no need to ask the Hub | |
| local = os.path.join(folder, FILE) | |
| return load(folder) if os.path.isfile(local) else Manifest(folder=folder, repo=repo) | |
| local = os.path.join(folder, FILE) | |
| if not os.path.isfile(local) and network and not _offline(): | |
| print(f"moth: {repo} isn't a folder here, so it's a Hugging Face repo id: checking it for a moth.json", | |
| file=sys.stderr, flush=True) | |
| _fetch_manifest(repo, folder) | |
| if os.path.isfile(local): | |
| m = load(folder) | |
| if m.backend == "openai": # a downloaded file must never send moth's text somewhere: endpoints are yours | |
| raise ManifestError(f"{local}: a moth.json from the Hub ({repo}) can't name an openai endpoint; " | |
| "to use a server, write openai:<url>#<model> or a moth.json of your own") | |
| upd = {"repo": m.repo or repo} | |
| if m.backend == "transformers" and not m.model: | |
| upd["model"] = repo | |
| return with_updates(m, **upd) | |
| # no moth.json: a general chat model, loaded by transformers from the HF cache | |
| return with_updates(parse({"backend": "transformers", "model": repo, "repo": repo}, None, f"model {repo}"), | |
| source="spec") | |
| def _offline() -> bool: | |
| try: | |
| from moth import config | |
| return config.offline() | |
| except Exception: # noqa: BLE001 | |
| return False | |
| def _fetch_manifest(repo: str, folder: str) -> None: | |
| """repo's moth.json into folder (nothing else). No such file, no network or no huggingface_hub: nothing.""" | |
| try: | |
| import huggingface_hub as hub | |
| os.makedirs(folder, exist_ok=True) | |
| hub.hf_hub_download(repo_id=repo, filename=FILE, local_dir=folder) | |
| except Exception as e: # noqa: BLE001 most often: the repo simply has no moth.json | |
| name = type(e).__name__ | |
| if "NotFound" not in name and "Gated" not in name: | |
| print(f"moth: couldn't check {repo} for a moth.json ({name}); treating it as a transformers model", | |
| file=sys.stderr, flush=True) | |
| def prepare(m: Manifest) -> None: | |
| """Before loading: a tiny-agent model from the Hub gets its weights downloaded (moth/weights.py, with the same | |
| checks and messages as moth-112m's first run). Others: nothing (transformers uses its own HF cache).""" | |
| if m.backend == "tiny-agent" and m.repo and m.folder: | |
| from moth import weights | |
| if os.path.realpath(m.folder) == os.path.realpath(weights.cache_dir(m.repo)): | |
| weights.ensure(m.folder, m.repo) | |
| def privacy_note(m: Manifest) -> str | None: | |
| """What doctor and serve say when moth's text leaves this machine (an openai endpoint that isn't loopback).""" | |
| if m.backend == "openai" and not is_local_url(m.base_url or ""): | |
| return (f"the model endpoint {m.base_url} is not on this machine: every agent message moth reacts to and " | |
| "everything you type to moth is sent there") | |
| return None | |
| def load_backend(m: Manifest, a): | |
| """The backend object for m (imports torch / transformers only now).""" | |
| if m.backend == "tiny-agent": | |
| from moth.backends.tiny_agent import TinyAgentBackend | |
| return TinyAgentBackend(a) | |
| if m.backend == "transformers": | |
| from moth.backends.hf_transformers import TransformersBackend | |
| return TransformersBackend(m, a) | |
| from moth.backends.openai_compat import OpenAIBackend | |
| return OpenAIBackend(m, a) | |