| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| |
| [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)." } |
|
|
| |
| $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'." |
| } |
|
|
| |
| $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." |
| } |
|
|