@mlx-node/vlm 0.0.6 → 0.0.7

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  # @mlx-node/vlm
2
2
 
3
- Vision-language models and document processing pipelines for Node.js on Apple Silicon. Extract text, tables, and structure from documents and images using PaddleOCR-VL and the PP-StructureV3 pipeline — all running locally on Metal GPU.
3
+ Vision-language models and document processing pipelines for Node.js on Apple Silicon. Extract text, tables, and structure from documents and images using PaddleOCR-VL, Qianfan-OCR (InternVL), and the PP-StructureV3 pipeline — all running locally on Metal GPU.
4
4
 
5
5
  ## Requirements
6
6
 
@@ -13,6 +13,39 @@ Vision-language models and document processing pipelines for Node.js on Apple Si
13
13
  npm install @mlx-node/vlm
14
14
  ```
15
15
 
16
+ ## Multi-turn chat with VLMs
17
+
18
+ `QianfanOCRModel` conforms to the same `ChatSession<M>` surface as the language models in `@mlx-node/lm`, so a single session handle drives both single-shot and multi-turn VLM conversations:
19
+
20
+ ```typescript
21
+ import { ChatSession } from '@mlx-node/lm';
22
+ import { QianfanOCRModel } from '@mlx-node/vlm';
23
+ import { readFileSync } from 'node:fs';
24
+
25
+ const model = await QianfanOCRModel.load('./models/Qianfan-VL');
26
+ const session = new ChatSession(model, { system: 'You read documents precisely.' });
27
+
28
+ // First turn with an image.
29
+ const r1 = await session.send('Extract the text from this receipt.', {
30
+ images: [readFileSync('./receipt.jpg')],
31
+ });
32
+ console.log(r1.text);
33
+
34
+ // Text-only follow-up reuses the same KV cache against the same image.
35
+ const r2 = await session.send('What is the total price?');
36
+ console.log(r2.text);
37
+
38
+ // Swapping the image mid-session forcibly restarts the cache with the new image.
39
+ const r3 = await session.send('And this one?', {
40
+ images: [readFileSync('./other-receipt.jpg')],
41
+ });
42
+ console.log(r3.text);
43
+ ```
44
+
45
+ `loadSession()` from `@mlx-node/lm` cannot load Qianfan-OCR (that would introduce a circular package dependency), so construct the wrapper here with `QianfanOCRModel.load()` and pass it to `ChatSession` directly.
46
+
47
+ The one-shot `VLModel.chat()` API shown below for PaddleOCR-VL is a single-turn OCR entry point and is intentionally kept out of the session surface.
48
+
16
49
  ## Quick Start
17
50
 
18
51
  ### Document Structure Analysis
package/dist/index.d.ts CHANGED
@@ -33,6 +33,9 @@ export { DocOrientationModel, type OrientationResult, type ClassifyRotateResult,
33
33
  export { StructureV3Pipeline, type StructureV3Config, type AnalyzeOptions, type StructuredElement, type StructuredDocument, type TextLine, } from './pipeline/structure-v3.js';
34
34
  export type { VisionConfig, TextConfig, ModelConfig, VlmChatConfig, VlmChatMessage, VlmBatchItem, } from '@mlx-node/core';
35
35
  export { PADDLEOCR_VL_CONFIGS, type PaddleOCRVLConfig } from './models/paddleocr-vl-configs.js';
36
+ export { QianfanOCRModel } from './models/qianfan-ocr.js';
37
+ export { createQianfanOcrConfig } from '@mlx-node/core';
38
+ export type { QianfanOcrConfig, InternVisionConfig, Qwen3LmConfig } from '@mlx-node/core';
36
39
  export { VlmChatResult, type VLMChatResult } from '@mlx-node/core';
37
40
  export { parsePaddleResponse, parseVlmOutput, formatDocument, type ParsedDocument, type DocumentElement, type Table, type TableRow, type TableCell, type Paragraph, type ParserConfig, OutputFormat, } from '@mlx-node/core';
38
41
  export { documentToXlsx, saveToXlsx } from '@mlx-node/core';
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAKH,OAAO,EAAE,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAGlE,OAAO,EAAE,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGpE,OAAO,EAAE,YAAY,EAAE,KAAK,OAAO,EAAE,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG1F,OAAO,EACL,mBAAmB,EACnB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,cAAc,EACd,KAAK,YAAY,GAClB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,mBAAmB,EACnB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,QAAQ,GACd,MAAM,4BAA4B,CAAC;AAGpC,YAAY,EACV,YAAY,EACZ,UAAU,EACV,WAAW,EACX,aAAa,EACb,cAAc,EACd,YAAY,GACb,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,oBAAoB,EAAE,KAAK,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAGhG,OAAO,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGnE,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,KAAK,EACV,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,YAAY,GACb,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAG5D,OAAO,EAAE,cAAc,IAAI,SAAS,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,gBAAgB,CAAC"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;;;;;;;;;;;GA2BG;AAKH,OAAO,EAAE,OAAO,EAAE,uBAAuB,EAAE,MAAM,gBAAgB,CAAC;AAGlE,OAAO,EAAE,cAAc,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGpE,OAAO,EAAE,YAAY,EAAE,KAAK,OAAO,EAAE,YAAY,EAAE,KAAK,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAG1F,OAAO,EACL,mBAAmB,EACnB,KAAK,iBAAiB,EACtB,KAAK,oBAAoB,EACzB,cAAc,EACd,KAAK,YAAY,GAClB,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EACL,mBAAmB,EACnB,KAAK,iBAAiB,EACtB,KAAK,cAAc,EACnB,KAAK,iBAAiB,EACtB,KAAK,kBAAkB,EACvB,KAAK,QAAQ,GACd,MAAM,4BAA4B,CAAC;AAGpC,YAAY,EACV,YAAY,EACZ,UAAU,EACV,WAAW,EACX,aAAa,EACb,cAAc,EACd,YAAY,GACb,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,oBAAoB,EAAE,KAAK,iBAAiB,EAAE,MAAM,kCAAkC,CAAC;AAGhG,OAAO,EAAE,eAAe,EAAE,MAAM,yBAAyB,CAAC;AAC1D,OAAO,EAAE,sBAAsB,EAAE,MAAM,gBAAgB,CAAC;AACxD,YAAY,EAAE,gBAAgB,EAAE,kBAAkB,EAAE,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAG1F,OAAO,EAAE,aAAa,EAAE,KAAK,aAAa,EAAE,MAAM,gBAAgB,CAAC;AAGnE,OAAO,EACL,mBAAmB,EACnB,cAAc,EACd,cAAc,EACd,KAAK,cAAc,EACnB,KAAK,eAAe,EACpB,KAAK,KAAK,EACV,KAAK,QAAQ,EACb,KAAK,SAAS,EACd,KAAK,SAAS,EACd,KAAK,YAAY,EACjB,YAAY,GACb,MAAM,gBAAgB,CAAC;AAGxB,OAAO,EAAE,cAAc,EAAE,UAAU,EAAE,MAAM,gBAAgB,CAAC;AAG5D,OAAO,EAAE,cAAc,IAAI,SAAS,EAAE,MAAM,cAAc,CAAC;AAC3D,OAAO,EAAE,OAAO,EAAE,KAAK,KAAK,EAAE,MAAM,gBAAgB,CAAC"}
package/dist/index.js CHANGED
@@ -39,6 +39,9 @@ export { DocOrientationModel, DocUnwarpModel, } from '@mlx-node/core';
39
39
  export { StructureV3Pipeline, } from './pipeline/structure-v3.js';
40
40
  // Model-specific configs
41
41
  export { PADDLEOCR_VL_CONFIGS } from './models/paddleocr-vl-configs.js';
42
+ // Qianfan-OCR model (InternVL architecture)
43
+ export { QianfanOCRModel } from './models/qianfan-ocr.js';
44
+ export { createQianfanOcrConfig } from '@mlx-node/core';
42
45
  // Chat result type
43
46
  export { VlmChatResult } from '@mlx-node/core';
44
47
  // Output parsing and formatting (Rust implementation)
@@ -0,0 +1,27 @@
1
+ import { QianfanOCRModel as QianfanOCRModelNative } from '@mlx-node/core';
2
+ import type { ChatConfig, ChatMessage } from '@mlx-node/core';
3
+ import type { ChatStreamEvent } from '@mlx-node/lm';
4
+ /**
5
+ * Qianfan-OCR Vision-Language Model wrapper.
6
+ *
7
+ * Streaming is driven through the `ChatSession` API — the overrides
8
+ * below adapt the callback-based native methods to
9
+ * `AsyncGenerator<ChatStreamEvent>` so the wrapper structurally
10
+ * satisfies `SessionCapableModel` from `@mlx-node/lm`.
11
+ *
12
+ * Qianfan-OCR is a VLM (InternViT + Qwen3 language model). The
13
+ * continue path cannot splice new vision features into a live KV
14
+ * cache — image changes always require a fresh session start, which
15
+ * the high-level `ChatSession` wrapper handles via its
16
+ * `lastImagesKey` check.
17
+ */
18
+ export declare class QianfanOCRModel extends QianfanOCRModelNative {
19
+ static load(modelPath: string): Promise<QianfanOCRModel>;
20
+ /** Streaming variant of {@link QianfanOCRModel#chatSessionStart}. */
21
+ chatStreamSessionStart(messages: ChatMessage[], config?: ChatConfig | null, signal?: AbortSignal): AsyncGenerator<ChatStreamEvent>;
22
+ /** Streaming variant of {@link QianfanOCRModel#chatSessionContinue}. */
23
+ chatStreamSessionContinue(userMessage: string, images: Uint8Array[] | null, config?: ChatConfig | null, signal?: AbortSignal): AsyncGenerator<ChatStreamEvent>;
24
+ /** Streaming variant of {@link QianfanOCRModel#chatSessionContinueTool}. */
25
+ chatStreamSessionContinueTool(toolCallId: string, content: string, config?: ChatConfig | null, signal?: AbortSignal): AsyncGenerator<ChatStreamEvent>;
26
+ }
27
+ //# sourceMappingURL=qianfan-ocr.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"qianfan-ocr.d.ts","sourceRoot":"","sources":["../../src/models/qianfan-ocr.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,eAAe,IAAI,qBAAqB,EAAE,MAAM,gBAAgB,CAAC;AAC1E,OAAO,KAAK,EAAE,UAAU,EAAE,WAAW,EAAE,MAAM,gBAAgB,CAAC;AAE9D,OAAO,KAAK,EAAE,eAAe,EAAuB,MAAM,cAAc,CAAC;AAezE;;;;;;;;;;;;;GAaG;AACH,qBAAa,eAAgB,SAAQ,qBAAqB;WAClC,IAAI,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,eAAe,CAAC;IAMvE,qEAAqE;IAE9D,sBAAsB,CAC3B,QAAQ,EAAE,WAAW,EAAE,EACvB,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI,EAC1B,MAAM,CAAC,EAAE,WAAW,GACnB,cAAc,CAAC,eAAe,CAAC;IAOlC,wEAAwE;IAEjE,yBAAyB,CAC9B,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,UAAU,EAAE,GAAG,IAAI,EAC3B,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI,EAC1B,MAAM,CAAC,EAAE,WAAW,GACnB,cAAc,CAAC,eAAe,CAAC;IAQlC,4EAA4E;IAErE,6BAA6B,CAClC,UAAU,EAAE,MAAM,EAClB,OAAO,EAAE,MAAM,EACf,MAAM,CAAC,EAAE,UAAU,GAAG,IAAI,EAC1B,MAAM,CAAC,EAAE,WAAW,GACnB,cAAc,CAAC,eAAe,CAAC;CAOnC"}
@@ -0,0 +1,63 @@
1
+ import { QianfanOCRModel as QianfanOCRModelNative } from '@mlx-node/core';
2
+ import { _runChatStream } from '@mlx-node/lm';
3
+ // Save references to the native callback-based session streaming methods
4
+ // before we override them. Captured at module load time so the subclass
5
+ // overrides can delegate without recursing into themselves. Each wrapper
6
+ // method below bridges the callback API to `AsyncGenerator<ChatStreamEvent>`
7
+ // so the wrapper structurally satisfies `SessionCapableModel` and can be
8
+ // passed to `ChatSession<QianfanOCRModel>` from `@mlx-node/lm`.
9
+ // oxlint-disable-next-line @typescript-eslint/unbound-method
10
+ const _nativeQianfanOcrChatStreamSessionStart = QianfanOCRModelNative.prototype.chatStreamSessionStart;
11
+ // oxlint-disable-next-line @typescript-eslint/unbound-method
12
+ const _nativeQianfanOcrChatStreamSessionContinue = QianfanOCRModelNative.prototype.chatStreamSessionContinue;
13
+ // oxlint-disable-next-line @typescript-eslint/unbound-method
14
+ const _nativeQianfanOcrChatStreamSessionContinueTool = QianfanOCRModelNative.prototype.chatStreamSessionContinueTool;
15
+ /**
16
+ * Qianfan-OCR Vision-Language Model wrapper.
17
+ *
18
+ * Streaming is driven through the `ChatSession` API — the overrides
19
+ * below adapt the callback-based native methods to
20
+ * `AsyncGenerator<ChatStreamEvent>` so the wrapper structurally
21
+ * satisfies `SessionCapableModel` from `@mlx-node/lm`.
22
+ *
23
+ * Qianfan-OCR is a VLM (InternViT + Qwen3 language model). The
24
+ * continue path cannot splice new vision features into a live KV
25
+ * cache — image changes always require a fresh session start, which
26
+ * the high-level `ChatSession` wrapper handles via its
27
+ * `lastImagesKey` check.
28
+ */
29
+ export class QianfanOCRModel extends QianfanOCRModelNative {
30
+ static async load(modelPath) {
31
+ const instance = await QianfanOCRModelNative.load(modelPath);
32
+ Object.setPrototypeOf(instance, QianfanOCRModel.prototype);
33
+ return instance;
34
+ }
35
+ /** Streaming variant of {@link QianfanOCRModel#chatSessionStart}. */
36
+ // @ts-expect-error — override callback-based native method with AsyncGenerator
37
+ async *chatStreamSessionStart(messages, config, signal) {
38
+ yield* _runChatStream((callback) => _nativeQianfanOcrChatStreamSessionStart.call(this, messages, config ?? null, callback), signal);
39
+ }
40
+ /** Streaming variant of {@link QianfanOCRModel#chatSessionContinue}. */
41
+ // @ts-expect-error — override callback-based native method with AsyncGenerator
42
+ async *chatStreamSessionContinue(userMessage, images, config, signal) {
43
+ yield* _runChatStream((callback) => _nativeQianfanOcrChatStreamSessionContinue.call(this, userMessage, images, config ?? null, callback), signal);
44
+ }
45
+ /** Streaming variant of {@link QianfanOCRModel#chatSessionContinueTool}. */
46
+ // @ts-expect-error — override callback-based native method with AsyncGenerator
47
+ async *chatStreamSessionContinueTool(toolCallId, content, config, signal) {
48
+ yield* _runChatStream((callback) => _nativeQianfanOcrChatStreamSessionContinueTool.call(this, toolCallId, content, config ?? null, callback), signal);
49
+ }
50
+ }
51
+ // -------------------------------------------------------------------
52
+ // Compile-time conformance check
53
+ // -------------------------------------------------------------------
54
+ //
55
+ // Ensures the wrapper structurally satisfies `SessionCapableModel` so
56
+ // `ChatSession<QianfanOCRModel>` will type-check in downstream code.
57
+ // The assignment is compile-only — the `null as unknown as T`
58
+ // placeholder never runs.
59
+ function _assertSessionCapable() {
60
+ const _qianfan = null;
61
+ void _qianfan;
62
+ }
63
+ void _assertSessionCapable;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@mlx-node/vlm",
3
- "version": "0.0.6",
3
+ "version": "0.0.7",
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,11 +28,11 @@
28
28
  "test": "vite test run"
29
29
  },
30
30
  "dependencies": {
31
- "@mlx-node/core": "0.0.6",
32
- "@mlx-node/lm": "0.0.6",
31
+ "@mlx-node/core": "0.0.7",
32
+ "@mlx-node/lm": "0.0.7",
33
33
  "@napi-rs/image": "^1.12.0"
34
34
  },
35
35
  "devDependencies": {
36
- "@types/node": "@types/node@25.5.0"
36
+ "@types/node": "@types/node@24.12.2"
37
37
  }
38
38
  }