@customize-agent/knowledge 4.0.8 → 4.0.9
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.
|
@@ -324,7 +324,7 @@ export class TextChunker {
|
|
|
324
324
|
withHeader(text, file, config) {
|
|
325
325
|
if (!config.headerInjection)
|
|
326
326
|
return text;
|
|
327
|
-
return
|
|
327
|
+
return `资料类型: ${file.category}/${file.format}\n\n${text}`;
|
|
328
328
|
}
|
|
329
329
|
createChunk(index, candidate, file, metadata) {
|
|
330
330
|
const text = candidate.text.trim();
|
|
@@ -278,7 +278,7 @@ export class IndexStateStore {
|
|
|
278
278
|
`).all(matchQuery, limit * 8);
|
|
279
279
|
return rows
|
|
280
280
|
.map(row => {
|
|
281
|
-
const keyword = this.scoreChunkDetailed(`${String(row.
|
|
281
|
+
const keyword = this.scoreChunkDetailed(`${String(row.section_title ?? '')}\n${String(row.category)}\n${String(row.format)}\n${String(row.content)}`, terms);
|
|
282
282
|
const bm25Score = this.bm25ToPositiveScore(Number(row.bm25_score));
|
|
283
283
|
return this.rowToChunk(row, keyword.keywordScore + bm25Score, { ...keyword, bm25Score });
|
|
284
284
|
})
|
|
@@ -299,7 +299,7 @@ export class IndexStateStore {
|
|
|
299
299
|
`).all(...terms.flatMap(term => [`%${term}%`, `%${term}%`, `%${term}%`, `%${term}%`]), limit * 6);
|
|
300
300
|
return rows
|
|
301
301
|
.map(row => {
|
|
302
|
-
const keyword = this.scoreChunkDetailed(`${String(row.
|
|
302
|
+
const keyword = this.scoreChunkDetailed(`${String(row.section_title ?? '')}\n${String(row.category)}\n${String(row.format)}\n${String(row.content)}`, terms);
|
|
303
303
|
return this.rowToChunk(row, keyword.keywordScore, keyword);
|
|
304
304
|
})
|
|
305
305
|
.filter(row => row.score > 0)
|
|
@@ -16,6 +16,9 @@ export declare class ContentExtractor {
|
|
|
16
16
|
private extractTextClipping;
|
|
17
17
|
private swapUtf16Bytes;
|
|
18
18
|
private extractReadableFragments;
|
|
19
|
+
private cleanCadReadableText;
|
|
20
|
+
private isReadableCadValue;
|
|
21
|
+
private cleanExtractedText;
|
|
19
22
|
private textScore;
|
|
20
23
|
private extractCad;
|
|
21
24
|
private extractDxf;
|
|
@@ -5,6 +5,8 @@ import { tmpdir } from 'node:os';
|
|
|
5
5
|
import { pathToFileURL } from 'node:url';
|
|
6
6
|
import { ExternalExtractorRegistry } from './external-extractor.js';
|
|
7
7
|
import { resolveAndImport, resolvePackage, getNodeModulesRoot } from './module-resolver.js';
|
|
8
|
+
const CAD_INTERNAL_TOKEN_RE = /\b(?:TDbPipe|TDbPipeValve|TDbPipeFitting|TDbWellh|AcDb\w+|Dwg\w+|ObjectId|Handle|ByLayer|Continuous|Model|Layout\d*)\b/giu;
|
|
9
|
+
const CAD_INTERNAL_LINE_RE = /^(?:TDb\w+|AcDb\w+|[A-F0-9]{8,}|\d+|Model|Layout\d*|ByLayer|Continuous)$/iu;
|
|
8
10
|
/** 文件内容提取器,支持文档、表格、图片、CAD 等多种文件格式的内容抽取 */
|
|
9
11
|
export class ContentExtractor {
|
|
10
12
|
externalExtractors;
|
|
@@ -102,7 +104,7 @@ export class ContentExtractor {
|
|
|
102
104
|
}
|
|
103
105
|
}
|
|
104
106
|
return {
|
|
105
|
-
text: text.trim(),
|
|
107
|
+
text: this.cleanExtractedText(text, file).trim(),
|
|
106
108
|
metadata,
|
|
107
109
|
warnings,
|
|
108
110
|
extractionTimeMs: Date.now() - start,
|
|
@@ -177,9 +179,36 @@ export class ContentExtractor {
|
|
|
177
179
|
return value
|
|
178
180
|
.replace(/[^\p{L}\p{N}\p{P}\p{S}\s]/gu, '\n')
|
|
179
181
|
.split(/[\r\n]+/u)
|
|
180
|
-
.map(line =>
|
|
182
|
+
.map(line => this.cleanCadReadableText(line))
|
|
181
183
|
.filter(line => line.length >= 2 && /[\p{L}\p{N}]/u.test(line));
|
|
182
184
|
}
|
|
185
|
+
cleanCadReadableText(value) {
|
|
186
|
+
return value
|
|
187
|
+
.replace(CAD_INTERNAL_TOKEN_RE, '')
|
|
188
|
+
.replace(/\b(?:LINE|LWPOLYLINE|POLYLINE|INSERT|HATCH|CIRCLE|ARC|DIMENSION|TEXT|MTEXT)\b/giu, '')
|
|
189
|
+
.replace(/\s+/gu, ' ')
|
|
190
|
+
.trim();
|
|
191
|
+
}
|
|
192
|
+
isReadableCadValue(value) {
|
|
193
|
+
const cleaned = this.cleanCadReadableText(value);
|
|
194
|
+
return cleaned.length >= 2 && !CAD_INTERNAL_LINE_RE.test(cleaned) && /[\p{Script=Han}\p{Letter}\d]/u.test(cleaned);
|
|
195
|
+
}
|
|
196
|
+
cleanExtractedText(value, file) {
|
|
197
|
+
const normalized = [...value]
|
|
198
|
+
.filter(char => {
|
|
199
|
+
const code = char.charCodeAt(0);
|
|
200
|
+
return code === 9 || code === 10 || code === 13 || code >= 32;
|
|
201
|
+
})
|
|
202
|
+
.join('');
|
|
203
|
+
if (file.category !== 'cad')
|
|
204
|
+
return normalized.replace(/\n{3,}/gu, '\n\n');
|
|
205
|
+
return normalized
|
|
206
|
+
.split(/\r?\n/u)
|
|
207
|
+
.map(line => this.cleanCadReadableText(line))
|
|
208
|
+
.filter(line => line && !CAD_INTERNAL_LINE_RE.test(line))
|
|
209
|
+
.join('\n')
|
|
210
|
+
.replace(/\n{3,}/gu, '\n\n');
|
|
211
|
+
}
|
|
183
212
|
textScore(value) {
|
|
184
213
|
const cjk = (value.match(/[\p{Script=Han}]/gu) ?? []).length;
|
|
185
214
|
const alnum = (value.match(/[\p{L}\p{N}]/gu) ?? []).length;
|
|
@@ -202,9 +231,9 @@ export class ContentExtractor {
|
|
|
202
231
|
}
|
|
203
232
|
if (file.format === 'autocad' && ext === '.dxf') {
|
|
204
233
|
const raw = fs.readFileSync(file.absolutePath, 'utf8');
|
|
205
|
-
const layers = this.matchAll(raw, /\n\s*8\s*\n([^\n]+)/gu).slice(0, 300);
|
|
234
|
+
const layers = this.matchAll(raw, /\n\s*8\s*\n([^\n]+)/gu).filter(value => this.isReadableCadValue(value)).slice(0, 300);
|
|
206
235
|
const textEntities = this.extractDxfTextAnnotations(raw).slice(0, 500);
|
|
207
|
-
const blocks = this.matchAll(raw, /\n\s*2\s*\n([^\n]+)/gu).slice(0, 300);
|
|
236
|
+
const blocks = this.matchAll(raw, /\n\s*2\s*\n([^\n]+)/gu).filter(value => this.isReadableCadValue(value)).slice(0, 300);
|
|
208
237
|
const entityTypes = this.matchAll(raw, /\n\s*0\s*\n([A-Z][A-Z0-9_]+)/gu).slice(0, 1000);
|
|
209
238
|
const uniqueLayers = Array.from(new Set(layers));
|
|
210
239
|
const uniqueBlocks = Array.from(new Set(blocks));
|
|
@@ -278,7 +307,7 @@ export class ContentExtractor {
|
|
|
278
307
|
if (result.text.trim())
|
|
279
308
|
return result;
|
|
280
309
|
}
|
|
281
|
-
const readable = this.extractBinaryReadableFragments(file.absolutePath).slice(0,
|
|
310
|
+
const readable = this.extractBinaryReadableFragments(file.absolutePath).filter(value => this.isReadableCadValue(value)).slice(0, 5000);
|
|
282
311
|
metadata.extractionMode = 'builtin_cad_readable_fragments';
|
|
283
312
|
metadata.contentCoverage = readable.length > 0 ? 'cad_readable_text_fragments' : 'metadata';
|
|
284
313
|
metadata.stringCount = readable.length;
|
|
@@ -303,10 +332,10 @@ export class ContentExtractor {
|
|
|
303
332
|
catch {
|
|
304
333
|
warnings.push('dxf-parser 解析失败,已使用 DXF 文本结构抽取回退');
|
|
305
334
|
}
|
|
306
|
-
const layers = this.matchAll(raw,
|
|
307
|
-
const textEntities = this.extractDxfTextAnnotations(raw).slice(0,
|
|
308
|
-
const blocks = this.matchAll(raw,
|
|
309
|
-
const entityTypes = this.matchAll(raw,
|
|
335
|
+
const layers = this.matchAll(raw, /(?:^|\r?\n)\s*8\s*\r?\n([^\r\n]+)/gu).filter(value => this.isReadableCadValue(value)).slice(0, 300);
|
|
336
|
+
const textEntities = this.extractDxfTextAnnotations(raw).slice(0, 5000);
|
|
337
|
+
const blocks = this.matchAll(raw, /(?:^|\r?\n)\s*2\s*\r?\n([^\r\n]+)/gu).filter(value => this.isReadableCadValue(value)).slice(0, 300);
|
|
338
|
+
const entityTypes = this.matchAll(raw, /(?:^|\r?\n)\s*0\s*\r?\n([A-Z][A-Z0-9_]+)/gu).slice(0, 1200);
|
|
310
339
|
const uniqueLayers = Array.from(new Set(layers));
|
|
311
340
|
const uniqueBlocks = Array.from(new Set(blocks));
|
|
312
341
|
const uniqueEntityTypes = Array.from(new Set(entityTypes));
|
|
@@ -350,18 +379,18 @@ export class ContentExtractor {
|
|
|
350
379
|
});
|
|
351
380
|
}
|
|
352
381
|
extractDxfTextAnnotations(raw) {
|
|
353
|
-
const entities = raw.split(
|
|
382
|
+
const entities = raw.split(/(?:^|\r?\n)\s*0\s*\r?\n/u).filter(section => /^(?:TEXT|MTEXT|DIMENSION|LEADER)/u.test(section.trim()));
|
|
354
383
|
return entities.flatMap(section => {
|
|
355
|
-
const text =
|
|
356
|
-
if (!text)
|
|
384
|
+
const text = this.cleanCadReadableText(/(?:^|\r?\n)\s*(?:1|3)\s*\r?\n([^\r\n]+)/u.exec(section)?.[1] ?? '');
|
|
385
|
+
if (!text || !this.isReadableCadValue(text))
|
|
357
386
|
return [];
|
|
358
387
|
return [{
|
|
359
388
|
text,
|
|
360
|
-
layer:
|
|
361
|
-
block:
|
|
389
|
+
layer: /(?:^|\r?\n)\s*8\s*\r?\n([^\r\n]+)/u.exec(section)?.[1]?.trim(),
|
|
390
|
+
block: /(?:^|\r?\n)\s*2\s*\r?\n([^\r\n]+)/u.exec(section)?.[1]?.trim(),
|
|
362
391
|
entityType: section.trim().split(/\s+/u)[0],
|
|
363
|
-
x: Number(
|
|
364
|
-
y: Number(
|
|
392
|
+
x: Number(/(?:^|\r?\n)\s*10\s*\r?\n([^\r\n]+)/u.exec(section)?.[1]),
|
|
393
|
+
y: Number(/(?:^|\r?\n)\s*20\s*\r?\n([^\r\n]+)/u.exec(section)?.[1]),
|
|
365
394
|
}].map(item => ({ ...item, x: Number.isFinite(item.x) ? item.x : undefined, y: Number.isFinite(item.y) ? item.y : undefined }));
|
|
366
395
|
});
|
|
367
396
|
}
|
|
@@ -1165,7 +1194,7 @@ process.stdout.write(JSON.stringify({ pageCount: doc.numPages, pageLimit, text:
|
|
|
1165
1194
|
if (pages.length > 0) {
|
|
1166
1195
|
const combined = pages.join('\n\n');
|
|
1167
1196
|
if (combined.trim())
|
|
1168
|
-
return combined
|
|
1197
|
+
return combined;
|
|
1169
1198
|
}
|
|
1170
1199
|
}
|
|
1171
1200
|
catch (e) {
|
|
@@ -1179,7 +1208,7 @@ process.stdout.write(JSON.stringify({ pageCount: doc.numPages, pageLimit, text:
|
|
|
1179
1208
|
if (pdfParse) {
|
|
1180
1209
|
const result = await pdfParse(buffer);
|
|
1181
1210
|
if (result.text.trim())
|
|
1182
|
-
return result.text
|
|
1211
|
+
return result.text;
|
|
1183
1212
|
}
|
|
1184
1213
|
}
|
|
1185
1214
|
catch {
|
|
@@ -1450,15 +1479,8 @@ process.stdout.write(JSON.stringify({ pageCount: doc.numPages, pageLimit, text:
|
|
|
1450
1479
|
return lines.filter(line => !line.endsWith(':'));
|
|
1451
1480
|
}
|
|
1452
1481
|
metadataOnlyText(file) {
|
|
1453
|
-
const fileName = path.basename(file.relativePath);
|
|
1454
|
-
const directory = path.dirname(file.relativePath);
|
|
1455
|
-
const searchableName = fileName.replace(/[_\-.]+/gu, ' ');
|
|
1456
1482
|
return [
|
|
1457
|
-
|
|
1458
|
-
`文件路径: ${file.relativePath}`,
|
|
1459
|
-
`所在目录: ${directory === '.' ? 'knowledgeBase' : directory}`,
|
|
1460
|
-
`可搜索名称: ${searchableName}`,
|
|
1461
|
-
`文件类型: ${file.category}/${file.format}`,
|
|
1483
|
+
`资料类型: ${file.category}/${file.format}`,
|
|
1462
1484
|
`MIME: ${file.mimeType}`,
|
|
1463
1485
|
`文件大小: ${file.fileSize} bytes`,
|
|
1464
1486
|
].join('\n');
|