@qvac/sdk 0.1.1 → 0.2.0-dev.1760804157.f3c48d2
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/README.md +3 -4
- package/dist/client/api/completion-stream.d.ts +19 -4
- package/dist/client/api/completion-stream.js +19 -4
- package/dist/client/api/delete-cache.d.ts +29 -0
- package/dist/client/api/delete-cache.js +47 -0
- package/dist/client/api/index.d.ts +1 -0
- package/dist/client/api/index.js +1 -0
- package/dist/client/rpc/bare-client.d.ts +5 -0
- package/dist/examples/kv-cache-cleanup.d.ts +2 -0
- package/dist/examples/kv-cache-cleanup.js +88 -0
- package/dist/examples/kv-cache-custom-key.d.ts +2 -0
- package/dist/examples/kv-cache-custom-key.js +87 -0
- package/dist/index.d.ts +1 -1
- package/dist/index.js +1 -1
- package/dist/schemas/common.d.ts +18 -6
- package/dist/schemas/common.js +3 -0
- package/dist/schemas/completion-stream.d.ts +5 -3
- package/dist/schemas/completion-stream.js +3 -2
- package/dist/schemas/delete-cache.d.ts +17 -0
- package/dist/schemas/delete-cache.js +17 -0
- package/dist/schemas/index.d.ts +1 -0
- package/dist/schemas/index.js +1 -0
- package/dist/schemas/llamacpp-config.d.ts +1 -1
- package/dist/schemas/llamacpp-config.js +1 -1
- package/dist/schemas/load-model.d.ts +6 -6
- package/dist/server/bare/addons/llamacpp/handlers/completion-stream.js +110 -54
- package/dist/server/bare/addons/llamacpp/kv-cache-utils.d.ts +13 -4
- package/dist/server/bare/addons/llamacpp/kv-cache-utils.js +70 -7
- package/dist/server/bare/addons/load-model.js +1 -1
- package/dist/server/bare/registry/model-registry.d.ts +7 -1
- package/dist/server/bare/registry/model-registry.js +9 -0
- package/dist/server/rpc/handle-request.js +11 -0
- package/dist/server/rpc/handlers/delete-cache.d.ts +3 -0
- package/dist/server/rpc/handlers/delete-cache.js +29 -0
- package/package.json +2 -2
- package/dist/models/hyperdrive/validation.d.ts +0 -6
- package/dist/models/hyperdrive/validation.js +0 -12
- package/dist/server/bare/utilities/audio/decoder.d.ts +0 -4
- package/dist/server/bare/utilities/audio/decoder.js +0 -38
- package/dist/server/bare/utilities/index.d.ts +0 -2
- package/dist/server/bare/utilities/index.js +0 -1
- package/dist/server/rpc/handlers/load-model/cache.d.ts +0 -2
- package/dist/server/rpc/handlers/load-model/cache.js +0 -15
|
@@ -14,7 +14,7 @@ export declare const llmConfigSchema: z.ZodObject<{
|
|
|
14
14
|
lora: z.ZodOptional<z.ZodString>;
|
|
15
15
|
device: z.ZodDefault<z.ZodString>;
|
|
16
16
|
predict: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<-1>, z.ZodLiteral<-2>, z.ZodNumber]>>;
|
|
17
|
-
system_prompt: z.
|
|
17
|
+
system_prompt: z.ZodDefault<z.ZodString>;
|
|
18
18
|
no_mmap: z.ZodOptional<z.ZodBoolean>;
|
|
19
19
|
verbosity: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<-1>, z.ZodLiteral<0>, z.ZodLiteral<1>]>>;
|
|
20
20
|
presence_penalty: z.ZodOptional<z.ZodNumber>;
|
|
@@ -25,7 +25,7 @@ export const llmConfigSchema = z.object({
|
|
|
25
25
|
z.number().int().min(1), // positive integer: fixed token count
|
|
26
26
|
])
|
|
27
27
|
.optional(),
|
|
28
|
-
system_prompt: z.string().
|
|
28
|
+
system_prompt: z.string().default("You are a helpful assistant."),
|
|
29
29
|
no_mmap: z.boolean().optional(),
|
|
30
30
|
verbosity: verbositySchema.optional(),
|
|
31
31
|
presence_penalty: z.number().optional(),
|
|
@@ -12,7 +12,7 @@ export declare const loadModelOptionsSchema: z.ZodPipe<z.ZodUnion<readonly [z.Zo
|
|
|
12
12
|
lora: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
13
13
|
device: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
14
14
|
predict: z.ZodOptional<z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<-1>, z.ZodLiteral<-2>, z.ZodNumber]>>>;
|
|
15
|
-
system_prompt: z.ZodOptional<z.
|
|
15
|
+
system_prompt: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
16
16
|
no_mmap: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
|
|
17
17
|
verbosity: z.ZodOptional<z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<-1>, z.ZodLiteral<0>, z.ZodLiteral<1>]>>>;
|
|
18
18
|
presence_penalty: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
@@ -363,7 +363,7 @@ export declare const loadLlmModelRequestSchema: z.ZodObject<{
|
|
|
363
363
|
lora: z.ZodOptional<z.ZodString>;
|
|
364
364
|
device: z.ZodDefault<z.ZodString>;
|
|
365
365
|
predict: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<-1>, z.ZodLiteral<-2>, z.ZodNumber]>>;
|
|
366
|
-
system_prompt: z.
|
|
366
|
+
system_prompt: z.ZodDefault<z.ZodString>;
|
|
367
367
|
no_mmap: z.ZodOptional<z.ZodBoolean>;
|
|
368
368
|
verbosity: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<-1>, z.ZodLiteral<0>, z.ZodLiteral<1>]>>;
|
|
369
369
|
presence_penalty: z.ZodOptional<z.ZodNumber>;
|
|
@@ -543,7 +543,7 @@ export declare const loadModelRequestSchema: z.ZodPipe<z.ZodDiscriminatedUnion<[
|
|
|
543
543
|
lora: z.ZodOptional<z.ZodString>;
|
|
544
544
|
device: z.ZodDefault<z.ZodString>;
|
|
545
545
|
predict: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<-1>, z.ZodLiteral<-2>, z.ZodNumber]>>;
|
|
546
|
-
system_prompt: z.
|
|
546
|
+
system_prompt: z.ZodDefault<z.ZodString>;
|
|
547
547
|
no_mmap: z.ZodOptional<z.ZodBoolean>;
|
|
548
548
|
verbosity: z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<-1>, z.ZodLiteral<0>, z.ZodLiteral<1>]>>;
|
|
549
549
|
presence_penalty: z.ZodOptional<z.ZodNumber>;
|
|
@@ -704,13 +704,13 @@ export declare const loadModelRequestSchema: z.ZodPipe<z.ZodDiscriminatedUnion<[
|
|
|
704
704
|
ctx_size: number;
|
|
705
705
|
gpu_layers: number;
|
|
706
706
|
device: string;
|
|
707
|
+
system_prompt: string;
|
|
707
708
|
temp?: number | undefined;
|
|
708
709
|
top_p?: number | undefined;
|
|
709
710
|
top_k?: number | undefined;
|
|
710
711
|
seed?: number | undefined;
|
|
711
712
|
lora?: string | undefined;
|
|
712
713
|
predict?: number | undefined;
|
|
713
|
-
system_prompt?: string | undefined;
|
|
714
714
|
no_mmap?: boolean | undefined;
|
|
715
715
|
verbosity?: 0 | 1 | -1 | undefined;
|
|
716
716
|
presence_penalty?: number | undefined;
|
|
@@ -801,13 +801,13 @@ export declare const loadModelRequestSchema: z.ZodPipe<z.ZodDiscriminatedUnion<[
|
|
|
801
801
|
ctx_size: number;
|
|
802
802
|
gpu_layers: number;
|
|
803
803
|
device: string;
|
|
804
|
+
system_prompt: string;
|
|
804
805
|
temp?: number | undefined;
|
|
805
806
|
top_p?: number | undefined;
|
|
806
807
|
top_k?: number | undefined;
|
|
807
808
|
seed?: number | undefined;
|
|
808
809
|
lora?: string | undefined;
|
|
809
810
|
predict?: number | undefined;
|
|
810
|
-
system_prompt?: string | undefined;
|
|
811
811
|
no_mmap?: boolean | undefined;
|
|
812
812
|
verbosity?: 0 | 1 | -1 | undefined;
|
|
813
813
|
presence_penalty?: number | undefined;
|
|
@@ -925,7 +925,7 @@ export declare const loadModelServerParamsSchema: z.ZodObject<{
|
|
|
925
925
|
lora: z.ZodOptional<z.ZodOptional<z.ZodString>>;
|
|
926
926
|
device: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
927
927
|
predict: z.ZodOptional<z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<-1>, z.ZodLiteral<-2>, z.ZodNumber]>>>;
|
|
928
|
-
system_prompt: z.ZodOptional<z.
|
|
928
|
+
system_prompt: z.ZodOptional<z.ZodDefault<z.ZodString>>;
|
|
929
929
|
no_mmap: z.ZodOptional<z.ZodOptional<z.ZodBoolean>>;
|
|
930
930
|
verbosity: z.ZodOptional<z.ZodOptional<z.ZodUnion<readonly [z.ZodLiteral<-1>, z.ZodLiteral<0>, z.ZodLiteral<1>]>>>;
|
|
931
931
|
presence_penalty: z.ZodOptional<z.ZodOptional<z.ZodNumber>>;
|
|
@@ -1,65 +1,121 @@
|
|
|
1
1
|
import { completionParamsSchema, } from "../../../../../schemas/index.js";
|
|
2
|
-
import { getModel } from "../../../registry/model-registry.js";
|
|
3
|
-
import { findMatchingCache, getCurrentCacheInfo, renameCacheFile, } from "../kv-cache-utils";
|
|
4
|
-
|
|
5
|
-
const
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
type: "media",
|
|
14
|
-
});
|
|
15
|
-
}
|
|
2
|
+
import { getModel, getModelConfig, } from "../../../registry/model-registry.js";
|
|
3
|
+
import { findMatchingCache, getCurrentCacheInfo, renameCacheFile, getCacheFilePath, extractSystemPrompt, generateConfigHash, customCacheExists, } from "../kv-cache-utils";
|
|
4
|
+
function transformMessage(message) {
|
|
5
|
+
const transformed = [];
|
|
6
|
+
if (message.attachments && message.attachments.length > 0) {
|
|
7
|
+
for (const attachment of message.attachments) {
|
|
8
|
+
transformed.push({
|
|
9
|
+
role: message.role,
|
|
10
|
+
content: attachment.path,
|
|
11
|
+
type: "media",
|
|
12
|
+
});
|
|
16
13
|
}
|
|
17
|
-
transformedHistory.push({
|
|
18
|
-
role: message.role,
|
|
19
|
-
content: message.content,
|
|
20
|
-
});
|
|
21
14
|
}
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
15
|
+
transformed.push({
|
|
16
|
+
role: message.role,
|
|
17
|
+
content: message.content,
|
|
18
|
+
});
|
|
19
|
+
return transformed;
|
|
20
|
+
}
|
|
21
|
+
function transformMessages(messages) {
|
|
22
|
+
const transformed = [];
|
|
23
|
+
for (const message of messages) {
|
|
24
|
+
transformed.push(...transformMessage(message));
|
|
25
|
+
}
|
|
26
|
+
return transformed;
|
|
27
|
+
}
|
|
28
|
+
async function initSystemPromptCache(model, cachePathToUse, systemPromptToUse) {
|
|
29
|
+
const primeMessages = [
|
|
30
|
+
{ role: "session", content: cachePathToUse },
|
|
31
|
+
{ role: "system", content: systemPromptToUse },
|
|
32
|
+
];
|
|
33
|
+
const primeResponse = await model.run(primeMessages);
|
|
34
|
+
primeResponse.once("output", () => {
|
|
35
|
+
void primeResponse.cancel();
|
|
36
|
+
});
|
|
37
|
+
await primeResponse.await();
|
|
38
|
+
}
|
|
39
|
+
function prepareMessagesForCache(cachePathToUse, cacheExists, history) {
|
|
40
|
+
if (cacheExists && history.length > 0) {
|
|
41
|
+
const lastMessage = history[history.length - 1];
|
|
42
|
+
const lastTransformedMessages = transformMessage(lastMessage);
|
|
43
|
+
return [
|
|
35
44
|
{ role: "session", content: cachePathToUse },
|
|
36
|
-
...
|
|
45
|
+
...lastTransformedMessages,
|
|
37
46
|
];
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
47
|
+
}
|
|
48
|
+
const historyWithoutSystem = history.filter((msg) => msg.role !== "system");
|
|
49
|
+
const transformedHistoryWithoutSystem = transformMessages(historyWithoutSystem);
|
|
50
|
+
return [
|
|
51
|
+
{ role: "session", content: cachePathToUse },
|
|
52
|
+
...transformedHistoryWithoutSystem,
|
|
53
|
+
];
|
|
54
|
+
}
|
|
55
|
+
async function* processModelResponse(model, messagesToSend, shouldSaveCache) {
|
|
56
|
+
const response = await model.run(messagesToSend);
|
|
57
|
+
for await (const token of response.iterate()) {
|
|
58
|
+
yield token;
|
|
59
|
+
}
|
|
60
|
+
if (shouldSaveCache) {
|
|
42
61
|
await model.run([{ role: "session", content: "save" }]);
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
62
|
+
}
|
|
63
|
+
const responseWithStats = response;
|
|
64
|
+
return {
|
|
65
|
+
timeToFirstToken: responseWithStats.stats?.TTFT ?? 0,
|
|
66
|
+
tokensPerSecond: responseWithStats.stats?.TPS ?? 0,
|
|
67
|
+
cacheTokens: responseWithStats.stats?.CacheTokens ?? 0,
|
|
68
|
+
};
|
|
69
|
+
}
|
|
70
|
+
export async function* completion(params) {
|
|
71
|
+
const { history, modelId, kvCache } = completionParamsSchema.parse(params);
|
|
72
|
+
const transformedHistory = transformMessages(history);
|
|
73
|
+
const model = getModel(modelId);
|
|
74
|
+
if (kvCache) {
|
|
75
|
+
const modelConfig = getModelConfig(modelId);
|
|
76
|
+
const systemPromptFromHistory = extractSystemPrompt(history);
|
|
77
|
+
const configHash = generateConfigHash(modelConfig, systemPromptFromHistory);
|
|
78
|
+
const systemPromptToUse = systemPromptFromHistory ||
|
|
79
|
+
modelConfig.system_prompt ||
|
|
80
|
+
"You are a helpful assistant.";
|
|
81
|
+
let cachePathToUse;
|
|
82
|
+
if (typeof kvCache === "string") {
|
|
83
|
+
cachePathToUse = await getCacheFilePath(modelId, configHash, kvCache);
|
|
84
|
+
const cacheExists = await customCacheExists(modelId, configHash, kvCache);
|
|
85
|
+
if (!cacheExists) {
|
|
86
|
+
await initSystemPromptCache(model, cachePathToUse, systemPromptToUse);
|
|
87
|
+
}
|
|
88
|
+
const messagesToSend = prepareMessagesForCache(cachePathToUse, cacheExists, history);
|
|
89
|
+
return yield* processModelResponse(model, messagesToSend, true);
|
|
90
|
+
}
|
|
91
|
+
else {
|
|
92
|
+
// Auto-generate cache key based on conversation history
|
|
93
|
+
const cacheMessages = history.map((msg) => ({
|
|
94
|
+
role: msg.role,
|
|
95
|
+
content: msg.content,
|
|
96
|
+
attachments: msg.attachments ?? undefined,
|
|
97
|
+
}));
|
|
98
|
+
const existingCache = await findMatchingCache(modelId, configHash, cacheMessages);
|
|
99
|
+
const currentCacheInfo = await getCurrentCacheInfo(modelId, configHash, cacheMessages);
|
|
100
|
+
cachePathToUse =
|
|
101
|
+
existingCache !== null
|
|
102
|
+
? existingCache.cachePath
|
|
103
|
+
: currentCacheInfo.cachePath;
|
|
104
|
+
const cacheExists = existingCache !== null;
|
|
105
|
+
if (!cacheExists) {
|
|
106
|
+
await initSystemPromptCache(model, cachePathToUse, systemPromptToUse);
|
|
107
|
+
}
|
|
108
|
+
const messagesToSend = prepareMessagesForCache(cachePathToUse, cacheExists, history);
|
|
109
|
+
const result = yield* processModelResponse(model, messagesToSend, true);
|
|
110
|
+
//If there was an existing cache, we rename it with new hash that includes the current message
|
|
111
|
+
if (existingCache !== null &&
|
|
112
|
+
existingCache.cachePath !== currentCacheInfo.cachePath) {
|
|
113
|
+
await renameCacheFile(existingCache.cachePath, currentCacheInfo.cachePath);
|
|
114
|
+
}
|
|
115
|
+
return result;
|
|
47
116
|
}
|
|
48
|
-
const responseWithStats = response;
|
|
49
|
-
return {
|
|
50
|
-
timeToFirstToken: responseWithStats.stats?.TTFT ?? 0,
|
|
51
|
-
tokensPerSecond: responseWithStats.stats?.TPS ?? 0,
|
|
52
|
-
};
|
|
53
117
|
}
|
|
54
118
|
else {
|
|
55
|
-
|
|
56
|
-
for await (const token of response.iterate()) {
|
|
57
|
-
yield token;
|
|
58
|
-
}
|
|
59
|
-
const responseWithStats = response;
|
|
60
|
-
return {
|
|
61
|
-
timeToFirstToken: responseWithStats.stats?.TTFT ?? 0,
|
|
62
|
-
tokensPerSecond: responseWithStats.stats?.TPS ?? 0,
|
|
63
|
-
};
|
|
119
|
+
return yield* processModelResponse(model, transformedHistory, false);
|
|
64
120
|
}
|
|
65
121
|
}
|
|
@@ -5,15 +5,24 @@ export interface CacheMessage {
|
|
|
5
5
|
path: string;
|
|
6
6
|
}[] | undefined;
|
|
7
7
|
}
|
|
8
|
+
export declare function extractSystemPrompt(messages: CacheMessage[]): string | null;
|
|
9
|
+
export declare function generateConfigHash(modelConfig: unknown, systemPrompt: string | null): string;
|
|
8
10
|
export declare function generateCacheKey(messages: CacheMessage[]): string;
|
|
9
|
-
export declare function getCacheFilePath(cacheKey: string): string
|
|
10
|
-
export declare function findMatchingCache(currentHistory: CacheMessage[]): Promise<{
|
|
11
|
+
export declare function getCacheFilePath(modelId: string, configHash: string, cacheKey: string): Promise<string>;
|
|
12
|
+
export declare function findMatchingCache(modelId: string, configHash: string, currentHistory: CacheMessage[]): Promise<{
|
|
11
13
|
cacheKey: string;
|
|
12
14
|
cachePath: string;
|
|
13
15
|
} | null>;
|
|
14
|
-
export declare function getCurrentCacheInfo(currentHistory: CacheMessage[]): {
|
|
16
|
+
export declare function getCurrentCacheInfo(modelId: string, configHash: string, currentHistory: CacheMessage[]): Promise<{
|
|
15
17
|
cacheKey: string;
|
|
16
18
|
cachePath: string;
|
|
17
|
-
}
|
|
19
|
+
}>;
|
|
18
20
|
export declare function renameCacheFile(oldPath: string, newPath: string): Promise<void>;
|
|
21
|
+
export declare function customCacheExists(modelId: string, configHash: string, cacheKey: string): Promise<boolean>;
|
|
22
|
+
export declare function deleteCache(options: {
|
|
23
|
+
all: true;
|
|
24
|
+
} | {
|
|
25
|
+
kvCacheKey: string;
|
|
26
|
+
modelId?: string;
|
|
27
|
+
}): Promise<void>;
|
|
19
28
|
//# sourceMappingURL=kv-cache-utils.d.ts.map
|
|
@@ -2,6 +2,18 @@ import crypto from "bare-crypto";
|
|
|
2
2
|
import { promises as fsPromises } from "bare-fs";
|
|
3
3
|
import path from "bare-path";
|
|
4
4
|
import { getKVCacheDir } from "../../../utils/index.js";
|
|
5
|
+
export function extractSystemPrompt(messages) {
|
|
6
|
+
const systemMessage = messages.find((msg) => msg.role === "system");
|
|
7
|
+
return systemMessage ? systemMessage.content : null;
|
|
8
|
+
}
|
|
9
|
+
export function generateConfigHash(modelConfig, systemPrompt) {
|
|
10
|
+
const hash = crypto.createHash("sha256");
|
|
11
|
+
const configStr = JSON.stringify({ config: modelConfig, systemPrompt });
|
|
12
|
+
const configBuffer = Buffer.from(configStr, "utf8");
|
|
13
|
+
hash.update(configBuffer);
|
|
14
|
+
const hashString = hash.digest("hex");
|
|
15
|
+
return hashString.substring(0, 16);
|
|
16
|
+
}
|
|
5
17
|
export function generateCacheKey(messages) {
|
|
6
18
|
const hash = crypto.createHash("sha256");
|
|
7
19
|
const historyString = JSON.stringify(messages);
|
|
@@ -10,18 +22,28 @@ export function generateCacheKey(messages) {
|
|
|
10
22
|
const hashString = hash.digest("hex");
|
|
11
23
|
return hashString.substring(0, 16);
|
|
12
24
|
}
|
|
13
|
-
export function getCacheFilePath(cacheKey) {
|
|
25
|
+
export async function getCacheFilePath(modelId, configHash, cacheKey) {
|
|
14
26
|
const cacheDir = getKVCacheDir();
|
|
15
|
-
|
|
27
|
+
const sessionCacheDir = path.join(cacheDir, cacheKey);
|
|
28
|
+
const modelCacheDir = path.join(sessionCacheDir, modelId);
|
|
29
|
+
try {
|
|
30
|
+
await fsPromises.mkdir(sessionCacheDir, { recursive: true });
|
|
31
|
+
await fsPromises.mkdir(modelCacheDir, { recursive: true });
|
|
32
|
+
}
|
|
33
|
+
catch {
|
|
34
|
+
// Ignore if directories already exist
|
|
35
|
+
}
|
|
36
|
+
return path.join(modelCacheDir, `${configHash}.bin`);
|
|
16
37
|
}
|
|
17
|
-
|
|
38
|
+
// Used for auto-generated cache key
|
|
39
|
+
export async function findMatchingCache(modelId, configHash, currentHistory) {
|
|
18
40
|
if (currentHistory.length <= 1) {
|
|
19
41
|
return null;
|
|
20
42
|
}
|
|
21
43
|
// Generate cache key for history minus the last message
|
|
22
44
|
const previousHistory = currentHistory.slice(0, -1);
|
|
23
45
|
const cacheKey = generateCacheKey(previousHistory);
|
|
24
|
-
const cachePath = getCacheFilePath(cacheKey);
|
|
46
|
+
const cachePath = await getCacheFilePath(modelId, configHash, cacheKey);
|
|
25
47
|
try {
|
|
26
48
|
await fsPromises.access(cachePath);
|
|
27
49
|
return { cacheKey, cachePath };
|
|
@@ -30,10 +52,9 @@ export async function findMatchingCache(currentHistory) {
|
|
|
30
52
|
return null;
|
|
31
53
|
}
|
|
32
54
|
}
|
|
33
|
-
export function getCurrentCacheInfo(currentHistory) {
|
|
34
|
-
// Generate cache path for the current conversation state
|
|
55
|
+
export async function getCurrentCacheInfo(modelId, configHash, currentHistory) {
|
|
35
56
|
const cacheKey = generateCacheKey(currentHistory);
|
|
36
|
-
const cachePath = getCacheFilePath(cacheKey);
|
|
57
|
+
const cachePath = await getCacheFilePath(modelId, configHash, cacheKey);
|
|
37
58
|
return { cacheKey, cachePath };
|
|
38
59
|
}
|
|
39
60
|
export async function renameCacheFile(oldPath, newPath) {
|
|
@@ -44,3 +65,45 @@ export async function renameCacheFile(oldPath, newPath) {
|
|
|
44
65
|
console.error("Error renaming cache file:", error instanceof Error ? error.message : String(error));
|
|
45
66
|
}
|
|
46
67
|
}
|
|
68
|
+
export async function customCacheExists(modelId, configHash, cacheKey) {
|
|
69
|
+
const cachePath = await getCacheFilePath(modelId, configHash, cacheKey);
|
|
70
|
+
try {
|
|
71
|
+
await fsPromises.access(cachePath);
|
|
72
|
+
return true;
|
|
73
|
+
}
|
|
74
|
+
catch {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
export async function deleteCache(options) {
|
|
79
|
+
const cacheDir = getKVCacheDir();
|
|
80
|
+
if ("all" in options && options.all) {
|
|
81
|
+
try {
|
|
82
|
+
await fsPromises.rm(cacheDir, { recursive: true });
|
|
83
|
+
await fsPromises.mkdir(cacheDir, { recursive: true });
|
|
84
|
+
}
|
|
85
|
+
catch (error) {
|
|
86
|
+
console.error("Error deleting all caches:", error instanceof Error ? error.message : String(error));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
else if ("kvCacheKey" in options) {
|
|
90
|
+
const kvCacheDir = path.join(cacheDir, options.kvCacheKey);
|
|
91
|
+
if (options.modelId) {
|
|
92
|
+
const modelCacheDir = path.join(kvCacheDir, options.modelId);
|
|
93
|
+
try {
|
|
94
|
+
await fsPromises.rm(modelCacheDir, { recursive: true });
|
|
95
|
+
}
|
|
96
|
+
catch (error) {
|
|
97
|
+
console.error(`Error deleting model cache ${options.kvCacheKey}/${options.modelId}:`, error instanceof Error ? error.message : String(error));
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
else {
|
|
101
|
+
try {
|
|
102
|
+
await fsPromises.rm(kvCacheDir, { recursive: true });
|
|
103
|
+
}
|
|
104
|
+
catch (error) {
|
|
105
|
+
console.error(`Error deleting cache ${options.kvCacheKey}:`, error instanceof Error ? error.message : String(error));
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -51,5 +51,5 @@ export async function loadModel(params) {
|
|
|
51
51
|
console.log(`${modelType}: Loading model ${modelId}...`);
|
|
52
52
|
await model.load(false);
|
|
53
53
|
console.log(`${modelType} model ${modelId} loaded`);
|
|
54
|
-
registerModel(modelId, { model, path: modelPath });
|
|
54
|
+
registerModel(modelId, { model, path: modelPath, config: modelConfig });
|
|
55
55
|
}
|
|
@@ -1,9 +1,10 @@
|
|
|
1
|
+
import type EventEmitter from "events";
|
|
1
2
|
interface AddonInterface {
|
|
2
3
|
cancel(jobId?: string): Promise<void>;
|
|
3
4
|
}
|
|
4
5
|
export interface AnyModel {
|
|
5
6
|
load(...args: unknown[]): Promise<void>;
|
|
6
|
-
run(input: unknown): Promise<{
|
|
7
|
+
run(input: unknown): Promise<EventEmitter & {
|
|
7
8
|
onUpdate(callback: (data: string) => void): {
|
|
8
9
|
await: () => Promise<void>;
|
|
9
10
|
};
|
|
@@ -12,6 +13,7 @@ export interface AnyModel {
|
|
|
12
13
|
}>) => void): {
|
|
13
14
|
await: () => Promise<void>;
|
|
14
15
|
};
|
|
16
|
+
cancel(): Promise<void>;
|
|
15
17
|
iterate(): AsyncIterableIterator<string | Array<{
|
|
16
18
|
text: string;
|
|
17
19
|
}>>;
|
|
@@ -29,6 +31,7 @@ interface LocalOptions {
|
|
|
29
31
|
model: AnyModel;
|
|
30
32
|
path: string;
|
|
31
33
|
loadedAt: Date;
|
|
34
|
+
config: unknown;
|
|
32
35
|
}
|
|
33
36
|
export interface ModelEntry {
|
|
34
37
|
id: string;
|
|
@@ -39,6 +42,7 @@ export interface ModelEntry {
|
|
|
39
42
|
export declare function registerModel(id: string, options: {
|
|
40
43
|
model: AnyModel;
|
|
41
44
|
path: string;
|
|
45
|
+
config: unknown;
|
|
42
46
|
} | {
|
|
43
47
|
topic: string;
|
|
44
48
|
providerPublicKey: string;
|
|
@@ -53,7 +57,9 @@ export declare function getModelInfo(id: string): {
|
|
|
53
57
|
id: string;
|
|
54
58
|
path: string;
|
|
55
59
|
loadedAt: Date;
|
|
60
|
+
config: unknown;
|
|
56
61
|
} | null;
|
|
62
|
+
export declare function getModelConfig(id: string): unknown;
|
|
57
63
|
export declare function clearRegistry(): void;
|
|
58
64
|
export declare function getRegistryStats(): {
|
|
59
65
|
totalModels: number;
|
|
@@ -26,6 +26,7 @@ export function registerModel(id, options) {
|
|
|
26
26
|
model: options.model,
|
|
27
27
|
path: options.path,
|
|
28
28
|
loadedAt: new Date(),
|
|
29
|
+
config: options.config,
|
|
29
30
|
},
|
|
30
31
|
});
|
|
31
32
|
console.log(`Local model registered: ${id} -> ${options.path}`);
|
|
@@ -69,8 +70,16 @@ export function getModelInfo(id) {
|
|
|
69
70
|
id: entry.id,
|
|
70
71
|
path: entry.local.path,
|
|
71
72
|
loadedAt: entry.local.loadedAt,
|
|
73
|
+
config: entry.local.config,
|
|
72
74
|
};
|
|
73
75
|
}
|
|
76
|
+
export function getModelConfig(id) {
|
|
77
|
+
const entry = modelRegistry.get(id);
|
|
78
|
+
if (!entry || !entry.local) {
|
|
79
|
+
throw new Error(`Model with ID "${id}" not found or is delegated`);
|
|
80
|
+
}
|
|
81
|
+
return entry.local.config;
|
|
82
|
+
}
|
|
74
83
|
export function clearRegistry() {
|
|
75
84
|
modelRegistry.clear();
|
|
76
85
|
console.log("Model registry cleared");
|
|
@@ -13,6 +13,7 @@ import { handleLoggingStream } from "./handlers/logging-stream.js";
|
|
|
13
13
|
import { cancelHandler } from "./handlers/cancelHandler.js";
|
|
14
14
|
import { provideHandler } from "./handlers/provideHandler.js";
|
|
15
15
|
import { handleRag } from "./handlers/rag.js";
|
|
16
|
+
import { handleDeleteCache } from "./handlers/delete-cache.js";
|
|
16
17
|
import { sendErrorResponse, sendStreamErrorResponse } from "../error-handlers";
|
|
17
18
|
export async function handleRequest(req) {
|
|
18
19
|
try {
|
|
@@ -209,6 +210,16 @@ export async function handleRequest(req) {
|
|
|
209
210
|
}
|
|
210
211
|
break;
|
|
211
212
|
}
|
|
213
|
+
case "deleteCache": {
|
|
214
|
+
try {
|
|
215
|
+
const response = await handleDeleteCache(request);
|
|
216
|
+
req.reply(JSON.stringify(responseSchema.parse(response)), "utf-8");
|
|
217
|
+
}
|
|
218
|
+
catch (error) {
|
|
219
|
+
sendErrorResponse(req, error);
|
|
220
|
+
}
|
|
221
|
+
break;
|
|
222
|
+
}
|
|
212
223
|
default: {
|
|
213
224
|
throw new Error(`Unknown request type`);
|
|
214
225
|
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
import { deleteCache as deleteCacheUtil } from "../../bare/addons/llamacpp/kv-cache-utils.js";
|
|
2
|
+
export async function handleDeleteCache(request) {
|
|
3
|
+
try {
|
|
4
|
+
if ("all" in request && request.all) {
|
|
5
|
+
await deleteCacheUtil({ all: true });
|
|
6
|
+
}
|
|
7
|
+
else if ("kvCacheKey" in request) {
|
|
8
|
+
const params = {
|
|
9
|
+
kvCacheKey: request.kvCacheKey,
|
|
10
|
+
};
|
|
11
|
+
if (request.modelId !== undefined) {
|
|
12
|
+
params.modelId = request.modelId;
|
|
13
|
+
}
|
|
14
|
+
await deleteCacheUtil(params);
|
|
15
|
+
}
|
|
16
|
+
return {
|
|
17
|
+
type: "deleteCache",
|
|
18
|
+
success: true,
|
|
19
|
+
};
|
|
20
|
+
}
|
|
21
|
+
catch (error) {
|
|
22
|
+
console.error("Error deleting cache:", error);
|
|
23
|
+
return {
|
|
24
|
+
type: "deleteCache",
|
|
25
|
+
success: false,
|
|
26
|
+
error: error instanceof Error ? error.message : "Unknown error",
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@qvac/sdk",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0-dev.1760804157.f3c48d2",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"main": "./dist/index.js",
|
|
6
6
|
"types": "./dist/index.d.ts",
|
|
@@ -60,7 +60,7 @@
|
|
|
60
60
|
"@qvac/langdetect-text": "^0.1.0",
|
|
61
61
|
"@qvac/logging": "^0.1.0",
|
|
62
62
|
"@qvac/rag": "^0.1.0",
|
|
63
|
-
"@qvac/transcription-whispercpp": "^0.1.
|
|
63
|
+
"@qvac/transcription-whispercpp": "^0.1.0",
|
|
64
64
|
"@qvac/translation-nmtcpp": "^0.1.0",
|
|
65
65
|
"fast-safe-stringify": "2.1.1",
|
|
66
66
|
"zod": "^4.0.17"
|
|
@@ -1,12 +0,0 @@
|
|
|
1
|
-
// Parse hyperdrive URLs: pear://key/path/to/model.gguf
|
|
2
|
-
export function parseHyperdriveUrl(url) {
|
|
3
|
-
const match = url.match(/^pear:\/\/([0-9a-fA-F]{64})\/(.+)$/);
|
|
4
|
-
if (!match || !match[1] || !match[2]) {
|
|
5
|
-
throw new Error(`Invalid hyperdrive URL: "${url}". Expected format: pear://64-char-hex-key/path/to/model.gguf`);
|
|
6
|
-
}
|
|
7
|
-
return { key: match[1], path: match[2] };
|
|
8
|
-
}
|
|
9
|
-
// Hyperdrive key validation (32 bytes = 64 hex chars)
|
|
10
|
-
export function isValidHyperdriveKey(key) {
|
|
11
|
-
return /^[0-9a-fA-F]{64}$/.test(key);
|
|
12
|
-
}
|
|
@@ -1,38 +0,0 @@
|
|
|
1
|
-
import fs from "bare-fs";
|
|
2
|
-
import path from "bare-path";
|
|
3
|
-
import GSTDecoder from "@qvac/decoder-audio";
|
|
4
|
-
import { Readable } from "bare-stream";
|
|
5
|
-
import { FORMATS_NEEDING_DECODE } from "../../../../constants/audio.js";
|
|
6
|
-
export function needsDecoding(filePath) {
|
|
7
|
-
const ext = path.extname(filePath).toLowerCase();
|
|
8
|
-
return FORMATS_NEEDING_DECODE.includes(ext);
|
|
9
|
-
}
|
|
10
|
-
export async function decodeAudioToStream(inputPath) {
|
|
11
|
-
const decoder = new GSTDecoder({
|
|
12
|
-
config: {
|
|
13
|
-
audioFormat: "encoded",
|
|
14
|
-
},
|
|
15
|
-
});
|
|
16
|
-
try {
|
|
17
|
-
await decoder.load();
|
|
18
|
-
const audioStream = fs.createReadStream(inputPath);
|
|
19
|
-
const response = await decoder.run(audioStream);
|
|
20
|
-
const decodedBuffers = [];
|
|
21
|
-
await response
|
|
22
|
-
.onUpdate((output) => {
|
|
23
|
-
const bytes = new Uint8Array(output.outputArray);
|
|
24
|
-
decodedBuffers.push(bytes);
|
|
25
|
-
})
|
|
26
|
-
.onFinish(() => {
|
|
27
|
-
console.log("Audio decoded to buffer");
|
|
28
|
-
})
|
|
29
|
-
.await();
|
|
30
|
-
// Create a stream from the decoded buffers
|
|
31
|
-
const combinedBuffer = Buffer.concat(decodedBuffers);
|
|
32
|
-
return Readable.from([combinedBuffer]);
|
|
33
|
-
}
|
|
34
|
-
catch (error) {
|
|
35
|
-
console.error("Decoding failed:", error instanceof Error ? error.message : String(error));
|
|
36
|
-
throw error;
|
|
37
|
-
}
|
|
38
|
-
}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
export * from "./audio/decoder.js";
|
|
@@ -1,15 +0,0 @@
|
|
|
1
|
-
import fs from "bare-fs";
|
|
2
|
-
import path from "bare-path";
|
|
3
|
-
import { getEnv } from "../../../worker.js";
|
|
4
|
-
export function getCacheDir() {
|
|
5
|
-
const homeDir = getEnv().HOME_DIR;
|
|
6
|
-
const cacheDir = path.join(homeDir, ".qvac", "models");
|
|
7
|
-
// Ensure cache directory exists
|
|
8
|
-
try {
|
|
9
|
-
fs.mkdirSync(cacheDir, { recursive: true });
|
|
10
|
-
}
|
|
11
|
-
catch (error) {
|
|
12
|
-
console.error("Error creating cache directory:", error instanceof Error ? error.message : String(error));
|
|
13
|
-
}
|
|
14
|
-
return cacheDir;
|
|
15
|
-
}
|