veryfront 0.1.867 → 0.1.869
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/esm/cli/commands/demo/steps.js +1 -1
- package/esm/cli/commands/dev/command.d.ts.map +1 -1
- package/esm/cli/commands/dev/command.js +6 -1
- package/esm/cli/commands/start/command.d.ts +12 -0
- package/esm/cli/commands/start/command.d.ts.map +1 -1
- package/esm/cli/commands/start/command.js +32 -6
- package/esm/cli/shared/runtime-auth.d.ts +12 -0
- package/esm/cli/shared/runtime-auth.d.ts.map +1 -0
- package/esm/cli/shared/runtime-auth.js +34 -0
- package/esm/deno.js +1 -1
- package/esm/src/agent/ag-ui/handler.d.ts.map +1 -1
- package/esm/src/agent/ag-ui/handler.js +17 -5
- package/esm/src/agent/react/index.d.ts +1 -1
- package/esm/src/agent/react/index.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/index.d.ts +1 -1
- package/esm/src/agent/react/use-chat/index.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/types.d.ts +2 -10
- package/esm/src/agent/react/use-chat/types.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/use-chat.d.ts +3 -4
- package/esm/src/agent/react/use-chat/use-chat.d.ts.map +1 -1
- package/esm/src/agent/react/use-chat/use-chat.js +16 -107
- package/esm/src/agent/runtime/index.d.ts.map +1 -1
- package/esm/src/agent/runtime/index.js +7 -12
- package/esm/src/agent/runtime/model-resolution.d.ts +2 -2
- package/esm/src/agent/runtime/model-resolution.d.ts.map +1 -1
- package/esm/src/agent/runtime/model-resolution.js +59 -13
- package/esm/src/agent/types.d.ts +2 -2
- package/esm/src/chat/index.d.ts +2 -2
- package/esm/src/chat/index.d.ts.map +1 -1
- package/esm/src/chat/index.js +1 -1
- package/esm/src/embedding/upload-handler.d.ts +2 -1
- package/esm/src/embedding/upload-handler.d.ts.map +1 -1
- package/esm/src/embedding/upload-handler.js +2 -1
- package/esm/src/html/styles-builder/plugin-loader.d.ts.map +1 -1
- package/esm/src/html/styles-builder/plugin-loader.js +2 -12
- package/esm/src/integrations/feature-flags.d.ts.map +1 -1
- package/esm/src/integrations/feature-flags.js +2 -10
- package/esm/src/platform/compat/dns.d.ts +6 -0
- package/esm/src/platform/compat/dns.d.ts.map +1 -0
- package/esm/src/platform/compat/dns.js +34 -0
- package/esm/src/platform/compat/opaque-dependency-versions.d.ts +1 -1
- package/esm/src/platform/compat/opaque-dependency-versions.js +1 -1
- package/esm/src/platform/index.d.ts +1 -0
- package/esm/src/platform/index.d.ts.map +1 -1
- package/esm/src/platform/index.js +2 -0
- package/esm/src/provider/index.d.ts +1 -1
- package/esm/src/provider/index.d.ts.map +1 -1
- package/esm/src/provider/index.js +1 -1
- package/esm/src/provider/local/env.d.ts +3 -0
- package/esm/src/provider/local/env.d.ts.map +1 -1
- package/esm/src/provider/local/env.js +31 -0
- package/esm/src/provider/local/local-engine.d.ts +38 -5
- package/esm/src/provider/local/local-engine.d.ts.map +1 -1
- package/esm/src/provider/local/local-engine.js +191 -19
- package/esm/src/provider/local/model-catalog.d.ts +8 -4
- package/esm/src/provider/local/model-catalog.d.ts.map +1 -1
- package/esm/src/provider/local/model-catalog.js +36 -41
- package/esm/src/provider/local/model-runtime-adapter.js +5 -5
- package/esm/src/provider/model-registry.d.ts +2 -12
- package/esm/src/provider/model-registry.d.ts.map +1 -1
- package/esm/src/provider/model-registry.js +4 -92
- package/esm/src/react/components/chat/chat/components/inference-badge.d.ts +2 -3
- package/esm/src/react/components/chat/chat/components/inference-badge.d.ts.map +1 -1
- package/esm/src/react/components/chat/chat/components/inference-badge.js +3 -31
- package/esm/src/react/components/chat/chat/composition/chat-message-list.d.ts +1 -2
- package/esm/src/react/components/chat/chat/composition/chat-message-list.d.ts.map +1 -1
- package/esm/src/react/components/chat/chat/composition/chat-message-list.js +5 -11
- package/esm/src/react/components/chat/chat/index.d.ts +2 -4
- package/esm/src/react/components/chat/chat/index.d.ts.map +1 -1
- package/esm/src/react/components/chat/chat/index.js +4 -6
- package/esm/src/react/components/chat/chat-with-sidebar.d.ts +0 -1
- package/esm/src/react/components/chat/chat-with-sidebar.d.ts.map +1 -1
- package/esm/src/react/components/chat/chat-with-sidebar.js +0 -1
- package/esm/src/react/components/chat/chat.d.ts +1 -1
- package/esm/src/react/components/chat/chat.d.ts.map +1 -1
- package/esm/src/react/components/chat/chat.js +1 -1
- package/esm/src/security/sandbox/worker-egress-guard.d.ts +1 -1
- package/esm/src/security/sandbox/worker-egress-guard.d.ts.map +1 -1
- package/esm/src/security/sandbox/worker-egress-guard.js +4 -17
- package/esm/src/server/handlers/dev/framework-candidates.generated.d.ts.map +1 -1
- package/esm/src/server/handlers/dev/framework-candidates.generated.js +3 -54
- package/esm/src/tool/project-scoped-remote-tools.d.ts.map +1 -1
- package/esm/src/tool/project-scoped-remote-tools.js +17 -2
- package/esm/src/utils/version-constant.d.ts +1 -1
- package/esm/src/utils/version-constant.js +1 -1
- package/esm/src/workflow/worker/dynamic-run-entrypoint.d.ts +3 -2
- package/esm/src/workflow/worker/dynamic-run-entrypoint.d.ts.map +1 -1
- package/esm/src/workflow/worker/run-entrypoint.d.ts +6 -4
- package/esm/src/workflow/worker/run-entrypoint.d.ts.map +1 -1
- package/esm/src/workflow/worker/run-entrypoint.js +3 -2
- package/package.json +2 -2
- package/esm/src/agent/react/use-chat/browser-inference/browser-engine.d.ts +0 -18
- package/esm/src/agent/react/use-chat/browser-inference/browser-engine.d.ts.map +0 -1
- package/esm/src/agent/react/use-chat/browser-inference/browser-engine.js +0 -62
- package/esm/src/agent/react/use-chat/browser-inference/types.d.ts +0 -44
- package/esm/src/agent/react/use-chat/browser-inference/types.d.ts.map +0 -1
- package/esm/src/agent/react/use-chat/browser-inference/types.js +0 -4
- package/esm/src/agent/react/use-chat/browser-inference/worker-client.d.ts +0 -30
- package/esm/src/agent/react/use-chat/browser-inference/worker-client.d.ts.map +0 -1
- package/esm/src/agent/react/use-chat/browser-inference/worker-client.js +0 -66
- package/esm/src/agent/react/use-chat/browser-inference/worker-script.d.ts +0 -8
- package/esm/src/agent/react/use-chat/browser-inference/worker-script.d.ts.map +0 -1
- package/esm/src/agent/react/use-chat/browser-inference/worker-script.js +0 -97
- package/esm/src/react/components/chat/chat/components/upgrade-cta.d.ts +0 -9
- package/esm/src/react/components/chat/chat/components/upgrade-cta.d.ts.map +0 -1
- package/esm/src/react/components/chat/chat/components/upgrade-cta.js +0 -35
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* local LLM inference. Provides lazy model loading and streaming text
|
|
6
6
|
* generation via async generators.
|
|
7
7
|
*
|
|
8
|
-
* Uses ONNX Runtime for inference with q4 quantization
|
|
8
|
+
* Uses ONNX Runtime for inference with q4 quantization, not q4f16,
|
|
9
9
|
* due to a known ONNX bug with f16 LayerNorm on CPU.
|
|
10
10
|
*
|
|
11
11
|
* @module provider/local
|
|
@@ -14,7 +14,7 @@ import { serverLogger } from "../../utils/index.js";
|
|
|
14
14
|
import { createError, toError } from "../../errors/veryfront-error.js";
|
|
15
15
|
import { importTransformers } from "../../platform/compat/opaque-deps.js";
|
|
16
16
|
import { DEFAULT_LOCAL_MODEL, resolveLocalModel } from "./model-catalog.js";
|
|
17
|
-
import { throwIfLocalAIDisabled } from "./env.js";
|
|
17
|
+
import { getLocalAIDevice, getLocalAIThinkingEnabled, throwIfLocalAIDisabled, } from "./env.js";
|
|
18
18
|
import { createPipelineCache } from "./pipeline-cache.js";
|
|
19
19
|
const logger = serverLogger.component("local-llm");
|
|
20
20
|
/** Default maximum new tokens for local model generation */
|
|
@@ -25,7 +25,7 @@ const DEFAULT_MAX_NEW_TOKENS = 512;
|
|
|
25
25
|
*
|
|
26
26
|
* Transformers.js (>=3.x) does not expose a `stop_strings` generate option,
|
|
27
27
|
* so we decode the running sequence with the tokenizer and match the stop
|
|
28
|
-
* strings against the suffix
|
|
28
|
+
* strings against the suffix through the documented `stopping_criteria` mechanism.
|
|
29
29
|
*
|
|
30
30
|
* Transformers.js passes the full sequence (prompt + generated tokens) to
|
|
31
31
|
* `_call` on every step. We must scan only the generated suffix: if a system
|
|
@@ -101,9 +101,9 @@ export async function getTransformers() {
|
|
|
101
101
|
// expected: ONNX runtime not available in some environments (e.g. compiled binaries)
|
|
102
102
|
throw toError(createError({
|
|
103
103
|
type: "no_ai_available",
|
|
104
|
-
message: "Local AI model unavailable
|
|
105
|
-
"(e.g. compiled binaries).
|
|
106
|
-
"configure OPENAI_API_KEY, ANTHROPIC_API_KEY, or GOOGLE_API_KEY to use a cloud provider
|
|
104
|
+
message: "Local AI model unavailable. Native ONNX Runtime is not supported in this environment " +
|
|
105
|
+
"(e.g. compiled binaries). Run veryfront login, set VERYFRONT_API_TOKEN, or " +
|
|
106
|
+
"configure OPENAI_API_KEY, ANTHROPIC_API_KEY, or GOOGLE_API_KEY to use a cloud provider.",
|
|
107
107
|
}));
|
|
108
108
|
}
|
|
109
109
|
// Configure cache directory for model files
|
|
@@ -113,6 +113,47 @@ export async function getTransformers() {
|
|
|
113
113
|
transformersModule = mod;
|
|
114
114
|
return mod;
|
|
115
115
|
}
|
|
116
|
+
async function ensureWebGpuAvailable() {
|
|
117
|
+
if (typeof navigator === "undefined" || !("gpu" in navigator)) {
|
|
118
|
+
throw toError(createError({
|
|
119
|
+
type: "no_ai_available",
|
|
120
|
+
message: "Local AI WebGPU unavailable. This runtime does not expose navigator.gpu. " +
|
|
121
|
+
"Use VERYFRONT_LOCAL_AI_DEVICE=cpu or run in a runtime with WebGPU support.",
|
|
122
|
+
}));
|
|
123
|
+
}
|
|
124
|
+
const gpu = navigator.gpu;
|
|
125
|
+
const adapter = await gpu?.requestAdapter?.();
|
|
126
|
+
if (!adapter) {
|
|
127
|
+
throw toError(createError({
|
|
128
|
+
type: "no_ai_available",
|
|
129
|
+
message: "Local AI WebGPU unavailable. No WebGPU adapter was found. " +
|
|
130
|
+
"Use VERYFRONT_LOCAL_AI_DEVICE=cpu or run on a machine with a supported GPU.",
|
|
131
|
+
}));
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
async function getLocalInferenceDevice() {
|
|
135
|
+
const device = getLocalAIDevice();
|
|
136
|
+
if (device === "webgpu") {
|
|
137
|
+
await ensureWebGpuAvailable();
|
|
138
|
+
}
|
|
139
|
+
return device;
|
|
140
|
+
}
|
|
141
|
+
function formatDType(dtype) {
|
|
142
|
+
return typeof dtype === "string" ? dtype : JSON.stringify(dtype);
|
|
143
|
+
}
|
|
144
|
+
function getConditionalModelConstructor(transformers, modelInfo) {
|
|
145
|
+
switch (modelInfo.modelClass) {
|
|
146
|
+
case "gemma4":
|
|
147
|
+
return transformers.Gemma4ForConditionalGeneration;
|
|
148
|
+
case "qwen3_5":
|
|
149
|
+
return transformers.Qwen3_5ForConditionalGeneration;
|
|
150
|
+
default:
|
|
151
|
+
throw toError(createError({
|
|
152
|
+
type: "config",
|
|
153
|
+
message: `Local model "${modelInfo.hfId}" requires a supported conditional-generation model class.`,
|
|
154
|
+
}));
|
|
155
|
+
}
|
|
156
|
+
}
|
|
116
157
|
/**
|
|
117
158
|
* Bounded, dedup-aware cache of text-generation pipelines keyed by HuggingFace
|
|
118
159
|
* model id. Only loads a model on a cold cache miss; concurrent loads of the
|
|
@@ -121,10 +162,10 @@ export async function getTransformers() {
|
|
|
121
162
|
const textGenerationPipelines = createPipelineCache(async (modelInfo) => {
|
|
122
163
|
try {
|
|
123
164
|
const transformers = await getTransformers();
|
|
124
|
-
logger.info(`Loading local model: ${modelInfo.hfId} (${modelInfo.dtype}, ~${modelInfo.sizeMB}MB)...`);
|
|
165
|
+
logger.info(`Loading local model: ${modelInfo.hfId} (${formatDType(modelInfo.dtype)}, ${modelInfo.device}, ~${modelInfo.sizeMB}MB)...`);
|
|
125
166
|
const pipe = (await transformers.pipeline("text-generation", modelInfo.hfId, {
|
|
126
167
|
dtype: modelInfo.dtype,
|
|
127
|
-
device:
|
|
168
|
+
device: modelInfo.device,
|
|
128
169
|
}));
|
|
129
170
|
logger.info(`Model loaded: ${modelInfo.hfId}`);
|
|
130
171
|
return pipe;
|
|
@@ -140,28 +181,151 @@ const textGenerationPipelines = createPipelineCache(async (modelInfo) => {
|
|
|
140
181
|
transformersModule = null;
|
|
141
182
|
throw toError(createError({
|
|
142
183
|
type: "no_ai_available",
|
|
143
|
-
message: "Local AI model unavailable
|
|
144
|
-
"(e.g. compiled binaries).
|
|
145
|
-
"configure OPENAI_API_KEY, ANTHROPIC_API_KEY, or GOOGLE_API_KEY to use a cloud provider
|
|
184
|
+
message: "Local AI model unavailable. Native ONNX Runtime is not supported in this environment " +
|
|
185
|
+
"(e.g. compiled binaries). Run veryfront login, set VERYFRONT_API_TOKEN, or " +
|
|
186
|
+
"configure OPENAI_API_KEY, ANTHROPIC_API_KEY, or GOOGLE_API_KEY to use a cloud provider.",
|
|
146
187
|
}));
|
|
147
188
|
}
|
|
148
189
|
throw error;
|
|
149
190
|
}
|
|
150
191
|
});
|
|
192
|
+
const conditionalGenerationRuntimes = createPipelineCache(async (modelInfo) => {
|
|
193
|
+
try {
|
|
194
|
+
const transformers = await getTransformers();
|
|
195
|
+
const ModelClass = getConditionalModelConstructor(transformers, modelInfo);
|
|
196
|
+
logger.info(`Loading local model: ${modelInfo.hfId} (${formatDType(modelInfo.dtype)}, ${modelInfo.device}, ~${modelInfo.sizeMB}MB)...`);
|
|
197
|
+
const [processor, model] = await Promise.all([
|
|
198
|
+
transformers.AutoProcessor.from_pretrained(modelInfo.hfId),
|
|
199
|
+
ModelClass.from_pretrained(modelInfo.hfId, {
|
|
200
|
+
dtype: modelInfo.dtype,
|
|
201
|
+
device: modelInfo.device,
|
|
202
|
+
}),
|
|
203
|
+
]);
|
|
204
|
+
logger.info(`Model loaded: ${modelInfo.hfId}`);
|
|
205
|
+
return { processor, model };
|
|
206
|
+
}
|
|
207
|
+
catch (error) {
|
|
208
|
+
const msg = error instanceof Error ? error.message : String(error);
|
|
209
|
+
if (msg.includes("onnx") || msg.includes("ONNX") ||
|
|
210
|
+
msg.includes("dlopen") || msg.includes("dynamic linking") ||
|
|
211
|
+
msg.includes("native module") || msg.includes("SharedArrayBuffer")) {
|
|
212
|
+
transformersModule = null;
|
|
213
|
+
throw toError(createError({
|
|
214
|
+
type: "no_ai_available",
|
|
215
|
+
message: "Local AI model unavailable. Native ONNX Runtime is not supported in this environment " +
|
|
216
|
+
"(e.g. compiled binaries). Run veryfront login, set VERYFRONT_API_TOKEN, or " +
|
|
217
|
+
"configure OPENAI_API_KEY, ANTHROPIC_API_KEY, or GOOGLE_API_KEY to use a cloud provider.",
|
|
218
|
+
}));
|
|
219
|
+
}
|
|
220
|
+
throw error;
|
|
221
|
+
}
|
|
222
|
+
});
|
|
223
|
+
function getModelCacheKey(modelInfo, device) {
|
|
224
|
+
return `${modelInfo.hfId}:${device}`;
|
|
225
|
+
}
|
|
226
|
+
async function loadLocalRuntime(modelInfo) {
|
|
227
|
+
const device = await getLocalInferenceDevice();
|
|
228
|
+
const loadInfo = { ...modelInfo, device };
|
|
229
|
+
const cacheKey = getModelCacheKey(modelInfo, device);
|
|
230
|
+
return modelInfo.engine === "conditional-generation"
|
|
231
|
+
? conditionalGenerationRuntimes.load(cacheKey, loadInfo)
|
|
232
|
+
: textGenerationPipelines.load(cacheKey, loadInfo);
|
|
233
|
+
}
|
|
234
|
+
function toConditionalMessages(messages) {
|
|
235
|
+
return messages.map((message) => ({
|
|
236
|
+
role: message.role,
|
|
237
|
+
content: [{ type: "text", text: message.content }],
|
|
238
|
+
}));
|
|
239
|
+
}
|
|
240
|
+
export function buildConditionalGenerateOptions(options, transformers, tokenizer, streamer) {
|
|
241
|
+
const { maxNewTokens = DEFAULT_MAX_NEW_TOKENS, temperature = 0.7, topP, topK, stopSequences, } = options;
|
|
242
|
+
const generateOptions = {
|
|
243
|
+
max_new_tokens: maxNewTokens,
|
|
244
|
+
temperature,
|
|
245
|
+
top_p: topP,
|
|
246
|
+
top_k: topK,
|
|
247
|
+
do_sample: temperature > 0,
|
|
248
|
+
streamer,
|
|
249
|
+
};
|
|
250
|
+
if (stopSequences && stopSequences.length > 0) {
|
|
251
|
+
generateOptions.stopping_criteria = buildStopStringCriteria(transformers, tokenizer, stopSequences);
|
|
252
|
+
}
|
|
253
|
+
return generateOptions;
|
|
254
|
+
}
|
|
255
|
+
export function buildConditionalChatTemplateOptions(modelInfo) {
|
|
256
|
+
return {
|
|
257
|
+
add_generation_prompt: true,
|
|
258
|
+
...(modelInfo.modelClass === "gemma4" ? { enable_thinking: getLocalAIThinkingEnabled() } : {}),
|
|
259
|
+
};
|
|
260
|
+
}
|
|
261
|
+
async function prepareConditionalInputs(runtime, modelInfo, messages) {
|
|
262
|
+
const prompt = runtime.processor.apply_chat_template(toConditionalMessages(messages), buildConditionalChatTemplateOptions(modelInfo));
|
|
263
|
+
if (modelInfo.modelClass === "gemma4") {
|
|
264
|
+
return await runtime.processor(prompt, undefined, undefined, {
|
|
265
|
+
add_special_tokens: false,
|
|
266
|
+
});
|
|
267
|
+
}
|
|
268
|
+
return await runtime.processor(prompt);
|
|
269
|
+
}
|
|
270
|
+
async function* generateConditionalStream(modelInfo, messages, options) {
|
|
271
|
+
const runtime = await loadLocalRuntime(modelInfo);
|
|
272
|
+
const transformers = await getTransformers();
|
|
273
|
+
const inputs = await prepareConditionalInputs(runtime, modelInfo, messages);
|
|
274
|
+
const tokenQueue = [];
|
|
275
|
+
let resolveWaiting = null;
|
|
276
|
+
let done = false;
|
|
277
|
+
function flushWaiting() {
|
|
278
|
+
if (resolveWaiting) {
|
|
279
|
+
resolveWaiting();
|
|
280
|
+
resolveWaiting = null;
|
|
281
|
+
}
|
|
282
|
+
}
|
|
283
|
+
const streamer = new transformers.TextStreamer(runtime.processor.tokenizer, {
|
|
284
|
+
skip_prompt: true,
|
|
285
|
+
skip_special_tokens: true,
|
|
286
|
+
callback_function: (text) => {
|
|
287
|
+
tokenQueue.push(text);
|
|
288
|
+
flushWaiting();
|
|
289
|
+
},
|
|
290
|
+
});
|
|
291
|
+
const generatePromise = (async () => {
|
|
292
|
+
try {
|
|
293
|
+
await runtime.model.generate({
|
|
294
|
+
...inputs,
|
|
295
|
+
...buildConditionalGenerateOptions(options, transformers, runtime.processor.tokenizer, streamer),
|
|
296
|
+
});
|
|
297
|
+
}
|
|
298
|
+
finally {
|
|
299
|
+
done = true;
|
|
300
|
+
flushWaiting();
|
|
301
|
+
}
|
|
302
|
+
})();
|
|
303
|
+
while (true) {
|
|
304
|
+
while (tokenQueue.length > 0) {
|
|
305
|
+
yield tokenQueue.shift();
|
|
306
|
+
}
|
|
307
|
+
if (done)
|
|
308
|
+
break;
|
|
309
|
+
await new Promise((resolve) => {
|
|
310
|
+
resolveWaiting = resolve;
|
|
311
|
+
});
|
|
312
|
+
}
|
|
313
|
+
await generatePromise;
|
|
314
|
+
}
|
|
151
315
|
/**
|
|
152
316
|
* Load a text-generation pipeline for the given model.
|
|
153
317
|
* Returns a cached pipeline if already loaded.
|
|
154
318
|
*/
|
|
155
|
-
function loadPipeline(modelInfo) {
|
|
156
|
-
return
|
|
319
|
+
async function loadPipeline(modelInfo) {
|
|
320
|
+
return await loadLocalRuntime(modelInfo);
|
|
157
321
|
}
|
|
158
322
|
/**
|
|
159
323
|
* Eagerly verify that the local AI runtime (@huggingface/transformers + ONNX)
|
|
160
324
|
* is available by loading the default model pipeline.
|
|
161
325
|
*
|
|
162
326
|
* Call this *before* creating the HTTP response stream so that failures surface
|
|
163
|
-
* as a thrown error (
|
|
164
|
-
* (
|
|
327
|
+
* as a thrown error (503) rather than being swallowed inside a ReadableStream
|
|
328
|
+
* (200 with in-band SSE error).
|
|
165
329
|
*
|
|
166
330
|
* In compiled binaries, `import("@huggingface/transformers")` itself fails
|
|
167
331
|
* because `onnxruntime-node` eagerly `require()`s a native `.node` addon at
|
|
@@ -173,7 +337,7 @@ function loadPipeline(modelInfo) {
|
|
|
173
337
|
*/
|
|
174
338
|
export async function verifyLocalRuntime(modelId) {
|
|
175
339
|
const modelInfo = resolveLocalModel(modelId || DEFAULT_LOCAL_MODEL);
|
|
176
|
-
await
|
|
340
|
+
await loadLocalRuntime(modelInfo);
|
|
177
341
|
}
|
|
178
342
|
/**
|
|
179
343
|
* Generate text in a streaming fashion using an async generator.
|
|
@@ -182,9 +346,13 @@ export async function verifyLocalRuntime(modelId) {
|
|
|
182
346
|
*/
|
|
183
347
|
export async function* generateStream(modelId, messages, options = {}) {
|
|
184
348
|
const modelInfo = resolveLocalModel(modelId);
|
|
349
|
+
if (modelInfo.engine === "conditional-generation") {
|
|
350
|
+
yield* generateConditionalStream(modelInfo, messages, options);
|
|
351
|
+
return;
|
|
352
|
+
}
|
|
185
353
|
const pipe = await loadPipeline(modelInfo);
|
|
186
354
|
const transformers = await getTransformers();
|
|
187
|
-
// Use a queue to bridge TextStreamer callbacks
|
|
355
|
+
// Use a queue to bridge TextStreamer callbacks to an async generator.
|
|
188
356
|
const tokenQueue = [];
|
|
189
357
|
let resolveWaiting = null;
|
|
190
358
|
let done = false;
|
|
@@ -243,12 +411,16 @@ export async function generate(modelId, messages, options = {}) {
|
|
|
243
411
|
*/
|
|
244
412
|
export async function preloadModel(modelId) {
|
|
245
413
|
const modelInfo = resolveLocalModel(modelId);
|
|
246
|
-
await
|
|
414
|
+
await loadLocalRuntime(modelInfo);
|
|
247
415
|
}
|
|
248
416
|
/**
|
|
249
417
|
* Check if a model is currently loaded in memory.
|
|
250
418
|
*/
|
|
251
419
|
export function isModelLoaded(modelId) {
|
|
252
420
|
const modelInfo = resolveLocalModel(modelId);
|
|
253
|
-
|
|
421
|
+
const device = getLocalAIDevice();
|
|
422
|
+
const cacheKey = getModelCacheKey(modelInfo, device);
|
|
423
|
+
return modelInfo.engine === "conditional-generation"
|
|
424
|
+
? conditionalGenerationRuntimes.has(cacheKey)
|
|
425
|
+
: textGenerationPipelines.has(cacheKey);
|
|
254
426
|
}
|
|
@@ -10,7 +10,11 @@ export interface ModelInfo {
|
|
|
10
10
|
/** HuggingFace model repository ID */
|
|
11
11
|
hfId: string;
|
|
12
12
|
/** Quantization dtype for ONNX Runtime */
|
|
13
|
-
dtype:
|
|
13
|
+
dtype: ModelDType | Record<string, ModelDType>;
|
|
14
|
+
/** Runtime path required by the model. */
|
|
15
|
+
engine?: "text-generation" | "conditional-generation";
|
|
16
|
+
/** Transformers.js model class used by conditional-generation models. */
|
|
17
|
+
modelClass?: "gemma4" | "qwen3_5";
|
|
14
18
|
/** Approximate download size in MB */
|
|
15
19
|
sizeMB: number;
|
|
16
20
|
/** Human-readable description */
|
|
@@ -18,8 +22,9 @@ export interface ModelInfo {
|
|
|
18
22
|
/** Pooling strategy for embedding models (default: "mean") */
|
|
19
23
|
pooling?: "mean" | "last_token";
|
|
20
24
|
}
|
|
25
|
+
export type ModelDType = "q4" | "q8" | "q4f16" | "fp16" | "fp32";
|
|
21
26
|
/** Default model used when no specific model ID is provided */
|
|
22
|
-
export declare const DEFAULT_LOCAL_MODEL = "
|
|
27
|
+
export declare const DEFAULT_LOCAL_MODEL = "qwen3.5-0.8b";
|
|
23
28
|
/** Default embedding model used when no specific model ID is provided */
|
|
24
29
|
export declare const DEFAULT_LOCAL_EMBEDDING_MODEL = "all-MiniLM-L6-v2";
|
|
25
30
|
/**
|
|
@@ -28,8 +33,7 @@ export declare const DEFAULT_LOCAL_EMBEDDING_MODEL = "all-MiniLM-L6-v2";
|
|
|
28
33
|
*/
|
|
29
34
|
export declare function resolveLocalEmbeddingModel(modelId: string): ModelInfo;
|
|
30
35
|
/**
|
|
31
|
-
* Resolve a
|
|
32
|
-
* Falls back to treating the ID as a raw HuggingFace repository ID.
|
|
36
|
+
* Resolve a supported local model ID to its HuggingFace model info.
|
|
33
37
|
*/
|
|
34
38
|
export declare function resolveLocalModel(modelId: string): ModelInfo;
|
|
35
39
|
/**
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-catalog.d.ts","sourceRoot":"","sources":["../../../../src/src/provider/local/model-catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;
|
|
1
|
+
{"version":3,"file":"model-catalog.d.ts","sourceRoot":"","sources":["../../../../src/src/provider/local/model-catalog.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAIH,MAAM,WAAW,SAAS;IACxB,sCAAsC;IACtC,IAAI,EAAE,MAAM,CAAC;IACb,0CAA0C;IAC1C,KAAK,EAAE,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,UAAU,CAAC,CAAC;IAC/C,0CAA0C;IAC1C,MAAM,CAAC,EAAE,iBAAiB,GAAG,wBAAwB,CAAC;IACtD,yEAAyE;IACzE,UAAU,CAAC,EAAE,QAAQ,GAAG,SAAS,CAAC;IAClC,sCAAsC;IACtC,MAAM,EAAE,MAAM,CAAC;IACf,iCAAiC;IACjC,WAAW,EAAE,MAAM,CAAC;IACpB,8DAA8D;IAC9D,OAAO,CAAC,EAAE,MAAM,GAAG,YAAY,CAAC;CACjC;AAED,MAAM,MAAM,UAAU,GAAG,IAAI,GAAG,IAAI,GAAG,OAAO,GAAG,MAAM,GAAG,MAAM,CAAC;AAuCjE,+DAA+D;AAC/D,eAAO,MAAM,mBAAmB,iBAAiB,CAAC;AAmClD,yEAAyE;AACzE,eAAO,MAAM,6BAA6B,qBAAqB,CAAC;AAEhE;;;GAGG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAUrE;AAED;;GAEG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,SAAS,CAU5D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,IAAI,MAAM,EAAE,CAE3C"}
|
|
@@ -6,46 +6,45 @@
|
|
|
6
6
|
*
|
|
7
7
|
* @module provider/local
|
|
8
8
|
*/
|
|
9
|
+
import { createError, toError } from "../../errors/veryfront-error.js";
|
|
9
10
|
/**
|
|
10
11
|
* Catalog of supported local models.
|
|
11
12
|
*
|
|
12
|
-
*
|
|
13
|
-
*
|
|
13
|
+
* Keep this list intentionally small. Local model support means the model has
|
|
14
|
+
* been smoke-tested through the Veryfront local runtime.
|
|
14
15
|
*/
|
|
15
16
|
const MODEL_CATALOG = {
|
|
16
|
-
"
|
|
17
|
-
hfId: "
|
|
18
|
-
dtype:
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
sizeMB:
|
|
26
|
-
description: "
|
|
17
|
+
"qwen3.5-0.8b": {
|
|
18
|
+
hfId: "onnx-community/Qwen3.5-0.8B-ONNX",
|
|
19
|
+
dtype: {
|
|
20
|
+
embed_tokens: "q4",
|
|
21
|
+
vision_encoder: "fp16",
|
|
22
|
+
decoder_model_merged: "q4",
|
|
23
|
+
},
|
|
24
|
+
engine: "conditional-generation",
|
|
25
|
+
modelClass: "qwen3_5",
|
|
26
|
+
sizeMB: 900,
|
|
27
|
+
description: "Qwen3.5 0.8B - compact current-generation Qwen model",
|
|
27
28
|
},
|
|
28
|
-
"
|
|
29
|
-
hfId: "
|
|
29
|
+
"gemma4-e2b-it": {
|
|
30
|
+
hfId: "onnx-community/gemma-4-E2B-it-ONNX",
|
|
30
31
|
dtype: "q4",
|
|
31
|
-
|
|
32
|
-
|
|
32
|
+
engine: "conditional-generation",
|
|
33
|
+
modelClass: "gemma4",
|
|
34
|
+
sizeMB: 1800,
|
|
35
|
+
description: "Gemma 4 E2B IT - compact current-generation Gemma model",
|
|
33
36
|
},
|
|
34
|
-
"
|
|
35
|
-
hfId: "onnx-community/
|
|
37
|
+
"gemma4-e4b-it": {
|
|
38
|
+
hfId: "onnx-community/gemma-4-E4B-it-ONNX",
|
|
36
39
|
dtype: "q4",
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
hfId: "onnx-community/gemma-3-1b-it-ONNX",
|
|
42
|
-
dtype: "q4",
|
|
43
|
-
sizeMB: 700,
|
|
44
|
-
description: "Gemma 3 1B — Google's compact instruction-tuned model",
|
|
40
|
+
engine: "conditional-generation",
|
|
41
|
+
modelClass: "gemma4",
|
|
42
|
+
sizeMB: 6000,
|
|
43
|
+
description: "Gemma 4 E4B IT - larger current-generation Gemma model",
|
|
45
44
|
},
|
|
46
45
|
};
|
|
47
46
|
/** Default model used when no specific model ID is provided */
|
|
48
|
-
export const DEFAULT_LOCAL_MODEL = "
|
|
47
|
+
export const DEFAULT_LOCAL_MODEL = "qwen3.5-0.8b";
|
|
49
48
|
/**
|
|
50
49
|
* Catalog of supported local embedding models.
|
|
51
50
|
*
|
|
@@ -56,26 +55,26 @@ const EMBEDDING_MODEL_CATALOG = {
|
|
|
56
55
|
hfId: "Xenova/all-MiniLM-L6-v2",
|
|
57
56
|
dtype: "q4",
|
|
58
57
|
sizeMB: 23,
|
|
59
|
-
description: "All-MiniLM-L6-v2
|
|
58
|
+
description: "All-MiniLM-L6-v2 - fast 384-dim embeddings",
|
|
60
59
|
},
|
|
61
60
|
"nomic-embed-text-v1.5": {
|
|
62
61
|
hfId: "nomic-ai/nomic-embed-text-v1.5",
|
|
63
62
|
dtype: "q4",
|
|
64
63
|
sizeMB: 130,
|
|
65
|
-
description: "Nomic Embed Text v1.5
|
|
64
|
+
description: "Nomic Embed Text v1.5 - 768-dim, variable-length embeddings",
|
|
66
65
|
},
|
|
67
66
|
"bge-base-en-v1.5": {
|
|
68
67
|
hfId: "Xenova/bge-base-en-v1.5",
|
|
69
68
|
dtype: "q4",
|
|
70
69
|
sizeMB: 110,
|
|
71
|
-
description: "BGE Base EN v1.5
|
|
70
|
+
description: "BGE Base EN v1.5 - 768-dim, strong English embeddings",
|
|
72
71
|
},
|
|
73
72
|
"qwen3-embedding-0.6b": {
|
|
74
73
|
hfId: "onnx-community/Qwen3-Embedding-0.6B-ONNX",
|
|
75
74
|
dtype: "q8",
|
|
76
75
|
sizeMB: 620,
|
|
77
76
|
pooling: "last_token",
|
|
78
|
-
description: "Qwen3 Embedding 0.6B
|
|
77
|
+
description: "Qwen3 Embedding 0.6B - SOTA multilingual embeddings",
|
|
79
78
|
},
|
|
80
79
|
};
|
|
81
80
|
/** Default embedding model used when no specific model ID is provided */
|
|
@@ -96,20 +95,16 @@ export function resolveLocalEmbeddingModel(modelId) {
|
|
|
96
95
|
};
|
|
97
96
|
}
|
|
98
97
|
/**
|
|
99
|
-
* Resolve a
|
|
100
|
-
* Falls back to treating the ID as a raw HuggingFace repository ID.
|
|
98
|
+
* Resolve a supported local model ID to its HuggingFace model info.
|
|
101
99
|
*/
|
|
102
100
|
export function resolveLocalModel(modelId) {
|
|
103
101
|
const catalogEntry = MODEL_CATALOG[modelId];
|
|
104
102
|
if (catalogEntry)
|
|
105
103
|
return catalogEntry;
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
sizeMB: 0,
|
|
111
|
-
description: `Custom model: ${modelId}`,
|
|
112
|
-
};
|
|
104
|
+
throw toError(createError({
|
|
105
|
+
type: "config",
|
|
106
|
+
message: `Unsupported local model "${modelId}". Supported local models: ${getLocalModelIds().join(", ")}.`,
|
|
107
|
+
}));
|
|
113
108
|
}
|
|
114
109
|
/**
|
|
115
110
|
* Get all available local model IDs.
|
|
@@ -24,7 +24,7 @@ const DEFAULT_MAX_NEW_TOKENS = 512;
|
|
|
24
24
|
function convertPrompt(prompt) {
|
|
25
25
|
const messages = [];
|
|
26
26
|
for (const msg of prompt) {
|
|
27
|
-
// Skip tool messages
|
|
27
|
+
// Skip tool messages. Local models do not support tool calling.
|
|
28
28
|
if (msg.role === "tool")
|
|
29
29
|
continue;
|
|
30
30
|
const mappedRole = msg.role === "system"
|
|
@@ -79,7 +79,7 @@ export function createLocalModel(modelId) {
|
|
|
79
79
|
async doGenerate(options) {
|
|
80
80
|
const messages = convertPrompt(options.prompt);
|
|
81
81
|
const genOptions = toGenerateOptions(options);
|
|
82
|
-
logger.debug(`[local] doGenerate: ${messages.length} messages
|
|
82
|
+
logger.debug(`[local] doGenerate: ${messages.length} messages -> ${resolvedId}`);
|
|
83
83
|
const text = await generate(resolvedId, messages, genOptions);
|
|
84
84
|
return {
|
|
85
85
|
content: [{ type: "text", text }],
|
|
@@ -93,7 +93,7 @@ export function createLocalModel(modelId) {
|
|
|
93
93
|
};
|
|
94
94
|
},
|
|
95
95
|
async doStream(options) {
|
|
96
|
-
// Eagerly check if local AI is disabled
|
|
96
|
+
// Eagerly check if local AI is disabled. This must throw before creating the
|
|
97
97
|
// ReadableStream, otherwise the 200 response headers are already committed.
|
|
98
98
|
// Note: getTransformers() in local-engine.ts also checks this, but we need
|
|
99
99
|
// the check here too because doStream creates a ReadableStream wrapper and
|
|
@@ -101,7 +101,7 @@ export function createLocalModel(modelId) {
|
|
|
101
101
|
throwIfLocalAIDisabled();
|
|
102
102
|
const messages = convertPrompt(options.prompt);
|
|
103
103
|
const genOptions = toGenerateOptions(options);
|
|
104
|
-
logger.debug(`[local] doStream: ${messages.length} messages
|
|
104
|
+
logger.debug(`[local] doStream: ${messages.length} messages -> ${resolvedId}`);
|
|
105
105
|
const textId = `text-${Date.now()}`;
|
|
106
106
|
const stream = new ReadableStream({
|
|
107
107
|
async start(controller) {
|
|
@@ -140,7 +140,7 @@ export function createLocalModel(modelId) {
|
|
|
140
140
|
controller.close();
|
|
141
141
|
}
|
|
142
142
|
catch (error) {
|
|
143
|
-
// Let no_ai_available propagate
|
|
143
|
+
// Let no_ai_available propagate. The chat handler needs it
|
|
144
144
|
// for a proper 503 response instead of a 200 with in-band error.
|
|
145
145
|
const vfError = fromError(error);
|
|
146
146
|
if (vfError?.type === "no_ai_available")
|
|
@@ -24,16 +24,6 @@ export type ModelProviderFactory = (modelId: string) => ModelRuntime;
|
|
|
24
24
|
* ```
|
|
25
25
|
*/
|
|
26
26
|
export declare function registerModelProvider(name: string, factory: ModelProviderFactory): void;
|
|
27
|
-
/**
|
|
28
|
-
* Find the first cloud provider with a valid API key.
|
|
29
|
-
*
|
|
30
|
-
* Returns a "provider/model" string that can be passed to `resolveModel`,
|
|
31
|
-
* or `null` if no cloud provider is available.
|
|
32
|
-
*
|
|
33
|
-
* This is intentionally a **query** — it does NOT resolve the model.
|
|
34
|
-
* The caller (agent runtime) decides whether to use it.
|
|
35
|
-
*/
|
|
36
|
-
export declare function findAvailableCloudModel(): string | null;
|
|
37
27
|
/**
|
|
38
28
|
* Resolve a "provider/model" string to a framework-compatible model runtime.
|
|
39
29
|
*
|
|
@@ -59,8 +49,8 @@ export declare function getRegisteredModelProviders(): string[];
|
|
|
59
49
|
* For real local-engine models (created by `createLocalModel()`) this
|
|
60
50
|
* eagerly loads the ONNX pipeline to surface `no_ai_available` errors
|
|
61
51
|
* **before** the HTTP response stream is created. Must happen before the
|
|
62
|
-
* ReadableStream so the chat handler can return a proper 503
|
|
63
|
-
*
|
|
52
|
+
* ReadableStream so the chat handler can return a proper 503 rather than a
|
|
53
|
+
* 200 with an in-band SSE error.
|
|
64
54
|
*
|
|
65
55
|
* Uses the `_isVfLocalModel` marker set by `createLocalModel()` to
|
|
66
56
|
* distinguish real local-engine models from mock/custom providers that
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"model-registry.d.ts","sourceRoot":"","sources":["../../../src/src/provider/model-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;
|
|
1
|
+
{"version":3,"file":"model-registry.d.ts","sourceRoot":"","sources":["../../../src/src/provider/model-registry.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAeH,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,YAAY,CAAC;AAE/C,sDAAsD;AACtD,MAAM,MAAM,oBAAoB,GAAG,CAAC,OAAO,EAAE,MAAM,KAAK,YAAY,CAAC;AAOrE;;;;;;;GAOG;AACH,wBAAgB,qBAAqB,CACnC,IAAI,EAAE,MAAM,EACZ,OAAO,EAAE,oBAAoB,GAC5B,IAAI,CAEN;AAsJD;;;;;;;;;GASG;AACH,wBAAgB,YAAY,CAAC,WAAW,EAAE,MAAM,GAAG,YAAY,CAuC9D;AAED;;GAEG;AACH,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAGtD;AAED;;GAEG;AACH,wBAAgB,2BAA2B,IAAI,MAAM,EAAE,CAGtD;AAED;;;;;;;;;;;;GAYG;AACH,wBAAsB,gBAAgB,CACpC,KAAK,EAAE,YAAY,GAClB,OAAO,CAAC,IAAI,CAAC,CAKf;AAED;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,IAAI,CAG1C"}
|