@xpert-ai/plugin-rag-vlm 0.0.1 → 0.1.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.
|
@@ -1,6 +1,9 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import { IconType
|
|
3
|
-
|
|
1
|
+
import { FileSystemPermission, IImageUnderstandingStrategy, LLMPermission, TImageUnderstandingConfig, TImageUnderstandingResult } from '@xpert-ai/plugin-sdk';
|
|
2
|
+
import { IconType } from '@xpert-ai/contracts';
|
|
3
|
+
type VlmConfig = TImageUnderstandingConfig & {
|
|
4
|
+
promptTemplate?: string;
|
|
5
|
+
};
|
|
6
|
+
export declare class VlmDefaultStrategy implements IImageUnderstandingStrategy<VlmConfig> {
|
|
4
7
|
readonly permissions: (FileSystemPermission | LLMPermission)[];
|
|
5
8
|
readonly meta: {
|
|
6
9
|
name: string;
|
|
@@ -23,7 +26,8 @@ export declare class VlmDefaultStrategy implements IImageUnderstandingStrategy {
|
|
|
23
26
|
};
|
|
24
27
|
};
|
|
25
28
|
validateConfig(config: TImageUnderstandingConfig): Promise<void>;
|
|
26
|
-
understandImages(doc:
|
|
29
|
+
understandImages(doc: Parameters<IImageUnderstandingStrategy['understandImages']>[0], config: VlmConfig): Promise<TImageUnderstandingResult>;
|
|
27
30
|
private runV;
|
|
28
31
|
}
|
|
32
|
+
export {};
|
|
29
33
|
//# sourceMappingURL=vlm.strategy.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"vlm.strategy.d.ts","sourceRoot":"","sources":["../../src/lib/vlm.strategy.ts"],"names":[],"mappings":"AAEA,OAAO,
|
|
1
|
+
{"version":3,"file":"vlm.strategy.d.ts","sourceRoot":"","sources":["../../src/lib/vlm.strategy.ts"],"names":[],"mappings":"AAEA,OAAO,EAEL,oBAAoB,EACpB,2BAA2B,EAE3B,aAAa,EACb,yBAAyB,EAEzB,yBAAyB,EAC1B,MAAM,sBAAsB,CAAA;AAC7B,OAAO,EAAqC,QAAQ,EAAE,MAAM,qBAAqB,CAAA;AASjF,KAAK,SAAS,GAAG,yBAAyB,GAAG;IAAE,cAAc,CAAC,EAAE,MAAM,CAAA;CAAE,CAAA;AAExE,qBAEa,kBAAmB,YAAW,2BAA2B,CAAC,SAAS,CAAC;IAC/E,QAAQ,CAAC,WAAW,2CAUnB;IAED,QAAQ,CAAC,IAAI;;;;;;;;;;;;;;;kBAYM,QAAQ;;;;MAI1B;IAEK,cAAc,CAAC,MAAM,EAAE,yBAAyB,GAAG,OAAO,CAAC,IAAI,CAAC;IAMhE,gBAAgB,CACpB,GAAG,EAAE,UAAU,CAAC,2BAA2B,CAAC,kBAAkB,CAAC,CAAC,CAAC,CAAC,CAAC,EACnE,MAAM,EAAE,SAAS,GAChB,OAAO,CAAC,yBAAyB,CAAC;YA0DvB,IAAI;CAqDnB"}
|
package/dist/lib/vlm.strategy.js
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { __decorate } from "tslib";
|
|
2
2
|
import { Injectable } from '@nestjs/common';
|
|
3
3
|
import { ImageUnderstandingStrategy } from '@xpert-ai/plugin-sdk';
|
|
4
|
-
import { buildChunkTree, collectTreeLeaves } from '@
|
|
4
|
+
import { buildChunkTree, collectTreeLeaves } from '@xpert-ai/contracts';
|
|
5
5
|
import { Document } from '@langchain/core/documents';
|
|
6
6
|
import sharp from 'sharp';
|
|
7
7
|
import { v4 as uuid } from 'uuid';
|
|
@@ -18,7 +18,7 @@ let VlmDefaultStrategy = class VlmDefaultStrategy {
|
|
|
18
18
|
},
|
|
19
19
|
{
|
|
20
20
|
type: 'llm',
|
|
21
|
-
capability: 'vision'
|
|
21
|
+
capability: 'vision'
|
|
22
22
|
}
|
|
23
23
|
];
|
|
24
24
|
this.meta = {
|
|
@@ -47,71 +47,101 @@ let VlmDefaultStrategy = class VlmDefaultStrategy {
|
|
|
47
47
|
async understandImages(doc, config) {
|
|
48
48
|
await this.validateConfig(config);
|
|
49
49
|
const client = config.visionModel; // ✅ Injected by the core system
|
|
50
|
-
const
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
};
|
|
54
|
-
const tree = buildChunkTree(doc.chunks);
|
|
55
|
-
const leaves = collectTreeLeaves(tree);
|
|
50
|
+
const files = doc.metadata?.assets?.filter((asset) => asset.type === 'image') ?? [];
|
|
51
|
+
const leaves = collectTreeLeaves(buildChunkTree(doc.chunks));
|
|
52
|
+
// Keep context parents as well as retrieval leaves, and avoid repeated OCR for overlapping chunks.
|
|
56
53
|
const chunks = [];
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
54
|
+
const leafIds = new Set(leaves.map((chunk) => chunk.metadata.chunkId));
|
|
55
|
+
chunks.push(...doc.chunks.filter((chunk) => !leafIds.has(chunk.metadata.chunkId)));
|
|
56
|
+
const processed = new Set();
|
|
57
|
+
const warnings = [];
|
|
58
|
+
for (const chunk of leaves) {
|
|
62
59
|
chunks.push(chunk);
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
const
|
|
70
|
-
|
|
60
|
+
for (const match of chunk.pageContent.matchAll(IMAGE_REGEX)) {
|
|
61
|
+
const asset = files.find((item) => item.url === match[1]);
|
|
62
|
+
if (!asset || processed.has(asset.filePath))
|
|
63
|
+
continue;
|
|
64
|
+
processed.add(asset.filePath);
|
|
65
|
+
try {
|
|
66
|
+
const result = await this.runV(client, asset, config, chunk.pageContent);
|
|
67
|
+
if (result.type === 'skipped') {
|
|
68
|
+
warnings.push({ type: 'image_understanding_skipped', message: result.reason, imagePath: asset.filePath });
|
|
69
|
+
continue;
|
|
70
|
+
}
|
|
71
|
+
const description = result.text;
|
|
72
|
+
if (!description.trim())
|
|
73
|
+
throw new Error('The vision model returned no text.');
|
|
71
74
|
chunks.push(new Document({
|
|
72
75
|
pageContent: description,
|
|
73
76
|
metadata: {
|
|
74
77
|
mediaType: 'image',
|
|
75
|
-
chunkId:
|
|
76
|
-
|
|
78
|
+
chunkId: uuid(),
|
|
79
|
+
// Page transcriptions are independent source text and will be split by the host.
|
|
80
|
+
...(asset.sourceType === 'pdf_page'
|
|
81
|
+
? { page: asset.page, sourceType: 'pdf_page', contentFormat: 'markdown' }
|
|
82
|
+
: { parentId: chunk.metadata.chunkId }),
|
|
77
83
|
imagePath: asset.filePath,
|
|
78
|
-
|
|
84
|
+
imageUrl: asset.url,
|
|
79
85
|
parser: 'vlm'
|
|
80
86
|
}
|
|
81
87
|
}));
|
|
82
88
|
}
|
|
89
|
+
catch (error) {
|
|
90
|
+
warnings.push({
|
|
91
|
+
type: 'image_understanding_failed',
|
|
92
|
+
message: error instanceof Error ? error.message : 'Image recognition failed.',
|
|
93
|
+
imagePath: asset.filePath
|
|
94
|
+
});
|
|
95
|
+
}
|
|
83
96
|
}
|
|
84
97
|
}
|
|
85
|
-
return {
|
|
86
|
-
chunks,
|
|
87
|
-
metadata: {}
|
|
88
|
-
};
|
|
98
|
+
return { chunks, metadata: { warnings } };
|
|
89
99
|
}
|
|
90
|
-
async runV(client,
|
|
91
|
-
const imageStr = await config.permissions.fileSystem.readFile(
|
|
92
|
-
const
|
|
93
|
-
const
|
|
94
|
-
const
|
|
95
|
-
|
|
100
|
+
async runV(client, asset, config, context) {
|
|
101
|
+
const imageStr = await config.permissions.fileSystem.readFile(asset.filePath);
|
|
102
|
+
const page = asset.sourceType === 'pdf_page';
|
|
103
|
+
const image = sharp(imageStr);
|
|
104
|
+
const { width, height } = await image.metadata();
|
|
105
|
+
// Embedded tracking/spacer pixels contain no readable content. Never silently skip a PDF page.
|
|
106
|
+
if (width === 1 || height === 1) {
|
|
107
|
+
if (page)
|
|
108
|
+
throw new Error(`PDF page image is too small to recognize (${width}×${height}).`);
|
|
109
|
+
return { type: 'skipped', reason: `Skipped a ${width}×${height} placeholder image.` };
|
|
110
|
+
}
|
|
111
|
+
const imageData = await image
|
|
112
|
+
.resize({ width: page ? 2200 : 1024, height: page ? 2200 : 1024, fit: 'inside', withoutEnlargement: true })
|
|
113
|
+
.png()
|
|
114
|
+
.toBuffer();
|
|
96
115
|
const response = await client.invoke([
|
|
97
116
|
{
|
|
98
117
|
role: 'system',
|
|
99
|
-
content:
|
|
118
|
+
content: page
|
|
119
|
+
? 'Transcribe all visible content of this document page into Markdown in reading order. Preserve headings, lists, table rows and merged-cell relationships. Copy numbers, leading zeroes, codes and punctuation exactly. Do not summarize, describe the page, invent text, or add a preface. Mark unreadable text as [unreadable].'
|
|
120
|
+
: 'You are a professional assistant, helping people understand images in context. Please provide a narrative description of the image.'
|
|
100
121
|
},
|
|
101
122
|
{
|
|
102
123
|
role: 'user',
|
|
103
124
|
content: [
|
|
104
|
-
|
|
125
|
+
...(config.promptTemplate?.trim()
|
|
126
|
+
? [{ type: 'text', text: config.promptTemplate.replaceAll('{{context}}', () => context) }]
|
|
127
|
+
: []),
|
|
105
128
|
{
|
|
106
129
|
type: 'image_url',
|
|
107
130
|
image_url: {
|
|
108
|
-
url: `data
|
|
131
|
+
url: `data:image/png;base64,${imageData.toString('base64')}`
|
|
109
132
|
}
|
|
110
133
|
}
|
|
111
134
|
]
|
|
112
135
|
}
|
|
113
136
|
]);
|
|
114
|
-
return
|
|
137
|
+
return {
|
|
138
|
+
type: 'recognized',
|
|
139
|
+
text: typeof response.content === 'string'
|
|
140
|
+
? response.content
|
|
141
|
+
: response.content
|
|
142
|
+
.flatMap((part) => (part.type === 'text' && typeof part.text === 'string' ? [part.text] : []))
|
|
143
|
+
.join('\n')
|
|
144
|
+
};
|
|
115
145
|
}
|
|
116
146
|
};
|
|
117
147
|
VlmDefaultStrategy = __decorate([
|
package/package.json
CHANGED
|
@@ -1,6 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@xpert-ai/plugin-rag-vlm",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.1.1",
|
|
4
|
+
"repository": {
|
|
5
|
+
"type": "git",
|
|
6
|
+
"url": "https://github.com/xpert-ai/xpert-plugins"
|
|
7
|
+
},
|
|
4
8
|
"type": "module",
|
|
5
9
|
"main": "./dist/index.js",
|
|
6
10
|
"module": "./dist/index.js",
|
|
@@ -23,13 +27,13 @@
|
|
|
23
27
|
"tslib": "^2.3.0"
|
|
24
28
|
},
|
|
25
29
|
"peerDependencies": {
|
|
26
|
-
"@xpert-ai/plugin-sdk": "^3.
|
|
30
|
+
"@xpert-ai/plugin-sdk": "^3.15.18",
|
|
27
31
|
"zod": "3.25.67",
|
|
28
32
|
"@nestjs/config": "^4.0.2",
|
|
29
33
|
"chalk": "4.1.2",
|
|
30
34
|
"@langchain/core": "0.3.72",
|
|
31
35
|
"@nestjs/common": "^11.1.6",
|
|
32
|
-
"@
|
|
36
|
+
"@xpert-ai/contracts": "^3.6.0",
|
|
33
37
|
"uuid": "8.3.2"
|
|
34
38
|
}
|
|
35
39
|
}
|