@customize-agent/knowledge 4.0.18 → 4.0.19
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.
|
@@ -295,6 +295,18 @@ export class TextChunker {
|
|
|
295
295
|
return chunks.flatMap(chunk => this.estimateTokens(chunk) > maxTokens ? this.splitByWindow(chunk, maxTokens, overlapTokens) : [chunk]);
|
|
296
296
|
}
|
|
297
297
|
splitByWindow(text, maxTokens, overlapTokens = 0) {
|
|
298
|
+
if (!/[\s\n\r\t。?!;;.!?,、]/u.test(text) && text.length > maxTokens * 3) {
|
|
299
|
+
const maxChars = Math.max(1, maxTokens * 3);
|
|
300
|
+
const overlapChars = Math.max(0, Math.min(Math.floor(maxChars / 2), overlapTokens * 3));
|
|
301
|
+
const step = Math.max(1, maxChars - overlapChars);
|
|
302
|
+
const chunks = [];
|
|
303
|
+
for (let start = 0; start < text.length; start += step) {
|
|
304
|
+
chunks.push(text.slice(start, start + maxChars).trim());
|
|
305
|
+
if (start + maxChars >= text.length)
|
|
306
|
+
break;
|
|
307
|
+
}
|
|
308
|
+
return chunks.filter(Boolean);
|
|
309
|
+
}
|
|
298
310
|
const tokens = this.tokenizer.encode(text);
|
|
299
311
|
if (tokens.length <= maxTokens)
|
|
300
312
|
return [text.trim()].filter(Boolean);
|
|
@@ -470,6 +482,8 @@ export class TextChunker {
|
|
|
470
482
|
return text.slice(Math.max(0, text.length - chars));
|
|
471
483
|
}
|
|
472
484
|
estimateTokens(text) {
|
|
485
|
+
if (!/[\s\n\r\t。?!;;.!?,、]/u.test(text) && text.length > 2000)
|
|
486
|
+
return Math.max(1, Math.ceil(text.length / 3));
|
|
473
487
|
return Math.max(1, this.tokenizer.countTokens(text));
|
|
474
488
|
}
|
|
475
489
|
}
|
|
@@ -180,6 +180,7 @@ export declare class KnowledgeBaseManager {
|
|
|
180
180
|
private statRelativePaths;
|
|
181
181
|
private moveUploadedFile;
|
|
182
182
|
private hasUsableContent;
|
|
183
|
+
private isMetadataOnlyNonBlocking;
|
|
183
184
|
private defaultUploadRelativePath;
|
|
184
185
|
private validateUploadRelativePath;
|
|
185
186
|
private resolveKbRelativePath;
|
|
@@ -148,8 +148,9 @@ export class KnowledgeBaseManager {
|
|
|
148
148
|
extraction.metadata.textLength = extraction.text.length;
|
|
149
149
|
if (!this.hasUsableContent(extraction.text, extraction.metadata)) {
|
|
150
150
|
const reason = extraction.warnings[0] ?? '未解析出可用于模型的正文内容,已跳过向量化';
|
|
151
|
+
const metadataOnly = this.isMetadataOnlyNonBlocking(file, extraction.metadata);
|
|
151
152
|
diff.skippedFiles.push({ file, reason });
|
|
152
|
-
this.updateJobsForFile(file.relativePath, 'ERROR', 100, reason, reason);
|
|
153
|
+
this.updateJobsForFile(file.relativePath, metadataOnly ? 'SUCCESS' : 'ERROR', 100, reason, metadataOnly ? undefined : reason);
|
|
153
154
|
this.store.upsertRecord({
|
|
154
155
|
relativePath: file.relativePath,
|
|
155
156
|
category: file.category,
|
|
@@ -161,9 +162,9 @@ export class KnowledgeBaseManager {
|
|
|
161
162
|
collectionName,
|
|
162
163
|
indexedAt: now,
|
|
163
164
|
lastVerifiedAt: now,
|
|
164
|
-
status: 'error',
|
|
165
|
-
errorMessage: reason,
|
|
166
|
-
metadataJson: JSON.stringify({ mimeType: file.mimeType, warnings: extraction.warnings }),
|
|
165
|
+
status: metadataOnly ? 'active' : 'error',
|
|
166
|
+
errorMessage: metadataOnly ? undefined : reason,
|
|
167
|
+
metadataJson: JSON.stringify({ mimeType: file.mimeType, ...extraction.metadata, warnings: extraction.warnings, metadataOnly }),
|
|
167
168
|
});
|
|
168
169
|
continue;
|
|
169
170
|
}
|
|
@@ -973,6 +974,10 @@ export class KnowledgeBaseManager {
|
|
|
973
974
|
return false;
|
|
974
975
|
return text.trim().length > 0;
|
|
975
976
|
}
|
|
977
|
+
isMetadataOnlyNonBlocking(file, metadata) {
|
|
978
|
+
const coverage = String(metadata.contentCoverage ?? '');
|
|
979
|
+
return file.category === 'image' && ['image_too_small_for_ocr', 'ocr_no_text'].includes(coverage);
|
|
980
|
+
}
|
|
976
981
|
defaultUploadRelativePath(fileName) {
|
|
977
982
|
const classification = this.classifier.classifyVirtual(fileName);
|
|
978
983
|
const configDirs = this.projectConfig?.categoryDirs ?? DEFAULT_CATEGORY_DIRS;
|
|
@@ -906,6 +906,15 @@ export class ContentExtractor {
|
|
|
906
906
|
metadata.parseError = validationError;
|
|
907
907
|
return { text: '', metadata, warnings: [`图片文件无效或不完整:${validationError},未入库`] };
|
|
908
908
|
}
|
|
909
|
+
const dimensions = await this.readImageDimensions(file.absolutePath);
|
|
910
|
+
if (dimensions) {
|
|
911
|
+
metadata.imageWidth = dimensions.width;
|
|
912
|
+
metadata.imageHeight = dimensions.height;
|
|
913
|
+
if (this.isTooSmallForOcr(dimensions.width, dimensions.height)) {
|
|
914
|
+
metadata.contentCoverage = 'image_too_small_for_ocr';
|
|
915
|
+
return { text: this.metadataOnlyText(file), metadata, warnings: [`图片尺寸过小(${dimensions.width}x${dimensions.height}),已跳过 OCR 并仅索引元数据`] };
|
|
916
|
+
}
|
|
917
|
+
}
|
|
909
918
|
// 1. 尝试外部 PaddleOCR 命令(CUSTOMIZE_PADDLE_OCR_CMD)
|
|
910
919
|
const paddleExternal = await this.tryPaddleOcrLayout(file.absolutePath);
|
|
911
920
|
if (paddleExternal) {
|
|
@@ -918,10 +927,10 @@ export class ContentExtractor {
|
|
|
918
927
|
let imageData;
|
|
919
928
|
try {
|
|
920
929
|
imageData = await this.loadImagePixels(file.absolutePath);
|
|
930
|
+
metadata.imageWidth = imageData.width;
|
|
931
|
+
metadata.imageHeight = imageData.height;
|
|
921
932
|
if (this.isTooSmallForOcr(imageData.width, imageData.height)) {
|
|
922
933
|
metadata.contentCoverage = 'image_too_small_for_ocr';
|
|
923
|
-
metadata.imageWidth = imageData.width;
|
|
924
|
-
metadata.imageHeight = imageData.height;
|
|
925
934
|
return { text: this.metadataOnlyText(file), metadata, warnings: [`图片尺寸过小(${imageData.width}x${imageData.height}),已跳过 OCR 并仅索引元数据`] };
|
|
926
935
|
}
|
|
927
936
|
}
|
|
@@ -950,7 +959,7 @@ export class ContentExtractor {
|
|
|
950
959
|
};
|
|
951
960
|
}
|
|
952
961
|
metadata.contentCoverage = 'ocr_no_text';
|
|
953
|
-
warnings.push(`${metadata.ocrProvider}
|
|
962
|
+
warnings.push(`${metadata.ocrProvider} 未识别到文字,未生成可检索文本切片`);
|
|
954
963
|
return { text: '', metadata, warnings };
|
|
955
964
|
}
|
|
956
965
|
catch (e) {
|
|
@@ -1101,7 +1110,12 @@ export class ContentExtractor {
|
|
|
1101
1110
|
const imgPath = pageImages[i];
|
|
1102
1111
|
try {
|
|
1103
1112
|
const dimensions = await this.readImageDimensions(imgPath);
|
|
1104
|
-
if (dimensions
|
|
1113
|
+
if (!dimensions) {
|
|
1114
|
+
failedPages.push({ page: i + 1, reason: 'image_dimensions_unavailable' });
|
|
1115
|
+
warnings.push(`PDF 第 ${i + 1} 页渲染图片无法读取尺寸,已跳过 OCR`);
|
|
1116
|
+
continue;
|
|
1117
|
+
}
|
|
1118
|
+
if (this.isTooSmallForOcr(dimensions.width, dimensions.height)) {
|
|
1105
1119
|
failedPages.push({ page: i + 1, reason: `image_too_small_for_ocr_${dimensions.width}x${dimensions.height}` });
|
|
1106
1120
|
warnings.push(`PDF 第 ${i + 1} 页渲染图片尺寸过小(${dimensions.width}x${dimensions.height}),已跳过 OCR`);
|
|
1107
1121
|
continue;
|
|
@@ -1109,7 +1123,7 @@ export class ContentExtractor {
|
|
|
1109
1123
|
const provider = await getOcrProvider();
|
|
1110
1124
|
const ocrResult = await provider.recognize({
|
|
1111
1125
|
data: new Uint8Array(0),
|
|
1112
|
-
width: dimensions
|
|
1126
|
+
width: dimensions.width, height: dimensions.height, channels: 0,
|
|
1113
1127
|
filePath: imgPath,
|
|
1114
1128
|
});
|
|
1115
1129
|
const ocrText = this.cleanOcrText(ocrResult.text);
|