jackailocal / factory /powershell /Install-LoraAdapters.ps1
jackboy70's picture
Deploy: accurate lite-builder note
f25362a
Raw
History Blame Contribute Delete
3.84 kB
# Installs LoRA adapters into the package's Ollama model store at build time.
#
# Reads manifest\lora-adapters.json (see manifest\lora-adapters.example.json):
# each entry declares a base Ollama model, a GGUF LoRA adapter file (local path
# or URL + sha256), optional system prompt and inference parameters. The script
# stages the adapter under models\lora\, generates an Ollama Modelfile, and
# runs `ollama create` against the builder's Ollama store so the fine-tuned
# model ships on the key like any other model.
#
# Requires the builder Ollama running (same convention as model pulls):
# OLLAMA_HOST / OLLAMA_MODELS must point at the target store.
[CmdletBinding()]
param(
[string]$DriveRoot = (Resolve-Path "$PSScriptRoot\..\..").Path,
[string]$ManifestPath = "manifest\lora-adapters.json",
[string]$OllamaExe = ""
)
$ErrorActionPreference = "Stop"
Set-Location $DriveRoot
$full = Join-Path $DriveRoot $ManifestPath
if (!(Test-Path $full)) {
Write-Host "No LoRA manifest found at $ManifestPath - nothing to do."
exit 0
}
if (-not $OllamaExe) {
$candidates = @(
(Join-Path $DriveRoot "backends\ollama\windows\ollama.exe"),
"ollama"
)
$OllamaExe = $candidates | Where-Object { (Test-Path $_) -or (Get-Command $_ -ErrorAction SilentlyContinue) } | Select-Object -First 1
}
if (-not $OllamaExe) { throw "ollama.exe not found. Stage backends\ollama\windows\ollama.exe first." }
$manifest = Get-Content $full -Raw | ConvertFrom-Json
$loraDir = Join-Path $DriveRoot "models\lora"
New-Item -ItemType Directory -Force -Path $loraDir | Out-Null
foreach ($adapter in $manifest.adapters) {
if (-not $adapter.name) { throw "Each adapter needs a 'name' (the Ollama model name to create)." }
if (-not $adapter.base_model) { throw "Adapter $($adapter.name): 'base_model' is required (e.g. qwen3.5:4b)." }
# Stage the adapter GGUF locally (URL + sha256, or a path already on disk).
$adapterFile = Join-Path $loraDir ("{0}.gguf" -f $adapter.name)
if ($adapter.url) {
if (!(Test-Path $adapterFile) -or ((Get-FileHash $adapterFile -Algorithm SHA256).Hash.ToLowerInvariant() -ne $adapter.sha256.ToLowerInvariant())) {
Write-Host "Downloading LoRA adapter $($adapter.name)" -ForegroundColor Cyan
Invoke-WebRequest -Uri $adapter.url -OutFile $adapterFile -UseBasicParsing
}
if ($adapter.sha256) {
$h = (Get-FileHash $adapterFile -Algorithm SHA256).Hash.ToLowerInvariant()
if ($h -ne $adapter.sha256.ToLowerInvariant()) { throw "SHA256 mismatch for adapter $($adapter.name)" }
}
} elseif ($adapter.path) {
$source = Join-Path $DriveRoot $adapter.path
if (!(Test-Path $source)) { throw "Adapter file not found: $source" }
Copy-Item $source $adapterFile -Force
} else {
throw "Adapter $($adapter.name): provide 'url' (+sha256) or 'path'."
}
# Generate the Modelfile next to the adapter for traceability.
$lines = @("FROM $($adapter.base_model)", "ADAPTER $adapterFile")
if ($adapter.system) { $lines += "SYSTEM `"`"`"$($adapter.system)`"`"`"" }
if ($adapter.parameters) {
foreach ($p in $adapter.parameters.PSObject.Properties) { $lines += "PARAMETER $($p.Name) $($p.Value)" }
}
$modelfile = Join-Path $loraDir ("{0}.Modelfile" -f $adapter.name)
$lines -join "`n" | Set-Content -Encoding utf8 $modelfile
Write-Host "Creating Ollama model $($adapter.name) (base $($adapter.base_model) + LoRA)" -ForegroundColor Cyan
& $OllamaExe create $adapter.name -f $modelfile
if ($LASTEXITCODE -ne 0) { throw "ollama create failed for $($adapter.name) (exit $LASTEXITCODE)" }
Write-Host "Created: $($adapter.name)" -ForegroundColor Green
Write-Host "Reminder: add '$($adapter.name)' to config\model-catalog.json (Add-ModelToCatalog.ps1) so the runtime can select it, and record the adapter license in licenses\THIRD_PARTY_NOTICES.md."
}