@customize-agent/knowledge 4.0.1 → 4.0.3
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/bge-tokenizer.d.ts +12 -0
- package/dist/chunking/bge-tokenizer.js +70 -0
- package/dist/chunking/text-chunker.d.ts +20 -0
- package/dist/chunking/text-chunker.js +166 -53
- package/dist/classification/classifier.d.ts +1 -0
- package/dist/classification/classifier.js +1 -1
- package/dist/constants.d.ts +7 -0
- package/dist/constants.js +7 -0
- package/dist/core/change-tracker.d.ts +13 -0
- package/dist/core/change-tracker.js +13 -0
- package/dist/core/file-scanner.d.ts +13 -0
- package/dist/core/file-scanner.js +12 -0
- package/dist/core/index-state-store.d.ts +67 -0
- package/dist/core/index-state-store.js +204 -50
- package/dist/core/knowledge-base-manager.d.ts +24 -2
- package/dist/core/knowledge-base-manager.js +204 -52
- package/dist/core/multi-project-manager.d.ts +10 -0
- package/dist/core/multi-project-manager.js +21 -2
- package/dist/core/project-config.d.ts +4 -0
- package/dist/core/project-config.js +4 -0
- package/dist/core/project-id.d.ts +5 -0
- package/dist/core/project-id.js +5 -0
- package/dist/core/project-registry.d.ts +1 -0
- package/dist/core/project-registry.js +1 -0
- package/dist/dedup/dedup-engine.d.ts +3 -0
- package/dist/dedup/dedup-engine.js +1 -0
- package/dist/dedup/relationship-detector.d.ts +7 -0
- package/dist/dedup/relationship-detector.js +7 -0
- package/dist/embedding/embedding-provider.d.ts +32 -0
- package/dist/embedding/embedding-provider.js +136 -2
- package/dist/extraction/content-extractor.d.ts +32 -2
- package/dist/extraction/content-extractor.js +524 -124
- package/dist/extraction/external-extractor.d.ts +10 -0
- package/dist/extraction/external-extractor.js +6 -0
- package/dist/extraction/module-resolver.js +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +4 -4
- package/dist/search/federation-search.d.ts +8 -0
- package/dist/search/federation-search.js +2 -0
- package/dist/types.d.ts +10 -0
- package/dist/vector/collection-manager.d.ts +3 -0
- package/dist/vector/collection-manager.js +3 -0
- package/dist/vector/hnsw-vector-store.d.ts +21 -0
- package/dist/vector/hnsw-vector-store.js +108 -0
- package/dist/vector/types.d.ts +8 -0
- package/dist/vector/vector-indexer.d.ts +15 -1
- package/dist/vector/vector-indexer.js +34 -5
- package/models/bge-small-zh-v1.5/config.json +31 -0
- package/models/bge-small-zh-v1.5/onnx/model_quantized.onnx +0 -0
- package/models/bge-small-zh-v1.5/special_tokens_map.json +7 -0
- package/models/bge-small-zh-v1.5/tokenizer.json +21278 -0
- package/models/bge-small-zh-v1.5/tokenizer_config.json +15 -0
- package/package.json +16 -9
- package/scripts/install-hnsw.cjs +47 -0
- package/dist/vector/sqlite-vec-store.d.ts +0 -38
- package/dist/vector/sqlite-vec-store.js +0 -203
|
@@ -1,6 +1,18 @@
|
|
|
1
1
|
import type { TextChunk } from '../chunking/text-chunker.js';
|
|
2
2
|
import type { FileCategory, IndexStateRecord } from '../types.js';
|
|
3
|
+
export type KnowledgeJobStatus = 'PENDING' | 'PARSING' | 'CHUNKING' | 'INDEXING' | 'SUCCESS' | 'ERROR';
|
|
4
|
+
export interface KnowledgeIndexJob {
|
|
5
|
+
id: string;
|
|
6
|
+
relativePath: string;
|
|
7
|
+
status: KnowledgeJobStatus;
|
|
8
|
+
percent: number;
|
|
9
|
+
message: string;
|
|
10
|
+
errorMessage?: string;
|
|
11
|
+
createdAt: number;
|
|
12
|
+
updatedAt: number;
|
|
13
|
+
}
|
|
3
14
|
export interface StoredChunk {
|
|
15
|
+
rowid: number;
|
|
4
16
|
id: string;
|
|
5
17
|
relativePath: string;
|
|
6
18
|
chunkIndex: number;
|
|
@@ -34,6 +46,18 @@ export interface StoredParentChunk {
|
|
|
34
46
|
metadataJson?: string;
|
|
35
47
|
createdAt: number;
|
|
36
48
|
}
|
|
49
|
+
export interface StoredDocumentChunk {
|
|
50
|
+
id: string;
|
|
51
|
+
relativePath: string;
|
|
52
|
+
content: string;
|
|
53
|
+
category: FileCategory;
|
|
54
|
+
format: string;
|
|
55
|
+
collectionName: string;
|
|
56
|
+
parentCount: number;
|
|
57
|
+
chunkCount: number;
|
|
58
|
+
metadataJson?: string;
|
|
59
|
+
createdAt: number;
|
|
60
|
+
}
|
|
37
61
|
export interface FileHashRecord {
|
|
38
62
|
contentHash: string;
|
|
39
63
|
filePath: string;
|
|
@@ -60,14 +84,40 @@ export interface FileRelationship {
|
|
|
60
84
|
userConfirmed: number;
|
|
61
85
|
createdAt: number;
|
|
62
86
|
}
|
|
87
|
+
/** 索引状态存储器,使用 SQLite 管理知识库索引的持久化状态 */
|
|
63
88
|
export declare class IndexStateStore {
|
|
64
89
|
private readonly db;
|
|
65
90
|
private ftsEnabled;
|
|
66
91
|
constructor(dbPath: string);
|
|
92
|
+
/** 加载所有活跃的索引记录 */
|
|
67
93
|
loadActiveRecords(): Map<string, IndexStateRecord>;
|
|
94
|
+
/** 插入或更新索引记录 */
|
|
68
95
|
upsertRecord(record: IndexStateRecord): void;
|
|
96
|
+
/** 更新已验证文件的时间戳和状态 */
|
|
69
97
|
updateVerified(relativePath: string, mtime: number): void;
|
|
70
98
|
listRecords(): IndexStateRecord[];
|
|
99
|
+
enqueueIndexJob(job: {
|
|
100
|
+
id: string;
|
|
101
|
+
relativePath: string;
|
|
102
|
+
message?: string;
|
|
103
|
+
}): KnowledgeIndexJob;
|
|
104
|
+
updateIndexJob(id: string, patch: {
|
|
105
|
+
status?: KnowledgeJobStatus;
|
|
106
|
+
percent?: number;
|
|
107
|
+
message?: string;
|
|
108
|
+
errorMessage?: string;
|
|
109
|
+
}): void;
|
|
110
|
+
getIndexJob(id: string): KnowledgeIndexJob | undefined;
|
|
111
|
+
listPendingIndexJobs(limit?: number): KnowledgeIndexJob[];
|
|
112
|
+
countPendingIndexJobs(): number;
|
|
113
|
+
listActiveIndexJobsByPath(relativePath: string): KnowledgeIndexJob[];
|
|
114
|
+
listIndexJobsByPrefix(prefix: string): KnowledgeIndexJob[];
|
|
115
|
+
/**
|
|
116
|
+
* 替换指定文件的切片数据(使用事务批量更新)
|
|
117
|
+
* @param relativePath 文件相对路径
|
|
118
|
+
* @param chunks 文本切片列表
|
|
119
|
+
* @param file 文件分类信息
|
|
120
|
+
*/
|
|
71
121
|
replaceChunks(relativePath: string, chunks: TextChunk[], file: {
|
|
72
122
|
category: FileCategory;
|
|
73
123
|
format: string;
|
|
@@ -78,10 +128,19 @@ export declare class IndexStateStore {
|
|
|
78
128
|
relativePath?: string;
|
|
79
129
|
limit?: number;
|
|
80
130
|
}): StoredChunk[];
|
|
131
|
+
getChunkByRowid(rowid: number): StoredChunk | undefined;
|
|
132
|
+
getChunksByRowids(rowids: number[]): StoredChunk[];
|
|
81
133
|
getContextChunks(relativePath: string, chunkIndex: number, window?: number): StoredChunk[];
|
|
82
134
|
listParentChunks(relativePath: string): StoredParentChunk[];
|
|
83
135
|
getParentChunk(relativePath: string, parentId: string): StoredParentChunk | undefined;
|
|
136
|
+
getDocumentChunk(relativePath: string): StoredDocumentChunk | undefined;
|
|
84
137
|
getChunksByParent(relativePath: string, parentId: string, limit?: number): StoredChunk[];
|
|
138
|
+
/**
|
|
139
|
+
* 使用关键词搜索切片(支持 FTS5 全文搜索和 LIKE 模糊匹配)
|
|
140
|
+
* @param query 搜索查询
|
|
141
|
+
* @param limit 返回结果数量上限
|
|
142
|
+
* @returns 搜索结果列表(按相关性得分排序)
|
|
143
|
+
*/
|
|
85
144
|
searchChunks(query: string, limit?: number): ChunkSearchResult[];
|
|
86
145
|
private searchChunksFts;
|
|
87
146
|
private searchChunksLike;
|
|
@@ -106,6 +165,10 @@ export declare class IndexStateStore {
|
|
|
106
165
|
enabled: boolean;
|
|
107
166
|
createdAt: number;
|
|
108
167
|
}>;
|
|
168
|
+
/**
|
|
169
|
+
* 删除指定文件的全部索引数据(含切片、哈希、MinHash、标签、关系等)
|
|
170
|
+
* @param relativePath 文件相对路径
|
|
171
|
+
*/
|
|
109
172
|
deleteRecord(relativePath: string): void;
|
|
110
173
|
setMetadata(key: string, value: string): void;
|
|
111
174
|
getMetadata(key: string): string | undefined;
|
|
@@ -127,11 +190,15 @@ export declare class IndexStateStore {
|
|
|
127
190
|
private rowToFileHash;
|
|
128
191
|
private rowToRelationship;
|
|
129
192
|
private rowToParentChunk;
|
|
193
|
+
private rowToDocumentChunk;
|
|
130
194
|
private splitParentGroups;
|
|
195
|
+
private rowToJob;
|
|
131
196
|
private metadataString;
|
|
132
197
|
private rowToChunk;
|
|
133
198
|
private expandSearchTerms;
|
|
134
199
|
private toFtsQuery;
|
|
200
|
+
private mergeKeywordResults;
|
|
201
|
+
private chineseNgrams;
|
|
135
202
|
private bm25ToPositiveScore;
|
|
136
203
|
private scoreChunkDetailed;
|
|
137
204
|
private countOccurrences;
|
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import Database from 'better-sqlite3';
|
|
2
2
|
import * as fs from 'node:fs';
|
|
3
3
|
import * as path from 'node:path';
|
|
4
|
+
/** 索引状态存储器,使用 SQLite 管理知识库索引的持久化状态 */
|
|
4
5
|
export class IndexStateStore {
|
|
5
6
|
db;
|
|
6
7
|
ftsEnabled = false;
|
|
@@ -10,6 +11,7 @@ export class IndexStateStore {
|
|
|
10
11
|
this.db.pragma('journal_mode = WAL');
|
|
11
12
|
this.initTables();
|
|
12
13
|
}
|
|
14
|
+
/** 加载所有活跃的索引记录 */
|
|
13
15
|
loadActiveRecords() {
|
|
14
16
|
const rows = this.db.prepare(`
|
|
15
17
|
SELECT * FROM kb_index_state
|
|
@@ -20,6 +22,7 @@ export class IndexStateStore {
|
|
|
20
22
|
return [record.relativePath, record];
|
|
21
23
|
}));
|
|
22
24
|
}
|
|
25
|
+
/** 插入或更新索引记录 */
|
|
23
26
|
upsertRecord(record) {
|
|
24
27
|
this.db.prepare(`
|
|
25
28
|
INSERT INTO kb_index_state (
|
|
@@ -41,6 +44,7 @@ export class IndexStateStore {
|
|
|
41
44
|
metadata_json = excluded.metadata_json
|
|
42
45
|
`).run(record.relativePath, record.category, record.format, record.contentHash, record.fileSize, Math.round(record.mtime), record.chunkCount, record.collectionName, record.indexedAt, record.lastVerifiedAt, record.status, record.errorMessage ?? null, record.metadataJson ?? null);
|
|
43
46
|
}
|
|
47
|
+
/** 更新已验证文件的时间戳和状态 */
|
|
44
48
|
updateVerified(relativePath, mtime) {
|
|
45
49
|
this.db.prepare(`
|
|
46
50
|
UPDATE kb_index_state
|
|
@@ -56,11 +60,70 @@ export class IndexStateStore {
|
|
|
56
60
|
`).all();
|
|
57
61
|
return rows.map(row => this.rowToRecord(row));
|
|
58
62
|
}
|
|
63
|
+
enqueueIndexJob(job) {
|
|
64
|
+
const now = Date.now();
|
|
65
|
+
this.db.prepare(`
|
|
66
|
+
INSERT INTO kb_index_jobs (id, relative_path, status, percent, message, created_at, updated_at)
|
|
67
|
+
VALUES (?, ?, 'PENDING', 0, ?, ?, ?)
|
|
68
|
+
ON CONFLICT(id) DO UPDATE SET status = 'PENDING', percent = 0, message = excluded.message, error_message = NULL, updated_at = excluded.updated_at
|
|
69
|
+
`).run(job.id, job.relativePath, job.message ?? '等待后台索引', now, now);
|
|
70
|
+
return this.getIndexJob(job.id);
|
|
71
|
+
}
|
|
72
|
+
updateIndexJob(id, patch) {
|
|
73
|
+
const current = this.getIndexJob(id);
|
|
74
|
+
if (!current)
|
|
75
|
+
return;
|
|
76
|
+
this.db.prepare(`
|
|
77
|
+
UPDATE kb_index_jobs
|
|
78
|
+
SET status = ?, percent = ?, message = ?, error_message = ?, updated_at = ?
|
|
79
|
+
WHERE id = ?
|
|
80
|
+
`).run(patch.status ?? current.status, patch.percent ?? current.percent, patch.message ?? current.message, patch.errorMessage ?? null, Date.now(), id);
|
|
81
|
+
}
|
|
82
|
+
getIndexJob(id) {
|
|
83
|
+
const row = this.db.prepare('SELECT * FROM kb_index_jobs WHERE id = ?').get(id);
|
|
84
|
+
return row ? this.rowToJob(row) : undefined;
|
|
85
|
+
}
|
|
86
|
+
listPendingIndexJobs(limit = 20) {
|
|
87
|
+
const rows = this.db.prepare(`
|
|
88
|
+
SELECT * FROM kb_index_jobs
|
|
89
|
+
WHERE status = 'PENDING'
|
|
90
|
+
ORDER BY created_at ASC
|
|
91
|
+
LIMIT ?
|
|
92
|
+
`).all(limit);
|
|
93
|
+
return rows.map(row => this.rowToJob(row));
|
|
94
|
+
}
|
|
95
|
+
countPendingIndexJobs() {
|
|
96
|
+
const row = this.db.prepare("SELECT COUNT(*) as count FROM kb_index_jobs WHERE status = 'PENDING'").get();
|
|
97
|
+
return Number(row?.count ?? 0);
|
|
98
|
+
}
|
|
99
|
+
listActiveIndexJobsByPath(relativePath) {
|
|
100
|
+
const rows = this.db.prepare(`
|
|
101
|
+
SELECT * FROM kb_index_jobs
|
|
102
|
+
WHERE relative_path = ? AND status IN ('PENDING', 'PARSING', 'CHUNKING', 'INDEXING')
|
|
103
|
+
ORDER BY created_at ASC
|
|
104
|
+
`).all(relativePath);
|
|
105
|
+
return rows.map(row => this.rowToJob(row));
|
|
106
|
+
}
|
|
107
|
+
listIndexJobsByPrefix(prefix) {
|
|
108
|
+
const rows = this.db.prepare(`
|
|
109
|
+
SELECT * FROM kb_index_jobs
|
|
110
|
+
WHERE id LIKE ?
|
|
111
|
+
ORDER BY created_at ASC
|
|
112
|
+
`).all(`${prefix}%`);
|
|
113
|
+
return rows.map(row => this.rowToJob(row));
|
|
114
|
+
}
|
|
115
|
+
/**
|
|
116
|
+
* 替换指定文件的切片数据(使用事务批量更新)
|
|
117
|
+
* @param relativePath 文件相对路径
|
|
118
|
+
* @param chunks 文本切片列表
|
|
119
|
+
* @param file 文件分类信息
|
|
120
|
+
*/
|
|
59
121
|
replaceChunks(relativePath, chunks, file) {
|
|
60
122
|
const now = Date.now();
|
|
61
123
|
const transaction = this.db.transaction(() => {
|
|
62
124
|
this.db.prepare('DELETE FROM kb_chunks WHERE relative_path = ?').run(relativePath);
|
|
63
125
|
this.db.prepare('DELETE FROM kb_parent_chunks WHERE relative_path = ?').run(relativePath);
|
|
126
|
+
this.db.prepare('DELETE FROM kb_document_chunks WHERE relative_path = ?').run(relativePath);
|
|
64
127
|
if (this.ftsEnabled)
|
|
65
128
|
this.db.prepare('DELETE FROM kb_chunks_fts WHERE relative_path = ?').run(relativePath);
|
|
66
129
|
const insert = this.db.prepare(`
|
|
@@ -78,6 +141,12 @@ export class IndexStateStore {
|
|
|
78
141
|
id, relative_path, parent_id, content, category, format,
|
|
79
142
|
collection_name, section_title, chunk_count, metadata_json, created_at
|
|
80
143
|
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
144
|
+
`);
|
|
145
|
+
const insertDocument = this.db.prepare(`
|
|
146
|
+
INSERT INTO kb_document_chunks (
|
|
147
|
+
id, relative_path, content, category, format,
|
|
148
|
+
collection_name, parent_count, chunk_count, metadata_json, created_at
|
|
149
|
+
) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
|
|
81
150
|
`);
|
|
82
151
|
const parentGroups = new Map();
|
|
83
152
|
const groupedChunks = this.splitParentGroups(relativePath, chunks);
|
|
@@ -87,9 +156,13 @@ export class IndexStateStore {
|
|
|
87
156
|
group.push(chunk);
|
|
88
157
|
parentGroups.set(parentId, group);
|
|
89
158
|
}
|
|
159
|
+
const parentContents = [];
|
|
90
160
|
for (const [parentId, group] of parentGroups.entries()) {
|
|
91
|
-
|
|
161
|
+
const parentContent = group.map(chunk => chunk.text).join('\n\n---\n\n');
|
|
162
|
+
parentContents.push(parentContent);
|
|
163
|
+
insertParent.run(parentId, relativePath, parentId, parentContent, file.category, file.format, file.collectionName, group.find(chunk => chunk.sectionTitle)?.sectionTitle ?? null, group.length, JSON.stringify({ parentId, splitStrategy: this.metadataString(group[0]?.metadata.splitStrategy), chunkKind: this.metadataString(group[0]?.metadata.chunkKind) }), now);
|
|
92
164
|
}
|
|
165
|
+
insertDocument.run(`${relativePath}#document`, relativePath, parentContents.join('\n\n=== SECTION ===\n\n'), file.category, file.format, file.collectionName, parentGroups.size, groupedChunks.length, JSON.stringify({ parentType: 'document', splitStrategy: 'document_section_child_v1' }), now);
|
|
93
166
|
for (const chunk of groupedChunks) {
|
|
94
167
|
const chunkId = `${relativePath}#${chunk.index}`;
|
|
95
168
|
insert.run(chunkId, relativePath, chunk.index, chunk.text, file.category, file.format, file.collectionName, chunk.tokenCount, chunk.sectionTitle ?? null, JSON.stringify(chunk.metadata), now);
|
|
@@ -114,16 +187,28 @@ export class IndexStateStore {
|
|
|
114
187
|
if (options.limit)
|
|
115
188
|
params.push(options.limit);
|
|
116
189
|
const rows = this.db.prepare(`
|
|
117
|
-
SELECT * FROM kb_chunks
|
|
190
|
+
SELECT rowid, * FROM kb_chunks
|
|
118
191
|
${where}
|
|
119
192
|
ORDER BY relative_path, chunk_index
|
|
120
193
|
${limit}
|
|
121
194
|
`).all(...params);
|
|
122
195
|
return rows.map(row => this.rowToChunk(row, 0));
|
|
123
196
|
}
|
|
197
|
+
getChunkByRowid(rowid) {
|
|
198
|
+
const row = this.db.prepare('SELECT rowid, * FROM kb_chunks WHERE rowid = ?').get(rowid);
|
|
199
|
+
return row ? this.rowToChunk(row, 0) : undefined;
|
|
200
|
+
}
|
|
201
|
+
getChunksByRowids(rowids) {
|
|
202
|
+
if (rowids.length === 0)
|
|
203
|
+
return [];
|
|
204
|
+
const placeholders = rowids.map(() => '?').join(',');
|
|
205
|
+
const rows = this.db.prepare(`SELECT rowid, * FROM kb_chunks WHERE rowid IN (${placeholders})`).all(...rowids);
|
|
206
|
+
const byRowid = new Map(rows.map(row => [Number(row.rowid), this.rowToChunk(row, 0)]));
|
|
207
|
+
return rowids.flatMap(rowid => byRowid.get(rowid) ?? []);
|
|
208
|
+
}
|
|
124
209
|
getContextChunks(relativePath, chunkIndex, window = 1) {
|
|
125
210
|
const rows = this.db.prepare(`
|
|
126
|
-
SELECT * FROM kb_chunks
|
|
211
|
+
SELECT rowid, * FROM kb_chunks
|
|
127
212
|
WHERE relative_path = ? AND chunk_index BETWEEN ? AND ?
|
|
128
213
|
ORDER BY chunk_index
|
|
129
214
|
`).all(relativePath, Math.max(0, chunkIndex - window), chunkIndex + window);
|
|
@@ -145,25 +230,38 @@ export class IndexStateStore {
|
|
|
145
230
|
`).get(relativePath, parentId);
|
|
146
231
|
return row ? this.rowToParentChunk(row) : undefined;
|
|
147
232
|
}
|
|
233
|
+
getDocumentChunk(relativePath) {
|
|
234
|
+
const row = this.db.prepare(`
|
|
235
|
+
SELECT * FROM kb_document_chunks
|
|
236
|
+
WHERE relative_path = ?
|
|
237
|
+
LIMIT 1
|
|
238
|
+
`).get(relativePath);
|
|
239
|
+
return row ? this.rowToDocumentChunk(row) : undefined;
|
|
240
|
+
}
|
|
148
241
|
getChunksByParent(relativePath, parentId, limit = 6) {
|
|
149
242
|
const rows = this.db.prepare(`
|
|
150
|
-
SELECT * FROM kb_chunks
|
|
243
|
+
SELECT rowid, * FROM kb_chunks
|
|
151
244
|
WHERE relative_path = ? AND metadata_json LIKE ?
|
|
152
245
|
ORDER BY chunk_index
|
|
153
246
|
LIMIT ?
|
|
154
247
|
`).all(relativePath, `%"parentId":"${parentId.replace(/[%_]/gu, '')}"%`, limit);
|
|
155
248
|
return rows.map(row => this.rowToChunk(row, 0));
|
|
156
249
|
}
|
|
250
|
+
/**
|
|
251
|
+
* 使用关键词搜索切片(支持 FTS5 全文搜索和 LIKE 模糊匹配)
|
|
252
|
+
* @param query 搜索查询
|
|
253
|
+
* @param limit 返回结果数量上限
|
|
254
|
+
* @returns 搜索结果列表(按相关性得分排序)
|
|
255
|
+
*/
|
|
157
256
|
searchChunks(query, limit = 10) {
|
|
158
257
|
const terms = this.expandSearchTerms(query);
|
|
159
258
|
if (terms.length === 0)
|
|
160
259
|
return [];
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
return this.searchChunksLike(terms, limit);
|
|
260
|
+
const results = [
|
|
261
|
+
...(this.ftsEnabled ? this.searchChunksFts(terms, limit) : []),
|
|
262
|
+
...this.searchChunksLike(terms, limit),
|
|
263
|
+
];
|
|
264
|
+
return this.mergeKeywordResults(results, limit);
|
|
167
265
|
}
|
|
168
266
|
searchChunksFts(terms, limit) {
|
|
169
267
|
try {
|
|
@@ -171,7 +269,7 @@ export class IndexStateStore {
|
|
|
171
269
|
if (!matchQuery)
|
|
172
270
|
return [];
|
|
173
271
|
const rows = this.db.prepare(`
|
|
174
|
-
SELECT c.*, bm25(kb_chunks_fts, 1.2, 0.8, 0.6, 1.0, 2.0) as bm25_score
|
|
272
|
+
SELECT c.rowid, c.*, bm25(kb_chunks_fts, 1.2, 0.8, 0.6, 1.0, 2.0) as bm25_score
|
|
175
273
|
FROM kb_chunks_fts
|
|
176
274
|
INNER JOIN kb_chunks c ON c.id = kb_chunks_fts.id
|
|
177
275
|
WHERE kb_chunks_fts MATCH ?
|
|
@@ -194,7 +292,7 @@ export class IndexStateStore {
|
|
|
194
292
|
}
|
|
195
293
|
searchChunksLike(terms, limit) {
|
|
196
294
|
const rows = this.db.prepare(`
|
|
197
|
-
SELECT * FROM kb_chunks
|
|
295
|
+
SELECT rowid, * FROM kb_chunks
|
|
198
296
|
WHERE ${terms.map(() => '(LOWER(content) LIKE ? OR LOWER(relative_path) LIKE ? OR LOWER(category) LIKE ? OR LOWER(format) LIKE ?)').join(' OR ')}
|
|
199
297
|
ORDER BY created_at DESC
|
|
200
298
|
LIMIT ?
|
|
@@ -328,9 +426,14 @@ export class IndexStateStore {
|
|
|
328
426
|
createdAt: Number(row.created_at),
|
|
329
427
|
}));
|
|
330
428
|
}
|
|
429
|
+
/**
|
|
430
|
+
* 删除指定文件的全部索引数据(含切片、哈希、MinHash、标签、关系等)
|
|
431
|
+
* @param relativePath 文件相对路径
|
|
432
|
+
*/
|
|
331
433
|
deleteRecord(relativePath) {
|
|
332
434
|
this.db.prepare('DELETE FROM kb_chunks WHERE relative_path = ?').run(relativePath);
|
|
333
435
|
this.db.prepare('DELETE FROM kb_parent_chunks WHERE relative_path = ?').run(relativePath);
|
|
436
|
+
this.db.prepare('DELETE FROM kb_document_chunks WHERE relative_path = ?').run(relativePath);
|
|
334
437
|
if (this.ftsEnabled)
|
|
335
438
|
this.db.prepare('DELETE FROM kb_chunks_fts WHERE relative_path = ?').run(relativePath);
|
|
336
439
|
this.db.prepare('DELETE FROM kb_index_state WHERE relative_path = ?').run(relativePath);
|
|
@@ -398,7 +501,8 @@ export class IndexStateStore {
|
|
|
398
501
|
CREATE INDEX IF NOT EXISTS idx_kb_state_collection ON kb_index_state(collection_name);
|
|
399
502
|
|
|
400
503
|
CREATE TABLE IF NOT EXISTS kb_chunks (
|
|
401
|
-
|
|
504
|
+
rowid INTEGER PRIMARY KEY AUTOINCREMENT,
|
|
505
|
+
id TEXT NOT NULL UNIQUE,
|
|
402
506
|
relative_path TEXT NOT NULL,
|
|
403
507
|
chunk_index INTEGER NOT NULL,
|
|
404
508
|
content TEXT NOT NULL,
|
|
@@ -430,6 +534,20 @@ export class IndexStateStore {
|
|
|
430
534
|
CREATE INDEX IF NOT EXISTS idx_kb_parent_path ON kb_parent_chunks(relative_path);
|
|
431
535
|
CREATE INDEX IF NOT EXISTS idx_kb_parent_id ON kb_parent_chunks(parent_id);
|
|
432
536
|
|
|
537
|
+
CREATE TABLE IF NOT EXISTS kb_document_chunks (
|
|
538
|
+
id TEXT PRIMARY KEY,
|
|
539
|
+
relative_path TEXT NOT NULL,
|
|
540
|
+
content TEXT NOT NULL,
|
|
541
|
+
category TEXT NOT NULL,
|
|
542
|
+
format TEXT NOT NULL,
|
|
543
|
+
collection_name TEXT NOT NULL,
|
|
544
|
+
parent_count INTEGER NOT NULL,
|
|
545
|
+
chunk_count INTEGER NOT NULL,
|
|
546
|
+
metadata_json TEXT,
|
|
547
|
+
created_at INTEGER NOT NULL
|
|
548
|
+
);
|
|
549
|
+
CREATE INDEX IF NOT EXISTS idx_kb_document_path ON kb_document_chunks(relative_path);
|
|
550
|
+
|
|
433
551
|
CREATE TABLE IF NOT EXISTS kb_file_hashes (
|
|
434
552
|
content_hash TEXT NOT NULL,
|
|
435
553
|
file_path TEXT PRIMARY KEY,
|
|
@@ -481,6 +599,19 @@ export class IndexStateStore {
|
|
|
481
599
|
);
|
|
482
600
|
CREATE INDEX IF NOT EXISTS idx_tags_tag ON kb_tags(tag);
|
|
483
601
|
|
|
602
|
+
CREATE TABLE IF NOT EXISTS kb_index_jobs (
|
|
603
|
+
id TEXT PRIMARY KEY,
|
|
604
|
+
relative_path TEXT NOT NULL,
|
|
605
|
+
status TEXT NOT NULL,
|
|
606
|
+
percent INTEGER NOT NULL DEFAULT 0,
|
|
607
|
+
message TEXT NOT NULL DEFAULT '',
|
|
608
|
+
error_message TEXT,
|
|
609
|
+
created_at INTEGER NOT NULL,
|
|
610
|
+
updated_at INTEGER NOT NULL
|
|
611
|
+
);
|
|
612
|
+
CREATE INDEX IF NOT EXISTS idx_kb_jobs_status ON kb_index_jobs(status);
|
|
613
|
+
CREATE INDEX IF NOT EXISTS idx_kb_jobs_path ON kb_index_jobs(relative_path);
|
|
614
|
+
|
|
484
615
|
CREATE TABLE IF NOT EXISTS kb_metadata (
|
|
485
616
|
key TEXT PRIMARY KEY,
|
|
486
617
|
value TEXT NOT NULL
|
|
@@ -575,7 +706,7 @@ export class IndexStateStore {
|
|
|
575
706
|
relativePath: String(row.relative_path),
|
|
576
707
|
parentId: String(row.parent_id),
|
|
577
708
|
content: String(row.content),
|
|
578
|
-
category:
|
|
709
|
+
category: row.category,
|
|
579
710
|
format: String(row.format),
|
|
580
711
|
collectionName: String(row.collection_name),
|
|
581
712
|
sectionTitle: row.section_title == null ? undefined : String(row.section_title),
|
|
@@ -584,45 +715,41 @@ export class IndexStateStore {
|
|
|
584
715
|
createdAt: Number(row.created_at),
|
|
585
716
|
};
|
|
586
717
|
}
|
|
587
|
-
|
|
588
|
-
|
|
589
|
-
|
|
590
|
-
|
|
591
|
-
|
|
592
|
-
|
|
593
|
-
|
|
594
|
-
|
|
595
|
-
|
|
596
|
-
|
|
597
|
-
|
|
598
|
-
|
|
599
|
-
|
|
600
|
-
|
|
601
|
-
|
|
602
|
-
|
|
603
|
-
|
|
604
|
-
|
|
605
|
-
|
|
606
|
-
|
|
607
|
-
|
|
608
|
-
|
|
609
|
-
|
|
610
|
-
|
|
611
|
-
|
|
612
|
-
|
|
613
|
-
|
|
614
|
-
|
|
615
|
-
tokenCount += chunk.tokenCount;
|
|
616
|
-
}
|
|
617
|
-
flush();
|
|
618
|
-
}
|
|
619
|
-
return result.sort((a, b) => a.index - b.index);
|
|
718
|
+
rowToDocumentChunk(row) {
|
|
719
|
+
return {
|
|
720
|
+
id: String(row.id),
|
|
721
|
+
relativePath: String(row.relative_path),
|
|
722
|
+
content: String(row.content),
|
|
723
|
+
category: row.category,
|
|
724
|
+
format: String(row.format),
|
|
725
|
+
collectionName: String(row.collection_name),
|
|
726
|
+
parentCount: Number(row.parent_count),
|
|
727
|
+
chunkCount: Number(row.chunk_count),
|
|
728
|
+
metadataJson: row.metadata_json == null ? undefined : String(row.metadata_json),
|
|
729
|
+
createdAt: Number(row.created_at),
|
|
730
|
+
};
|
|
731
|
+
}
|
|
732
|
+
splitParentGroups(_relativePath, chunks) {
|
|
733
|
+
return chunks;
|
|
734
|
+
}
|
|
735
|
+
rowToJob(row) {
|
|
736
|
+
return {
|
|
737
|
+
id: String(row.id),
|
|
738
|
+
relativePath: String(row.relative_path),
|
|
739
|
+
status: String(row.status),
|
|
740
|
+
percent: Number(row.percent),
|
|
741
|
+
message: String(row.message ?? ''),
|
|
742
|
+
errorMessage: row.error_message == null ? undefined : String(row.error_message),
|
|
743
|
+
createdAt: Number(row.created_at),
|
|
744
|
+
updatedAt: Number(row.updated_at),
|
|
745
|
+
};
|
|
620
746
|
}
|
|
621
747
|
metadataString(value) {
|
|
622
748
|
return typeof value === 'string' ? value : undefined;
|
|
623
749
|
}
|
|
624
750
|
rowToChunk(row, score, scoreDetails) {
|
|
625
751
|
return {
|
|
752
|
+
rowid: Number(row.rowid ?? 0),
|
|
626
753
|
id: String(row.id),
|
|
627
754
|
relativePath: String(row.relative_path),
|
|
628
755
|
chunkIndex: Number(row.chunk_index),
|
|
@@ -641,8 +768,13 @@ export class IndexStateStore {
|
|
|
641
768
|
expandSearchTerms(query) {
|
|
642
769
|
const normalized = query.toLowerCase().trim();
|
|
643
770
|
const terms = new Set(normalized ? [normalized] : []);
|
|
644
|
-
for (const term of normalized.split(/[\s,,。;;::、]+/u).filter(Boolean))
|
|
771
|
+
for (const term of normalized.split(/[\s,,。;;::、]+/u).filter(Boolean)) {
|
|
645
772
|
terms.add(term);
|
|
773
|
+
for (const gram of this.chineseNgrams(term))
|
|
774
|
+
terms.add(gram);
|
|
775
|
+
}
|
|
776
|
+
for (const gram of this.chineseNgrams(normalized))
|
|
777
|
+
terms.add(gram);
|
|
646
778
|
const synonyms = {
|
|
647
779
|
招标: ['招标', '投标', '标书', '招标文件', '投标文件', 'bid', 'tender', 'bidding'],
|
|
648
780
|
投标: ['招标', '投标', '标书', '招标文件', '投标文件', 'bid', 'tender', 'bidding'],
|
|
@@ -656,7 +788,7 @@ export class IndexStateStore {
|
|
|
656
788
|
for (const value of values)
|
|
657
789
|
terms.add(value.toLowerCase());
|
|
658
790
|
}
|
|
659
|
-
return [...terms];
|
|
791
|
+
return [...terms].filter(term => term.length > 0).slice(0, 40);
|
|
660
792
|
}
|
|
661
793
|
toFtsQuery(terms) {
|
|
662
794
|
const normalized = terms.map(term => term.replace(/["*^:(){}\]\\[]/gu, ' ').trim()).filter(term => term.length > 0);
|
|
@@ -664,10 +796,32 @@ export class IndexStateStore {
|
|
|
664
796
|
const weak = normalized.slice(1).filter(term => term.length >= 2).slice(0, 12);
|
|
665
797
|
return [exact ? `"${exact}"` : '', ...weak.map(term => `"${term}"`)].filter(Boolean).join(' OR ');
|
|
666
798
|
}
|
|
799
|
+
mergeKeywordResults(results, limit) {
|
|
800
|
+
const byId = new Map();
|
|
801
|
+
for (const result of results) {
|
|
802
|
+
const existing = byId.get(result.id);
|
|
803
|
+
if (!existing || result.score > existing.score)
|
|
804
|
+
byId.set(result.id, result);
|
|
805
|
+
}
|
|
806
|
+
return [...byId.values()].sort((a, b) => b.score - a.score).slice(0, limit);
|
|
807
|
+
}
|
|
808
|
+
chineseNgrams(text) {
|
|
809
|
+
const han = text.match(/[\p{Script=Han}]+/gu) ?? [];
|
|
810
|
+
const grams = [];
|
|
811
|
+
for (const token of han) {
|
|
812
|
+
for (let size = 2; size <= 3; size++) {
|
|
813
|
+
if (token.length <= size)
|
|
814
|
+
continue;
|
|
815
|
+
for (let index = 0; index <= token.length - size; index++)
|
|
816
|
+
grams.push(token.slice(index, index + size));
|
|
817
|
+
}
|
|
818
|
+
}
|
|
819
|
+
return grams;
|
|
820
|
+
}
|
|
667
821
|
bm25ToPositiveScore(score) {
|
|
668
822
|
if (!Number.isFinite(score))
|
|
669
823
|
return 0;
|
|
670
|
-
return 1 / (1 +
|
|
824
|
+
return score < 0 ? -score : 1 / (1 + score);
|
|
671
825
|
}
|
|
672
826
|
scoreChunkDetailed(content, terms) {
|
|
673
827
|
const lower = content.toLowerCase();
|
|
@@ -34,7 +34,7 @@ export declare class KnowledgeBaseManager {
|
|
|
34
34
|
readonly projectId?: string;
|
|
35
35
|
readonly kbPath: string;
|
|
36
36
|
readonly store: IndexStateStore;
|
|
37
|
-
private readonly
|
|
37
|
+
private readonly vectorRoot;
|
|
38
38
|
private readonly classifier;
|
|
39
39
|
private readonly scanner;
|
|
40
40
|
private readonly collections;
|
|
@@ -51,9 +51,19 @@ export declare class KnowledgeBaseManager {
|
|
|
51
51
|
private onProgress?;
|
|
52
52
|
constructor(options: KnowledgeBaseManagerOptions);
|
|
53
53
|
initialize(): void;
|
|
54
|
+
forceReindexAll(options?: {
|
|
55
|
+
onProgress?: (progress: KnowledgeIndexProgress) => void;
|
|
56
|
+
vectorMode?: 'sync' | 'defer';
|
|
57
|
+
}): Promise<DiffResult>;
|
|
58
|
+
consumePendingIndexJobs(options?: {
|
|
59
|
+
onProgress?: (progress: KnowledgeIndexProgress) => void;
|
|
60
|
+
vectorMode?: 'sync' | 'defer';
|
|
61
|
+
limit?: number;
|
|
62
|
+
}): Promise<DiffResult>;
|
|
54
63
|
incrementalIndex(options?: {
|
|
55
64
|
onProgress?: (progress: KnowledgeIndexProgress) => void;
|
|
56
65
|
vectorMode?: 'sync' | 'defer';
|
|
66
|
+
onlyRelativePaths?: string[];
|
|
57
67
|
}): Promise<DiffResult>;
|
|
58
68
|
search(query: string, limit?: number): ChunkSearchResult[];
|
|
59
69
|
keywordSearchItems(query: string, limit?: number): FederatedSearchItem[];
|
|
@@ -90,6 +100,11 @@ export declare class KnowledgeBaseManager {
|
|
|
90
100
|
uploadFile(fileName: string, content: Buffer, targetRelativePath?: string, onProgress?: (progress: KnowledgeIndexProgress) => void, options?: {
|
|
91
101
|
vectorMode?: 'sync' | 'defer';
|
|
92
102
|
}): Promise<DiffResult>;
|
|
103
|
+
stageUploadedFiles(files: Array<{
|
|
104
|
+
fileName: string;
|
|
105
|
+
content: Buffer;
|
|
106
|
+
targetRelativePath?: string;
|
|
107
|
+
}>, operationId?: string): Promise<import("./index-state-store.js").KnowledgeIndexJob[]>;
|
|
93
108
|
uploadFiles(files: Array<{
|
|
94
109
|
fileName: string;
|
|
95
110
|
content: Buffer;
|
|
@@ -119,6 +134,9 @@ export declare class KnowledgeBaseManager {
|
|
|
119
134
|
}): Promise<VectorIndexResult[]>;
|
|
120
135
|
getProjectConfig(): ProjectConfig | undefined;
|
|
121
136
|
getStats(): KnowledgeBaseStats;
|
|
137
|
+
listIndexJobsByPrefix(prefix: string): import("./index-state-store.js").KnowledgeIndexJob[];
|
|
138
|
+
countPendingIndexJobs(): number;
|
|
139
|
+
failPendingIndexJobs(message: string): void;
|
|
122
140
|
getVectorStatus(): {
|
|
123
141
|
status: string;
|
|
124
142
|
error?: string;
|
|
@@ -131,8 +149,11 @@ export declare class KnowledgeBaseManager {
|
|
|
131
149
|
private retrievalWeights;
|
|
132
150
|
private heuristicRerank;
|
|
133
151
|
private llmRerank;
|
|
152
|
+
private hydrateVectorResultsFromSqlite;
|
|
134
153
|
private toFederatedItem;
|
|
135
|
-
private
|
|
154
|
+
private mergeHybridRankedLists;
|
|
155
|
+
private mergeContexts;
|
|
156
|
+
private contextKey;
|
|
136
157
|
private parseChunkIndex;
|
|
137
158
|
private parseMetadataString;
|
|
138
159
|
private parseMetadata;
|
|
@@ -140,6 +161,7 @@ export declare class KnowledgeBaseManager {
|
|
|
140
161
|
private metadataFacets;
|
|
141
162
|
close(): void;
|
|
142
163
|
private reportProgress;
|
|
164
|
+
private updateJobsForFile;
|
|
143
165
|
private ensureVectorStore;
|
|
144
166
|
private deleteVectorFile;
|
|
145
167
|
private ensureVectorIndexFresh;
|