@customize-agent/knowledge 4.1.5 → 4.2.0
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/dist/chunking/text-chunker.d.ts +0 -2
- package/dist/chunking/text-chunker.js +11 -16
- package/dist/cleaning/text-cleaner.d.ts +2 -0
- package/dist/cleaning/text-cleaner.js +24 -1
- package/dist/extraction/content-extractor.d.ts +0 -2
- package/dist/extraction/content-extractor.js +16 -41
- package/package.json +1 -1
|
@@ -13,7 +13,6 @@ export interface TextChunk {
|
|
|
13
13
|
export interface ChunkConfig {
|
|
14
14
|
maxChunkSize: number;
|
|
15
15
|
overlap: number;
|
|
16
|
-
headerInjection: boolean;
|
|
17
16
|
}
|
|
18
17
|
/** 文本切片器,支持文档、表格、代码等多类型文件的递归式切片 */
|
|
19
18
|
export declare class TextChunker {
|
|
@@ -56,7 +55,6 @@ export declare class TextChunker {
|
|
|
56
55
|
private chunkPartStartOffsets;
|
|
57
56
|
private enforceCandidateLimit;
|
|
58
57
|
private buildTitlePaths;
|
|
59
|
-
private withHeader;
|
|
60
58
|
private createChunk;
|
|
61
59
|
private kindForCategory;
|
|
62
60
|
private extractSectionTitle;
|
|
@@ -1,15 +1,15 @@
|
|
|
1
1
|
import { BgeTokenizer } from './bge-tokenizer.js';
|
|
2
2
|
const DEFAULT_CONFIGS = {
|
|
3
|
-
document: { maxChunkSize: 800, overlap: 100
|
|
4
|
-
spreadsheet: { maxChunkSize: 1000, overlap: 120
|
|
5
|
-
image: { maxChunkSize: 512, overlap: 0
|
|
6
|
-
cad: { maxChunkSize: 600, overlap: 80
|
|
7
|
-
code: { maxChunkSize: 1000, overlap: 120
|
|
8
|
-
data: { maxChunkSize: 600, overlap: 80
|
|
9
|
-
web: { maxChunkSize: 800, overlap: 100
|
|
10
|
-
diagram: { maxChunkSize: 512, overlap: 0
|
|
11
|
-
archive: { maxChunkSize: 500, overlap: 50
|
|
12
|
-
other: { maxChunkSize: 500, overlap: 50
|
|
3
|
+
document: { maxChunkSize: 800, overlap: 100 },
|
|
4
|
+
spreadsheet: { maxChunkSize: 1000, overlap: 120 },
|
|
5
|
+
image: { maxChunkSize: 512, overlap: 0 },
|
|
6
|
+
cad: { maxChunkSize: 600, overlap: 80 },
|
|
7
|
+
code: { maxChunkSize: 1000, overlap: 120 },
|
|
8
|
+
data: { maxChunkSize: 600, overlap: 80 },
|
|
9
|
+
web: { maxChunkSize: 800, overlap: 100 },
|
|
10
|
+
diagram: { maxChunkSize: 512, overlap: 0 },
|
|
11
|
+
archive: { maxChunkSize: 500, overlap: 50 },
|
|
12
|
+
other: { maxChunkSize: 500, overlap: 50 },
|
|
13
13
|
};
|
|
14
14
|
const RECURSIVE_SEPARATORS = [
|
|
15
15
|
/\n(?=#{1,6}\s)/u,
|
|
@@ -44,7 +44,7 @@ export class TextChunker {
|
|
|
44
44
|
if (source.length === 0)
|
|
45
45
|
return [];
|
|
46
46
|
const config = DEFAULT_CONFIGS[file.category];
|
|
47
|
-
const normalized =
|
|
47
|
+
const normalized = source;
|
|
48
48
|
const rawCandidates = this.createCandidates(normalized, file, config);
|
|
49
49
|
const candidates = this.enforceCandidateLimit(rawCandidates, config);
|
|
50
50
|
return candidates.map((candidate, index) => this.createChunk(index, candidate, file, metadata));
|
|
@@ -493,11 +493,6 @@ export class TextChunker {
|
|
|
493
493
|
return stack.map(item => item.title).join(' > ') || section.title;
|
|
494
494
|
});
|
|
495
495
|
}
|
|
496
|
-
withHeader(text, file, config) {
|
|
497
|
-
if (!config.headerInjection)
|
|
498
|
-
return text;
|
|
499
|
-
return `资料类型: ${file.category}/${file.format}\n\n${text}`;
|
|
500
|
-
}
|
|
501
496
|
createChunk(index, candidate, file, metadata) {
|
|
502
497
|
const text = candidate.text.trim();
|
|
503
498
|
return {
|
|
@@ -44,6 +44,8 @@ export interface TextCleanStats {
|
|
|
44
44
|
cadTitleBlockLines: number;
|
|
45
45
|
/** 图纸:CAD 属性行数(图层/颜色/线型,K3) */
|
|
46
46
|
cadAttributeLines: number;
|
|
47
|
+
/** 图纸:CAD 图元属性枚举行数(管道符表格形态的实体罗列,K3) */
|
|
48
|
+
cadEntityPropertyLines: number;
|
|
47
49
|
/** 清单:纯报价表格段行数(费汇总/暂估价/规费/税金,K3) */
|
|
48
50
|
billPricingLines: number;
|
|
49
51
|
/** 清单:扉页签章段行数(K3) */
|
|
@@ -3,7 +3,7 @@ function zeroStats() {
|
|
|
3
3
|
headerFooterLines: 0, pageNumberLines: 0, tocRegionLines: 0, blankLines: 0,
|
|
4
4
|
tenderFormatLines: 0, tenderGenericLines: 0, clarificationLines: 0, cadNoiseLines: 0,
|
|
5
5
|
contractGeneralClauseLines: 0, announcementProcedureLines: 0, businessReviewLines: 0,
|
|
6
|
-
cadTitleBlockLines: 0, cadAttributeLines: 0, billPricingLines: 0, billTitlePageLines: 0,
|
|
6
|
+
cadTitleBlockLines: 0, cadAttributeLines: 0, cadEntityPropertyLines: 0, billPricingLines: 0, billTitlePageLines: 0,
|
|
7
7
|
};
|
|
8
8
|
}
|
|
9
9
|
/** 文档类型判定:文件名 + 文首内容双重特征(招标文件 / 补疑答疑 / 工程量清单 / 其他) */
|
|
@@ -145,6 +145,23 @@ function isCadAttributeLine(trimmed) {
|
|
|
145
145
|
return false;
|
|
146
146
|
return true;
|
|
147
147
|
}
|
|
148
|
+
/**
|
|
149
|
+
* 图纸:CAD 图元属性枚举行(「| 图层: X | 块: Y | 实体类型: … | 坐标: (…)」
|
|
150
|
+
* 管道符表格形态或「└── 标注文本: …」图元注释行)——专业转换器/历史版本产物形态,
|
|
151
|
+
* 图元属性包装与施组编制无关,「坐标/关联对象/状态」类纯图元定位信息会稀释检索语义。
|
|
152
|
+
* 行首键名 + 冒号 + 短值多重特征判定;「图纸节点: 文件名」锚定行不删(文件溯源有用)。
|
|
153
|
+
*/
|
|
154
|
+
function isCadEntityPropertyLine(trimmed) {
|
|
155
|
+
if (trimmed.length > 50)
|
|
156
|
+
return false;
|
|
157
|
+
if (/^[|└\s]*(?:图层|块|实体类型|坐标|关联对象|状态)\s*[::]/u.test(trimmed))
|
|
158
|
+
return true;
|
|
159
|
+
if (/^└──\s*(?:标注文本\s*[::])?/u.test(trimmed))
|
|
160
|
+
return true;
|
|
161
|
+
if (/^图纸节点\s*[::].{0,100}(?:图层|块|实体类型)\s*[::]/u.test(trimmed))
|
|
162
|
+
return true;
|
|
163
|
+
return false;
|
|
164
|
+
}
|
|
148
165
|
/** 合同通用条款章节标题(GF 系列示范文本的标准通用条款,不含项目数据) */
|
|
149
166
|
const CONTRACT_GENERAL_TITLE_RE = /通用合同条款|合同通用条款/u;
|
|
150
167
|
/** 专用合同条款标题:项目数据所在章节,必须保留,同时作为通用条款章节的边界 */
|
|
@@ -398,6 +415,12 @@ export function cleanExtractedText(input) {
|
|
|
398
415
|
index += 1;
|
|
399
416
|
continue;
|
|
400
417
|
}
|
|
418
|
+
// 10. 图纸:CAD 图元属性枚举行(管道符表格形态实体罗列)
|
|
419
|
+
if (isCad && isCadEntityPropertyLine(trimmed)) {
|
|
420
|
+
drop(line, s => { s.cadEntityPropertyLines += 1; });
|
|
421
|
+
index += 1;
|
|
422
|
+
continue;
|
|
423
|
+
}
|
|
401
424
|
kept.push(line);
|
|
402
425
|
index += 1;
|
|
403
426
|
}
|
|
@@ -23,8 +23,6 @@ export declare class ContentExtractor {
|
|
|
23
23
|
private extractDxf;
|
|
24
24
|
private buildCadSemanticNodes;
|
|
25
25
|
private extractDxfTextAnnotations;
|
|
26
|
-
private findNearestCadAnnotation;
|
|
27
|
-
private inferCadEntityType;
|
|
28
26
|
private tryConvertDwgWithBundledWasm;
|
|
29
27
|
private tryConvertDwgToDxf;
|
|
30
28
|
private extractCadMesh;
|
|
@@ -120,7 +120,7 @@ export class ContentExtractor {
|
|
|
120
120
|
.slice(0, 50);
|
|
121
121
|
const text = unique.join('\n');
|
|
122
122
|
return {
|
|
123
|
-
text: text
|
|
123
|
+
text: text ?? this.metadataOnlyText(file),
|
|
124
124
|
metadata: {
|
|
125
125
|
extractionMode: 'builtin_text_clipping',
|
|
126
126
|
vectorizable: true,
|
|
@@ -348,14 +348,13 @@ export class ContentExtractor {
|
|
|
348
348
|
}
|
|
349
349
|
metadata.contentCoverage = 'dxf_semantic_layer_block_annotations';
|
|
350
350
|
metadata.characterDataCount = characterDataCount;
|
|
351
|
-
const semanticNodes = this.buildCadSemanticNodes(file,
|
|
351
|
+
const semanticNodes = this.buildCadSemanticNodes(file, textEntities);
|
|
352
352
|
return {
|
|
353
353
|
text: [
|
|
354
|
-
this.metadataOnlyText(file),
|
|
355
354
|
`CAD DXF 图层: ${uniqueLayers.join(', ')}`,
|
|
356
355
|
`CAD DXF 块/符号: ${uniqueBlocks.join(', ')}`,
|
|
357
356
|
`CAD DXF 实体类型: ${uniqueEntityTypes.join(', ')}`,
|
|
358
|
-
'CAD
|
|
357
|
+
'CAD 语义标注文本:',
|
|
359
358
|
...semanticNodes,
|
|
360
359
|
].join('\n'),
|
|
361
360
|
metadata,
|
|
@@ -378,7 +377,6 @@ export class ContentExtractor {
|
|
|
378
377
|
metadata.contentCoverage = 'step_products_materials_entities_names';
|
|
379
378
|
return {
|
|
380
379
|
text: [
|
|
381
|
-
this.metadataOnlyText(file),
|
|
382
380
|
`STEP 产品/零件:\n${products.join('\n')}`,
|
|
383
381
|
`STEP 材料: ${materials.join(', ')}`,
|
|
384
382
|
`STEP 实体类型: ${uniqueStepEntities.join(', ')}`,
|
|
@@ -399,7 +397,7 @@ export class ContentExtractor {
|
|
|
399
397
|
metadata.entityTypes = uniqueIgesTypes.slice(0, 80);
|
|
400
398
|
metadata.contentCoverage = 'iges_entity_names_types';
|
|
401
399
|
return {
|
|
402
|
-
text: [
|
|
400
|
+
text: [`IGES 实体类型: ${uniqueIgesTypes.join(', ')}`, `IGES 实体/名称:\n${names.join('\n')}`].join('\n'),
|
|
403
401
|
metadata,
|
|
404
402
|
warnings,
|
|
405
403
|
};
|
|
@@ -462,7 +460,7 @@ export class ContentExtractor {
|
|
|
462
460
|
else
|
|
463
461
|
warnings.push(`${file.format} 内置 DWG→DXF 转换未成功,已使用低置信度可读标注/标题块兜底抽取并过滤疑似乱码 ${filteredCount} 条;该结果仅作为兜底证据,不应等同于完整图层、块、标注和尺寸语义解析`);
|
|
464
462
|
return {
|
|
465
|
-
text: hasCharacterData ?
|
|
463
|
+
text: hasCharacterData ? `CAD 图纸可读标注/标题块/属性:\n${readable.join('\n')}` : this.metadataOnlyText(file),
|
|
466
464
|
metadata,
|
|
467
465
|
warnings,
|
|
468
466
|
};
|
|
@@ -503,35 +501,29 @@ export class ContentExtractor {
|
|
|
503
501
|
}
|
|
504
502
|
metadata.contentCoverage = 'dxf_semantic_layer_block_annotations';
|
|
505
503
|
metadata.characterDataCount = characterDataCount;
|
|
506
|
-
const semanticNodes = this.buildCadSemanticNodes(file,
|
|
504
|
+
const semanticNodes = this.buildCadSemanticNodes(file, textEntities);
|
|
507
505
|
return {
|
|
508
506
|
text: [
|
|
509
|
-
this.metadataOnlyText(file),
|
|
510
507
|
`CAD DXF 图层: ${uniqueLayers.join(', ')}`,
|
|
511
508
|
`CAD DXF 块/符号: ${uniqueBlocks.join(', ')}`,
|
|
512
509
|
`CAD DXF 实体类型: ${uniqueEntityTypes.join(', ')}`,
|
|
513
|
-
'CAD
|
|
510
|
+
'CAD 语义标注文本:',
|
|
514
511
|
...semanticNodes,
|
|
515
512
|
].join('\n'),
|
|
516
513
|
metadata,
|
|
517
514
|
warnings,
|
|
518
515
|
};
|
|
519
516
|
}
|
|
520
|
-
buildCadSemanticNodes(file,
|
|
517
|
+
buildCadSemanticNodes(file, texts) {
|
|
518
|
+
// 图纸语义节点 = 文件名锚定 + 纯标注文本列表。逐实体枚举的图元属性包装
|
|
519
|
+
// (图层/块/实体类型/坐标/关联对象/状态)是重复结构模板噪音,会稀释检索语义
|
|
520
|
+
// (如「混凝土强度 C35」被「实体类型:|坐标:(…)」图元罗列干扰);图层/块/实体
|
|
521
|
+
// 类型汇总已在 CAD DXF 汇总行体现,此处只保留图纸真实文字(图名/说明/尺寸标注值)
|
|
521
522
|
const fileName = path.basename(file.relativePath);
|
|
522
|
-
const
|
|
523
|
-
|
|
524
|
-
|
|
525
|
-
|
|
526
|
-
return annotations.map((annotation, index) => {
|
|
527
|
-
const nearest = this.findNearestCadAnnotation(annotation, annotations);
|
|
528
|
-
const layer = annotation.layer ?? layers[index % Math.max(1, layers.length)] ?? defaultLayer;
|
|
529
|
-
const block = annotation.block ?? blocks[index % Math.max(1, blocks.length)] ?? defaultBlock;
|
|
530
|
-
const entity = annotation.entityType ?? this.inferCadEntityType(annotation.text, defaultEntity);
|
|
531
|
-
const status = /关键|critical|尺寸|dim|mm|cm|m\b|°|φ|Φ|R\d/iu.test(annotation.text) ? '关键尺寸/约束候选' : '普通标注';
|
|
532
|
-
const position = annotation.x != null && annotation.y != null ? ` | 坐标: (${annotation.x.toFixed(2)}, ${annotation.y.toFixed(2)})` : '';
|
|
533
|
-
return [`图纸节点: ${fileName} | 图层: ${layer} | 块: ${block} | 实体类型: ${entity}${position}`, `└── 标注文本: ${annotation.text} | 关联对象: ${nearest ? `邻近标注 ${nearest.text}` : '空间邻近候选'} | 状态: ${status}`].join('\n');
|
|
534
|
-
});
|
|
523
|
+
const annotations = texts.filter(item => item.text && item.text.trim().length > 0);
|
|
524
|
+
if (annotations.length === 0)
|
|
525
|
+
return [`图纸节点: ${fileName} | 未提取到文字标注`];
|
|
526
|
+
return [`图纸节点: ${fileName}`, ...annotations.map(item => item.text)];
|
|
535
527
|
}
|
|
536
528
|
extractDxfTextAnnotations(raw) {
|
|
537
529
|
const entities = raw.split(/(?:^|\r?\n)\s*0\s*\r?\n/u).filter(section => /^(?:TEXT|MTEXT|DIMENSION|LEADER)/u.test(section.trim()));
|
|
@@ -553,23 +545,6 @@ export class ContentExtractor {
|
|
|
553
545
|
}].map(item => ({ ...item, x: Number.isFinite(item.x) ? item.x : undefined, y: Number.isFinite(item.y) ? item.y : undefined }));
|
|
554
546
|
});
|
|
555
547
|
}
|
|
556
|
-
findNearestCadAnnotation(target, annotations) {
|
|
557
|
-
if (target.x == null || target.y == null)
|
|
558
|
-
return undefined;
|
|
559
|
-
return annotations
|
|
560
|
-
.filter(item => item !== target && item.x != null && item.y != null)
|
|
561
|
-
.map(item => ({ item, distance: Math.hypot((item.x ?? 0) - target.x, (item.y ?? 0) - target.y) }))
|
|
562
|
-
.sort((a, b) => a.distance - b.distance)[0]?.item;
|
|
563
|
-
}
|
|
564
|
-
inferCadEntityType(text, fallback) {
|
|
565
|
-
if (/\b\d+(?:\.\d+)?\s*(?:mm|cm|m)\b|φ|Φ|R\d/iu.test(text))
|
|
566
|
-
return '线性尺寸/半径尺寸';
|
|
567
|
-
if (/°|angle|角度/iu.test(text))
|
|
568
|
-
return '角度尺寸';
|
|
569
|
-
if (/note|说明|备注/iu.test(text))
|
|
570
|
-
return '文字说明';
|
|
571
|
-
return fallback;
|
|
572
|
-
}
|
|
573
548
|
async tryConvertDwgWithBundledWasm(filePath) {
|
|
574
549
|
try {
|
|
575
550
|
const mod = await resolveAndImport('dwgdxf');
|