@customize-agent/knowledge 4.0.5 → 4.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/dist/core/knowledge-base-manager.d.ts +9 -0
- package/dist/core/knowledge-base-manager.js +86 -13
- package/dist/index.d.ts +1 -1
- package/dist/vector/hnsw-vector-store.d.ts +6 -3
- package/dist/vector/hnsw-vector-store.js +25 -6
- package/dist/vector/types.d.ts +6 -2
- package/dist/vector/vector-indexer.d.ts +1 -0
- package/dist/vector/vector-indexer.js +2 -1
- package/package.json +1 -1
|
@@ -108,6 +108,11 @@ export declare class KnowledgeBaseManager {
|
|
|
108
108
|
content: Buffer;
|
|
109
109
|
targetRelativePath?: string;
|
|
110
110
|
}>, operationId?: string): Promise<import("./index-state-store.js").KnowledgeIndexJob[]>;
|
|
111
|
+
stageUploadedFilePaths(files: Array<{
|
|
112
|
+
fileName: string;
|
|
113
|
+
sourcePath: string;
|
|
114
|
+
targetRelativePath?: string;
|
|
115
|
+
}>, operationId?: string, offset?: number): Promise<import("./index-state-store.js").KnowledgeIndexJob[]>;
|
|
111
116
|
uploadFiles(files: Array<{
|
|
112
117
|
fileName: string;
|
|
113
118
|
content: Buffer;
|
|
@@ -133,7 +138,10 @@ export declare class KnowledgeBaseManager {
|
|
|
133
138
|
indexVectors(options?: {
|
|
134
139
|
collectionName?: string;
|
|
135
140
|
relativePath?: string;
|
|
141
|
+
relativePaths?: string[];
|
|
142
|
+
cleanupCollectionNames?: Iterable<string>;
|
|
136
143
|
limit?: number;
|
|
144
|
+
rebuild?: boolean;
|
|
137
145
|
}): Promise<VectorIndexResult[]>;
|
|
138
146
|
getProjectConfig(): ProjectConfig | undefined;
|
|
139
147
|
getStats(): KnowledgeBaseStats;
|
|
@@ -170,6 +178,7 @@ export declare class KnowledgeBaseManager {
|
|
|
170
178
|
private ensureVectorIndexFresh;
|
|
171
179
|
private hasUsableContent;
|
|
172
180
|
private defaultUploadRelativePath;
|
|
181
|
+
private validateUploadRelativePath;
|
|
173
182
|
private resolveKbRelativePath;
|
|
174
183
|
private normalizeRelativePath;
|
|
175
184
|
}
|
|
@@ -105,6 +105,7 @@ export class KnowledgeBaseManager {
|
|
|
105
105
|
const tracker = new ChangeTracker(this.store);
|
|
106
106
|
const diff = await tracker.computeDiff(diskFiles, this.classifier, this.kbPath);
|
|
107
107
|
const onlyRelativePaths = options.onlyRelativePaths ? new Set(options.onlyRelativePaths) : undefined;
|
|
108
|
+
let vectorDeletesApplied = 0;
|
|
108
109
|
if (onlyRelativePaths) {
|
|
109
110
|
diff.newFiles = diff.newFiles.filter(file => onlyRelativePaths.has(file.relativePath));
|
|
110
111
|
diff.modifiedFiles = diff.modifiedFiles.filter(file => onlyRelativePaths.has(file.relativePath));
|
|
@@ -118,18 +119,24 @@ export class KnowledgeBaseManager {
|
|
|
118
119
|
}
|
|
119
120
|
for (const deleted of diff.deletedFiles) {
|
|
120
121
|
await this.deleteVectorFile(deleted.collectionName, deleted.relativePath);
|
|
122
|
+
vectorDeletesApplied += 1;
|
|
121
123
|
this.store.deleteRecord(deleted.relativePath);
|
|
122
124
|
this.updateJobsForFile(deleted.relativePath, 'SUCCESS', 100, '文件已删除,索引记录和向量已清理');
|
|
123
125
|
}
|
|
124
126
|
const now = Date.now();
|
|
125
127
|
const indexedBefore = [...this.store.loadActiveRecords().values()];
|
|
126
128
|
const filesToIndex = [...diff.newFiles, ...diff.modifiedFiles];
|
|
129
|
+
const vectorRelativePaths = [];
|
|
130
|
+
const changedCollectionNames = new Set();
|
|
127
131
|
for (const [index, file] of filesToIndex.entries()) {
|
|
128
132
|
const hash = tracker.hashFile(file.absolutePath);
|
|
129
133
|
const duplicate = this.store.findExactDuplicate(hash, file.relativePath);
|
|
130
134
|
const collectionName = this.scope === 'global'
|
|
131
135
|
? this.collections.getCollectionName('global', file.category)
|
|
132
136
|
: this.collections.getCollectionName('project', file.category, this.projectId);
|
|
137
|
+
const previousRecord = indexedBefore.find(record => record.relativePath === file.relativePath);
|
|
138
|
+
if (previousRecord?.collectionName)
|
|
139
|
+
changedCollectionNames.add(previousRecord.collectionName);
|
|
133
140
|
const basePercent = filesToIndex.length === 0 ? 40 : 20 + Math.round((index / filesToIndex.length) * 45);
|
|
134
141
|
this.updateJobsForFile(file.relativePath, 'PARSING', basePercent, `正在解析 ${file.relativePath}`);
|
|
135
142
|
this.reportProgress({ stage: 'parsing', percent: basePercent, message: `正在解析 ${file.relativePath}`, filePath: file.relativePath });
|
|
@@ -244,6 +251,8 @@ export class KnowledgeBaseManager {
|
|
|
244
251
|
format: file.format,
|
|
245
252
|
collectionName,
|
|
246
253
|
});
|
|
254
|
+
vectorRelativePaths.push(file.relativePath);
|
|
255
|
+
changedCollectionNames.add(collectionName);
|
|
247
256
|
this.updateJobsForFile(file.relativePath, options.vectorMode === 'defer' ? 'SUCCESS' : 'INDEXING', options.vectorMode === 'defer' ? 100 : 85, options.vectorMode === 'defer' ? '解析和切片已完成' : '等待向量入库');
|
|
248
257
|
}
|
|
249
258
|
const stats = this.getStats();
|
|
@@ -257,7 +266,7 @@ export class KnowledgeBaseManager {
|
|
|
257
266
|
this.reportProgress({ stage: 'vectorizing', percent: 85, message: '解析和切片已完成,向量入库转入后台/稍后执行', chunkCount: stats.chunkCount, vectorStatus: this.getVectorStatus() });
|
|
258
267
|
}
|
|
259
268
|
else {
|
|
260
|
-
await this.ensureVectorIndexFresh(stats.chunkCount,
|
|
269
|
+
await this.ensureVectorIndexFresh(stats.chunkCount, { changedRelativePaths: vectorRelativePaths, changedCollectionNames, deletesApplied: vectorDeletesApplied });
|
|
261
270
|
}
|
|
262
271
|
this.lastSkippedFiles = diff.skippedFiles;
|
|
263
272
|
const vectorStatus = this.getVectorStatus();
|
|
@@ -417,7 +426,7 @@ export class KnowledgeBaseManager {
|
|
|
417
426
|
return this.incrementalIndex();
|
|
418
427
|
}
|
|
419
428
|
getUploadRelativePath(fileName, targetRelativePath) {
|
|
420
|
-
return targetRelativePath ? this.normalizeRelativePath(targetRelativePath) : this.defaultUploadRelativePath(fileName);
|
|
429
|
+
return this.validateUploadRelativePath(targetRelativePath ? this.normalizeRelativePath(targetRelativePath) : this.defaultUploadRelativePath(fileName));
|
|
421
430
|
}
|
|
422
431
|
async uploadFile(fileName, content, targetRelativePath, onProgress, options = {}) {
|
|
423
432
|
return this.uploadFiles([{ fileName, content, targetRelativePath }], onProgress, options);
|
|
@@ -439,6 +448,23 @@ export class KnowledgeBaseManager {
|
|
|
439
448
|
}
|
|
440
449
|
return jobs;
|
|
441
450
|
}
|
|
451
|
+
async stageUploadedFilePaths(files, operationId = `upload-${Date.now()}`, offset = 0) {
|
|
452
|
+
this.initialize();
|
|
453
|
+
const jobs = [];
|
|
454
|
+
for (let index = 0; index < files.length; index++) {
|
|
455
|
+
const file = files[index];
|
|
456
|
+
const relativePath = this.getUploadRelativePath(file.fileName, file.targetRelativePath);
|
|
457
|
+
const targetPath = this.resolveKbRelativePath(relativePath);
|
|
458
|
+
fs.mkdirSync(path.dirname(targetPath), { recursive: true });
|
|
459
|
+
fs.copyFileSync(file.sourcePath, targetPath);
|
|
460
|
+
const record = this.store.listRecords().find(item => item.relativePath === relativePath);
|
|
461
|
+
if (record)
|
|
462
|
+
await this.deleteVectorFile(record.collectionName, relativePath);
|
|
463
|
+
this.store.deleteRecord(relativePath);
|
|
464
|
+
jobs.push(this.store.enqueueIndexJob({ id: `${operationId}-${offset + index}`, relativePath, message: '文件已落盘,等待后台解析' }));
|
|
465
|
+
}
|
|
466
|
+
return jobs;
|
|
467
|
+
}
|
|
442
468
|
async uploadFiles(files, onProgress, options = {}) {
|
|
443
469
|
await this.stageUploadedFiles(files);
|
|
444
470
|
return this.incrementalIndex({ onProgress, vectorMode: options.vectorMode });
|
|
@@ -482,19 +508,36 @@ export class KnowledgeBaseManager {
|
|
|
482
508
|
return this.store.listIgnoreRules();
|
|
483
509
|
}
|
|
484
510
|
async indexVectors(options = {}) {
|
|
485
|
-
const chunks =
|
|
511
|
+
const chunks = options.relativePaths?.length
|
|
512
|
+
? options.relativePaths.flatMap(relativePath => this.store.listChunks({ collectionName: options.collectionName, relativePath }))
|
|
513
|
+
: this.store.listChunks(options);
|
|
486
514
|
const collectionNames = new Set(chunks.map(chunk => chunk.collectionName));
|
|
487
|
-
|
|
515
|
+
const cleanupCollectionNames = new Set([...collectionNames, ...(options.cleanupCollectionNames ?? [])]);
|
|
516
|
+
for (const collectionName of cleanupCollectionNames)
|
|
488
517
|
this.ensureVectorStore(collectionName);
|
|
489
|
-
if (!options.relativePath) {
|
|
518
|
+
if (options.rebuild || (!options.relativePath && !options.relativePaths?.length)) {
|
|
490
519
|
for (const collectionName of collectionNames)
|
|
491
520
|
await this.vectorStores.get(collectionName)?.clearCollection?.();
|
|
492
521
|
}
|
|
493
522
|
else {
|
|
494
|
-
for (const
|
|
495
|
-
|
|
523
|
+
for (const relativePath of options.relativePaths ?? [options.relativePath].filter(Boolean)) {
|
|
524
|
+
for (const collectionName of cleanupCollectionNames)
|
|
525
|
+
await this.vectorStores.get(collectionName)?.deleteByFilePath(relativePath, { persist: false });
|
|
526
|
+
}
|
|
527
|
+
if (chunks.length === 0) {
|
|
528
|
+
for (const collectionName of cleanupCollectionNames)
|
|
529
|
+
await this.vectorStores.get(collectionName)?.flush?.();
|
|
530
|
+
}
|
|
496
531
|
}
|
|
497
532
|
this.reportProgress({ stage: 'vectorizing', percent: 85, message: `正在写入 HNSWLib 向量库,共 ${chunks.length} 个切片`, chunkCount: chunks.length });
|
|
533
|
+
if (chunks.length === 0) {
|
|
534
|
+
const totalChunks = this.getStats().chunkCount;
|
|
535
|
+
this.store.setMetadata('vector_indexed_chunks', String(totalChunks));
|
|
536
|
+
this.store.setMetadata('vector_index_status', 'ready');
|
|
537
|
+
this.store.setMetadata('vector_index_error', '');
|
|
538
|
+
this.store.setMetadata('last_vector_index_at', String(Date.now()));
|
|
539
|
+
return [];
|
|
540
|
+
}
|
|
498
541
|
const indexer = new VectorIndexer(this.embeddingProvider, this.vectorStores);
|
|
499
542
|
let lastVectorPercent = -1;
|
|
500
543
|
try {
|
|
@@ -512,9 +555,10 @@ export class KnowledgeBaseManager {
|
|
|
512
555
|
});
|
|
513
556
|
const actualModel = results[0]?.embeddingModel ?? this.embeddingProvider.model;
|
|
514
557
|
const actualDimension = results[0]?.embeddingDimension ?? this.embeddingProvider.dimensions;
|
|
558
|
+
const totalChunks = this.getStats().chunkCount;
|
|
515
559
|
this.store.setMetadata('embedding_model', actualModel);
|
|
516
560
|
this.store.setMetadata('embedding_dimension', String(actualDimension));
|
|
517
|
-
this.store.setMetadata('vector_indexed_chunks', String(
|
|
561
|
+
this.store.setMetadata('vector_indexed_chunks', String(totalChunks));
|
|
518
562
|
this.store.setMetadata('vector_index_status', 'ready');
|
|
519
563
|
this.store.setMetadata('vector_index_error', '');
|
|
520
564
|
this.store.setMetadata('last_vector_index_at', String(Date.now()));
|
|
@@ -854,20 +898,34 @@ ${resultsText}
|
|
|
854
898
|
this.store.setMetadata('vector_index_error', error instanceof Error ? error.message : String(error));
|
|
855
899
|
}
|
|
856
900
|
}
|
|
857
|
-
async ensureVectorIndexFresh(chunkCount,
|
|
901
|
+
async ensureVectorIndexFresh(chunkCount, options = {}) {
|
|
858
902
|
if (chunkCount === 0)
|
|
859
903
|
return;
|
|
860
904
|
for (const record of this.store.listRecords())
|
|
861
905
|
this.ensureVectorStore(record.collectionName);
|
|
862
|
-
if ([...this.vectorStores.values()].some(store => store.needsRebuild?.())) {
|
|
863
|
-
await this.indexVectors();
|
|
906
|
+
if (options.rebuild || [...this.vectorStores.values()].some(store => store.needsRebuild?.())) {
|
|
907
|
+
await this.indexVectors({ rebuild: true });
|
|
864
908
|
return;
|
|
865
909
|
}
|
|
866
910
|
const indexedChunks = Number(this.store.getMetadata('vector_indexed_chunks') ?? 0);
|
|
867
911
|
const status = this.store.getMetadata('vector_index_status');
|
|
868
|
-
|
|
912
|
+
const changedRelativePaths = [...new Set(options.changedRelativePaths ?? [])];
|
|
913
|
+
if (changedRelativePaths.length === 0 && options.deletesApplied && status === 'ready') {
|
|
914
|
+
this.store.setMetadata('vector_indexed_chunks', String(chunkCount));
|
|
915
|
+
this.store.setMetadata('vector_index_status', 'ready');
|
|
916
|
+
this.store.setMetadata('vector_index_error', '');
|
|
917
|
+
this.store.setMetadata('last_vector_index_at', String(Date.now()));
|
|
918
|
+
return;
|
|
919
|
+
}
|
|
920
|
+
if (changedRelativePaths.length > 0 && status === 'ready') {
|
|
921
|
+
for (const collectionName of options.changedCollectionNames ?? [])
|
|
922
|
+
this.ensureVectorStore(collectionName);
|
|
923
|
+
await this.indexVectors({ relativePaths: changedRelativePaths, cleanupCollectionNames: options.changedCollectionNames });
|
|
869
924
|
return;
|
|
870
|
-
|
|
925
|
+
}
|
|
926
|
+
if (indexedChunks === chunkCount && status === 'ready')
|
|
927
|
+
return;
|
|
928
|
+
await this.indexVectors({ rebuild: true });
|
|
871
929
|
}
|
|
872
930
|
hasUsableContent(text, metadata) {
|
|
873
931
|
const coverage = String(metadata.contentCoverage ?? '');
|
|
@@ -881,6 +939,21 @@ ${resultsText}
|
|
|
881
939
|
const dir = configDirs[classification.category] ?? DEFAULT_CATEGORY_DIRS[classification.category];
|
|
882
940
|
return `${dir}/${path.basename(fileName)}`;
|
|
883
941
|
}
|
|
942
|
+
validateUploadRelativePath(relativePath) {
|
|
943
|
+
const normalized = this.normalizeRelativePath(relativePath);
|
|
944
|
+
if (!normalized || normalized === '.')
|
|
945
|
+
throw new Error('上传文件路径无效');
|
|
946
|
+
if (normalized.length > 1000)
|
|
947
|
+
throw new Error('上传文件路径过长,请缩短文件夹层级或文件名');
|
|
948
|
+
if (normalized.includes('\0'))
|
|
949
|
+
throw new Error('上传文件路径包含非法字符');
|
|
950
|
+
const parts = normalized.split('/');
|
|
951
|
+
if (parts.some(part => !part || part === '..'))
|
|
952
|
+
throw new Error('上传文件路径无效');
|
|
953
|
+
if (parts.some(part => part.length > 255))
|
|
954
|
+
throw new Error('上传文件名过长,请缩短文件名后重试');
|
|
955
|
+
return normalized;
|
|
956
|
+
}
|
|
884
957
|
resolveKbRelativePath(relativePath) {
|
|
885
958
|
const normalized = this.normalizeRelativePath(relativePath);
|
|
886
959
|
const targetPath = path.resolve(this.kbPath, normalized);
|
package/dist/index.d.ts
CHANGED
|
@@ -15,7 +15,7 @@ export { ensureProjectCustomizeFile, getProjectConfigPath, getProjectKbPath, Pro
|
|
|
15
15
|
export { ProjectRegistry } from './core/project-registry.js';
|
|
16
16
|
export { HNSWVectorStore } from './vector/hnsw-vector-store.js';
|
|
17
17
|
export { CollectionManager, globalCollectionName, projectCollectionName } from './vector/collection-manager.js';
|
|
18
|
-
export type { CollectionClient, VectorCollectionInfo, VectorDocument, VectorSearchQuery, VectorSearchResult, VectorStoreInterface } from './vector/types.js';
|
|
18
|
+
export type { CollectionClient, VectorCollectionInfo, VectorDocument, VectorSearchQuery, VectorSearchResult, VectorStoreInterface, VectorWriteOptions } from './vector/types.js';
|
|
19
19
|
export { VectorIndexer, type VectorIndexResult } from './vector/vector-indexer.js';
|
|
20
20
|
export { FederationSearch, type FederatedQuery, type FederatedResult, type FederatedSearchItem, type SearchFilters, type SearchScope } from './search/federation-search.js';
|
|
21
21
|
export type { LLMChatMessage, LLMChatOptions, LLMChatResponse, LLMSearchProvider } from './llm/llm-search-provider.js';
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import type { VectorDocument, VectorSearchQuery, VectorSearchResult, VectorStoreInterface } from './types.js';
|
|
1
|
+
import type { VectorDocument, VectorSearchQuery, VectorSearchResult, VectorStoreInterface, VectorWriteOptions } from './types.js';
|
|
2
2
|
/** HNSW(分层可导航小世界图)向量存储,基于 hnswlib-node 实现的高效近似最近邻搜索 */
|
|
3
3
|
export declare class HNSWVectorStore implements VectorStoreInterface {
|
|
4
4
|
readonly collectionName: string;
|
|
@@ -7,15 +7,18 @@ export declare class HNSWVectorStore implements VectorStoreInterface {
|
|
|
7
7
|
private readonly maxElements;
|
|
8
8
|
private index?;
|
|
9
9
|
private deletedSinceRebuild;
|
|
10
|
+
private dirty;
|
|
10
11
|
private readonly documents;
|
|
11
12
|
constructor(collectionName: string, indexPath: string, dimensions?: number, maxElements?: number);
|
|
12
13
|
ensureCollection(): Promise<void>;
|
|
13
|
-
upsert(documents: VectorDocument[]): Promise<void>;
|
|
14
|
+
upsert(documents: VectorDocument[], options?: VectorWriteOptions): Promise<void>;
|
|
14
15
|
clearCollection(): Promise<void>;
|
|
15
|
-
deleteByFilePath(filePath: string): Promise<void>;
|
|
16
|
+
deleteByFilePath(filePath: string, options?: VectorWriteOptions): Promise<void>;
|
|
17
|
+
flush(): Promise<void>;
|
|
16
18
|
needsRebuild(): boolean;
|
|
17
19
|
search(query: VectorSearchQuery): Promise<VectorSearchResult[]>;
|
|
18
20
|
private persist;
|
|
21
|
+
private toStoredDocument;
|
|
19
22
|
private loadDocuments;
|
|
20
23
|
private metadataPath;
|
|
21
24
|
}
|
|
@@ -10,6 +10,7 @@ export class HNSWVectorStore {
|
|
|
10
10
|
maxElements;
|
|
11
11
|
index;
|
|
12
12
|
deletedSinceRebuild = 0;
|
|
13
|
+
dirty = false;
|
|
13
14
|
documents = new Map();
|
|
14
15
|
constructor(collectionName, indexPath, dimensions = 512, maxElements = 500_000) {
|
|
15
16
|
this.collectionName = collectionName;
|
|
@@ -29,16 +30,18 @@ export class HNSWVectorStore {
|
|
|
29
30
|
else
|
|
30
31
|
this.index.initIndex(this.maxElements, 16, 200, 100, true);
|
|
31
32
|
}
|
|
32
|
-
async upsert(documents) {
|
|
33
|
+
async upsert(documents, options = {}) {
|
|
33
34
|
await this.ensureCollection();
|
|
34
35
|
for (const document of documents) {
|
|
35
36
|
const rowid = Number(document.metadata.sqlite_rowid);
|
|
36
37
|
if (!Number.isFinite(rowid) || rowid <= 0)
|
|
37
38
|
throw new Error(`HNSW 向量写入缺少有效 sqlite_rowid: ${document.id}`);
|
|
38
39
|
this.index.addPoint(document.embedding, rowid, true);
|
|
39
|
-
this.documents.set(rowid, document);
|
|
40
|
+
this.documents.set(rowid, this.toStoredDocument(document));
|
|
41
|
+
this.dirty = true;
|
|
40
42
|
}
|
|
41
|
-
|
|
43
|
+
if (options.persist !== false)
|
|
44
|
+
this.persist();
|
|
42
45
|
}
|
|
43
46
|
async clearCollection() {
|
|
44
47
|
if (fs.existsSync(this.indexPath))
|
|
@@ -47,10 +50,11 @@ export class HNSWVectorStore {
|
|
|
47
50
|
fs.rmSync(this.metadataPath(), { force: true });
|
|
48
51
|
this.documents.clear();
|
|
49
52
|
this.deletedSinceRebuild = 0;
|
|
53
|
+
this.dirty = false;
|
|
50
54
|
this.index = undefined;
|
|
51
55
|
await this.ensureCollection();
|
|
52
56
|
}
|
|
53
|
-
async deleteByFilePath(filePath) {
|
|
57
|
+
async deleteByFilePath(filePath, options = {}) {
|
|
54
58
|
await this.ensureCollection();
|
|
55
59
|
for (const [rowid, document] of this.documents.entries()) {
|
|
56
60
|
if (document.metadata.file_path === filePath) {
|
|
@@ -60,9 +64,16 @@ export class HNSWVectorStore {
|
|
|
60
64
|
}
|
|
61
65
|
catch { /* 忽略缺失的标签 */ }
|
|
62
66
|
this.documents.delete(rowid);
|
|
67
|
+
this.dirty = true;
|
|
63
68
|
}
|
|
64
69
|
}
|
|
65
|
-
|
|
70
|
+
if (options.persist !== false)
|
|
71
|
+
this.persist();
|
|
72
|
+
}
|
|
73
|
+
async flush() {
|
|
74
|
+
await this.ensureCollection();
|
|
75
|
+
if (this.dirty)
|
|
76
|
+
this.persist();
|
|
66
77
|
}
|
|
67
78
|
needsRebuild() {
|
|
68
79
|
const total = this.documents.size + this.deletedSinceRebuild;
|
|
@@ -85,6 +96,14 @@ export class HNSWVectorStore {
|
|
|
85
96
|
persist() {
|
|
86
97
|
this.index.writeIndexSync(this.indexPath);
|
|
87
98
|
fs.writeFileSync(this.metadataPath(), JSON.stringify({ deletedSinceRebuild: this.deletedSinceRebuild, documents: [...this.documents.entries()] }), 'utf8');
|
|
99
|
+
this.dirty = false;
|
|
100
|
+
}
|
|
101
|
+
toStoredDocument(document) {
|
|
102
|
+
return {
|
|
103
|
+
id: document.id,
|
|
104
|
+
content: '',
|
|
105
|
+
metadata: document.metadata,
|
|
106
|
+
};
|
|
88
107
|
}
|
|
89
108
|
loadDocuments() {
|
|
90
109
|
const file = this.metadataPath();
|
|
@@ -96,7 +115,7 @@ export class HNSWVectorStore {
|
|
|
96
115
|
this.deletedSinceRebuild = Array.isArray(parsed) ? 0 : Number(parsed.deletedSinceRebuild ?? 0);
|
|
97
116
|
this.documents.clear();
|
|
98
117
|
for (const [rowid, document] of entries)
|
|
99
|
-
this.documents.set(Number(rowid), document);
|
|
118
|
+
this.documents.set(Number(rowid), { id: document.id, content: document.content ?? '', metadata: document.metadata });
|
|
100
119
|
}
|
|
101
120
|
catch {
|
|
102
121
|
this.documents.clear();
|
package/dist/vector/types.d.ts
CHANGED
|
@@ -24,11 +24,15 @@ export interface VectorCollectionInfo {
|
|
|
24
24
|
metadata?: Record<string, unknown>;
|
|
25
25
|
}
|
|
26
26
|
/** 向量存储接口,定义所有向量存储实现必须支持的方法 */
|
|
27
|
+
export interface VectorWriteOptions {
|
|
28
|
+
persist?: boolean;
|
|
29
|
+
}
|
|
27
30
|
export interface VectorStoreInterface {
|
|
28
31
|
readonly collectionName: string;
|
|
29
32
|
ensureCollection(metadata?: Record<string, unknown>): Promise<void>;
|
|
30
|
-
upsert(documents: VectorDocument[]): Promise<void>;
|
|
31
|
-
deleteByFilePath(filePath: string): Promise<void>;
|
|
33
|
+
upsert(documents: VectorDocument[], options?: VectorWriteOptions): Promise<void>;
|
|
34
|
+
deleteByFilePath(filePath: string, options?: VectorWriteOptions): Promise<void>;
|
|
35
|
+
flush?(): Promise<void>;
|
|
32
36
|
clearCollection?(): Promise<void>;
|
|
33
37
|
needsRebuild?(): boolean;
|
|
34
38
|
search(query: VectorSearchQuery): Promise<VectorSearchResult[]>;
|
|
@@ -32,11 +32,12 @@ export class VectorIndexer {
|
|
|
32
32
|
const texts = batchChunks.map(chunk => chunk.content);
|
|
33
33
|
const embeddings = await this.embedDocuments(texts);
|
|
34
34
|
const documents = batchChunks.map((chunk, index) => this.toVectorDocument(chunk, embeddings[index] ?? []));
|
|
35
|
-
await store.upsert(documents);
|
|
35
|
+
await store.upsert(documents, { persist: options.persistEachBatch === true });
|
|
36
36
|
processedChunks += documents.length;
|
|
37
37
|
processedTotalChunks += documents.length;
|
|
38
38
|
options.onProgress?.({ collectionName, processedChunks: processedTotalChunks, totalChunks, batchSize: documents.length });
|
|
39
39
|
}
|
|
40
|
+
await store.flush?.();
|
|
40
41
|
results.push({
|
|
41
42
|
collectionName,
|
|
42
43
|
chunkCount: processedChunks,
|