llama-swap: 249 -> 262 (#559567)

This commit is contained in:
j-k
2026-10-12 14:48:27 +00:00
committed by GitHub
4 changed files with 81 additions and 14 deletions

View File

@@ -7,7 +7,29 @@
let
cfg = config.services.llama-swap;
settingsFormat = pkgs.formats.yaml { };
configFile = settingsFormat.generate "config.yaml" cfg.settings;
unvalidatedConfigFile = settingsFormat.generate "llama-swap-config.yaml" cfg.settings;
# similar to writeNginxConfig
validateLlamaSwapConfig =
file:
pkgs.runCommandLocal "llama-swap-validated-config.yaml"
{
inherit file;
__structuredAttrs = true;
nativeBuildInputs = [
cfg.package
];
} # sh
''
llama-swap -validate -config $file
cp $file $out
'';
configFile =
if cfg.validateConfigFile then
validateLlamaSwapConfig unvalidatedConfigFile
else
unvalidatedConfigFile;
in
{
options.services.llama-swap = {
@@ -15,6 +37,10 @@ in
package = lib.mkPackageOption pkgs "llama-swap" { };
validateConfigFile = lib.mkEnableOption "validating configuration with `llama-swap -validate`" // {
default = true;
};
listenAddress = lib.mkOption {
type = lib.types.str;
default = "localhost";

View File

@@ -54,6 +54,7 @@ let
machine =
{ pkgs, ... }:
{
environment.systemPackages = [ pkgs.jq ];
# running models can be memory intensive but
# default `virtualisation.memorySize` is fine
@@ -118,6 +119,7 @@ let
''
# core tests
import json
import datetime as dt
def get_json(route):
args = [
@@ -144,7 +146,7 @@ let
''
+ lib.optionalString withUI ''
with subtest('check is serving ui'):
machine.succeed('curl --fail http:/localhost:8080/ui/')
machine.succeed('curl --fail http://localhost:8080/ui/')
''
+ ''
@@ -156,7 +158,9 @@ let
+ lib.optionalString useSmollm2-135m ''
# extended tests using SmolLM2
with subtest('check `/running` for preloaded smollm2'):
machine.wait_until_succeeds('curl --silent --fail http://localhost:8080/running | grep "smollm2"')
# wait until quick jq check passes
machine.wait_until_succeeds('curl --silent --fail http://localhost:8080/running | jq -e ".running[] | select(.model == \\"smollm2\\" and .state == \\"ready\\")"')
# now check granular details
running_response = get_json('/running')
assert len(running_response['running']) == 1, f'running doesn\'t have one entry as expected: {running_response}'
running_model = running_response['running'][0]
@@ -199,7 +203,7 @@ let
assert running_model['state'] == 'ready', f'running smollm2 is not ready: {running_response}'
with subtest('check `/running` for smollm2 to timeout'):
machine.wait_until_succeeds('curl --silent --fail http://localhost:8080/running | grep -v "smollm2"', timeout=11)
machine.wait_until_succeeds('curl --silent --fail http://localhost:8080/running | grep -v "smollm2"', timeout=dt.timedelta(seconds=11))
running_response = get_json('/running')
assert len(running_response['running']) == 0, "smollm2 was still running after timeout"

View File

@@ -2,7 +2,7 @@
lib,
stdenv,
buildGoModule,
buildGo127Module,
fetchFromGitHub,
versionCheckHook,
@@ -19,20 +19,23 @@
let
canExecute = stdenv.buildPlatform.canExecute stdenv.hostPlatform;
in
buildGoModule (finalAttrs: {
buildGo127Module (finalAttrs: {
pname = "llama-swap";
version = "249";
version = "262";
outputs = [
"out"
"kubeswap"
"wol" # wake on lan proxy
"vllm_wrapper" # helper for using `vllm` with `--enable-sleep-mode`
"utils"
];
src = fetchFromGitHub {
owner = "mostlygeek";
repo = "llama-swap";
tag = "v${finalAttrs.version}";
hash = "sha256-7wXOL8XtcKV6Abdxar25C85ODQ34RYOAGYCTaCXxPpY=";
hash = "sha256-DAYaR+N7alGbdLsGlaiDlsHiqxOMg65069i/vHOkLKk=";
# populate values that require us to use git. By doing this in postFetch we
# can delete .git afterwards and maintain better reproducibility of the src.
leaveDotGit = true;
@@ -45,7 +48,7 @@ buildGoModule (finalAttrs: {
'';
};
vendorHash = "sha256-MhR8B2+Yb/xqrTlIxaVHLoQf1eTOO49c65l72IAuZyU=";
vendorHash = "sha256-yelob7FlaGymASUP0DAUkALQm5vnXZnN5ThbnSkH2Ak=";
# Upstream only embeds the UI when this build tag is set.
tags = lib.optionals withUI [ "embed_ui" ];
@@ -68,6 +71,8 @@ buildGoModule (finalAttrs: {
postPatch = ''
substituteInPlace internal/process/process_command_forking_test.go \
--replace-fail "#!/bin/bash" "#!${lib.getExe bash}"
substituteInPlace cmd/vllm-wrapper/main_test.go \
--replace-fail "#!/bin/bash" "#!${lib.getExe bash}"
'';
preBuild = ''
@@ -86,8 +91,10 @@ buildGoModule (finalAttrs: {
"misc/process-cmd-test"
# benchmark/regression testing tool
"misc/benchmark-chatcompletion"
# testing tool for the performance monitoring code
"cmd/monitor-test"
]
++ lib.optionals (!canExecute) [
++ lib.optionals finalAttrs.doCheck [
# some tests expect to execute `simple-something`; if it can't be executed
# it's unneeded
"misc/simple-responder"
@@ -124,14 +131,44 @@ buildGoModule (finalAttrs: {
mkdir build
ln -s "$GOPATH/bin/simple-responder" "./build/simple-responder_''${GOOS}_''${GOARCH}"
'';
# doesn't need to be cleaned up outside of the check path as we don't build it
# due to `excludedPackages`
postCheck = ''
rm "$GOPATH/bin/simple-responder"
'';
postInstall = ''
# project regularly adds small utility binaries
# this `installPhase` means we need to sort them
# into outputs or skip building them
installPhase = ''
runHook preInstall
install -Dm444 -t "$out/share/llama-swap" config.example.yaml
mkdir -p "$out/bin"
mv "$GOPATH/bin/llama-swap" "$out/bin/"
mkdir -p "$kubeswap/bin"
mv "$GOPATH/bin/kubeswap" "$kubeswap/bin/"
mkdir -p "$wol/bin"
mv "$out/bin/wol-proxy" "$wol/bin/"
mv "$GOPATH/bin/wol-proxy" "$wol/bin/"
mkdir -p "$vllm_wrapper/bin"
mv "$GOPATH/bin/vllm-wrapper" "$vllm_wrapper/bin/"
mkdir -p "$utils/bin"
mv "$GOPATH/bin/"{fake-model,test-concurrency} "$utils/bin/"
# log if we don't handle a new executable
EXECUTABLES=$(find "$GOPATH/bin" -maxdepth 1 -type f -executable -print -quit)
if [ -n "$EXECUTABLES" ]; then
echo "Error: Directory '$GOPATH/bin' still contains executables." >&2
echo " $EXECUTABLES" >&2
exit 1
fi
runHook postInstall
'';
doInstallCheck = true;

View File

@@ -7,14 +7,14 @@
buildNpmPackage (finalAttrs: {
pname = "${llama-swap.pname}-ui";
inherit (llama-swap) version src;
npmDepsHash = "sha256-6MPXQtmaz97D9PUU2Nn5DH/2HZNP/rnAWVSck/FiCyk=";
npmDepsHash = "sha256-lmhRJ8275PIQ+7vHdr9aZ31lYeXUkXrWnlvuwOadjRQ=";
postPatch = ''
substituteInPlace vite.config.ts \
--replace-fail "../internal/server/ui_dist" "${placeholder "out"}/ui_dist"
'';
sourceRoot = "${finalAttrs.src.name}/ui-svelte";
sourceRoot = "${finalAttrs.src.name}/ui";
# bundled "ui_dist" doesn't need node_modules
postInstall = ''