node-llama-cpp
Version:
Run AI models locally on your machine with node.js bindings for llama.cpp. Enforce a JSON schema on the model output on the generation level
158 lines • 7.36 kB
JavaScript
import process from "process";
import fs from "fs-extra";
import chalk from "chalk";
import { cliModelsDirectory, documentationPageUrls } from "../../config.js";
import { combineModelDownloaders, createModelDownloader } from "../../utils/createModelDownloader.js";
import { getReadablePath } from "../utils/getReadablePath.js";
import { ConsoleInteraction, ConsoleInteractionKey } from "../utils/ConsoleInteraction.js";
import { getIsInDocumentationMode } from "../../state.js";
import { resolveHeaderFlag } from "../utils/resolveHeaderFlag.js";
import { withCliCommandDescriptionDocsUrl } from "../utils/withCliCommandDescriptionDocsUrl.js";
export const PullCommand = {
command: "pull [urls..]",
aliases: ["get"],
describe: withCliCommandDescriptionDocsUrl("Download models from URLs", documentationPageUrls.CLI.Pull),
builder(yargs) {
const isInDocumentationMode = getIsInDocumentationMode();
return yargs
.option("urls", {
type: "string",
alias: ["url", "uris", "uri"],
array: true,
description: [
"A `.gguf` model URI to pull.",
!isInDocumentationMode && "Automatically handles split and binary-split models files, so only pass the URI to the first file of a model.",
!isInDocumentationMode && "If a file already exists and its size matches the expected size, it will not be downloaded again unless the `--override` flag is used.",
"Pass multiple URIs to download multiple models at once."
].filter(Boolean).join(isInDocumentationMode
? "\n"
: " "),
demandOption: true,
group: "Required:"
})
.option("header", {
alias: ["H"],
type: "string",
array: true,
description: "Headers to use when downloading a model from a URL, in the format `key: value`. You can pass this option multiple times to add multiple headers.",
group: "Optional:"
})
.option("override", {
alias: ["o"],
type: "boolean",
description: "Override existing model files",
default: false,
group: "Optional:"
})
.option("noProgress", {
type: "boolean",
description: "Do not show a progress bar while downloading",
default: false,
group: "Optional:"
})
.option("noTempFile", {
alias: ["noTemp"],
type: "boolean",
description: "Delete the temporary file when canceling the download",
default: false,
group: "Optional:"
})
.option("directory", {
alias: ["d", "dir"],
type: "string",
description: "Directory to save the model to",
default: cliModelsDirectory,
defaultDescription: isInDocumentationMode
? "`" + getReadablePath(cliModelsDirectory) + "`"
: getReadablePath(cliModelsDirectory),
group: "Optional:"
})
.option("filename", {
alias: ["n", "name"],
type: "string",
description: "Filename to save the model as. Can only be used if a single URL is passed",
group: "Optional:"
})
.option("parallel", {
alias: ["p"],
type: "number",
description: "Maximum parallel downloads",
default: 4,
group: "Optional:"
});
},
async handler({ urls, header: headerArg, override, noProgress, noTempFile, directory, filename, parallel }) {
const headers = resolveHeaderFlag(headerArg);
if (urls.length === 0)
throw new Error("At least one URI must be provided");
else if (urls.length > 1 && filename != null)
throw new Error("The `--filename` flag can only be used when a single URI is passed");
if (urls.length === 1) {
const downloader = await createModelDownloader({
modelUri: urls[0],
dirPath: directory,
headers,
showCliProgress: !noProgress,
deleteTempFileOnCancel: noTempFile,
skipExisting: !override,
fileName: filename || undefined,
parallelDownloads: parallel,
_showUriResolvingProgress: !noProgress
});
if (!override && downloader.totalFiles === 1 && await fs.pathExists(downloader.entrypointFilePath)) {
const fileStats = await fs.stat(downloader.entrypointFilePath);
if (downloader.totalSize === fileStats.size) {
console.info(`${chalk.yellow("File:")} ${getReadablePath(downloader.entrypointFilePath)}`);
console.info("Skipping download of an existing file: " + chalk.yellow(getReadablePath(downloader.entrypointFilePath)));
process.exit(0);
}
}
const consoleInteraction = new ConsoleInteraction();
consoleInteraction.onKey(ConsoleInteractionKey.ctrlC, async () => {
await downloader.cancel();
consoleInteraction.stop();
process.exit(0);
});
if (!noProgress) {
console.info(`Downloading to ${chalk.yellow(getReadablePath(directory))}${downloader.splitBinaryParts != null
? chalk.gray(` (combining ${downloader.splitBinaryParts} parts into a single file)`)
: ""}`);
consoleInteraction.start();
}
await downloader.download();
if (!noProgress)
consoleInteraction.stop();
console.info(`Downloaded to ${chalk.yellow(getReadablePath(downloader.entrypointFilePath))}`);
}
else {
const downloader = await combineModelDownloaders(urls.map((uri) => createModelDownloader({
modelUri: uri,
dirPath: directory,
headers,
showCliProgress: false,
deleteTempFileOnCancel: noTempFile,
skipExisting: !override
})), {
showCliProgress: !noProgress,
parallelDownloads: parallel
});
const consoleInteraction = new ConsoleInteraction();
consoleInteraction.onKey(ConsoleInteractionKey.ctrlC, async () => {
await downloader.cancel();
consoleInteraction.stop();
process.exit(0);
});
if (!noProgress) {
console.info(`Downloading to ${chalk.yellow(getReadablePath(directory))}`);
consoleInteraction.start();
}
await downloader.download();
if (!noProgress)
consoleInteraction.stop();
console.info(`Downloaded ${downloader.modelDownloaders.length} models to ${chalk.yellow(getReadablePath(directory))}\n${chalk.gray("*")} ` +
downloader.modelDownloaders.map((downloader) => chalk.yellow(downloader.entrypointFilename))
.join(`\n${chalk.gray("*")} `));
}
}
};
//# sourceMappingURL=PullCommand.js.map