@qvac/sdk 0.2.3-dev.1760899404.f30d071 โ 0.2.3-dev.1760967206.fa44856
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/dist/examples/rag/rag-hyperdb-workspaces.js +2 -1
- package/dist/schemas/common.d.ts +1 -0
- package/dist/schemas/rag.d.ts +3 -0
- package/dist/schemas/rag.js +3 -1
- package/dist/server/rpc/handlers/load-model/download-manager.d.ts +0 -19
- package/dist/server/rpc/handlers/load-model/download-manager.js +10 -25
- package/dist/server/rpc/handlers/load-model/hyperdrive.js +113 -76
- package/package.json +2 -2
|
@@ -41,9 +41,10 @@ try {
|
|
|
41
41
|
documents: medicalArticle,
|
|
42
42
|
chunk: true,
|
|
43
43
|
chunkOpts: {
|
|
44
|
-
chunkSize:
|
|
44
|
+
chunkSize: 100,
|
|
45
45
|
chunkOverlap: 10,
|
|
46
46
|
chunkStrategy: "paragraph",
|
|
47
|
+
splitStrategy: "token",
|
|
47
48
|
},
|
|
48
49
|
});
|
|
49
50
|
console.log(`โ
Created ${medicalResult.processed.length} chunks from medical article`);
|
package/dist/schemas/common.d.ts
CHANGED
package/dist/schemas/rag.d.ts
CHANGED
|
@@ -25,6 +25,7 @@ declare const ragSaveEmbeddingsParamsSchema: z.ZodObject<{
|
|
|
25
25
|
paragraph: "paragraph";
|
|
26
26
|
}>>;
|
|
27
27
|
splitStrategy: z.ZodOptional<z.ZodEnum<{
|
|
28
|
+
token: "token";
|
|
28
29
|
character: "character";
|
|
29
30
|
word: "word";
|
|
30
31
|
sentence: "sentence";
|
|
@@ -45,6 +46,7 @@ export declare const ragSaveEmbeddingsOperationSchema: z.ZodObject<{
|
|
|
45
46
|
paragraph: "paragraph";
|
|
46
47
|
}>>;
|
|
47
48
|
splitStrategy: z.ZodOptional<z.ZodEnum<{
|
|
49
|
+
token: "token";
|
|
48
50
|
character: "character";
|
|
49
51
|
word: "word";
|
|
50
52
|
sentence: "sentence";
|
|
@@ -95,6 +97,7 @@ export declare const ragRequestSchema: z.ZodDiscriminatedUnion<[z.ZodObject<{
|
|
|
95
97
|
paragraph: "paragraph";
|
|
96
98
|
}>>;
|
|
97
99
|
splitStrategy: z.ZodOptional<z.ZodEnum<{
|
|
100
|
+
token: "token";
|
|
98
101
|
character: "character";
|
|
99
102
|
word: "word";
|
|
100
103
|
sentence: "sentence";
|
package/dist/schemas/rag.js
CHANGED
|
@@ -4,7 +4,9 @@ const llmChunkOptsSchema = z.object({
|
|
|
4
4
|
chunkSize: z.number().optional(),
|
|
5
5
|
chunkOverlap: z.number().optional(),
|
|
6
6
|
chunkStrategy: z.enum(["character", "paragraph"]).optional(),
|
|
7
|
-
splitStrategy: z
|
|
7
|
+
splitStrategy: z
|
|
8
|
+
.enum(["character", "word", "token", "sentence", "line"])
|
|
9
|
+
.optional(),
|
|
8
10
|
});
|
|
9
11
|
const ragSaveEmbeddingsResultSchema = z.object({
|
|
10
12
|
status: z.enum(["fulfilled", "rejected"]),
|
|
@@ -4,28 +4,9 @@ export declare const registerDownload: (key: string, entry: DownloadEntry) => vo
|
|
|
4
4
|
export declare const unregisterDownload: (key: string) => void;
|
|
5
5
|
export declare const createHyperdriveDownloadKey: (hyperdriveKey: string, modelFileName: string) => string;
|
|
6
6
|
export declare const createHttpDownloadKey: (url: string) => string;
|
|
7
|
-
export declare const getAllActiveDownloads: () => Map<string, DownloadEntry>;
|
|
8
7
|
export declare const setClearCacheFlag: (downloadKey: string, clearCache: boolean) => void;
|
|
9
8
|
export declare const shouldClearCache: (downloadKey: string) => boolean;
|
|
10
9
|
export declare const clearClearCacheFlag: (downloadKey: string) => void;
|
|
11
10
|
export declare const createCancelFunction: (downloadKey: string, clearCache?: boolean) => () => void;
|
|
12
11
|
export declare const cancelAllDownloads: () => void;
|
|
13
|
-
export declare const getDownloadStats: () => {
|
|
14
|
-
activeCount: number;
|
|
15
|
-
downloads: ({
|
|
16
|
-
hyperdriveKey: string;
|
|
17
|
-
modelFileName: string;
|
|
18
|
-
key: string;
|
|
19
|
-
startTime: number;
|
|
20
|
-
duration: number;
|
|
21
|
-
type: "hyperdrive" | "http";
|
|
22
|
-
} | {
|
|
23
|
-
url: string;
|
|
24
|
-
modelPath: string;
|
|
25
|
-
key: string;
|
|
26
|
-
startTime: number;
|
|
27
|
-
duration: number;
|
|
28
|
-
type: "hyperdrive" | "http";
|
|
29
|
-
})[];
|
|
30
|
-
};
|
|
31
12
|
//# sourceMappingURL=download-manager.d.ts.map
|
|
@@ -12,7 +12,6 @@ export const unregisterDownload = (key) => {
|
|
|
12
12
|
};
|
|
13
13
|
export const createHyperdriveDownloadKey = (hyperdriveKey, modelFileName) => `${hyperdriveKey}:${modelFileName}`;
|
|
14
14
|
export const createHttpDownloadKey = (url) => `http:${url}`;
|
|
15
|
-
export const getAllActiveDownloads = () => new Map(activeDownloads);
|
|
16
15
|
export const setClearCacheFlag = (downloadKey, clearCache) => {
|
|
17
16
|
if (clearCache) {
|
|
18
17
|
clearCacheFlags.set(downloadKey, true);
|
|
@@ -39,24 +38,6 @@ export const cancelAllDownloads = () => {
|
|
|
39
38
|
console.log(`๐งน Cancelling ${activeDownloads.size} active downloads`);
|
|
40
39
|
Array.from(activeDownloads.keys()).forEach((key) => createCancelFunction(key)());
|
|
41
40
|
};
|
|
42
|
-
export const getDownloadStats = () => ({
|
|
43
|
-
activeCount: activeDownloads.size,
|
|
44
|
-
downloads: Array.from(activeDownloads.entries()).map(([key, entry]) => ({
|
|
45
|
-
key,
|
|
46
|
-
startTime: entry.startTime,
|
|
47
|
-
duration: Date.now() - entry.startTime,
|
|
48
|
-
type: entry.type,
|
|
49
|
-
...(entry.type === "hyperdrive"
|
|
50
|
-
? {
|
|
51
|
-
hyperdriveKey: entry.hyperdriveKey,
|
|
52
|
-
modelFileName: entry.modelFileName,
|
|
53
|
-
}
|
|
54
|
-
: {
|
|
55
|
-
url: entry.url,
|
|
56
|
-
modelPath: entry.modelPath,
|
|
57
|
-
}),
|
|
58
|
-
})),
|
|
59
|
-
});
|
|
60
41
|
// Register cleanup handlers for graceful shutdown
|
|
61
42
|
let cleanupRegistered = false;
|
|
62
43
|
let isCleaningUp = false;
|
|
@@ -79,10 +60,14 @@ const registerCleanupHandlers = () => {
|
|
|
79
60
|
console.error("โ Error during download cleanup:", error);
|
|
80
61
|
}
|
|
81
62
|
};
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
63
|
+
let handlingExit = false;
|
|
64
|
+
const exitHandler = async () => {
|
|
65
|
+
if (handlingExit)
|
|
66
|
+
return;
|
|
67
|
+
handlingExit = true;
|
|
68
|
+
await cleanup();
|
|
69
|
+
process.exit(0);
|
|
70
|
+
};
|
|
71
|
+
process.once("SIGTERM", () => void exitHandler());
|
|
72
|
+
process.once("SIGINT", () => void exitHandler());
|
|
88
73
|
};
|
|
@@ -10,6 +10,24 @@ import { getEnv } from "../../../worker.js";
|
|
|
10
10
|
import { getModelsCacheDir, generateShortHash } from "../../../utils/index.js";
|
|
11
11
|
import { getModelBySrc } from "../../../../models/hyperdrive/index.js";
|
|
12
12
|
import { getActiveDownload, registerDownload, unregisterDownload, createHyperdriveDownloadKey, shouldClearCache, clearClearCacheFlag, } from "./download-manager.js";
|
|
13
|
+
function createProgressHandler(blob, progressCallback, downloadKey, initialBytes = 0) {
|
|
14
|
+
let downloadedBytes = initialBytes;
|
|
15
|
+
return (index, bytes) => {
|
|
16
|
+
if (index >= blob.blockOffset &&
|
|
17
|
+
index < blob.blockOffset + blob.blockLength) {
|
|
18
|
+
downloadedBytes += bytes;
|
|
19
|
+
if (progressCallback && downloadKey) {
|
|
20
|
+
progressCallback({
|
|
21
|
+
type: "modelProgress",
|
|
22
|
+
downloaded: downloadedBytes,
|
|
23
|
+
total: blob.byteLength,
|
|
24
|
+
percentage: blob.byteLength > 0 ? (downloadedBytes / blob.byteLength) * 100 : 0,
|
|
25
|
+
downloadKey,
|
|
26
|
+
});
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
};
|
|
30
|
+
}
|
|
13
31
|
async function setupHyperdrive(hyperdriveKey, corestoreDir) {
|
|
14
32
|
const corestore = new Corestore(corestoreDir);
|
|
15
33
|
await corestore.ready();
|
|
@@ -28,6 +46,14 @@ async function setupHyperdrive(hyperdriveKey, corestoreDir) {
|
|
|
28
46
|
}
|
|
29
47
|
async function cleanupHyperdrive(setup) {
|
|
30
48
|
console.log("๐งน Cleaning up hyperdrive setup");
|
|
49
|
+
if (!setup.swarm.suspended) {
|
|
50
|
+
try {
|
|
51
|
+
await setup.swarm.suspend();
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// If suspend fails, continue with cleanup
|
|
55
|
+
}
|
|
56
|
+
}
|
|
31
57
|
await setup.swarm.destroy();
|
|
32
58
|
await setup.drive.close();
|
|
33
59
|
await setup.corestore.close();
|
|
@@ -43,42 +69,75 @@ export async function deleteCorestoreDirectory(corestoreDir) {
|
|
|
43
69
|
}
|
|
44
70
|
async function findFileInHyperdrive(drive, modelFileName) {
|
|
45
71
|
const paths = [modelFileName, `/${modelFileName}`, `./${modelFileName}`];
|
|
46
|
-
let entry = null;
|
|
47
|
-
let targetPath = modelFileName;
|
|
48
72
|
for (const pathVariation of paths) {
|
|
49
73
|
try {
|
|
50
|
-
entry = await drive.entry(pathVariation);
|
|
74
|
+
const entry = await drive.entry(pathVariation);
|
|
51
75
|
if (entry) {
|
|
52
|
-
targetPath
|
|
53
|
-
break;
|
|
76
|
+
return { entry, targetPath: pathVariation };
|
|
54
77
|
}
|
|
55
78
|
}
|
|
56
79
|
catch {
|
|
57
80
|
// Continue trying other paths
|
|
58
81
|
}
|
|
59
82
|
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
83
|
+
// If not found, list available files for debugging
|
|
84
|
+
const availableFiles = [];
|
|
85
|
+
try {
|
|
86
|
+
// Try both with and without leading slash
|
|
87
|
+
for (const prefix of ["/", ""]) {
|
|
88
|
+
const files = drive.list(prefix, false);
|
|
89
|
+
for await (const file of files) {
|
|
66
90
|
availableFiles.push(file.key);
|
|
67
91
|
}
|
|
68
|
-
if (availableFiles.length
|
|
69
|
-
|
|
70
|
-
const noSlashFiles = drive.list("", false);
|
|
71
|
-
for await (const file of noSlashFiles) {
|
|
72
|
-
availableFiles.push(file.key);
|
|
73
|
-
}
|
|
74
|
-
}
|
|
75
|
-
throw new Error(`File ${modelFileName} not found in hyperdrive. Available files: ${availableFiles.join(", ")}`);
|
|
92
|
+
if (availableFiles.length > 0)
|
|
93
|
+
break;
|
|
76
94
|
}
|
|
77
|
-
|
|
78
|
-
|
|
95
|
+
}
|
|
96
|
+
catch {
|
|
97
|
+
// Ignore listing errors
|
|
98
|
+
}
|
|
99
|
+
const filesInfo = availableFiles.length > 0
|
|
100
|
+
? `Available files: ${availableFiles.join(", ")}`
|
|
101
|
+
: "Could not list available files";
|
|
102
|
+
throw new Error(`File ${modelFileName} not found in hyperdrive. ${filesInfo}`);
|
|
103
|
+
}
|
|
104
|
+
async function checkFileDownloaded(drive, entry) {
|
|
105
|
+
const blobs = await drive.getBlobs();
|
|
106
|
+
const blob = entry?.value?.blob;
|
|
107
|
+
if (!blob) {
|
|
108
|
+
return {
|
|
109
|
+
blobs,
|
|
110
|
+
blocksDownloaded: 0,
|
|
111
|
+
totalBlocks: 0,
|
|
112
|
+
totalBytes: 0,
|
|
113
|
+
};
|
|
114
|
+
}
|
|
115
|
+
// Check how many blocks are already downloaded
|
|
116
|
+
let blocksDownloaded = 0;
|
|
117
|
+
for (let i = blob.blockOffset; i < blob.blockOffset + blob.blockLength; i++) {
|
|
118
|
+
const hasBlock = await blobs.core.has(i);
|
|
119
|
+
if (hasBlock) {
|
|
120
|
+
blocksDownloaded++;
|
|
79
121
|
}
|
|
80
122
|
}
|
|
81
|
-
return {
|
|
123
|
+
return {
|
|
124
|
+
blobs,
|
|
125
|
+
blocksDownloaded,
|
|
126
|
+
totalBlocks: blob.blockLength,
|
|
127
|
+
totalBytes: blob.byteLength,
|
|
128
|
+
};
|
|
129
|
+
}
|
|
130
|
+
async function validateFileExistence(drive, file) {
|
|
131
|
+
const { entry, targetPath } = await findFileInHyperdrive(drive, file);
|
|
132
|
+
const { blobs, blocksDownloaded, totalBlocks, totalBytes } = await checkFileDownloaded(drive, entry);
|
|
133
|
+
return {
|
|
134
|
+
entry,
|
|
135
|
+
blobs,
|
|
136
|
+
targetPath,
|
|
137
|
+
blocksDownloaded,
|
|
138
|
+
totalBlocks,
|
|
139
|
+
totalBytes,
|
|
140
|
+
};
|
|
82
141
|
}
|
|
83
142
|
async function validateFileChecksum(filePath, expectedChecksum) {
|
|
84
143
|
console.log(`๐ Validating SHA256 checksum...`);
|
|
@@ -153,13 +212,15 @@ async function performHyperdriveDownload(modelPath, modelFileName, hyperdriveKey
|
|
|
153
212
|
}
|
|
154
213
|
console.log(`Downloading model from hyperdrive ${hyperdriveKey} to ${modelPath}`);
|
|
155
214
|
const setup = await setupHyperdrive(hyperdriveKey, corestoreDir);
|
|
156
|
-
let downloadedBytes = 0;
|
|
157
|
-
let totalBytes = 0;
|
|
158
215
|
// Set up abort handler
|
|
159
216
|
let readStream;
|
|
160
217
|
let writeStream;
|
|
161
218
|
const cleanup = () => {
|
|
162
219
|
console.log("๐ Aborting hyperdrive download...");
|
|
220
|
+
setup.swarm.suspended = true;
|
|
221
|
+
setup.swarm.suspend().catch(() => {
|
|
222
|
+
// Ignore errors during emergency suspend
|
|
223
|
+
});
|
|
163
224
|
// Destroy streams if they exist
|
|
164
225
|
if (readStream) {
|
|
165
226
|
readStream.destroy();
|
|
@@ -169,48 +230,19 @@ async function performHyperdriveDownload(modelPath, modelFileName, hyperdriveKey
|
|
|
169
230
|
}
|
|
170
231
|
};
|
|
171
232
|
signal?.addEventListener("abort", cleanup);
|
|
233
|
+
let downloadHandler;
|
|
172
234
|
try {
|
|
173
|
-
const
|
|
174
|
-
const { entry, targetPath } = result;
|
|
175
|
-
totalBytes = entry?.value?.blob?.byteLength || 0;
|
|
176
|
-
console.log(`๐ Model size: ${totalBytes} bytes (${(totalBytes / 1024 / 1024).toFixed(2)} MB)`);
|
|
177
|
-
// Get blobs and file blob range
|
|
178
|
-
const blobs = await setup.drive.getBlobs();
|
|
235
|
+
const { entry, blobs, targetPath, blocksDownloaded, totalBlocks, totalBytes, } = await validateFileExistence(setup.drive, modelFileName);
|
|
179
236
|
const blob = entry?.value?.blob;
|
|
180
237
|
if (!blob) {
|
|
181
238
|
throw new Error(`No blob found for ${modelFileName}`);
|
|
182
239
|
}
|
|
183
|
-
//
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
const hasBlock = await blobs.core.has(i);
|
|
187
|
-
if (hasBlock) {
|
|
188
|
-
alreadyDownloaded++;
|
|
189
|
-
}
|
|
190
|
-
}
|
|
191
|
-
downloadedBytes = 0;
|
|
192
|
-
if (alreadyDownloaded > 0) {
|
|
193
|
-
console.log(`๐ฅ Resuming download (${alreadyDownloaded}/${blob.blockLength} blocks already in corestore)`);
|
|
194
|
-
}
|
|
195
|
-
else {
|
|
196
|
-
console.log(`๐ฅ Starting fresh download`);
|
|
197
|
-
}
|
|
240
|
+
// Calculate already downloaded bytes for resuming
|
|
241
|
+
const bytesPerBlock = totalBlocks > 0 ? totalBytes / totalBlocks : 0;
|
|
242
|
+
const initialBytes = Math.floor(blocksDownloaded * bytesPerBlock);
|
|
198
243
|
// Track only downloads for blocks within our file's blob range
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
index < blob.blockOffset + blob.blockLength) {
|
|
202
|
-
downloadedBytes += bytes;
|
|
203
|
-
if (progressCallback) {
|
|
204
|
-
progressCallback({
|
|
205
|
-
type: "modelProgress",
|
|
206
|
-
downloaded: downloadedBytes,
|
|
207
|
-
total: totalBytes,
|
|
208
|
-
percentage: totalBytes > 0 ? (downloadedBytes / totalBytes) * 100 : 0,
|
|
209
|
-
downloadKey,
|
|
210
|
-
});
|
|
211
|
-
}
|
|
212
|
-
}
|
|
213
|
-
});
|
|
244
|
+
downloadHandler = createProgressHandler(blob, progressCallback, downloadKey, initialBytes);
|
|
245
|
+
blobs.core.on("download", downloadHandler);
|
|
214
246
|
// Check if aborted before starting streams
|
|
215
247
|
if (signal?.aborted) {
|
|
216
248
|
throw new Error("Download cancelled");
|
|
@@ -223,15 +255,13 @@ async function performHyperdriveDownload(modelPath, modelFileName, hyperdriveKey
|
|
|
223
255
|
readStream.pipe(writeStream);
|
|
224
256
|
// Wait for download to complete
|
|
225
257
|
await new Promise((resolve, reject) => {
|
|
226
|
-
writeStream.on("finish",
|
|
258
|
+
writeStream.on("finish", resolve);
|
|
227
259
|
writeStream.on("error", reject);
|
|
228
260
|
readStream.on("error", reject);
|
|
229
261
|
// Also reject on abort
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
}, { once: true });
|
|
234
|
-
}
|
|
262
|
+
signal?.addEventListener("abort", () => {
|
|
263
|
+
reject(new Error("Download cancelled"));
|
|
264
|
+
}, { once: true });
|
|
235
265
|
});
|
|
236
266
|
console.log(`โ
Model downloaded successfully to ${modelPath}`);
|
|
237
267
|
// Delete corestore directory after successful download (only when not seeding, to save space)
|
|
@@ -249,22 +279,29 @@ async function performHyperdriveDownload(modelPath, modelFileName, hyperdriveKey
|
|
|
249
279
|
}
|
|
250
280
|
finally {
|
|
251
281
|
signal?.removeEventListener("abort", cleanup);
|
|
282
|
+
if (downloadHandler) {
|
|
283
|
+
try {
|
|
284
|
+
const { blobs } = await validateFileExistence(setup.drive, modelFileName);
|
|
285
|
+
blobs.core.off("download", downloadHandler);
|
|
286
|
+
}
|
|
287
|
+
catch {
|
|
288
|
+
// If we can't get blobs, setup is already closed
|
|
289
|
+
}
|
|
290
|
+
}
|
|
252
291
|
if (!seed) {
|
|
253
292
|
await cleanupHyperdrive(setup);
|
|
254
293
|
}
|
|
255
294
|
// Only delete corestore and partial file if user explicitly requested clearCache
|
|
256
|
-
if (signal?.aborted) {
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
// no-op
|
|
263
|
-
}
|
|
264
|
-
await deleteCorestoreDirectory(corestoreDir);
|
|
295
|
+
if (signal?.aborted && shouldClearCache(downloadKey)) {
|
|
296
|
+
try {
|
|
297
|
+
await fsPromises.unlink(modelPath);
|
|
298
|
+
}
|
|
299
|
+
catch {
|
|
300
|
+
// no-op
|
|
265
301
|
}
|
|
266
|
-
|
|
302
|
+
await deleteCorestoreDirectory(corestoreDir);
|
|
267
303
|
}
|
|
304
|
+
clearClearCacheFlag(downloadKey);
|
|
268
305
|
}
|
|
269
306
|
}
|
|
270
307
|
export async function downloadModelFromHyperdrive(hyperdriveKey, modelFileName, seed, progressCallback, expectedChecksum) {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qvac/sdk",
|
|
3
|
-
"version": "0.2.3-dev.
|
|
3
|
+
"version": "0.2.3-dev.1760967206.fa44856",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -59,7 +59,7 @@
|
|
|
59
59
|
"@qvac/error": "^0.1.0",
|
|
60
60
|
"@qvac/langdetect-text": "^0.1.0",
|
|
61
61
|
"@qvac/logging": "^0.1.0",
|
|
62
|
-
"@qvac/rag": "^0.
|
|
62
|
+
"@qvac/rag": "^0.2.0",
|
|
63
63
|
"@qvac/transcription-whispercpp": "^0.1.4",
|
|
64
64
|
"@qvac/translation-nmtcpp": "^0.1.0",
|
|
65
65
|
"fast-safe-stringify": "2.1.1",
|