ensureModel method
Returns the path to the model file. Downloads the GGUF and its sibling
tokenizer.json if not already on disk. Calls onProgress with the
bytes-downloaded fraction.
The candle backend resolves the tokenizer as a tokenizer.json sibling
of the GGUF, so both land in the same directory.
Implementation
Future<String> ensureModel(
SyniModelSpec spec, {
required void Function(SyniInstallStage, double) onProgress,
}) async {
final dir = await _modelsDir();
final path = '${dir.path}/${spec.filename}';
final file = File(path);
if (!file.existsSync()) {
onProgress(SyniInstallStage.downloadingModel, 0.0);
await _download(spec.downloadUrl, file, onProgress: (p) {
// Reserve the last 5% of the download bar for the tokenizer.
onProgress(SyniInstallStage.downloadingModel, p * 0.95);
});
}
// tokenizer.json must sit next to the GGUF for the candle backend.
final tokenizerFile = File('${dir.path}/tokenizer.json');
if (!tokenizerFile.existsSync()) {
await _download(spec.tokenizerUrl, tokenizerFile, onProgress: (p) {
onProgress(SyniInstallStage.downloadingModel, 0.95 + p * 0.05);
});
}
onProgress(SyniInstallStage.downloadingModel, 1.0);
if (spec.sha256.isNotEmpty) {
onProgress(SyniInstallStage.verifyingModel, 0.0);
final actual = await _sha256(file);
onProgress(SyniInstallStage.verifyingModel, 1.0);
if (actual.toLowerCase() != spec.sha256.toLowerCase()) {
// Discard a possibly-corrupted file so the next attempt re-downloads.
try {
file.deleteSync();
} catch (_) {}
throw SyniInstallException(
'model SHA-256 mismatch: expected ${spec.sha256}, got $actual',
);
}
}
return path;
}