@tanstack-ai-browser/wllama 0.0.0-stage → 0.2.1
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/LICENSE +21 -0
- package/README.md +151 -2
- package/dist/adapter.d.ts +176 -0
- package/dist/adapter.d.ts.map +1 -0
- package/dist/adapter.js +195 -0
- package/dist/audio.d.ts +16 -0
- package/dist/audio.d.ts.map +1 -0
- package/dist/audio.js +80 -0
- package/dist/catalog.d.ts +104 -0
- package/dist/catalog.d.ts.map +1 -0
- package/dist/catalog.js +307 -0
- package/dist/client.d.ts +64 -0
- package/dist/client.d.ts.map +1 -0
- package/dist/client.js +148 -0
- package/dist/index.d.ts +12 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +7 -0
- package/dist/model-memory.d.ts +46 -0
- package/dist/model-memory.d.ts.map +1 -0
- package/dist/model-memory.js +79 -0
- package/dist/model-meta.d.ts +7 -0
- package/dist/model-meta.d.ts.map +1 -0
- package/dist/model-meta.js +5 -0
- package/dist/shared-load.d.ts +48 -0
- package/dist/shared-load.d.ts.map +1 -0
- package/dist/shared-load.js +103 -0
- package/dist/store.d.ts +8 -0
- package/dist/store.d.ts.map +1 -0
- package/dist/store.js +17 -0
- package/dist/template.d.ts +26 -0
- package/dist/template.d.ts.map +1 -0
- package/dist/template.js +25 -0
- package/dist/thinking.d.ts +39 -0
- package/dist/thinking.d.ts.map +1 -0
- package/dist/thinking.js +140 -0
- package/dist/tool-calls.d.ts +33 -0
- package/dist/tool-calls.d.ts.map +1 -0
- package/dist/tool-calls.js +203 -0
- package/package.json +63 -4
- package/src/adapter.ts +345 -0
- package/src/audio.test.ts +83 -0
- package/src/audio.ts +79 -0
- package/src/catalog.ts +399 -0
- package/src/client.ts +195 -0
- package/src/index.ts +11 -0
- package/src/runtime.test.ts +220 -0
- package/src/wllama-version.test.ts +19 -0
- package/src/wrapper.test.ts +186 -0
package/dist/audio.js
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Audio as llama.cpp reads it. Its multimodal library decodes WAV, MP3 and
|
|
3
|
+
* FLAC files itself, and resamples them to the model's rate. Anything else
|
|
4
|
+
* a browser records or plays (Opus in WebM or Ogg, AAC in MP4) is decoded
|
|
5
|
+
* here, with Web Audio, and handed over as WAV.
|
|
6
|
+
*/
|
|
7
|
+
/** The rate other formats are decoded at: the audio encoders' own, so llama.cpp resamples nothing. */
|
|
8
|
+
const SAMPLE_RATE = 16000;
|
|
9
|
+
/** Whether `bytes` holds `text` (ASCII) at `at`. */
|
|
10
|
+
const holds = (bytes, at, text) => [...text].every((character, i) => bytes[at + i] === character.charCodeAt(0));
|
|
11
|
+
/** Whether `data` is a WAV, FLAC or MP3 file, by how it starts: the files llama.cpp decodes. */
|
|
12
|
+
export function decodesInLlama(data) {
|
|
13
|
+
const bytes = new Uint8Array(data, 0, Math.min(data.byteLength, 12));
|
|
14
|
+
if (bytes.length < 4)
|
|
15
|
+
return false;
|
|
16
|
+
if (holds(bytes, 0, "RIFF") && holds(bytes, 8, "WAVE"))
|
|
17
|
+
return true;
|
|
18
|
+
if (holds(bytes, 0, "fLaC"))
|
|
19
|
+
return true;
|
|
20
|
+
// An MP3 that opens with an ID3 tag, or with a frame: 11 sync bits, then
|
|
21
|
+
// Layer III (AAC's ADTS frames open the same way, as layer 0).
|
|
22
|
+
if (holds(bytes, 0, "ID3"))
|
|
23
|
+
return true;
|
|
24
|
+
const [first = 0, second = 0] = bytes;
|
|
25
|
+
return first === 0xff && (second & 0xe0) === 0xe0 && (second & 0x06) === 0x02;
|
|
26
|
+
}
|
|
27
|
+
/** Mono samples, each from -1 to 1, as a 16-bit PCM WAV file. */
|
|
28
|
+
export function wavFile(samples, sampleRate) {
|
|
29
|
+
const size = samples.length * 2;
|
|
30
|
+
const file = new DataView(new ArrayBuffer(44 + size));
|
|
31
|
+
const text = (at, value) => {
|
|
32
|
+
for (let i = 0; i < value.length; i++)
|
|
33
|
+
file.setUint8(at + i, value.charCodeAt(i));
|
|
34
|
+
};
|
|
35
|
+
text(0, "RIFF");
|
|
36
|
+
file.setUint32(4, 36 + size, true);
|
|
37
|
+
text(8, "WAVE");
|
|
38
|
+
text(12, "fmt ");
|
|
39
|
+
file.setUint32(16, 16, true);
|
|
40
|
+
// PCM, one channel, 2 bytes a sample.
|
|
41
|
+
file.setUint16(20, 1, true);
|
|
42
|
+
file.setUint16(22, 1, true);
|
|
43
|
+
file.setUint32(24, sampleRate, true);
|
|
44
|
+
file.setUint32(28, sampleRate * 2, true);
|
|
45
|
+
file.setUint16(32, 2, true);
|
|
46
|
+
file.setUint16(34, 16, true);
|
|
47
|
+
text(36, "data");
|
|
48
|
+
file.setUint32(40, size, true);
|
|
49
|
+
samples.forEach((value, i) => {
|
|
50
|
+
const sample = Math.max(-1, Math.min(1, value));
|
|
51
|
+
file.setInt16(44 + i * 2, Math.round(sample < 0 ? sample * 0x8000 : sample * 0x7fff), true);
|
|
52
|
+
});
|
|
53
|
+
return file.buffer;
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* An audio file's bytes as llama.cpp can decode them: as they are for WAV,
|
|
57
|
+
* MP3 and FLAC, else decoded by the browser and written as mono WAV.
|
|
58
|
+
*/
|
|
59
|
+
export async function llamaAudio(data) {
|
|
60
|
+
if (decodesInLlama(data))
|
|
61
|
+
return data;
|
|
62
|
+
if (typeof OfflineAudioContext === "undefined") {
|
|
63
|
+
throw new Error("This audio isn't WAV, MP3 or FLAC, and there's no Web Audio here to decode it.");
|
|
64
|
+
}
|
|
65
|
+
let decoded;
|
|
66
|
+
try {
|
|
67
|
+
// A copy: decoding detaches the buffer it's given.
|
|
68
|
+
decoded = await new OfflineAudioContext(1, 1, SAMPLE_RATE).decodeAudioData(data.slice(0));
|
|
69
|
+
}
|
|
70
|
+
catch {
|
|
71
|
+
throw new Error("The browser couldn't decode this audio. WAV, MP3 and FLAC always work.");
|
|
72
|
+
}
|
|
73
|
+
const mono = new Float32Array(decoded.length);
|
|
74
|
+
for (let channel = 0; channel < decoded.numberOfChannels; channel++) {
|
|
75
|
+
decoded.getChannelData(channel).forEach((sample, i) => {
|
|
76
|
+
mono[i] = (mono[i] ?? 0) + sample / decoded.numberOfChannels;
|
|
77
|
+
});
|
|
78
|
+
}
|
|
79
|
+
return wavFile(mono, decoded.sampleRate);
|
|
80
|
+
}
|
|
@@ -0,0 +1,104 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The models this package knows: GGUF files on Hugging Face that run on the
|
|
3
|
+
* CPU with wllama, with what each supports, the sampling from its card, and
|
|
4
|
+
* how it writes tool calls.
|
|
5
|
+
*/
|
|
6
|
+
import type { ModelReasoning } from "@tanstack/ai";
|
|
7
|
+
import type { ModelReasoningCapability } from "@tanstack/ai/adapter-internals";
|
|
8
|
+
/**
|
|
9
|
+
* Sampling settings from a model's card, in llama.cpp's request fields (the
|
|
10
|
+
* names `modelOptions` takes): used unless a call's `modelOptions` sets them.
|
|
11
|
+
*/
|
|
12
|
+
export type Sampling = {
|
|
13
|
+
temperature: number;
|
|
14
|
+
/** Left out where the card names none: llama.cpp's default. */
|
|
15
|
+
top_k?: number;
|
|
16
|
+
/** llama.cpp's repetition penalty (`--repeat-penalty`). */
|
|
17
|
+
repeat_penalty: number;
|
|
18
|
+
/** Nucleus sampling: the likeliest tokens up to this probability. */
|
|
19
|
+
top_p?: number;
|
|
20
|
+
/** Drops tokens less likely than this share of the likeliest one's odds. */
|
|
21
|
+
min_p?: number;
|
|
22
|
+
/** Lowers the odds of tokens that have appeared at all. */
|
|
23
|
+
presence_penalty?: number;
|
|
24
|
+
};
|
|
25
|
+
export type WllamaModelMeta = {
|
|
26
|
+
/** The Hugging Face repository the GGUF file is in. */
|
|
27
|
+
name: string;
|
|
28
|
+
/** The GGUF file in the repository. */
|
|
29
|
+
file: string;
|
|
30
|
+
/** The projector's GGUF file in the repository, for a model that reads images or audio. */
|
|
31
|
+
mmproj?: string;
|
|
32
|
+
supports: {
|
|
33
|
+
/** Text, and images or audio for a model whose projector reads them. */
|
|
34
|
+
input: ReadonlyArray<"text" | "image" | "audio">;
|
|
35
|
+
output: ReadonlyArray<"text">;
|
|
36
|
+
capabilities: ReadonlyArray<"tools" | "thinking">;
|
|
37
|
+
};
|
|
38
|
+
/** Parameter count. */
|
|
39
|
+
size: string;
|
|
40
|
+
/** Reasoning levels for `chat({ reasoning })`, read from the chat template. */
|
|
41
|
+
reasoning?: ModelReasoning;
|
|
42
|
+
/** Sampling from the model card, used unless `modelOptions` overrides it. */
|
|
43
|
+
sampling: Sampling;
|
|
44
|
+
/** The card's sampling while the chat template thinks, where it differs. */
|
|
45
|
+
thinkingSampling?: Sampling;
|
|
46
|
+
};
|
|
47
|
+
export declare const WLLAMA_CHAT_MODELS: readonly ["LiquidAI/LFM3-1.7B-GGUF", "LiquidAI/LFM3-VL-2B-GGUF", "LiquidAI/LFM2.5-VL-3B-GGUF", "LiquidAI/LFM2.5-VL-1.6B-GGUF", "LiquidAI/LFM2.5-VL-450M-GGUF", "LiquidAI/LFM3-Omni-2B-GGUF", "LiquidAI/LFM3-Audio-2B-GGUF", "LiquidAI/LFM2.5-Audio-1.5B-GGUF", "LiquidAI/LFM2.5-1.2B-Instruct-GGUF", "LiquidAI/LFM2.5-1.2B-Thinking-GGUF", "LiquidAI/LFM2.5-350M-GGUF", "unsloth/Qwen3.5-2B-GGUF"];
|
|
48
|
+
/** A model this package lists. */
|
|
49
|
+
export type WllamaChatModel = (typeof WLLAMA_CHAT_MODELS)[number];
|
|
50
|
+
/** Each listed model's metadata, by name. */
|
|
51
|
+
export declare const WLLAMA_MODEL_META: Readonly<Record<WllamaChatModel, WllamaModelMeta>>;
|
|
52
|
+
declare const WLLAMA_REASONING: {
|
|
53
|
+
readonly "LiquidAI/LFM3-1.7B-GGUF": {
|
|
54
|
+
readonly levels: {
|
|
55
|
+
readonly off: "false";
|
|
56
|
+
readonly high: "true";
|
|
57
|
+
};
|
|
58
|
+
readonly budget: true;
|
|
59
|
+
};
|
|
60
|
+
readonly "LiquidAI/LFM3-VL-2B-GGUF": {
|
|
61
|
+
readonly levels: {
|
|
62
|
+
readonly off: "false";
|
|
63
|
+
readonly high: "true";
|
|
64
|
+
};
|
|
65
|
+
readonly budget: true;
|
|
66
|
+
};
|
|
67
|
+
readonly "LiquidAI/LFM2.5-1.2B-Thinking-GGUF": {
|
|
68
|
+
readonly levels: {
|
|
69
|
+
readonly high: "true";
|
|
70
|
+
};
|
|
71
|
+
readonly budget: true;
|
|
72
|
+
};
|
|
73
|
+
readonly "unsloth/Qwen3.5-2B-GGUF": {
|
|
74
|
+
readonly levels: {
|
|
75
|
+
readonly off: "false";
|
|
76
|
+
readonly high: "true";
|
|
77
|
+
};
|
|
78
|
+
readonly budget: true;
|
|
79
|
+
};
|
|
80
|
+
};
|
|
81
|
+
/** The reasoning levels of each listed model that reasons. */
|
|
82
|
+
export type WllamaModelReasoningByName = {
|
|
83
|
+
[K in keyof typeof WLLAMA_REASONING]: ModelReasoningCapability<(typeof WLLAMA_REASONING)[K]>;
|
|
84
|
+
};
|
|
85
|
+
/**
|
|
86
|
+
* Reasoning data of each listed model that reasons, for
|
|
87
|
+
* `supportedReasoningLevels()`. A model missing here doesn't reason.
|
|
88
|
+
*/
|
|
89
|
+
export declare const WLLAMA_MODEL_REASONING: Readonly<Record<string, ModelReasoning>>;
|
|
90
|
+
/** A listed model's metadata. */
|
|
91
|
+
export declare const modelMeta: (model: WllamaChatModel) => WllamaModelMeta;
|
|
92
|
+
/** A model the catalog lists, a GGUF file's URL, or a GGUF file on Hugging Face as `owner/repo/file.gguf`. */
|
|
93
|
+
export type WllamaModel = WllamaChatModel | (string & {});
|
|
94
|
+
/** Whether the catalog lists `model`. */
|
|
95
|
+
export declare const listed: (model: string) => model is WllamaChatModel;
|
|
96
|
+
/** Where a listed model's projector downloads from; none for a model that reads text only. */
|
|
97
|
+
export declare const mmprojUrl: (model: WllamaModel) => string | undefined;
|
|
98
|
+
/**
|
|
99
|
+
* Where a model's GGUF file downloads from: a listed model's file, or the
|
|
100
|
+
* named one, at its latest version (main) on Hugging Face.
|
|
101
|
+
*/
|
|
102
|
+
export declare const modelUrl: (model: WllamaModel) => string;
|
|
103
|
+
export {};
|
|
104
|
+
//# sourceMappingURL=catalog.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"catalog.d.ts","sourceRoot":"","sources":["../src/catalog.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AACH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,cAAc,CAAA;AAClD,OAAO,KAAK,EAAE,wBAAwB,EAAE,MAAM,gCAAgC,CAAA;AAE9E;;;GAGG;AACH,MAAM,MAAM,QAAQ,GAAG;IACrB,WAAW,EAAE,MAAM,CAAA;IACnB,+DAA+D;IAC/D,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,2DAA2D;IAC3D,cAAc,EAAE,MAAM,CAAA;IACtB,qEAAqE;IACrE,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,4EAA4E;IAC5E,KAAK,CAAC,EAAE,MAAM,CAAA;IACd,2DAA2D;IAC3D,gBAAgB,CAAC,EAAE,MAAM,CAAA;CAC1B,CAAA;AAED,MAAM,MAAM,eAAe,GAAG;IAC5B,uDAAuD;IACvD,IAAI,EAAE,MAAM,CAAA;IACZ,uCAAuC;IACvC,IAAI,EAAE,MAAM,CAAA;IACZ,2FAA2F;IAC3F,MAAM,CAAC,EAAE,MAAM,CAAA;IACf,QAAQ,EAAE;QACR,wEAAwE;QACxE,KAAK,EAAE,aAAa,CAAC,MAAM,GAAG,OAAO,GAAG,OAAO,CAAC,CAAA;QAChD,MAAM,EAAE,aAAa,CAAC,MAAM,CAAC,CAAA;QAC7B,YAAY,EAAE,aAAa,CAAC,OAAO,GAAG,UAAU,CAAC,CAAA;KAClD,CAAA;IACD,uBAAuB;IACvB,IAAI,EAAE,MAAM,CAAA;IACZ,+EAA+E;IAC/E,SAAS,CAAC,EAAE,cAAc,CAAA;IAC1B,6EAA6E;IAC7E,QAAQ,EAAE,QAAQ,CAAA;IAClB,4EAA4E;IAC5E,gBAAgB,CAAC,EAAE,QAAQ,CAAA;CAC5B,CAAA;AAqQD,eAAO,MAAM,kBAAkB,oYAarB,CAAA;AAEV,kCAAkC;AAClC,MAAM,MAAM,eAAe,GAAG,CAAC,OAAO,kBAAkB,CAAC,CAAC,MAAM,CAAC,CAAA;AAEjE,6CAA6C;AAC7C,eAAO,MAAM,iBAAiB,EAAE,QAAQ,CACtC,MAAM,CAAC,eAAe,EAAE,eAAe,CAAC,CAczC,CAAA;AAED,QAAA,MAAM,gBAAgB;;;qBAlSV,GAAG,EAAE,OAAO;qBAAE,IAAI,EAAE,MAAM;;;;;;;;;;;;;qBAGJ,IAAI,EAAE,MAAM;;;;;;;;;;;CAoSpC,CAAA;AAEV,8DAA8D;AAC9D,MAAM,MAAM,0BAA0B,GAAG;KACtC,CAAC,IAAI,MAAM,OAAO,gBAAgB,GAAG,wBAAwB,CAC5D,CAAC,OAAO,gBAAgB,CAAC,CAAC,CAAC,CAAC,CAC7B;CACF,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,sBAAsB,EAAE,QAAQ,CAAC,MAAM,CAAC,MAAM,EAAE,cAAc,CAAC,CAC1D,CAAA;AAElB,iCAAiC;AACjC,eAAO,MAAM,SAAS,UAAW,eAAe,KAAG,eACzB,CAAA;AAE1B,8GAA8G;AAC9G,MAAM,MAAM,WAAW,GAAG,eAAe,GAAG,CAAC,MAAM,GAAG,EAAE,CAAC,CAAA;AAEzD,yCAAyC;AACzC,eAAO,MAAM,MAAM,UAAW,MAAM,KAAG,KAAK,IAAI,eACgB,CAAA;AAEhE,8FAA8F;AAC9F,eAAO,MAAM,SAAS,UAAW,WAAW,uBAI3C,CAAA;AAED;;;GAGG;AACH,eAAO,MAAM,QAAQ,UAAW,WAAW,WAW1C,CAAA"}
|
package/dist/catalog.js
ADDED
|
@@ -0,0 +1,307 @@
|
|
|
1
|
+
// Reasoning shapes, each level's value being the chat template's
|
|
2
|
+
// `enable_thinking`. LFM3's template takes it (thinking by default), and
|
|
3
|
+
// Qwen3.5's (thinking off by default); LFM2.5 Thinking always thinks and
|
|
4
|
+
// takes no switch.
|
|
5
|
+
const THINKING_SWITCH = {
|
|
6
|
+
levels: { off: "false", high: "true" },
|
|
7
|
+
budget: true
|
|
8
|
+
};
|
|
9
|
+
const ALWAYS_THINKS = { levels: { high: "true" }, budget: true };
|
|
10
|
+
const TOOLS_AND_THINKING = {
|
|
11
|
+
input: ["text"],
|
|
12
|
+
output: ["text"],
|
|
13
|
+
capabilities: ["tools", "thinking"]
|
|
14
|
+
};
|
|
15
|
+
const TOOLS = {
|
|
16
|
+
input: ["text"],
|
|
17
|
+
output: ["text"],
|
|
18
|
+
capabilities: ["tools"]
|
|
19
|
+
};
|
|
20
|
+
const IMAGES_AND_TOOLS = {
|
|
21
|
+
input: ["text", "image"],
|
|
22
|
+
output: ["text"],
|
|
23
|
+
capabilities: ["tools"]
|
|
24
|
+
};
|
|
25
|
+
const IMAGES_TOOLS_AND_THINKING = {
|
|
26
|
+
input: ["text", "image"],
|
|
27
|
+
output: ["text"],
|
|
28
|
+
capabilities: ["tools", "thinking"]
|
|
29
|
+
};
|
|
30
|
+
const AUDIO_AND_TOOLS = {
|
|
31
|
+
input: ["text", "audio"],
|
|
32
|
+
output: ["text"],
|
|
33
|
+
capabilities: ["tools"]
|
|
34
|
+
};
|
|
35
|
+
const IMAGES_AUDIO_AND_TOOLS = {
|
|
36
|
+
input: ["text", "image", "audio"],
|
|
37
|
+
output: ["text"],
|
|
38
|
+
capabilities: ["tools"]
|
|
39
|
+
};
|
|
40
|
+
const AUDIO = {
|
|
41
|
+
input: ["text", "audio"],
|
|
42
|
+
output: ["text"],
|
|
43
|
+
capabilities: []
|
|
44
|
+
};
|
|
45
|
+
/** LFM3's card: `--temp 0.1 --top-k 50 --repeat-penalty 1.1`. */
|
|
46
|
+
const LFM3_SAMPLING = {
|
|
47
|
+
temperature: 0.1,
|
|
48
|
+
top_k: 50,
|
|
49
|
+
repeat_penalty: 1.1
|
|
50
|
+
};
|
|
51
|
+
/** The cards' llama.cpp settings: `--temp 0.1 --top-k 50 --repeat-penalty 1.05`. */
|
|
52
|
+
const LFM2_5_SAMPLING = {
|
|
53
|
+
temperature: 0.1,
|
|
54
|
+
top_k: 50,
|
|
55
|
+
repeat_penalty: 1.05
|
|
56
|
+
};
|
|
57
|
+
/** LFM2.5-VL 450M's and 1.6B's cards: `--temp 0.1 --min-p 0.15 --repeat-penalty 1.05`. */
|
|
58
|
+
const LFM2_5_VL_SAMPLING = {
|
|
59
|
+
temperature: 0.1,
|
|
60
|
+
min_p: 0.15,
|
|
61
|
+
repeat_penalty: 1.05
|
|
62
|
+
};
|
|
63
|
+
/** LFM2.5-VL 3B's card: `--temp 0.2 --top-k 50 --repeat-penalty 1.0`. */
|
|
64
|
+
const LFM2_5_VL_3B_SAMPLING = {
|
|
65
|
+
temperature: 0.2,
|
|
66
|
+
top_k: 50,
|
|
67
|
+
repeat_penalty: 1
|
|
68
|
+
};
|
|
69
|
+
/** LFM2.5 Thinking's card: `--temp 0.05 --top-k 50 --repeat-penalty 1.05`. */
|
|
70
|
+
const LFM2_5_THINKING_SAMPLING = {
|
|
71
|
+
temperature: 0.05,
|
|
72
|
+
top_k: 50,
|
|
73
|
+
repeat_penalty: 1.05
|
|
74
|
+
};
|
|
75
|
+
/** The audio models' cards: greedy decoding (`--temp 0`) for text. */
|
|
76
|
+
const GREEDY_SAMPLING = {
|
|
77
|
+
temperature: 0,
|
|
78
|
+
repeat_penalty: 1
|
|
79
|
+
};
|
|
80
|
+
/** Qwen3.5's card, text tasks without thinking (its default). */
|
|
81
|
+
const QWEN_3_5_SAMPLING = {
|
|
82
|
+
temperature: 1,
|
|
83
|
+
top_k: 20,
|
|
84
|
+
top_p: 1,
|
|
85
|
+
min_p: 0,
|
|
86
|
+
presence_penalty: 2,
|
|
87
|
+
repeat_penalty: 1
|
|
88
|
+
};
|
|
89
|
+
/** Qwen3.5's card, text tasks with thinking. */
|
|
90
|
+
const QWEN_3_5_THINKING_SAMPLING = { ...QWEN_3_5_SAMPLING, top_p: 0.95, presence_penalty: 1.5 };
|
|
91
|
+
// Liquid's models are Q4_K_M: in wllama's WebAssembly build it reads
|
|
92
|
+
// prompts and writes tokens about three times as fast as Q4_0 (llama.cpp
|
|
93
|
+
// has no WebAssembly SIMD kernel for Q4_0 to match its Q4_K one), and
|
|
94
|
+
// faster than Q5_K_M, Q6_K and Q8_0, at 5% more size than Q4_0. LFM2.5
|
|
95
|
+
// 1.2B on an M3 Ultra, 24 threads: Q4_K_M 231 prompt tokens/s and 112
|
|
96
|
+
// tokens/s, Q4_0 (Liquid's QAD build too) 65 and 45.
|
|
97
|
+
/**
|
|
98
|
+
* Liquid AI's LFM3 1.7B, from its official GGUF repository: thinks by
|
|
99
|
+
* default, or answers straight away with reasoning off. It needs wllama
|
|
100
|
+
* 3.13's llama.cpp, the first published with its architecture.
|
|
101
|
+
*/
|
|
102
|
+
const LFM3_1_7B = {
|
|
103
|
+
name: "LiquidAI/LFM3-1.7B-GGUF",
|
|
104
|
+
file: "LFM3-1.7B-Q4_K_M.gguf",
|
|
105
|
+
supports: TOOLS_AND_THINKING,
|
|
106
|
+
size: "1.7b",
|
|
107
|
+
reasoning: THINKING_SWITCH,
|
|
108
|
+
sampling: LFM3_SAMPLING
|
|
109
|
+
};
|
|
110
|
+
// The vision models load with their projector in Q8_0, the smallest Liquid
|
|
111
|
+
// publishes: it reads an image as the F16 one does in a third to half the size.
|
|
112
|
+
/**
|
|
113
|
+
* LFM3-VL 2B: LFM3 with a vision encoder, from Liquid's GGUF repository.
|
|
114
|
+
* Thinks by default, as LFM3 does. 1.10 GB, and a 203 MB projector.
|
|
115
|
+
*/
|
|
116
|
+
const LFM3_VL_2B = {
|
|
117
|
+
name: "LiquidAI/LFM3-VL-2B-GGUF",
|
|
118
|
+
file: "LFM3-VL-2B-Q4_K_M.gguf",
|
|
119
|
+
mmproj: "mmproj-LFM3-VL-2B-Q8_0.gguf",
|
|
120
|
+
supports: IMAGES_TOOLS_AND_THINKING,
|
|
121
|
+
size: "2b",
|
|
122
|
+
reasoning: THINKING_SWITCH,
|
|
123
|
+
sampling: LFM3_SAMPLING
|
|
124
|
+
};
|
|
125
|
+
/** LFM2.5-VL 3B, from Liquid's GGUF repository: 1.67 GB, and a 583 MB projector. */
|
|
126
|
+
const LFM2_5_VL_3B = {
|
|
127
|
+
name: "LiquidAI/LFM2.5-VL-3B-GGUF",
|
|
128
|
+
file: "LFM2.5-VL-3B-Q4_K_M.gguf",
|
|
129
|
+
mmproj: "mmproj-LFM2.5-VL-3B-Q8_0.gguf",
|
|
130
|
+
supports: IMAGES_AND_TOOLS,
|
|
131
|
+
size: "3b",
|
|
132
|
+
sampling: LFM2_5_VL_3B_SAMPLING
|
|
133
|
+
};
|
|
134
|
+
/** LFM2.5-VL 1.6B, from Liquid's GGUF repository: 731 MB, and a 583 MB projector. */
|
|
135
|
+
const LFM2_5_VL_1_6B = {
|
|
136
|
+
name: "LiquidAI/LFM2.5-VL-1.6B-GGUF",
|
|
137
|
+
file: "LFM2.5-VL-1.6B-Q4_K_M.gguf",
|
|
138
|
+
mmproj: "mmproj-LFM2.5-VL-1.6b-Q8_0.gguf",
|
|
139
|
+
supports: IMAGES_AND_TOOLS,
|
|
140
|
+
size: "1.6b",
|
|
141
|
+
sampling: LFM2_5_VL_SAMPLING
|
|
142
|
+
};
|
|
143
|
+
/** LFM2.5-VL 450M, from Liquid's GGUF repository: 229 MB, and a 103 MB projector. */
|
|
144
|
+
const LFM2_5_VL_450M = {
|
|
145
|
+
name: "LiquidAI/LFM2.5-VL-450M-GGUF",
|
|
146
|
+
file: "LFM2.5-VL-450M-Q4_K_M.gguf",
|
|
147
|
+
mmproj: "mmproj-LFM2.5-VL-450m-Q8_0.gguf",
|
|
148
|
+
supports: IMAGES_AND_TOOLS,
|
|
149
|
+
size: "450m",
|
|
150
|
+
sampling: LFM2_5_VL_SAMPLING
|
|
151
|
+
};
|
|
152
|
+
// The audio models answer in text here: their speech output needs
|
|
153
|
+
// llama.cpp's speech APIs, which this adapter doesn't call. Their projector
|
|
154
|
+
// is the audio encoder (and, for LFM3-Omni, the vision encoder too), in
|
|
155
|
+
// Q8_0, as their cards pair it with the Q4_K_M model.
|
|
156
|
+
/**
|
|
157
|
+
* LFM3-Omni 2B: reads images and audio (speech, or other sound) in one
|
|
158
|
+
* model, from Liquid's GGUF repository. It doesn't reason: every answer
|
|
159
|
+
* opens with an empty thinking block. 1.10 GB, and a 507 MB projector.
|
|
160
|
+
*/
|
|
161
|
+
const LFM3_OMNI_2B = {
|
|
162
|
+
name: "LiquidAI/LFM3-Omni-2B-GGUF",
|
|
163
|
+
file: "LFM3-Omni-2B-Q4_K_M.gguf",
|
|
164
|
+
mmproj: "mmproj-LFM3-Omni-2B-Q8_0.gguf",
|
|
165
|
+
supports: IMAGES_AUDIO_AND_TOOLS,
|
|
166
|
+
size: "2b",
|
|
167
|
+
sampling: GREEDY_SAMPLING
|
|
168
|
+
};
|
|
169
|
+
/**
|
|
170
|
+
* LFM3-Audio 2B: reads audio (transcribes and translates speech in 18
|
|
171
|
+
* languages, answers questions about a recording, calls tools from a
|
|
172
|
+
* spoken request), from Liquid's GGUF repository. 1.10 GB, and a 304 MB
|
|
173
|
+
* projector.
|
|
174
|
+
*/
|
|
175
|
+
const LFM3_AUDIO_2B = {
|
|
176
|
+
name: "LiquidAI/LFM3-Audio-2B-GGUF",
|
|
177
|
+
file: "LFM3-Audio-2B-Q4_K_M.gguf",
|
|
178
|
+
mmproj: "mmproj-LFM3-Audio-2B-Q8_0.gguf",
|
|
179
|
+
supports: AUDIO_AND_TOOLS,
|
|
180
|
+
size: "2b",
|
|
181
|
+
sampling: GREEDY_SAMPLING
|
|
182
|
+
};
|
|
183
|
+
/**
|
|
184
|
+
* LFM2.5-Audio 1.5B: reads audio, in English, from Liquid's GGUF
|
|
185
|
+
* repository, which has no Q4_K_M: Q4_0, 696 MB, and a 220 MB projector.
|
|
186
|
+
* It calls no tools: told of tools, it answers that none of them can hear
|
|
187
|
+
* a recording, so it's best sent none.
|
|
188
|
+
*/
|
|
189
|
+
const LFM2_5_AUDIO_1_5B = {
|
|
190
|
+
name: "LiquidAI/LFM2.5-Audio-1.5B-GGUF",
|
|
191
|
+
file: "LFM2.5-Audio-1.5B-Q4_0.gguf",
|
|
192
|
+
mmproj: "mmproj-LFM2.5-Audio-1.5B-Q4_0.gguf",
|
|
193
|
+
supports: AUDIO,
|
|
194
|
+
size: "1.5b",
|
|
195
|
+
sampling: GREEDY_SAMPLING
|
|
196
|
+
};
|
|
197
|
+
/** Liquid AI's LFM2.5 1.2B Instruct, from its official GGUF repository. */
|
|
198
|
+
const LFM2_5_1_2B = {
|
|
199
|
+
name: "LiquidAI/LFM2.5-1.2B-Instruct-GGUF",
|
|
200
|
+
file: "LFM2.5-1.2B-Instruct-Q4_K_M.gguf",
|
|
201
|
+
supports: TOOLS,
|
|
202
|
+
size: "1.2b",
|
|
203
|
+
sampling: LFM2_5_SAMPLING
|
|
204
|
+
};
|
|
205
|
+
/** LFM2.5 1.2B Thinking: thinks before every answer. */
|
|
206
|
+
const LFM2_5_1_2B_THINKING = {
|
|
207
|
+
name: "LiquidAI/LFM2.5-1.2B-Thinking-GGUF",
|
|
208
|
+
file: "LFM2.5-1.2B-Thinking-Q4_K_M.gguf",
|
|
209
|
+
supports: TOOLS_AND_THINKING,
|
|
210
|
+
size: "1.2b",
|
|
211
|
+
reasoning: ALWAYS_THINKS,
|
|
212
|
+
sampling: LFM2_5_THINKING_SAMPLING
|
|
213
|
+
};
|
|
214
|
+
/**
|
|
215
|
+
* LFM2.5 350M: the smallest and quickest, for short answers, extraction and
|
|
216
|
+
* tool calls rather than knowledge or code.
|
|
217
|
+
*/
|
|
218
|
+
const LFM2_5_350M = {
|
|
219
|
+
name: "LiquidAI/LFM2.5-350M-GGUF",
|
|
220
|
+
file: "LFM2.5-350M-Q4_K_M.gguf",
|
|
221
|
+
supports: TOOLS,
|
|
222
|
+
size: "350m",
|
|
223
|
+
sampling: LFM2_5_SAMPLING
|
|
224
|
+
};
|
|
225
|
+
/**
|
|
226
|
+
* Qwen3.5 2B, in Unsloth's GGUF build (Qwen publishes none): linear-attention
|
|
227
|
+
* layers with a full-attention layer every fourth, so the attention cache
|
|
228
|
+
* stays small. Thinking is off unless turned on. Q4_K_M, as Liquid's: 1.28
|
|
229
|
+
* GB.
|
|
230
|
+
*/
|
|
231
|
+
const QWEN_3_5_2B = {
|
|
232
|
+
name: "unsloth/Qwen3.5-2B-GGUF",
|
|
233
|
+
file: "Qwen3.5-2B-Q4_K_M.gguf",
|
|
234
|
+
supports: TOOLS_AND_THINKING,
|
|
235
|
+
size: "2b",
|
|
236
|
+
reasoning: THINKING_SWITCH,
|
|
237
|
+
sampling: QWEN_3_5_SAMPLING,
|
|
238
|
+
thinkingSampling: QWEN_3_5_THINKING_SAMPLING
|
|
239
|
+
};
|
|
240
|
+
export const WLLAMA_CHAT_MODELS = [
|
|
241
|
+
LFM3_1_7B.name,
|
|
242
|
+
LFM3_VL_2B.name,
|
|
243
|
+
LFM2_5_VL_3B.name,
|
|
244
|
+
LFM2_5_VL_1_6B.name,
|
|
245
|
+
LFM2_5_VL_450M.name,
|
|
246
|
+
LFM3_OMNI_2B.name,
|
|
247
|
+
LFM3_AUDIO_2B.name,
|
|
248
|
+
LFM2_5_AUDIO_1_5B.name,
|
|
249
|
+
LFM2_5_1_2B.name,
|
|
250
|
+
LFM2_5_1_2B_THINKING.name,
|
|
251
|
+
LFM2_5_350M.name,
|
|
252
|
+
QWEN_3_5_2B.name
|
|
253
|
+
];
|
|
254
|
+
/** Each listed model's metadata, by name. */
|
|
255
|
+
export const WLLAMA_MODEL_META = {
|
|
256
|
+
[LFM3_1_7B.name]: LFM3_1_7B,
|
|
257
|
+
[LFM3_VL_2B.name]: LFM3_VL_2B,
|
|
258
|
+
[LFM2_5_VL_3B.name]: LFM2_5_VL_3B,
|
|
259
|
+
[LFM2_5_VL_1_6B.name]: LFM2_5_VL_1_6B,
|
|
260
|
+
[LFM2_5_VL_450M.name]: LFM2_5_VL_450M,
|
|
261
|
+
[LFM3_OMNI_2B.name]: LFM3_OMNI_2B,
|
|
262
|
+
[LFM3_AUDIO_2B.name]: LFM3_AUDIO_2B,
|
|
263
|
+
[LFM2_5_AUDIO_1_5B.name]: LFM2_5_AUDIO_1_5B,
|
|
264
|
+
[LFM2_5_1_2B.name]: LFM2_5_1_2B,
|
|
265
|
+
[LFM2_5_1_2B_THINKING.name]: LFM2_5_1_2B_THINKING,
|
|
266
|
+
[LFM2_5_350M.name]: LFM2_5_350M,
|
|
267
|
+
[QWEN_3_5_2B.name]: QWEN_3_5_2B
|
|
268
|
+
};
|
|
269
|
+
const WLLAMA_REASONING = {
|
|
270
|
+
[LFM3_1_7B.name]: LFM3_1_7B.reasoning,
|
|
271
|
+
[LFM3_VL_2B.name]: LFM3_VL_2B.reasoning,
|
|
272
|
+
[LFM2_5_1_2B_THINKING.name]: LFM2_5_1_2B_THINKING.reasoning,
|
|
273
|
+
[QWEN_3_5_2B.name]: QWEN_3_5_2B.reasoning
|
|
274
|
+
};
|
|
275
|
+
/**
|
|
276
|
+
* Reasoning data of each listed model that reasons, for
|
|
277
|
+
* `supportedReasoningLevels()`. A model missing here doesn't reason.
|
|
278
|
+
*/
|
|
279
|
+
export const WLLAMA_MODEL_REASONING = WLLAMA_REASONING;
|
|
280
|
+
/** A listed model's metadata. */
|
|
281
|
+
export const modelMeta = (model) => WLLAMA_MODEL_META[model];
|
|
282
|
+
/** Whether the catalog lists `model`. */
|
|
283
|
+
export const listed = (model) => Object.prototype.hasOwnProperty.call(WLLAMA_MODEL_META, model);
|
|
284
|
+
/** Where a listed model's projector downloads from; none for a model that reads text only. */
|
|
285
|
+
export const mmprojUrl = (model) => {
|
|
286
|
+
if (!listed(model))
|
|
287
|
+
return undefined;
|
|
288
|
+
const { name, mmproj } = modelMeta(model);
|
|
289
|
+
return mmproj && `https://huggingface.co/${name}/resolve/main/${mmproj}`;
|
|
290
|
+
};
|
|
291
|
+
/**
|
|
292
|
+
* Where a model's GGUF file downloads from: a listed model's file, or the
|
|
293
|
+
* named one, at its latest version (main) on Hugging Face.
|
|
294
|
+
*/
|
|
295
|
+
export const modelUrl = (model) => {
|
|
296
|
+
if (listed(model)) {
|
|
297
|
+
const { name, file } = modelMeta(model);
|
|
298
|
+
return `https://huggingface.co/${name}/resolve/main/${file}`;
|
|
299
|
+
}
|
|
300
|
+
if (/^https?:\/\//.test(model))
|
|
301
|
+
return model;
|
|
302
|
+
const [owner, repo, ...path] = model.split("/");
|
|
303
|
+
if (!owner || !repo || path.length === 0 || !model.endsWith(".gguf")) {
|
|
304
|
+
throw new Error(`${model} isn't a model this package lists: name its GGUF file, as "owner/repo/file.gguf" or a URL.`);
|
|
305
|
+
}
|
|
306
|
+
return `https://huggingface.co/${owner}/${repo}/resolve/main/${path.join("/")}`;
|
|
307
|
+
};
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,64 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* The page's side of wllama: llama.cpp compiled to WebAssembly, which runs
|
|
3
|
+
* in wllama's own Web Worker. One model at a time: loading another frees
|
|
4
|
+
* the previous one. wllama downloads models to the Origin Private File
|
|
5
|
+
* System once, and loads them from there afterwards.
|
|
6
|
+
*
|
|
7
|
+
* wllama is the official `@wllama/wllama` package, unmodified, used through
|
|
8
|
+
* its public API. Its package has no `exports` and its `main` names a file
|
|
9
|
+
* it doesn't ship, so its built ES module is imported by path.
|
|
10
|
+
*/
|
|
11
|
+
import { Wllama } from "@wllama/wllama/esm/index.js";
|
|
12
|
+
import type { WllamaCompat } from "@wllama/wllama/esm/index.js";
|
|
13
|
+
import type { WllamaModel } from "./catalog.js";
|
|
14
|
+
/**
|
|
15
|
+
* Where a model stands in this browser, with the Prompt API's values
|
|
16
|
+
* (`LanguageModel.availability()`).
|
|
17
|
+
*/
|
|
18
|
+
export type Availability = "unavailable" | "downloadable" | "downloading" | "available";
|
|
19
|
+
/** A download's progress, from 0 to 1. */
|
|
20
|
+
export type DownloadProgressCallback = (progress: number) => void;
|
|
21
|
+
/** What a model loads with. A load with other settings loads the model again. */
|
|
22
|
+
export type LoadSettings = {
|
|
23
|
+
/** Most tokens a chat may hold; undefined for the model's training context. */
|
|
24
|
+
contextLength?: number;
|
|
25
|
+
/** Where wllama's WebAssembly file is served (`WLLAMA_WASM_URL` by default). */
|
|
26
|
+
wasmUrl?: string;
|
|
27
|
+
/** wllama's compat build, for browsers without JSPI (Safari), passed to `Wllama.setCompat`. */
|
|
28
|
+
compat?: WllamaCompat;
|
|
29
|
+
/** CPU threads; wllama's default otherwise. */
|
|
30
|
+
threads?: number;
|
|
31
|
+
/** `false` to run on the CPU only. */
|
|
32
|
+
gpu?: boolean;
|
|
33
|
+
/** wllama's and llama.cpp's logs, in the console. */
|
|
34
|
+
debug?: boolean;
|
|
35
|
+
};
|
|
36
|
+
/** The version of `@wllama/wllama` this package depends on. */
|
|
37
|
+
export declare const WLLAMA_VERSION = "3.13.0";
|
|
38
|
+
/**
|
|
39
|
+
* wllama's WebAssembly file on jsDelivr, for `WLLAMA_VERSION`: the URL of
|
|
40
|
+
* wllama's own `WasmFromCDN`. It has to match wllama's JavaScript, so it's
|
|
41
|
+
* the same version.
|
|
42
|
+
*/
|
|
43
|
+
export declare const WLLAMA_WASM_URL = "https://cdn.jsdelivr.net/npm/@wllama/wllama@3.13.0/esm/wasm/wllama.wasm";
|
|
44
|
+
/** Frees the loaded model, stopping its download. Given a model, only if it's the one loaded. */
|
|
45
|
+
export declare function unloadModel(model?: WllamaModel): Promise<void>;
|
|
46
|
+
/**
|
|
47
|
+
* The model, loaded with `settings` (downloading it the first time), in
|
|
48
|
+
* place of any other. `onDownloadProgress` hears the download, 0 to 1,
|
|
49
|
+
* then 1 once the model is ready. Aborting `signal` stops this caller's
|
|
50
|
+
* wait; `unloadModel` stops the load.
|
|
51
|
+
*/
|
|
52
|
+
export declare function loadModel(model: WllamaModel, settings: LoadSettings, { onDownloadProgress, signal }?: {
|
|
53
|
+
onDownloadProgress?: DownloadProgressCallback;
|
|
54
|
+
signal?: AbortSignal;
|
|
55
|
+
}): Promise<Wllama>;
|
|
56
|
+
/**
|
|
57
|
+
* Where `model` stands in this browser, with the Prompt API's values:
|
|
58
|
+
* "unavailable" where wllama can't run (no WebAssembly, Web Worker or
|
|
59
|
+
* Origin Private File System), "downloading" while this page downloads it,
|
|
60
|
+
* "available" when it's loaded or its file is in wllama's storage, else
|
|
61
|
+
* "downloadable". It reads storage only.
|
|
62
|
+
*/
|
|
63
|
+
export declare function availability(model: WllamaModel): Promise<Availability>;
|
|
64
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../src/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;;GASG;AACH,OAAO,EAAoC,MAAM,EAAE,MAAM,6BAA6B,CAAA;AACtF,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,6BAA6B,CAAA;AAG/D,OAAO,KAAK,EAAE,WAAW,EAAE,MAAM,cAAc,CAAA;AAE/C;;;GAGG;AACH,MAAM,MAAM,YAAY,GAAG,aAAa,GAAG,cAAc,GAAG,aAAa,GAAG,WAAW,CAAA;AAEvF,0CAA0C;AAC1C,MAAM,MAAM,wBAAwB,GAAG,CAAC,QAAQ,EAAE,MAAM,KAAK,IAAI,CAAA;AAEjE,iFAAiF;AACjF,MAAM,MAAM,YAAY,GAAG;IACzB,+EAA+E;IAC/E,aAAa,CAAC,EAAE,MAAM,CAAA;IACtB,gFAAgF;IAChF,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,+FAA+F;IAC/F,MAAM,CAAC,EAAE,YAAY,CAAA;IACrB,+CAA+C;IAC/C,OAAO,CAAC,EAAE,MAAM,CAAA;IAChB,sCAAsC;IACtC,GAAG,CAAC,EAAE,OAAO,CAAA;IACb,qDAAqD;IACrD,KAAK,CAAC,EAAE,OAAO,CAAA;CAChB,CAAA;AAED,+DAA+D;AAC/D,eAAO,MAAM,cAAc,WAAW,CAAA;AAEtC;;;;GAIG;AACH,eAAO,MAAM,eAAe,4EAAuF,CAAA;AAiCnH,iGAAiG;AACjG,wBAAsB,WAAW,CAAC,KAAK,CAAC,EAAE,WAAW,iBAQpD;AAED;;;;;GAKG;AACH,wBAAsB,SAAS,CAC7B,KAAK,EAAE,WAAW,EAClB,QAAQ,EAAE,YAAY,EACtB,EAAE,kBAAkB,EAAE,MAAM,EAAE,GAAE;IAAE,kBAAkB,CAAC,EAAE,wBAAwB,CAAC;IAAC,MAAM,CAAC,EAAE,WAAW,CAAA;CAAO,GAC3G,OAAO,CAAC,MAAM,CAAC,CA+DjB;AAKD;;;;;;GAMG;AACH,wBAAsB,YAAY,CAAC,KAAK,EAAE,WAAW,GAAG,OAAO,CAAC,YAAY,CAAC,CAgB5E"}
|