@mlx-node/lm 0.0.6 → 0.0.8
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 +71 -46
- package/dist/chat-session.d.ts +700 -0
- package/dist/chat-session.d.ts.map +1 -0
- package/dist/chat-session.js +1467 -0
- package/dist/index.d.ts +16 -11
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +47 -6
- package/dist/interfaces.d.ts +2 -7
- package/dist/interfaces.d.ts.map +1 -1
- package/dist/models/lfm2-configs.d.ts +6 -0
- package/dist/models/lfm2-configs.d.ts.map +1 -0
- package/dist/models/lfm2-configs.js +107 -0
- package/dist/models/model-loader.d.ts +194 -4
- package/dist/models/model-loader.d.ts.map +1 -1
- package/dist/models/model-loader.js +253 -21
- package/dist/models/paged-config-override.d.ts +60 -0
- package/dist/models/paged-config-override.d.ts.map +1 -0
- package/dist/models/paged-config-override.js +254 -0
- package/dist/models/qwen3_5-configs.d.ts.map +1 -1
- package/dist/models/qwen3_5-configs.js +5 -0
- package/dist/stream.d.ts +234 -29
- package/dist/stream.d.ts.map +1 -1
- package/dist/stream.js +294 -52
- package/dist/tools/index.d.ts +38 -11
- package/dist/tools/index.d.ts.map +1 -1
- package/dist/tools/index.js +38 -11
- package/dist/tools/types.d.ts +5 -25
- package/dist/tools/types.d.ts.map +1 -1
- package/dist/tools/types.js +5 -30
- package/package.json +3 -3
package/README.md
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
# @mlx-node/lm
|
|
2
2
|
|
|
3
|
-
High-level language model inference for Node.js on Apple Silicon. Supports Qwen3
|
|
3
|
+
High-level language model inference for Node.js on Apple Silicon. Supports Qwen3, Qwen3.5 (Dense and MoE), LFM2, and Gemma4 with streaming, multi-turn chat sessions, tool calling, and profiling — all running locally on Metal GPU.
|
|
4
4
|
|
|
5
5
|
## Requirements
|
|
6
6
|
|
|
@@ -15,26 +15,31 @@ npm install @mlx-node/lm
|
|
|
15
15
|
|
|
16
16
|
## Quick Start
|
|
17
17
|
|
|
18
|
-
|
|
19
|
-
import { loadModel } from '@mlx-node/lm';
|
|
18
|
+
Multi-turn chat runs through `ChatSession`, which owns the server-side KV cache and hides the session bookkeeping behind `send()` / `sendStream()`. The `loadSession()` convenience wrapper loads the model and constructs the session in one step:
|
|
20
19
|
|
|
21
|
-
|
|
20
|
+
```typescript
|
|
21
|
+
import { loadSession } from '@mlx-node/lm';
|
|
22
22
|
|
|
23
|
-
const
|
|
23
|
+
const session = await loadSession('./models/Qwen3-0.6B');
|
|
24
24
|
|
|
25
|
+
const result = await session.send('What is the capital of France?');
|
|
25
26
|
console.log(result.text);
|
|
27
|
+
|
|
28
|
+
// Follow-ups reuse the live KV cache — no prompt replay.
|
|
29
|
+
const followUp = await session.send('And its population?');
|
|
30
|
+
console.log(followUp.text);
|
|
26
31
|
```
|
|
27
32
|
|
|
28
33
|
## Streaming
|
|
29
34
|
|
|
30
|
-
|
|
35
|
+
Every generative model wrapper supports token-by-token streaming via `session.sendStream()`, which yields an `AsyncGenerator<ChatStreamEvent>`:
|
|
31
36
|
|
|
32
37
|
```typescript
|
|
33
|
-
import {
|
|
38
|
+
import { loadSession } from '@mlx-node/lm';
|
|
34
39
|
|
|
35
|
-
const
|
|
40
|
+
const session = await loadSession('./models/Qwen3.5-0.8B');
|
|
36
41
|
|
|
37
|
-
for await (const event of
|
|
42
|
+
for await (const event of session.sendStream('Write a haiku about TypeScript.')) {
|
|
38
43
|
if (!event.done) {
|
|
39
44
|
process.stdout.write(event.text);
|
|
40
45
|
} else {
|
|
@@ -43,16 +48,16 @@ for await (const event of model.chatStream(messages, config)) {
|
|
|
43
48
|
}
|
|
44
49
|
```
|
|
45
50
|
|
|
46
|
-
Breaking out of the loop automatically cancels generation.
|
|
51
|
+
Breaking out of the loop automatically cancels generation. The session tracks its turn state so the next `send()` / `sendStream()` continues the same conversation against the live cache.
|
|
47
52
|
|
|
48
53
|
## Tool Calling
|
|
49
54
|
|
|
50
|
-
OpenAI-compatible function calling with `createToolDefinition
|
|
55
|
+
OpenAI-compatible function calling with `createToolDefinition`. Tool-result turns feed back through the same session via `sendToolResult()`, which dispatches a native `chatSessionContinueTool` against the live KV cache:
|
|
51
56
|
|
|
52
57
|
```typescript
|
|
53
|
-
import {
|
|
58
|
+
import { loadSession, createToolDefinition } from '@mlx-node/lm';
|
|
54
59
|
|
|
55
|
-
const
|
|
60
|
+
const session = await loadSession('./models/Qwen3-0.6B');
|
|
56
61
|
|
|
57
62
|
const tools = [
|
|
58
63
|
createToolDefinition(
|
|
@@ -65,37 +70,52 @@ const tools = [
|
|
|
65
70
|
),
|
|
66
71
|
];
|
|
67
72
|
|
|
68
|
-
const result =
|
|
69
|
-
|
|
70
|
-
//
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
73
|
+
const result = await session.send('What is the weather in Tokyo?', { config: { tools } });
|
|
74
|
+
|
|
75
|
+
// The chat-session API only supports exactly one tool call per assistant turn:
|
|
76
|
+
// each `sendToolResult` dispatch immediately re-opens the assistant turn, so
|
|
77
|
+
// feeding a second result for the same turn would interleave a new assistant
|
|
78
|
+
// reply between the two results. `ChatSession` enforces this at runtime — a
|
|
79
|
+
// subsequent `sendToolResult*` after a multi-call turn throws with a clear
|
|
80
|
+
// error — and the caller must refuse multi-call turns up front. Tighten the
|
|
81
|
+
// prompt or tool spec so the model emits at most one call per turn.
|
|
82
|
+
const okCalls = result.toolCalls?.filter((tc) => tc.status === 'ok') ?? [];
|
|
83
|
+
if (okCalls.length > 1) {
|
|
84
|
+
throw new Error(
|
|
85
|
+
`ChatSession only supports one tool call per assistant turn; ` +
|
|
86
|
+
`model emitted ${okCalls.length}. Tighten the prompt or tool spec.`,
|
|
80
87
|
);
|
|
81
88
|
}
|
|
89
|
+
const call = okCalls[0];
|
|
90
|
+
if (call) {
|
|
91
|
+
const toolOutput = JSON.stringify(await executeMyTool(call));
|
|
92
|
+
const followUp = await session.sendToolResult(call.id, toolOutput, { config: { tools } });
|
|
93
|
+
console.log(followUp.text);
|
|
94
|
+
}
|
|
82
95
|
```
|
|
83
96
|
|
|
84
97
|
## Model Loading
|
|
85
98
|
|
|
86
|
-
`loadModel()` auto-detects the model architecture from `config.json
|
|
99
|
+
`loadModel()` auto-detects the model architecture from `config.json`. Use `loadSession()` when you want an ergonomic `ChatSession` handle in one step, or load a concrete model class and construct `new ChatSession(model)` when you need a reference to both the model and the session (e.g. for `generate()` calls, training, or model metadata):
|
|
87
100
|
|
|
88
101
|
```typescript
|
|
89
|
-
import {
|
|
102
|
+
import { loadSession, ChatSession, Qwen35Model, Qwen35MoeModel } from '@mlx-node/lm';
|
|
90
103
|
|
|
91
|
-
//
|
|
92
|
-
const
|
|
104
|
+
// Convenience: auto-detect architecture and wrap in a ChatSession.
|
|
105
|
+
const session = await loadSession('./models/Qwen3-0.6B', { system: 'Be concise.' });
|
|
93
106
|
|
|
94
|
-
// Or load a specific architecture directly
|
|
107
|
+
// Or load a specific architecture directly — every generative model wrapper
|
|
108
|
+
// structurally satisfies ChatSession's SessionCapableModel bound.
|
|
95
109
|
const dense = await Qwen35Model.load('./models/Qwen3.5-0.8B');
|
|
96
110
|
const moe = await Qwen35MoeModel.load('./models/Qwen3.5-35B-A3B');
|
|
111
|
+
const denseSession = new ChatSession(dense);
|
|
112
|
+
const moeSession = new ChatSession(moe);
|
|
97
113
|
```
|
|
98
114
|
|
|
115
|
+
`loadSession()` rejects embedding models (`HarrierModel`) and the native `QianfanOCRModel` — for the VLM case, import `QianfanOCRModel` from `@mlx-node/vlm` and wrap it with `new ChatSession(...)` directly.
|
|
116
|
+
|
|
117
|
+
`ChatSession` accepts an options bag with `{ system?, defaultConfig? }`. The system prompt is injected on the first turn and never re-sent. Per-call config passed to `send()` / `sendStream()` shallow-merges on top of `defaultConfig`. Call `session.reset()` to wipe the KV cache and start a fresh conversation.
|
|
118
|
+
|
|
99
119
|
### Pre-defined Configs
|
|
100
120
|
|
|
101
121
|
```typescript
|
|
@@ -104,8 +124,8 @@ import { QWEN3_CONFIGS, QWEN35_CONFIGS, getQwen3Config, getQwen35Config } from '
|
|
|
104
124
|
// Available Qwen3 configs: 'qwen3-0.6b', 'qwen3-1.7b', 'qwen3-7b'
|
|
105
125
|
const config = getQwen3Config('qwen3-0.6b');
|
|
106
126
|
|
|
107
|
-
// Available Qwen3.5 configs: 'qwen3.5-0.
|
|
108
|
-
const config35 = getQwen35Config('qwen3.5-0.
|
|
127
|
+
// Available Qwen3.5 configs: 'qwen3.5-0.6b'
|
|
128
|
+
const config35 = getQwen35Config('qwen3.5-0.6b');
|
|
109
129
|
```
|
|
110
130
|
|
|
111
131
|
## Profiling
|
|
@@ -128,12 +148,16 @@ Or set `MLX_PROFILE_DECODE=1` to auto-enable and write a report on exit.
|
|
|
128
148
|
|
|
129
149
|
### Classes
|
|
130
150
|
|
|
131
|
-
| Class | Description
|
|
132
|
-
| ---------------- |
|
|
133
|
-
| `loadModel()` | Auto-detect and load any supported model from disk
|
|
134
|
-
| `
|
|
135
|
-
| `
|
|
136
|
-
| `
|
|
151
|
+
| Class | Description |
|
|
152
|
+
| ---------------- | --------------------------------------------------------------------------------- |
|
|
153
|
+
| `loadModel()` | Auto-detect and load any supported model from disk |
|
|
154
|
+
| `loadSession()` | `loadModel()` + `new ChatSession(model)` in one step |
|
|
155
|
+
| `ChatSession<M>` | Multi-turn chat wrapper — `send()`, `sendStream()`, `sendToolResult()`, `reset()` |
|
|
156
|
+
| `Qwen3Model` | Qwen3 inference — `generate()` and paged attention |
|
|
157
|
+
| `Qwen35Model` | Qwen3.5 Dense — compiled forward, VLM, paged attention, native MTP |
|
|
158
|
+
| `Qwen35MoeModel` | Qwen3.5 MoE — compiled forward, expert routing, paged attention, native MTP |
|
|
159
|
+
| `Gemma4Model` | Gemma4 inference — multimodal generation and optional external-draft speculation |
|
|
160
|
+
| `Lfm2Model` | LFM2.5 hybrid conv+attention inference — `generate()` |
|
|
137
161
|
|
|
138
162
|
### Streaming Types
|
|
139
163
|
|
|
@@ -179,8 +203,6 @@ function createToolDefinition(
|
|
|
179
203
|
properties?: Record<string, FunctionParameterProperty>,
|
|
180
204
|
required?: string[],
|
|
181
205
|
): ToolDefinition;
|
|
182
|
-
|
|
183
|
-
function formatToolResponse(content: string): string;
|
|
184
206
|
```
|
|
185
207
|
|
|
186
208
|
### Functions
|
|
@@ -188,18 +210,21 @@ function formatToolResponse(content: string): string;
|
|
|
188
210
|
| Function | Description |
|
|
189
211
|
| ------------------------ | ---------------------------------------------------- |
|
|
190
212
|
| `createToolDefinition()` | Create an OpenAI-compatible tool definition |
|
|
191
|
-
| `formatToolResponse()` | Wrap tool output in `<tool_response>` tags |
|
|
192
213
|
| `detectModelType()` | Read `config.json` and return the `model_type` field |
|
|
193
214
|
| `enableProfiling()` | Start profiling with auto-report on exit |
|
|
194
215
|
| `disableProfiling()` | Stop profiling and write JSON report |
|
|
195
216
|
|
|
196
217
|
## Supported Models
|
|
197
218
|
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
|
201
|
-
|
|
|
202
|
-
| Qwen3
|
|
219
|
+
Every generative model wrapper exposes the same `ChatSession<M>` surface — `send()`, `sendStream()`, and `sendToolResult()` all work against any of the models below.
|
|
220
|
+
|
|
221
|
+
| Model | `generate()` | `ChatSession` | Training | Notes |
|
|
222
|
+
| ------------- | :----------: | :-----------: | :------: | ------------------------------------ |
|
|
223
|
+
| Qwen3 | Yes | Yes | GRPO/SFT | Paged attention |
|
|
224
|
+
| Qwen3.5 Dense | Yes | Yes | GRPO/SFT | Compiled forward, VLM, native MTP |
|
|
225
|
+
| Qwen3.5 MoE | Yes | Yes | GRPO/SFT | Expert routing, paged cache, MTP |
|
|
226
|
+
| Gemma4 | Yes | Yes | No | Multimodal, optional external draft |
|
|
227
|
+
| LFM2.5 | Yes | Yes | No | Hybrid conv + attention architecture |
|
|
203
228
|
|
|
204
229
|
## Performance
|
|
205
230
|
|