{ config, lib, pkgs, ... }: let cfg = config.services.llama-swap; settingsFormat = pkgs.formats.yaml { }; configFile = if cfg.configFile != null then cfg.configFile else settingsFormat.generate "config.yaml" cfg.settings; args = [ "--listen=${cfg.listenAddress}:${toString cfg.port}" "--config=${configFile}" ] ++ cfg.extraArgs; in { options.services.llama-swap = { enable = lib.mkEnableOption "the llama-swap service"; package = lib.mkPackageOption pkgs "llama-swap" { }; packages = lib.mkOption { default = lib.optionals (cfg.configFile != null) [ pkgs.llama-cpp ]; defaultText = lib.literalExpression '' lib.optionals (cfg.configFile != null) [ pkgs.llama-cpp ] ''; example = lib.literalExpression '' [ pkgs.llama-cpp-rocm pkgs.vllm ] ''; type = lib.types.listOf lib.types.package; description = '' Packages to add to llama-swap process's PATH. Useful if using `configFile` instead of `settings`. ''; }; exposePackages = lib.mkOption { default = cfg.configFile != null; defaultText = lib.literalExpression "cfg.configFile != null"; type = lib.types.bool; description = '' Whether to additionally add `packages` to user profile. Useful when using `configFile` instead of `settings`. ''; }; listenAddress = lib.mkOption { type = lib.types.str; default = "localhost"; example = "0.0.0.0"; description = '' Address that llama-swap listens on. ''; }; port = lib.mkOption { default = 8080; example = 11343; type = lib.types.port; description = '' Port that llama-swap listens on. ''; }; configFile = lib.mkOption { type = with lib.types; nullOr (either path str); default = null; description = '' Optional configuration file path to use instead of generating from settings ''; }; extraArgs = lib.mkOption { type = with lib.types; listOf str; default = [ ]; description = '' Additional arguments to pass on llama-swap startup ''; }; settings = lib.mkOption { type = lib.types.submodule { freeformType = settingsFormat.type; }; description = '' llama-swap configuration. Refer to the [llama-swap example configuration](https://github.com/mostlygeek/llama-swap/blob/main/config.example.yaml) for details on supported values. ''; example = lib.literalExpression '' let llama-cpp = pkgs.llama-cpp.override { rocmSupport = true; }; llama-server = lib.getExe' llama-cpp "llama-server"; in { healthCheckTimeout = 60; models = { "some-model" = { cmd = "''${llama-server} --port ''${PORT} -m /var/lib/llama-cpp/models/some-model.gguf -ngl 0 --no-webui"; aliases = [ "the-best" ]; }; "other-model" = { proxy = "http://127.0.0.1:5555"; cmd = "''${llama-server} --port 5555 -m /var/lib/llama-cpp/models/other-model.gguf -ngl 0 -c 4096 -np 4 --no-webui"; concurrencyLimit = 4; }; }; }; ''; }; }; config = lib.mkIf cfg.enable { assertions = [ { assertion = cfg.configFile != null -> (builtins.attrNames cfg.settings) == [ ]; message = "configFile is incompatible with non-empty settings"; } ]; # untested systemd.user.services.llama-swap = { description = "Model swapping for LLaMA C++ Server (or any local OpenAPI compatible server)"; serviceConfig = { Type = "exec"; ExecStart = "${lib.getExe cfg.package} ${lib.escapeShellArgs args}"; Restart = "on-failure"; RestartSec = 3; # for GPU acceleration PrivateDevices = false; }; path = cfg.packages; }; launchd.agents.llama-swap = { enable = true; config = { ProgramArguments = [ (lib.getExe cfg.package) ] ++ args; KeepAlive = { Crashed = true; SuccessfulExit = false; }; ProcessType = "Background"; EnvironmentVariables = { PATH = lib.makeBinPath cfg.packages; }; }; }; home.packages = [ cfg.package ] ++ (lib.optionals cfg.exposePackages cfg.packages); }; }