@qvac/llm-llamacpp 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/addon.js ADDED
@@ -0,0 +1,95 @@
1
+ const binding = require('./binding')
2
+
3
+ /**
4
+ * An interface between Bare addon in C++ and JS runtime.
5
+ */
6
+ class LlamaInterface {
7
+ /**
8
+ *
9
+ * @param {Object} configurationParams - all the required configuration for inference setup
10
+ * @param {Function} outputCb - to be called on any inference event ( started, new output, error, etc )
11
+ * @param {Function} transitionCb - to be called on addon state changes (LISTENING, IDLE, STOPPED, etc )
12
+ */
13
+ constructor (configurationParams, outputCb, transitionCb = null) {
14
+ this._handle = binding.createInstance(
15
+ this,
16
+ configurationParams,
17
+ outputCb,
18
+ transitionCb
19
+ )
20
+ }
21
+
22
+ /**
23
+ *
24
+ * @param {Object} weightsData
25
+ * @param {String} weightsData.filename
26
+ * @param {Uint8Array} weightsData.contents
27
+ * @param {Boolean} weightsData.completed
28
+ */
29
+ async loadWeights (weightsData) {
30
+ binding.loadWeights(this._handle, weightsData)
31
+ }
32
+
33
+ /**
34
+ * Moves addon to the LISTENING state after all the initialization is done
35
+ */
36
+ async activate () {
37
+ binding.activate(this._handle)
38
+ }
39
+
40
+ /**
41
+ * Pauses current inference process
42
+ */
43
+ async pause () {
44
+ binding.pause(this._handle)
45
+ }
46
+
47
+ /**
48
+ * Cancel a inference process by jobId, if no jobId is provided it cancel the whole queue
49
+ */
50
+ async cancel (jobId) {
51
+ binding.cancel(this._handle, jobId)
52
+ }
53
+
54
+ /**
55
+ * Adds new input to the processing queue
56
+ * @param {Object} data
57
+ * @param {String} data.type
58
+ * @param {String} data.input
59
+ * @returns {Number} - job ID
60
+ */
61
+ async append (data) {
62
+ return binding.append(this._handle, data)
63
+ }
64
+
65
+ /**
66
+ * Addon process status
67
+ * @returns {String}
68
+ */
69
+ async status () {
70
+ return binding.status(this._handle)
71
+ }
72
+
73
+ /**
74
+ * Stops addon process and clears resources (including memory).
75
+ */
76
+ async destroyInstance () {
77
+ if (!this._handle) return
78
+ binding.destroyInstance(this._handle)
79
+ this._handle = null
80
+ }
81
+
82
+ async unload () {
83
+ if (!this._handle) return
84
+ binding.destroyInstance(this._handle)
85
+ this._handle = null
86
+ }
87
+
88
+ async stop () {
89
+ binding.stop(this._handle)
90
+ }
91
+ }
92
+
93
+ module.exports = {
94
+ LlamaInterface
95
+ }
package/binding.js ADDED
@@ -0,0 +1 @@
1
+ module.exports = require.addon()
package/index.d.ts ADDED
@@ -0,0 +1,139 @@
1
+ /// <reference types="node" />
2
+
3
+ import BaseInference from '@tetherto/infer-base/WeightsProvider/BaseInference';
4
+ import WeightsProvider from '@tetherto/infer-base/WeightsProvider/WeightsProvider';
5
+ import type QvacResponse from '@tetherto/qvac-lib-response';
6
+ import type Logger from '@tetherto/qvac-lib-logging';
7
+ import type Loader from '@tetherto/qvac-lib-dl-base';
8
+ import type { Readable } from 'stream';
9
+
10
+ /**
11
+ * Loader interface provides the methods required by LlmLlamacpp to fetch and manage streams.
12
+ */
13
+ export interface Loader {
14
+ /** Prepare the loader for operations */
15
+ ready(): Promise<void>;
16
+ /** Clean up or close any underlying resources */
17
+ close(): Promise<void>;
18
+ /** Obtain a readable stream for the specified path */
19
+ getStream(path: string): Promise<Readable>;
20
+ /** (Optional) Retrieve the size of a remote file in bytes */
21
+ getFileSize?(path: string): Promise<number>;
22
+ }
23
+
24
+ /**
25
+ * Arguments required to construct an instance of LlmLlamacpp
26
+ */
27
+ export interface LlmLlamacppArgs {
28
+ /** External loader instance */
29
+ loader: Loader;
30
+ /** Optional structured logger */
31
+ logger?: Logger;
32
+ /** Optional inference options */
33
+ opts?: Record<string, any>;
34
+ /** Disk directory where model files are stored */
35
+ diskPath: string;
36
+ /** Name of the model directory or file */
37
+ modelName: string;
38
+ /** Name of the projection model directory or file */
39
+ projectionModel?: string;
40
+ }
41
+
42
+ /** Literal indicating end-of-input for the LLM job */
43
+ export type EndOfInput = 'end of job';
44
+
45
+ /** Input types accepted by the Llama addon */
46
+ export type AppendInput =
47
+ | { type: 'text'; input: string }
48
+ | { type: 'media'; input: Uint8Array }
49
+ | { type: EndOfInput };
50
+
51
+ /** Minimal interface for the native addon controlling the LLM */
52
+ export interface Addon {
53
+ activate(): Promise<void>;
54
+ append(input: AppendInput): Promise<number>;
55
+ cancel(jobId: number): Promise<void>;
56
+ }
57
+
58
+ /** Callback invoked with the number of bytes processed during downloads */
59
+ export type ProgressReportCallback = (bytes: number) => void;
60
+
61
+
62
+ /** Structure of a message for chat-style prompts */
63
+ export interface Message {
64
+ role: 'system' | 'user' | 'assistant' | 'tool' | string;
65
+ content: string;
66
+ }
67
+
68
+ /**
69
+ * GGML client implementation for the Llama LLM model.
70
+ */
71
+ declare class LlmLlamacpp extends BaseInference {
72
+ protected readonly _config: Record<string, any>;
73
+ protected readonly _diskPath: string;
74
+ protected readonly _modelName: string;
75
+ protected addon!: Addon;
76
+ protected weightsProvider: WeightsProvider;
77
+
78
+ /**
79
+ * @param args - Setup parameters including loader, logger, disk path, and model name
80
+ * @param config - Model-specific configuration settings
81
+ */
82
+ constructor(args: LlmLlamacppArgs, config: Record<string, any>);
83
+
84
+ /**
85
+ * Implementation of load method, to load model weights, initialize the native addon, and activate the model.
86
+ * @param closeLoader - Whether to close the loader when complete (default true)
87
+ * @param onDownloadProgress - Optional byte-level progress callback
88
+ */
89
+ protected _load(
90
+ closeLoader?: boolean,
91
+ onDownloadProgress?: ProgressReportCallback
92
+ ): Promise<void>;
93
+
94
+ /**
95
+ * Load model weights, initialize the native addon, and activate the model.
96
+ * @param closeLoader - Whether to close the loader when complete (default true)
97
+ * @param onDownloadProgress - Optional byte-level progress callback
98
+ */
99
+ public load(
100
+ closeLoader?: boolean,
101
+ onDownloadProgress?: ProgressReportCallback
102
+ ): Promise<void>;
103
+
104
+ /**
105
+ * Download the model weight files and return the local path to the primary file.
106
+ * @param onDownloadProgress - Callback invoked with bytes downloaded
107
+ * @returns Local file path for the model weights
108
+ */
109
+ public downloadWeights(
110
+ onDownloadProgress?: ProgressReportCallback,
111
+ opts?: {
112
+ closeLoader?: boolean
113
+ }
114
+ ): Promise<string>;
115
+
116
+ /**
117
+ * Instantiate the native addon with the given parameters.
118
+ * @param params.path - Local file or directory path
119
+ * @param params.settings - LLM-specific settings
120
+ */
121
+ protected _createAddon(
122
+ params: { path: string; settings: Record<string, any> }
123
+ ): Addon;
124
+
125
+ /**
126
+ * Internal method to start inference with a text prompt.
127
+ * @param prompt - Input prompt string
128
+ * @returns A QvacResponse representing the inference job
129
+ */
130
+ protected _runInternal(prompt: Message[]): Promise<QvacResponse>;
131
+
132
+ /**
133
+ * Public API to run inference. Delegates to _runInternal.
134
+ * @param prompt - Input prompt string
135
+ */
136
+ run(prompt: Message[]): Promise<QvacResponse>;
137
+ }
138
+
139
+ export = LlmLlamacpp;
package/index.js ADDED
@@ -0,0 +1,160 @@
1
+ 'use strict'
2
+
3
+ const path = require('bare-path')
4
+
5
+ const BaseInference = require('@tetherto/infer-base/WeightsProvider/BaseInference')
6
+ const WeightsProvider = require('@tetherto/infer-base/WeightsProvider/WeightsProvider')
7
+ const { LlamaInterface } = require('./addon')
8
+
9
+ const END_OF_INPUT = 'end of job'
10
+ const noop = () => { }
11
+
12
+ /**
13
+ * GGML client implementation for Llama LLM model
14
+ */
15
+ class LlmLlamacpp extends BaseInference {
16
+ /**
17
+ * Creates an instance of LlmLlamacpp.
18
+ * @constructor
19
+ * @param {Object} args - Setup parameters including loader, logger, disk path, and model name
20
+ * @param {Loader} args.loader - External loader instance
21
+ * @param {Logger} [args.logger] - Optional structured logger
22
+ * @param {Object} [args.opts] - Optional inference options
23
+ * @param {string} args.diskPath - Disk directory where model files are stored
24
+ * @param {string} args.modelName - Name of the model directory or file
25
+ * @param {string} args.projectionModel - Name of the projection model directory or file
26
+ * @param {Object} config - Model-specific configuration settings
27
+ */
28
+ constructor ({ opts = {}, loader, logger = null, diskPath, modelName, projectionModel }, config) {
29
+ super({ logger, opts })
30
+ this._config = config
31
+ this._diskPath = diskPath
32
+ this._modelName = modelName
33
+ this._projectionModel = projectionModel
34
+ this.weightsProvider = new WeightsProvider(loader, this.logger)
35
+ this._runQueueWaiter = Promise.resolve()
36
+ }
37
+
38
+ /**
39
+ * Load model weights, initialize the native addon, and activate the model.
40
+ * @param {boolean} [closeLoader=true] - Whether to close the loader when complete
41
+ * @param {ProgressReportCallback} [onDownloadProgress] - Optional byte-level progress callback
42
+ * @returns {Promise<void>}
43
+ */
44
+ async _load (closeLoader = true, onDownloadProgress = noop) {
45
+ this.logger.info('Starting model load')
46
+
47
+ try {
48
+ await this.downloadWeights(onDownloadProgress, { closeLoader })
49
+
50
+ const configurationParams = {
51
+ path: path.join(this._diskPath, this._modelName),
52
+ projectionPath: this._projectionModel ? path.join(this._diskPath, this._projectionModel) : '',
53
+ settings: this._config
54
+ }
55
+
56
+ this.logger.info('Creating addon with configuration:', configurationParams)
57
+ this.addon = this._createAddon(configurationParams)
58
+
59
+ this.logger.info('Activating addon')
60
+ await this.addon.activate()
61
+
62
+ this.logger.info('Model load completed successfully')
63
+ } catch (error) {
64
+ this.logger.error('Error during model load:', error)
65
+ throw error
66
+ }
67
+ }
68
+
69
+ /**
70
+ * Download the model weight files and return the local path to the primary file.
71
+ * @param {ProgressReportCallback} [onDownloadProgress] - Callback invoked with bytes downloaded
72
+ * @returns {Promise<{filePath: string, completed: boolean, error: boolean}[]>} Local file path for the model weights
73
+ */
74
+ async _downloadWeights (onDownloadProgress, opts) {
75
+ return await this.weightsProvider.downloadFiles(
76
+ this._projectionModel ? [this._modelName, this._projectionModel] : [this._modelName],
77
+ this._diskPath,
78
+ {
79
+ closeLoader: opts.closeLoader,
80
+ onDownloadProgress
81
+ }
82
+ )
83
+ }
84
+
85
+ /**
86
+ * Instantiate the native addon with the given parameters.
87
+ * @param {Object} configurationParams - Configuration parameters for the addon
88
+ * @param {string} configurationParams.path - Local file or directory path
89
+ * @param {Object} configurationParams.settings - LLM-specific settings
90
+ * @returns {Addon} The instantiated addon interface
91
+ */
92
+ _createAddon (configurationParams) {
93
+ this.logger.info(
94
+ 'Creating Llama interface with configuration:',
95
+ configurationParams
96
+ )
97
+ return new LlamaInterface(
98
+ configurationParams,
99
+ this._outputCallback.bind(this),
100
+ this.logger.info.bind(this.logger)
101
+ )
102
+ }
103
+
104
+ async _withExclusiveRun (fn) {
105
+ const prev = this._runQueueWaiter || Promise.resolve()
106
+ let release
107
+ this._runQueueWaiter = new Promise(resolve => { release = resolve })
108
+ await prev
109
+ try {
110
+ return await fn()
111
+ } finally {
112
+ release()
113
+ }
114
+ }
115
+
116
+ /**
117
+ * Internal method to start inference with a text prompt.
118
+ * @param {Message[]} prompt - Input prompt array of messages
119
+ * @returns {Promise<QvacResponse>} A QvacResponse representing the inference job
120
+ */
121
+ async _runInternal (prompt) {
122
+ this.logger.info('Starting inference with prompt:', prompt)
123
+ return this._withExclusiveRun(async () => {
124
+ // Process prompt to handle media content with user role
125
+ const processedPrompt = prompt.map(message => {
126
+ // Check if message has user role and media type with Uint8Array content
127
+ if (message.role === 'user' &&
128
+ message.type === 'media' &&
129
+ message.content instanceof Uint8Array) {
130
+ // Send media data as separate append call
131
+ this.addon.append({ type: 'media', input: message.content })
132
+ .catch(err => this.logger.error('Failed to send media data:', err))
133
+
134
+ // Return modified message with empty string for media content
135
+ return {
136
+ ...message,
137
+ content: ''
138
+ }
139
+ }
140
+
141
+ return message
142
+ })
143
+
144
+ const serializedPrompt = JSON.stringify(processedPrompt)
145
+
146
+ const jobId = await this.addon.append({ type: 'text', input: serializedPrompt })
147
+
148
+ this.logger.info('Created inference job with ID:', jobId)
149
+
150
+ const response = this._createResponse(jobId)
151
+ await this.addon.append({ type: END_OF_INPUT })
152
+
153
+ this.logger.info('Inference job started successfully')
154
+
155
+ return response
156
+ })
157
+ }
158
+ }
159
+
160
+ module.exports = LlmLlamacpp
package/package.json ADDED
@@ -0,0 +1,72 @@
1
+ {
2
+ "name": "@qvac/llm-llamacpp",
3
+ "version": "0.1.0",
4
+ "description": "llama addon for qvac",
5
+ "addon": true,
6
+ "publishConfig": {
7
+ "access": "restricted"
8
+ },
9
+ "scripts": {
10
+ "build": "bare-make generate && bare-make build && bare-make install",
11
+ "lint": "standard --ignore \"addon/**\"",
12
+ "lint:fix": "standard --ignore \"addon/**\" --fix",
13
+ "update:quickstart-section": "node ./scripts/quickstart-testing/quickstart_check.js && node ./scripts/quickstart-testing/update_readme.js",
14
+ "test": "npm run test:integration",
15
+ "test:integration": "npm run test:integration:generate && bare test/integration/all.js",
16
+ "model:init": "qvac-dev model init",
17
+ "model:new": "qvac-dev model new",
18
+ "model:generate": "qvac-dev model generate",
19
+ "test:dts": "tsc index.d.ts --noEmit",
20
+ "test:integration:generate": "brittle -r test/integration/all.js test/integration/*.test.js"
21
+ },
22
+ "standard": {
23
+ "ignore": [
24
+ "addon/lib/"
25
+ ]
26
+ },
27
+ "files": [
28
+ "binding.js",
29
+ "index.js",
30
+ "addon.js",
31
+ "prebuilds",
32
+ "index.d.ts"
33
+ ],
34
+ "repository": {
35
+ "type": "git",
36
+ "url": "git+https://github.com/tetherto/qvac-lib-infer-llamacpp-llm.git"
37
+ },
38
+ "author": "Tether",
39
+ "license": "Apache-2.0",
40
+ "bugs": "https://github.com/tetherto/qvac-lib-infer-llamacpp-llm/issues",
41
+ "homepage": "https://github.com/tetherto/qvac-lib-infer-llamacpp-llm#readme",
42
+ "devDependencies": {
43
+ "@babel/parser": "^7.28.0",
44
+ "@babel/traverse": "^7.28.0",
45
+ "@tetherto/qvac-lib-dl-base": "1.0.2",
46
+ "@tetherto/qvac-lib-dl-filesystem": "1.0.2",
47
+ "@tetherto/qvac-lib-dl-hyperdrive": "^2.2.1",
48
+ "@tetherto/qvac-lib-logging": "1.0.1",
49
+ "hyperswarm": "^4.12.1",
50
+ "bare-buffer": "^3.1.0",
51
+ "bare-process": "^4.2.1",
52
+ "brittle": "^3.16.5",
53
+ "cmake-bare": "^1.6.5",
54
+ "cmake-vcpkg": "^1.1.0",
55
+ "corestore": "^7.4.5",
56
+ "standard": "^17.0.0"
57
+ },
58
+ "dependencies": {
59
+ "@tetherto/infer-base": "^2.4.0",
60
+ "@tetherto/qvac-lib-response": "1.0.2",
61
+ "bare-path": "^3.0.0",
62
+ "require-asset": "^1.1.0"
63
+ },
64
+ "peerDependencies": {
65
+ "@tetherto/qvac-lib-dl-hyperdrive": "^2.2.1"
66
+ },
67
+ "exports": {
68
+ "./package": "./package.json",
69
+ ".": "./index.js"
70
+ },
71
+ "types": "index.d.ts"
72
+ }