@mlx-node/lm 0.0.1 → 0.0.3

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.
@@ -0,0 +1,32 @@
1
+ /**
2
+ * @mlx-node/lm - High-level inference API for MLX models
3
+ *
4
+ * This package provides everything needed for model loading and inference,
5
+ * aligned with Python's mlx-lm library.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { loadModel, Qwen3Model } from '@mlx-node/lm';
10
+ *
11
+ * const model = await loadModel('./models/qwen3-0.6b');
12
+ * const result = await model.generate([{ role: 'user', content: 'Hello!' }]);
13
+ * ```
14
+ */
15
+ export { Qwen3Model, Qwen3Tokenizer } from '@mlx-node/core';
16
+ export { Qwen35Model, Qwen35Model as Qwen3_5Model } from './stream';
17
+ export type { Qwen35Config, Qwen35GenerationConfig, Qwen35GenerationResult } from '@mlx-node/core';
18
+ export { Qwen35MoeModel, Qwen35MoeModel as Qwen3_5MoeModel } from './stream';
19
+ export type { Qwen35MoeConfig, Qwen35MoeGenerationConfig, Qwen35MoeGenerationResult } from '@mlx-node/core';
20
+ export type { DType } from '@mlx-node/core';
21
+ export type { SamplingConfig, BatchGenerationResult } from '@mlx-node/core';
22
+ export type { ChatConfig, ChatResult, ChatMessage, ToolCallResult, PerformanceMetrics } from '@mlx-node/core';
23
+ export type { ChatStreamDelta, ChatStreamFinal, ChatStreamEvent } from './stream';
24
+ export type { ChatStreamChunk, ChatStreamHandle } from '@mlx-node/core';
25
+ export { _createChatStream } from './stream';
26
+ export { type Qwen3Config, QWEN3_CONFIGS, type GenerationResult, type GenerationConfig, getQwen3Config, } from './models/qwen3-configs';
27
+ export { loadModel, detectModelType, type ModelType } from './models/model-loader';
28
+ export type { TrainableModel } from './interfaces';
29
+ export { QWEN35_CONFIGS, getQwen35Config } from './models/qwen3_5-configs';
30
+ export * from './tools';
31
+ export { enableProfiling, disableProfiling } from './profiling';
32
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAGH,OAAO,EAAE,UAAU,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAC5D,OAAO,EAAE,WAAW,EAAE,WAAW,IAAI,YAAY,EAAE,MAAM,UAAU,CAAC;AACpE,YAAY,EAAE,YAAY,EAAE,sBAAsB,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AAGnG,OAAO,EAAE,cAAc,EAAE,cAAc,IAAI,eAAe,EAAE,MAAM,UAAU,CAAC;AAC7E,YAAY,EAAE,eAAe,EAAE,yBAAyB,EAAE,yBAAyB,EAAE,MAAM,gBAAgB,CAAC;AAK5G,YAAY,EAAE,KAAK,EAAE,MAAM,gBAAgB,CAAC;AAC5C,YAAY,EAAE,cAAc,EAAE,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAG5E,YAAY,EAAE,UAAU,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,kBAAkB,EAAE,MAAM,gBAAgB,CAAC;AAG9G,YAAY,EAAE,eAAe,EAAE,eAAe,EAAE,eAAe,EAAE,MAAM,UAAU,CAAC;AAClF,YAAY,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,gBAAgB,CAAC;AAGxE,OAAO,EAAE,iBAAiB,EAAE,MAAM,UAAU,CAAC;AAG7C,OAAO,EACL,KAAK,WAAW,EAChB,aAAa,EACb,KAAK,gBAAgB,EACrB,KAAK,gBAAgB,EACrB,cAAc,GACf,MAAM,wBAAwB,CAAC;AAGhC,OAAO,EAAE,SAAS,EAAE,eAAe,EAAE,KAAK,SAAS,EAAE,MAAM,uBAAuB,CAAC;AAGnF,YAAY,EAAE,cAAc,EAAE,MAAM,cAAc,CAAC;AAEnD,OAAO,EAAE,cAAc,EAAE,eAAe,EAAE,MAAM,0BAA0B,CAAC;AAG3E,cAAc,SAAS,CAAC;AAGxB,OAAO,EAAE,eAAe,EAAE,gBAAgB,EAAE,MAAM,aAAa,CAAC"}
package/dist/index.js ADDED
@@ -0,0 +1,31 @@
1
+ /**
2
+ * @mlx-node/lm - High-level inference API for MLX models
3
+ *
4
+ * This package provides everything needed for model loading and inference,
5
+ * aligned with Python's mlx-lm library.
6
+ *
7
+ * @example
8
+ * ```typescript
9
+ * import { loadModel, Qwen3Model } from '@mlx-node/lm';
10
+ *
11
+ * const model = await loadModel('./models/qwen3-0.6b');
12
+ * const result = await model.generate([{ role: 'user', content: 'Hello!' }]);
13
+ * ```
14
+ */
15
+ // Model classes (for inference)
16
+ export { Qwen3Model, Qwen3Tokenizer } from '@mlx-node/core';
17
+ export { Qwen35Model, Qwen35Model as Qwen3_5Model } from './stream';
18
+ // MoE variant
19
+ export { Qwen35MoeModel, Qwen35MoeModel as Qwen3_5MoeModel } from './stream';
20
+ // Internal: exported for testing the callback-to-AsyncGenerator bridge
21
+ // Not part of the public API — may change without notice
22
+ export { _createChatStream } from './stream';
23
+ // Model utilities (TypeScript-only)
24
+ export { QWEN3_CONFIGS, getQwen3Config, } from './models/qwen3-configs';
25
+ // Model loading
26
+ export { loadModel, detectModelType } from './models/model-loader';
27
+ export { QWEN35_CONFIGS, getQwen35Config } from './models/qwen3_5-configs';
28
+ // Tool calling utilities
29
+ export * from './tools';
30
+ // Profiling API
31
+ export { enableProfiling, disableProfiling } from './profiling';
@@ -0,0 +1,8 @@
1
+ import type { Qwen3Model, Qwen35Model, Qwen35MoeModel } from '@mlx-node/core';
2
+ /**
3
+ * Union of all model classes that can be used with training engines.
4
+ * Uses the native (core) types so trainers can pass instances directly
5
+ * to Rust engine factory methods without type conflicts.
6
+ */
7
+ export type TrainableModel = Qwen3Model | Qwen35Model | Qwen35MoeModel;
8
+ //# sourceMappingURL=interfaces.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"interfaces.d.ts","sourceRoot":"","sources":["../src/interfaces.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,cAAc,EAAE,MAAM,gBAAgB,CAAC;AAE9E;;;;GAIG;AACH,MAAM,MAAM,cAAc,GAAG,UAAU,GAAG,WAAW,GAAG,cAAc,CAAC"}
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,13 @@
1
+ /**
2
+ * Model loading utilities for Qwen3 models
3
+ *
4
+ * Handles loading pretrained weights from MLX format or converting from HuggingFace.
5
+ */
6
+ import type { TrainableModel } from '../interfaces';
7
+ export type ModelType = 'qwen3' | 'qwen3_5' | 'qwen3_5_moe';
8
+ /**
9
+ * Load a language model from disk, auto-detecting architecture from config.json.
10
+ */
11
+ export declare function loadModel(modelPath: string): Promise<TrainableModel>;
12
+ export declare function detectModelType(modelPath: string): Promise<ModelType>;
13
+ //# sourceMappingURL=model-loader.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"model-loader.d.ts","sourceRoot":"","sources":["../../src/models/model-loader.ts"],"names":[],"mappings":"AAAA;;;;GAIG;AAMH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,eAAe,CAAC;AAEpD,MAAM,MAAM,SAAS,GAAG,OAAO,GAAG,SAAS,GAAG,aAAa,CAAC;AAI5D;;GAEG;AACH,wBAAsB,SAAS,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC,CAa1E;AAED,wBAAsB,eAAe,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,SAAS,CAAC,CAa3E"}
@@ -0,0 +1,42 @@
1
+ /**
2
+ * Model loading utilities for Qwen3 models
3
+ *
4
+ * Handles loading pretrained weights from MLX format or converting from HuggingFace.
5
+ */
6
+ import { readFile } from 'node:fs/promises';
7
+ import { join } from 'node:path';
8
+ import { Qwen3Model } from '@mlx-node/core';
9
+ import { Qwen35Model, Qwen35MoeModel } from '../stream';
10
+ const SUPPORTED_MODEL_TYPES = new Set(['qwen3', 'qwen3_5', 'qwen3_5_moe']);
11
+ /**
12
+ * Load a language model from disk, auto-detecting architecture from config.json.
13
+ */
14
+ export async function loadModel(modelPath) {
15
+ const modelType = await detectModelType(modelPath);
16
+ switch (modelType) {
17
+ case 'qwen3_5_moe':
18
+ // Cast: stream wrapper extends native — safe for instanceof and engine factories
19
+ return Qwen35MoeModel.load(modelPath);
20
+ case 'qwen3_5':
21
+ // load() auto-detects vision weights and loads encoder if present
22
+ return Qwen35Model.load(modelPath);
23
+ case 'qwen3':
24
+ return Qwen3Model.load(modelPath);
25
+ }
26
+ }
27
+ export async function detectModelType(modelPath) {
28
+ try {
29
+ const raw = await readFile(join(modelPath, 'config.json'), 'utf-8');
30
+ const config = JSON.parse(raw);
31
+ const modelType = config.model_type ?? 'qwen3';
32
+ if (!SUPPORTED_MODEL_TYPES.has(modelType)) {
33
+ throw new Error(`Unsupported model_type "${modelType}" in ${modelPath}/config.json`);
34
+ }
35
+ return modelType;
36
+ }
37
+ catch (e) {
38
+ if (e instanceof Error && e.message.startsWith('Unsupported model_type'))
39
+ throw e;
40
+ throw new Error(`Cannot detect model type: config.json not found in ${modelPath}`);
41
+ }
42
+ }
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Qwen3 Model Configurations and Type Definitions
3
+ *
4
+ * This module provides:
5
+ * - Default configurations for common Qwen3 model sizes
6
+ * - Type re-exports from Rust with enhanced documentation
7
+ * - Helper functions for config management
8
+ */
9
+ import type { Qwen3Config as RustQwen3Config, GenerationConfig as RustGenerationConfig, GenerationResult as RustGenerationResult } from '@mlx-node/core';
10
+ /**
11
+ * Configuration for Qwen3 models
12
+ *
13
+ * All fields are required when creating a model directly.
14
+ * Use QWEN3_CONFIGS for pre-configured model sizes.
15
+ */
16
+ export type Qwen3Config = RustQwen3Config;
17
+ /**
18
+ * Configuration for text generation
19
+ *
20
+ * Controls sampling behavior, temperature, and stopping criteria.
21
+ */
22
+ export type GenerationConfig = RustGenerationConfig;
23
+ /**
24
+ * Result from text generation with detailed metadata
25
+ *
26
+ * Includes generated tokens, log probabilities, finish reason, and token count.
27
+ */
28
+ export type GenerationResult = RustGenerationResult;
29
+ /**
30
+ * Default configurations for common Qwen3 models
31
+ *
32
+ * Includes optimized hyperparameters for:
33
+ * - qwen3-0.6b: Smallest model (1024 hidden size, 28 layers)
34
+ * - qwen3-1.8b: Medium model (1536 hidden size, 28 layers)
35
+ * - qwen3-7b: Large model (3072 hidden size, 32 layers)
36
+ */
37
+ export declare const QWEN3_CONFIGS: {
38
+ [key: string]: Qwen3Config;
39
+ };
40
+ /**
41
+ * Get a Qwen3 configuration by name
42
+ *
43
+ * @param name - Model name (e.g., "qwen3-0.6b", "qwen3-1.8b", "qwen3-7b")
44
+ * @returns Model configuration
45
+ * @throws Error if model name is not recognized
46
+ */
47
+ export declare function getQwen3Config(name: string): Qwen3Config;
48
+ //# sourceMappingURL=qwen3-configs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qwen3-configs.d.ts","sourceRoot":"","sources":["../../src/models/qwen3-configs.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EACV,WAAW,IAAI,eAAe,EAC9B,gBAAgB,IAAI,oBAAoB,EACxC,gBAAgB,IAAI,oBAAoB,EACzC,MAAM,gBAAgB,CAAC;AAExB;;;;;GAKG;AACH,MAAM,MAAM,WAAW,GAAG,eAAe,CAAC;AAE1C;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,CAAC;AAEpD;;;;GAIG;AACH,MAAM,MAAM,gBAAgB,GAAG,oBAAoB,CAAC;AAEpD;;;;;;;GAOG;AACH,eAAO,MAAM,aAAa,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,WAAW,CAAA;CAoDvD,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,GAAG,WAAW,CAMxD"}
@@ -0,0 +1,83 @@
1
+ /**
2
+ * Qwen3 Model Configurations and Type Definitions
3
+ *
4
+ * This module provides:
5
+ * - Default configurations for common Qwen3 model sizes
6
+ * - Type re-exports from Rust with enhanced documentation
7
+ * - Helper functions for config management
8
+ */
9
+ /**
10
+ * Default configurations for common Qwen3 models
11
+ *
12
+ * Includes optimized hyperparameters for:
13
+ * - qwen3-0.6b: Smallest model (1024 hidden size, 28 layers)
14
+ * - qwen3-1.8b: Medium model (1536 hidden size, 28 layers)
15
+ * - qwen3-7b: Large model (3072 hidden size, 32 layers)
16
+ */
17
+ export const QWEN3_CONFIGS = {
18
+ 'qwen3-0.6b': {
19
+ vocabSize: 151936,
20
+ hiddenSize: 1024,
21
+ numLayers: 28,
22
+ numHeads: 16,
23
+ numKvHeads: 8, // GQA with 2:1 ratio
24
+ headDim: 64, // hiddenSize / numHeads = 1024 / 16 = 64
25
+ intermediateSize: 3072,
26
+ rmsNormEps: 1e-6,
27
+ ropeTheta: 1000000.0,
28
+ maxPositionEmbeddings: 40960,
29
+ useQkNorm: true, // Qwen3 always uses QK normalization (core feature)
30
+ tieWordEmbeddings: true,
31
+ padTokenId: 151643,
32
+ eosTokenId: 151645,
33
+ bosTokenId: 151643,
34
+ },
35
+ 'qwen3-1.8b': {
36
+ vocabSize: 151936,
37
+ hiddenSize: 1536,
38
+ numLayers: 28,
39
+ numHeads: 12,
40
+ numKvHeads: 2, // GQA with 6:1 ratio
41
+ headDim: 128, // hiddenSize / numHeads = 1536 / 12 = 128
42
+ intermediateSize: 8960,
43
+ rmsNormEps: 1e-6,
44
+ ropeTheta: 1000000.0,
45
+ maxPositionEmbeddings: 131072,
46
+ useQkNorm: true, // Qwen3 always uses QK normalization (core feature)
47
+ tieWordEmbeddings: false,
48
+ padTokenId: 151643,
49
+ eosTokenId: 151645,
50
+ bosTokenId: 151643,
51
+ },
52
+ 'qwen3-7b': {
53
+ vocabSize: 151936,
54
+ hiddenSize: 3072,
55
+ numLayers: 32,
56
+ numHeads: 24,
57
+ numKvHeads: 4, // GQA with 6:1 ratio
58
+ headDim: 128, // hiddenSize / numHeads = 3072 / 24 = 128
59
+ intermediateSize: 18944,
60
+ rmsNormEps: 1e-6,
61
+ ropeTheta: 1000000.0,
62
+ maxPositionEmbeddings: 131072,
63
+ useQkNorm: true, // Qwen3 always uses QK normalization (core feature)
64
+ tieWordEmbeddings: false,
65
+ padTokenId: 151643,
66
+ eosTokenId: 151645,
67
+ bosTokenId: 151643,
68
+ },
69
+ };
70
+ /**
71
+ * Get a Qwen3 configuration by name
72
+ *
73
+ * @param name - Model name (e.g., "qwen3-0.6b", "qwen3-1.8b", "qwen3-7b")
74
+ * @returns Model configuration
75
+ * @throws Error if model name is not recognized
76
+ */
77
+ export function getQwen3Config(name) {
78
+ const config = QWEN3_CONFIGS[name];
79
+ if (!config) {
80
+ throw new Error(`Unknown model configuration: ${name}. Available models: ${Object.keys(QWEN3_CONFIGS).join(', ')}`);
81
+ }
82
+ return config;
83
+ }
@@ -0,0 +1,25 @@
1
+ /**
2
+ * Qwen3.5 Model Configurations and Type Definitions
3
+ *
4
+ * Supports both dense and MoE variants. MoE fields are optional -
5
+ * when `numExperts` is undefined, the model uses dense MLP layers.
6
+ */
7
+ import type { Qwen35Config as RustQwen35Config, Qwen35GenerationConfig as RustQwen35GenerationConfig, Qwen35GenerationResult as RustQwen35GenerationResult } from '@mlx-node/core';
8
+ export type Qwen35Config = RustQwen35Config;
9
+ export type Qwen35GenerationConfig = RustQwen35GenerationConfig;
10
+ export type Qwen35GenerationResult = RustQwen35GenerationResult;
11
+ /**
12
+ * Default configurations for common Qwen3.5 models
13
+ */
14
+ export declare const QWEN35_CONFIGS: {
15
+ [key: string]: Qwen35Config;
16
+ };
17
+ /**
18
+ * Get a Qwen3.5 configuration by name
19
+ *
20
+ * @param name - Model name (e.g., "qwen3.5-0.6b")
21
+ * @returns Model configuration
22
+ * @throws Error if model name is not recognized
23
+ */
24
+ export declare function getQwen35Config(name: string): Qwen35Config;
25
+ //# sourceMappingURL=qwen3_5-configs.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qwen3_5-configs.d.ts","sourceRoot":"","sources":["../../src/models/qwen3_5-configs.ts"],"names":[],"mappings":"AAAA;;;;;GAKG;AAEH,OAAO,KAAK,EACV,YAAY,IAAI,gBAAgB,EAChC,sBAAsB,IAAI,0BAA0B,EACpD,sBAAsB,IAAI,0BAA0B,EACrD,MAAM,gBAAgB,CAAC;AAExB,MAAM,MAAM,YAAY,GAAG,gBAAgB,CAAC;AAC5C,MAAM,MAAM,sBAAsB,GAAG,0BAA0B,CAAC;AAChE,MAAM,MAAM,sBAAsB,GAAG,0BAA0B,CAAC;AAEhE;;GAEG;AACH,eAAO,MAAM,cAAc,EAAE;IAAE,CAAC,GAAG,EAAE,MAAM,GAAG,YAAY,CAAA;CAyBzD,CAAC;AAEF;;;;;;GAMG;AACH,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,GAAG,YAAY,CAM1D"}
@@ -0,0 +1,49 @@
1
+ /**
2
+ * Qwen3.5 Model Configurations and Type Definitions
3
+ *
4
+ * Supports both dense and MoE variants. MoE fields are optional -
5
+ * when `numExperts` is undefined, the model uses dense MLP layers.
6
+ */
7
+ /**
8
+ * Default configurations for common Qwen3.5 models
9
+ */
10
+ export const QWEN35_CONFIGS = {
11
+ 'qwen3.5-0.6b': {
12
+ vocabSize: 151936,
13
+ hiddenSize: 1024,
14
+ numLayers: 28,
15
+ numHeads: 16,
16
+ numKvHeads: 8,
17
+ intermediateSize: 3072,
18
+ rmsNormEps: 1e-6,
19
+ headDim: 64,
20
+ tieWordEmbeddings: true,
21
+ attentionBias: false,
22
+ maxPositionEmbeddings: 131072,
23
+ padTokenId: 151643,
24
+ eosTokenId: 151645,
25
+ bosTokenId: 151643,
26
+ linearNumValueHeads: 64,
27
+ linearNumKeyHeads: 16,
28
+ linearKeyHeadDim: 192,
29
+ linearValueHeadDim: 128,
30
+ linearConvKernelDim: 4,
31
+ fullAttentionInterval: 4,
32
+ partialRotaryFactor: 0.25,
33
+ ropeTheta: 100000.0,
34
+ },
35
+ };
36
+ /**
37
+ * Get a Qwen3.5 configuration by name
38
+ *
39
+ * @param name - Model name (e.g., "qwen3.5-0.6b")
40
+ * @returns Model configuration
41
+ * @throws Error if model name is not recognized
42
+ */
43
+ export function getQwen35Config(name) {
44
+ const config = QWEN35_CONFIGS[name];
45
+ if (!config) {
46
+ throw new Error(`Unknown Qwen3.5 config: ${name}. Available: ${Object.keys(QWEN35_CONFIGS).join(', ')}`);
47
+ }
48
+ return config;
49
+ }
@@ -0,0 +1,20 @@
1
+ /**
2
+ * Enable profiling programmatically.
3
+ *
4
+ * When enabled, all subsequent model generate/chat calls will record
5
+ * timing, memory, and throughput data. Call `disableProfiling()` to
6
+ * stop recording and write the report.
7
+ *
8
+ * If `MLX_PROFILE_DECODE` env var is set, this is a no-op (env var
9
+ * takes precedence).
10
+ */
11
+ export declare function enableProfiling(): void;
12
+ /**
13
+ * Disable profiling and write the collected data to a JSON file.
14
+ *
15
+ * Returns the path to the written file, or empty string if no data
16
+ * was collected. If `MLX_PROFILE_DECODE` env var is set, this is a
17
+ * no-op (env var controls the lifecycle).
18
+ */
19
+ export declare function disableProfiling(): Promise<string>;
20
+ //# sourceMappingURL=profiling.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"profiling.d.ts","sourceRoot":"","sources":["../src/profiling.ts"],"names":[],"mappings":"AAaA;;;;;;;;;GASG;AACH,wBAAgB,eAAe,IAAI,IAAI,CAQtC;AAED;;;;;;GAMG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,MAAM,CAAC,CAOxD"}
@@ -0,0 +1,64 @@
1
+ import { setProfilingEnabled, isProfilingEnabled, getProfilingData, resetProfilingData } from '@mlx-node/core';
2
+ import { writeFile } from 'node:fs/promises';
3
+ const ENV_VAR = 'MLX_PROFILE_DECODE';
4
+ const envVarSet = !!process.env[ENV_VAR];
5
+ let exitHandlerRegistered = false;
6
+ // Auto-enable if env var set
7
+ if (envVarSet) {
8
+ setProfilingEnabled(true);
9
+ registerExitHandler();
10
+ }
11
+ /**
12
+ * Enable profiling programmatically.
13
+ *
14
+ * When enabled, all subsequent model generate/chat calls will record
15
+ * timing, memory, and throughput data. Call `disableProfiling()` to
16
+ * stop recording and write the report.
17
+ *
18
+ * If `MLX_PROFILE_DECODE` env var is set, this is a no-op (env var
19
+ * takes precedence).
20
+ */
21
+ export function enableProfiling() {
22
+ if (envVarSet) {
23
+ console.warn(`Warning: env var ${ENV_VAR} is set, ignoring explicit profiling API calls`);
24
+ return;
25
+ }
26
+ setProfilingEnabled(true);
27
+ resetProfilingData();
28
+ registerExitHandler();
29
+ }
30
+ /**
31
+ * Disable profiling and write the collected data to a JSON file.
32
+ *
33
+ * Returns the path to the written file, or empty string if no data
34
+ * was collected. If `MLX_PROFILE_DECODE` env var is set, this is a
35
+ * no-op (env var controls the lifecycle).
36
+ */
37
+ export async function disableProfiling() {
38
+ if (envVarSet) {
39
+ console.warn(`Warning: env var ${ENV_VAR} is set, ignoring explicit profiling API calls`);
40
+ return '';
41
+ }
42
+ setProfilingEnabled(false);
43
+ return writeProfilingReport();
44
+ }
45
+ async function writeProfilingReport() {
46
+ const data = getProfilingData();
47
+ if (data.generations.length === 0)
48
+ return '';
49
+ const path = `mlx-profile-${Date.now()}.json`;
50
+ await writeFile(path, JSON.stringify(data, null, 2));
51
+ console.info(`Profiling report written to ${path}`);
52
+ return path;
53
+ }
54
+ function registerExitHandler() {
55
+ if (exitHandlerRegistered)
56
+ return;
57
+ exitHandlerRegistered = true;
58
+ process.on('beforeExit', async () => {
59
+ if (isProfilingEnabled()) {
60
+ setProfilingEnabled(false);
61
+ await writeProfilingReport();
62
+ }
63
+ });
64
+ }
@@ -0,0 +1,56 @@
1
+ import { Qwen35Model as Qwen35ModelNative, Qwen35MoeModel as Qwen35MoeModelNative } from '@mlx-node/core';
2
+ import type { ChatConfig, ChatMessage, ChatStreamChunk, ChatStreamHandle, PerformanceMetrics, ToolCallResult } from '@mlx-node/core';
3
+ export interface ChatStreamDelta {
4
+ text: string;
5
+ done: false;
6
+ }
7
+ export interface ChatStreamFinal {
8
+ text: string;
9
+ done: true;
10
+ finishReason: string;
11
+ toolCalls: ToolCallResult[];
12
+ thinking: string | null;
13
+ numTokens: number;
14
+ rawText: string;
15
+ performance?: PerformanceMetrics;
16
+ }
17
+ export type ChatStreamEvent = ChatStreamDelta | ChatStreamFinal;
18
+ /**
19
+ * Shared AsyncGenerator implementation that wraps a native callback-based
20
+ * chatStream into a `for await...of`-compatible stream.
21
+ *
22
+ * Cancellation is automatic via the generator's `finally` block.
23
+ */
24
+ /** @internal Exported for testing only. */
25
+ export declare function _createChatStream(nativeMethod: (messages: ChatMessage[], config: any, callback: (err: Error | null, chunk: ChatStreamChunk) => void) => Promise<ChatStreamHandle>, self: unknown, messages: ChatMessage[], config: unknown): AsyncGenerator<ChatStreamEvent>;
26
+ /**
27
+ * Qwen3.5 dense model with AsyncGenerator-based `chatStream()`.
28
+ *
29
+ * @example
30
+ * ```typescript
31
+ * const model = await Qwen35Model.load('./models/qwen3.5-3b');
32
+ * for await (const event of model.chatStream(messages)) {
33
+ * if (!event.done) process.stdout.write(event.text);
34
+ * }
35
+ * ```
36
+ */
37
+ export declare class Qwen35Model extends Qwen35ModelNative {
38
+ static load(modelPath: string): Promise<Qwen35Model>;
39
+ chatStream(messages: ChatMessage[], config?: ChatConfig | null): AsyncGenerator<ChatStreamEvent>;
40
+ }
41
+ /**
42
+ * Qwen3.5 MoE model with AsyncGenerator-based `chatStream()`.
43
+ *
44
+ * @example
45
+ * ```typescript
46
+ * const model = await Qwen35MoeModel.load('./models/qwen3.5-moe');
47
+ * for await (const event of model.chatStream(messages)) {
48
+ * if (!event.done) process.stdout.write(event.text);
49
+ * }
50
+ * ```
51
+ */
52
+ export declare class Qwen35MoeModel extends Qwen35MoeModelNative {
53
+ static load(modelPath: string): Promise<Qwen35MoeModel>;
54
+ chatStream(messages: ChatMessage[], config?: ChatConfig | null): AsyncGenerator<ChatStreamEvent>;
55
+ }
56
+ //# sourceMappingURL=stream.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"stream.d.ts","sourceRoot":"","sources":["../src/stream.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,WAAW,IAAI,iBAAiB,EAAE,cAAc,IAAI,oBAAoB,EAAE,MAAM,gBAAgB,CAAC;AAC1G,OAAO,KAAK,EACV,UAAU,EACV,WAAW,EACX,eAAe,EACf,gBAAgB,EAChB,kBAAkB,EAClB,cAAc,EACf,MAAM,gBAAgB,CAAC;AAExB,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,KAAK,CAAC;CACb;AAED,MAAM,WAAW,eAAe;IAC9B,IAAI,EAAE,MAAM,CAAC;IACb,IAAI,EAAE,IAAI,CAAC;IACX,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,cAAc,EAAE,CAAC;IAC5B,QAAQ,EAAE,MAAM,GAAG,IAAI,CAAC;IACxB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,WAAW,CAAC,EAAE,kBAAkB,CAAC;CAClC;AAED,MAAM,MAAM,eAAe,GAAG,eAAe,GAAG,eAAe,CAAC;AAQhE;;;;;GAKG;AACH,2CAA2C;AAC3C,wBAAuB,iBAAiB,CAEtC,YAAY,EAAE,CACZ,QAAQ,EAAE,WAAW,EAAE,EACvB,MAAM,EAAE,GAAG,EACX,QAAQ,EAAE,CAAC,GAAG,EAAE,KAAK,GAAG,IAAI,EAAE,KAAK,EAAE,eAAe,KAAK,IAAI,KAC1D,OAAO,CAAC,gBAAgB,CAAC,EAC9B,IAAI,EAAE,OAAO,EACb,QAAQ,EAAE,WAAW,EAAE,EACvB,MAAM,EAAE,OAAO,GACd,cAAc,CAAC,eAAe,CAAC,CAoDjC;AAED;;;;;;;;;;GAUG;AACH,qBAAa,WAAY,SAAQ,iBAAiB;WAC1B,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,WAAW,CAAC;IAO5D,UAAU,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI,GAAG,cAAc,CAAC,eAAe,CAAC;CAGxG;AAED;;;;;;;;;;GAUG;AACH,qBAAa,cAAe,SAAQ,oBAAoB;WAChC,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,CAAC;IAO/D,UAAU,CAAC,QAAQ,EAAE,WAAW,EAAE,EAAE,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI,GAAG,cAAc,CAAC,eAAe,CAAC;CAGxG"}
package/dist/stream.js ADDED
@@ -0,0 +1,108 @@
1
+ import { Qwen35Model as Qwen35ModelNative, Qwen35MoeModel as Qwen35MoeModelNative } from '@mlx-node/core';
2
+ // Save references to the native callback-based methods before we override them
3
+ // oxlint-disable-next-line @typescript-eslint/unbound-method
4
+ const _nativeDenseChatStream = Qwen35ModelNative.prototype.chatStream;
5
+ // oxlint-disable-next-line @typescript-eslint/unbound-method
6
+ const _nativeMoeChatStream = Qwen35MoeModelNative.prototype.chatStream;
7
+ /**
8
+ * Shared AsyncGenerator implementation that wraps a native callback-based
9
+ * chatStream into a `for await...of`-compatible stream.
10
+ *
11
+ * Cancellation is automatic via the generator's `finally` block.
12
+ */
13
+ /** @internal Exported for testing only. */
14
+ export async function* _createChatStream(
15
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
16
+ nativeMethod, self, messages, config) {
17
+ const queue = [];
18
+ let resolve = null;
19
+ const waitForItem = () => queue.length > 0
20
+ ? Promise.resolve()
21
+ : new Promise((r) => {
22
+ resolve = r;
23
+ });
24
+ const notify = () => {
25
+ if (resolve) {
26
+ const r = resolve;
27
+ resolve = null;
28
+ r();
29
+ }
30
+ };
31
+ const callback = (err, chunk) => {
32
+ queue.push(err ? { error: err } : { chunk });
33
+ notify();
34
+ };
35
+ const handle = await nativeMethod.call(self, messages, config ?? null, callback);
36
+ try {
37
+ while (true) {
38
+ await waitForItem();
39
+ while (queue.length > 0) {
40
+ const item = queue.shift();
41
+ if (item.error)
42
+ throw item.error;
43
+ const chunk = item.chunk;
44
+ if (chunk.done) {
45
+ yield {
46
+ text: chunk.text,
47
+ done: true,
48
+ finishReason: chunk.finishReason,
49
+ toolCalls: chunk.toolCalls ?? [],
50
+ thinking: chunk.thinking ?? null,
51
+ numTokens: chunk.numTokens,
52
+ rawText: chunk.rawText,
53
+ performance: chunk.performance ?? undefined,
54
+ };
55
+ return;
56
+ }
57
+ yield { text: chunk.text, done: false };
58
+ }
59
+ }
60
+ }
61
+ finally {
62
+ handle.cancel();
63
+ }
64
+ }
65
+ /**
66
+ * Qwen3.5 dense model with AsyncGenerator-based `chatStream()`.
67
+ *
68
+ * @example
69
+ * ```typescript
70
+ * const model = await Qwen35Model.load('./models/qwen3.5-3b');
71
+ * for await (const event of model.chatStream(messages)) {
72
+ * if (!event.done) process.stdout.write(event.text);
73
+ * }
74
+ * ```
75
+ */
76
+ export class Qwen35Model extends Qwen35ModelNative {
77
+ static async load(modelPath) {
78
+ const instance = await Qwen35ModelNative.load(modelPath);
79
+ Object.setPrototypeOf(instance, Qwen35Model.prototype);
80
+ return instance;
81
+ }
82
+ // @ts-expect-error — override callback-based chatStream with AsyncGenerator
83
+ async *chatStream(messages, config) {
84
+ yield* _createChatStream(_nativeDenseChatStream, this, messages, config);
85
+ }
86
+ }
87
+ /**
88
+ * Qwen3.5 MoE model with AsyncGenerator-based `chatStream()`.
89
+ *
90
+ * @example
91
+ * ```typescript
92
+ * const model = await Qwen35MoeModel.load('./models/qwen3.5-moe');
93
+ * for await (const event of model.chatStream(messages)) {
94
+ * if (!event.done) process.stdout.write(event.text);
95
+ * }
96
+ * ```
97
+ */
98
+ export class Qwen35MoeModel extends Qwen35MoeModelNative {
99
+ static async load(modelPath) {
100
+ const instance = await Qwen35MoeModelNative.load(modelPath);
101
+ Object.setPrototypeOf(instance, Qwen35MoeModel.prototype);
102
+ return instance;
103
+ }
104
+ // @ts-expect-error — override callback-based chatStream with AsyncGenerator
105
+ async *chatStream(messages, config) {
106
+ yield* _createChatStream(_nativeMoeChatStream, this, messages, config);
107
+ }
108
+ }
@@ -0,0 +1,31 @@
1
+ /**
2
+ * Tool calling utilities for Qwen3
3
+ *
4
+ * Provides types and helpers for working with tool/function calling in the chat() API.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { createToolDefinition, formatToolResponse } from '@mlx-node/lm';
9
+ *
10
+ * const weatherTool = createToolDefinition(
11
+ * 'get_weather',
12
+ * 'Get weather for a location',
13
+ * { location: { type: 'string', description: 'City name' } },
14
+ * ['location']
15
+ * );
16
+ *
17
+ * const result = await model.chat(messages, { tools: [weatherTool] });
18
+ *
19
+ * for (const call of result.toolCalls) {
20
+ * if (call.status === 'ok') {
21
+ * const toolResult = await executeMyTool(call.name, call.arguments);
22
+ * // Continue conversation with tool result
23
+ * messages.push({ role: 'user', content: formatToolResponse(toolResult) });
24
+ * }
25
+ * }
26
+ * ```
27
+ *
28
+ * @module tools
29
+ */
30
+ export * from './types';
31
+ //# sourceMappingURL=index.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/tools/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA4BG;AAEH,cAAc,SAAS,CAAC"}
@@ -0,0 +1,30 @@
1
+ /**
2
+ * Tool calling utilities for Qwen3
3
+ *
4
+ * Provides types and helpers for working with tool/function calling in the chat() API.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * import { createToolDefinition, formatToolResponse } from '@mlx-node/lm';
9
+ *
10
+ * const weatherTool = createToolDefinition(
11
+ * 'get_weather',
12
+ * 'Get weather for a location',
13
+ * { location: { type: 'string', description: 'City name' } },
14
+ * ['location']
15
+ * );
16
+ *
17
+ * const result = await model.chat(messages, { tools: [weatherTool] });
18
+ *
19
+ * for (const call of result.toolCalls) {
20
+ * if (call.status === 'ok') {
21
+ * const toolResult = await executeMyTool(call.name, call.arguments);
22
+ * // Continue conversation with tool result
23
+ * messages.push({ role: 'user', content: formatToolResponse(toolResult) });
24
+ * }
25
+ * }
26
+ * ```
27
+ *
28
+ * @module tools
29
+ */
30
+ export * from './types';
@@ -0,0 +1,206 @@
1
+ /**
2
+ * OpenAI-compatible tool calling types for Qwen3
3
+ *
4
+ * These types match the OpenAI function calling API format and can be used
5
+ * with applyChatTemplate() when tools are provided.
6
+ *
7
+ * @remarks
8
+ * **Important**: Due to NAPI-RS limitations with recursive generic types,
9
+ * `FunctionParameters.properties` must be passed as a JSON string to the Rust layer.
10
+ * Use the {@link createToolDefinition} helper to automatically handle this conversion.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * // Recommended: Use the helper function
15
+ * const tool = createToolDefinition('get_weather', 'Get weather info', {
16
+ * location: { type: 'string', description: 'City name' },
17
+ * units: { type: 'string', enum: ['celsius', 'fahrenheit'] }
18
+ * }, ['location']);
19
+ *
20
+ * // Manual approach (if needed)
21
+ * const manualTool: ToolDefinition = {
22
+ * type: 'function',
23
+ * function: {
24
+ * name: 'get_weather',
25
+ * parameters: {
26
+ * type: 'object',
27
+ * properties: JSON.stringify({ location: { type: 'string' } }),
28
+ * required: ['location']
29
+ * }
30
+ * }
31
+ * };
32
+ * ```
33
+ */
34
+ /**
35
+ * Tool type - currently only "function" is supported
36
+ */
37
+ export type ToolType = 'function';
38
+ /**
39
+ * Function parameter property definition (JSON Schema subset)
40
+ *
41
+ * This type is used for the developer-friendly API in {@link createToolDefinition}.
42
+ * It represents the structure of JSON Schema properties.
43
+ */
44
+ export interface FunctionParameterProperty {
45
+ type: 'string' | 'number' | 'boolean' | 'integer' | 'array' | 'object';
46
+ description?: string;
47
+ enum?: string[];
48
+ items?: FunctionParameterProperty;
49
+ properties?: Record<string, FunctionParameterProperty>;
50
+ required?: string[];
51
+ }
52
+ /**
53
+ * Function parameters schema (JSON Schema subset)
54
+ *
55
+ * @remarks
56
+ * **NAPI Limitation**: The `properties` field must be a JSON string, not an object.
57
+ * This is because NAPI-RS cannot expose recursive generic types like
58
+ * `Record<string, FunctionParameterProperty>` directly to Rust.
59
+ *
60
+ * Use {@link createToolDefinition} to avoid manual JSON.stringify() calls.
61
+ */
62
+ export interface FunctionParameters {
63
+ /** Type of the parameters object (always "object") */
64
+ type: 'object';
65
+ /**
66
+ * JSON string of property definitions.
67
+ *
68
+ * @remarks
69
+ * Must be a JSON-stringified object, e.g.: `JSON.stringify({ name: { type: 'string' } })`
70
+ * Use {@link createToolDefinition} helper to avoid manual stringification.
71
+ */
72
+ properties?: string;
73
+ /** List of required parameter names */
74
+ required?: string[];
75
+ }
76
+ /**
77
+ * Function definition for tool calling
78
+ */
79
+ export interface FunctionDefinition {
80
+ /** Name of the function */
81
+ name: string;
82
+ /** Description of what the function does */
83
+ description?: string;
84
+ /** JSON Schema for the function parameters */
85
+ parameters?: FunctionParameters;
86
+ }
87
+ /**
88
+ * OpenAI-compatible tool definition
89
+ */
90
+ export interface ToolDefinition {
91
+ /** Tool type (currently only "function" is supported) */
92
+ type: ToolType;
93
+ /** Function definition */
94
+ function: FunctionDefinition;
95
+ }
96
+ /**
97
+ * Tool call made by an assistant
98
+ */
99
+ export interface ToolCall {
100
+ /** Optional unique identifier for the tool call */
101
+ id?: string;
102
+ /** Name of the tool/function to call */
103
+ name: string;
104
+ /** JSON string of arguments to pass to the tool */
105
+ arguments: string;
106
+ }
107
+ /**
108
+ * Chat message roles (matches core ChatMessage.role type)
109
+ */
110
+ export type ChatRole = 'system' | 'user' | 'assistant' | 'tool';
111
+ /**
112
+ * Chat message with tool calling support
113
+ *
114
+ * This extends the basic ChatMessage to support tool calls and responses.
115
+ */
116
+ export interface ChatMessageWithTools {
117
+ /** Message role */
118
+ role: ChatRole;
119
+ /** Message content */
120
+ content: string;
121
+ /** Tool calls made by the assistant (for assistant messages) */
122
+ toolCalls?: ToolCall[];
123
+ /** Tool call ID this message is responding to (for tool messages) */
124
+ toolCallId?: string;
125
+ /** Reasoning content for thinking mode (used with <think> tags) */
126
+ reasoningContent?: string;
127
+ }
128
+ /**
129
+ * Options for applying chat template with tools
130
+ */
131
+ export interface ApplyChatTemplateOptions {
132
+ /** Whether to add generation prompt at end (default: true) */
133
+ addGenerationPrompt?: boolean;
134
+ /** Array of tool definitions for function calling */
135
+ tools?: ToolDefinition[];
136
+ /**
137
+ * Control thinking mode behavior.
138
+ *
139
+ * @remarks
140
+ * **Counter-intuitive semantics** (from Qwen3's Jinja2 template):
141
+ * - `undefined` or `true`: Model thinks naturally (no tags added)
142
+ * - `false`: Adds empty `<think>\n\n</think>\n\n` tags to **disable** thinking
143
+ *
144
+ * The default is `false` for tool use, which disables thinking to avoid
145
+ * verbose reasoning during tool calls.
146
+ *
147
+ * @example
148
+ * ```typescript
149
+ * // Allow model to think (default behavior without tools)
150
+ * { enableThinking: true }
151
+ *
152
+ * // Disable thinking (adds empty <think></think> tags)
153
+ * { enableThinking: false }
154
+ * ```
155
+ */
156
+ enableThinking?: boolean;
157
+ }
158
+ /**
159
+ * Create a tool definition with automatic JSON stringification of properties.
160
+ *
161
+ * This helper handles the NAPI-RS limitation where `properties` must be a JSON string.
162
+ *
163
+ * @param name - The function name
164
+ * @param description - Description of what the function does
165
+ * @param properties - Object defining the function parameters (will be JSON stringified)
166
+ * @param required - Array of required parameter names
167
+ * @returns A properly formatted ToolDefinition ready for use with model.chat()
168
+ *
169
+ * @example
170
+ * ```typescript
171
+ * const weatherTool = createToolDefinition(
172
+ * 'get_weather',
173
+ * 'Get weather information for a location',
174
+ * {
175
+ * location: { type: 'string', description: 'City name' },
176
+ * units: { type: 'string', enum: ['celsius', 'fahrenheit'] }
177
+ * },
178
+ * ['location']
179
+ * );
180
+ *
181
+ * const result = await model.chat(messages, { tools: [weatherTool] });
182
+ * ```
183
+ */
184
+ export declare function createToolDefinition(name: string, description?: string, properties?: Record<string, FunctionParameterProperty>, required?: string[]): ToolDefinition;
185
+ /**
186
+ * Format a tool response for inclusion in a message
187
+ *
188
+ * Creates a properly formatted tool response string that can be used
189
+ * in tool messages when continuing a conversation after a tool call.
190
+ *
191
+ * @param content - The response content (will be JSON stringified if object)
192
+ * @returns Formatted tool response string wrapped in `<tool_response>` tags
193
+ *
194
+ * @example
195
+ * ```typescript
196
+ * // After executing a tool call from model.chat()
197
+ * const toolResult = await executeMyTool(call.arguments);
198
+ * const responseMessage = {
199
+ * role: 'user',
200
+ * content: formatToolResponse(toolResult)
201
+ * };
202
+ * const finalResult = await model.chat([...messages, responseMessage]);
203
+ * ```
204
+ */
205
+ export declare function formatToolResponse(content: unknown): string;
206
+ //# sourceMappingURL=types.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"types.d.ts","sourceRoot":"","sources":["../../src/tools/types.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GAgCG;AAEH;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,UAAU,CAAC;AAElC;;;;;GAKG;AACH,MAAM,WAAW,yBAAyB;IACxC,IAAI,EAAE,QAAQ,GAAG,QAAQ,GAAG,SAAS,GAAG,SAAS,GAAG,OAAO,GAAG,QAAQ,CAAC;IACvE,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,IAAI,CAAC,EAAE,MAAM,EAAE,CAAC;IAChB,KAAK,CAAC,EAAE,yBAAyB,CAAC;IAClC,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,CAAC;IACvD,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;;;;;;;;GASG;AACH,MAAM,WAAW,kBAAkB;IACjC,sDAAsD;IACtD,IAAI,EAAE,QAAQ,CAAC;IACf;;;;;;OAMG;IACH,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,uCAAuC;IACvC,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAC;CACrB;AAED;;GAEG;AACH,MAAM,WAAW,kBAAkB;IACjC,2BAA2B;IAC3B,IAAI,EAAE,MAAM,CAAC;IACb,4CAA4C;IAC5C,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,8CAA8C;IAC9C,UAAU,CAAC,EAAE,kBAAkB,CAAC;CACjC;AAED;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,yDAAyD;IACzD,IAAI,EAAE,QAAQ,CAAC;IACf,0BAA0B;IAC1B,QAAQ,EAAE,kBAAkB,CAAC;CAC9B;AAED;;GAEG;AACH,MAAM,WAAW,QAAQ;IACvB,mDAAmD;IACnD,EAAE,CAAC,EAAE,MAAM,CAAC;IACZ,wCAAwC;IACxC,IAAI,EAAE,MAAM,CAAC;IACb,mDAAmD;IACnD,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;GAEG;AACH,MAAM,MAAM,QAAQ,GAAG,QAAQ,GAAG,MAAM,GAAG,WAAW,GAAG,MAAM,CAAC;AAEhE;;;;GAIG;AACH,MAAM,WAAW,oBAAoB;IACnC,mBAAmB;IACnB,IAAI,EAAE,QAAQ,CAAC;IACf,sBAAsB;IACtB,OAAO,EAAE,MAAM,CAAC;IAChB,gEAAgE;IAChE,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC;IACvB,qEAAqE;IACrE,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mEAAmE;IACnE,gBAAgB,CAAC,EAAE,MAAM,CAAC;CAC3B;AAED;;GAEG;AACH,MAAM,WAAW,wBAAwB;IACvC,8DAA8D;IAC9D,mBAAmB,CAAC,EAAE,OAAO,CAAC;IAC9B,qDAAqD;IACrD,KAAK,CAAC,EAAE,cAAc,EAAE,CAAC;IACzB;;;;;;;;;;;;;;;;;;;OAmBG;IACH,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAED;;;;;;;;;;;;;;;;;;;;;;;;;GAyBG;AACH,wBAAgB,oBAAoB,CAClC,IAAI,EAAE,MAAM,EACZ,WAAW,CAAC,EAAE,MAAM,EACpB,UAAU,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,yBAAyB,CAAC,EACtD,QAAQ,CAAC,EAAE,MAAM,EAAE,GAClB,cAAc,CAehB;AAED;;;;;;;;;;;;;;;;;;;GAmBG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,GAAG,MAAM,CAK3D"}
@@ -0,0 +1,101 @@
1
+ /**
2
+ * OpenAI-compatible tool calling types for Qwen3
3
+ *
4
+ * These types match the OpenAI function calling API format and can be used
5
+ * with applyChatTemplate() when tools are provided.
6
+ *
7
+ * @remarks
8
+ * **Important**: Due to NAPI-RS limitations with recursive generic types,
9
+ * `FunctionParameters.properties` must be passed as a JSON string to the Rust layer.
10
+ * Use the {@link createToolDefinition} helper to automatically handle this conversion.
11
+ *
12
+ * @example
13
+ * ```typescript
14
+ * // Recommended: Use the helper function
15
+ * const tool = createToolDefinition('get_weather', 'Get weather info', {
16
+ * location: { type: 'string', description: 'City name' },
17
+ * units: { type: 'string', enum: ['celsius', 'fahrenheit'] }
18
+ * }, ['location']);
19
+ *
20
+ * // Manual approach (if needed)
21
+ * const manualTool: ToolDefinition = {
22
+ * type: 'function',
23
+ * function: {
24
+ * name: 'get_weather',
25
+ * parameters: {
26
+ * type: 'object',
27
+ * properties: JSON.stringify({ location: { type: 'string' } }),
28
+ * required: ['location']
29
+ * }
30
+ * }
31
+ * };
32
+ * ```
33
+ */
34
+ /**
35
+ * Create a tool definition with automatic JSON stringification of properties.
36
+ *
37
+ * This helper handles the NAPI-RS limitation where `properties` must be a JSON string.
38
+ *
39
+ * @param name - The function name
40
+ * @param description - Description of what the function does
41
+ * @param properties - Object defining the function parameters (will be JSON stringified)
42
+ * @param required - Array of required parameter names
43
+ * @returns A properly formatted ToolDefinition ready for use with model.chat()
44
+ *
45
+ * @example
46
+ * ```typescript
47
+ * const weatherTool = createToolDefinition(
48
+ * 'get_weather',
49
+ * 'Get weather information for a location',
50
+ * {
51
+ * location: { type: 'string', description: 'City name' },
52
+ * units: { type: 'string', enum: ['celsius', 'fahrenheit'] }
53
+ * },
54
+ * ['location']
55
+ * );
56
+ *
57
+ * const result = await model.chat(messages, { tools: [weatherTool] });
58
+ * ```
59
+ */
60
+ export function createToolDefinition(name, description, properties, required) {
61
+ return {
62
+ type: 'function',
63
+ function: {
64
+ name,
65
+ description,
66
+ parameters: properties
67
+ ? {
68
+ type: 'object',
69
+ properties: JSON.stringify(properties),
70
+ required,
71
+ }
72
+ : undefined,
73
+ },
74
+ };
75
+ }
76
+ /**
77
+ * Format a tool response for inclusion in a message
78
+ *
79
+ * Creates a properly formatted tool response string that can be used
80
+ * in tool messages when continuing a conversation after a tool call.
81
+ *
82
+ * @param content - The response content (will be JSON stringified if object)
83
+ * @returns Formatted tool response string wrapped in `<tool_response>` tags
84
+ *
85
+ * @example
86
+ * ```typescript
87
+ * // After executing a tool call from model.chat()
88
+ * const toolResult = await executeMyTool(call.arguments);
89
+ * const responseMessage = {
90
+ * role: 'user',
91
+ * content: formatToolResponse(toolResult)
92
+ * };
93
+ * const finalResult = await model.chat([...messages, responseMessage]);
94
+ * ```
95
+ */
96
+ export function formatToolResponse(content) {
97
+ const contentStr = typeof content === 'string' ? content : JSON.stringify(content);
98
+ // Qwen3/3.5 expects <tool_response> XML wrapping for tool results.
99
+ // Other model families may require a different format.
100
+ return `<tool_response>\n${contentStr}\n</tool_response>`;
101
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mlx-node/lm",
3
- "version": "0.0.1",
3
+ "version": "0.0.3",
4
4
  "homepage": "https://github.com/mlx-node/mlx-node",
5
5
  "bugs": {
6
6
  "url": "https://github.com/mlx-node/mlx-node/issues"
@@ -28,6 +28,6 @@
28
28
  "test": "vite test run"
29
29
  },
30
30
  "dependencies": {
31
- "@mlx-node/core": "0.0.1"
31
+ "@mlx-node/core": "0.0.3"
32
32
  }
33
33
  }