@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.
Files changed (56) hide show
  1. package/dist/chunking/bge-tokenizer.d.ts +12 -0
  2. package/dist/chunking/bge-tokenizer.js +70 -0
  3. package/dist/chunking/text-chunker.d.ts +20 -0
  4. package/dist/chunking/text-chunker.js +166 -53
  5. package/dist/classification/classifier.d.ts +1 -0
  6. package/dist/classification/classifier.js +1 -1
  7. package/dist/constants.d.ts +7 -0
  8. package/dist/constants.js +7 -0
  9. package/dist/core/change-tracker.d.ts +13 -0
  10. package/dist/core/change-tracker.js +13 -0
  11. package/dist/core/file-scanner.d.ts +13 -0
  12. package/dist/core/file-scanner.js +12 -0
  13. package/dist/core/index-state-store.d.ts +67 -0
  14. package/dist/core/index-state-store.js +204 -50
  15. package/dist/core/knowledge-base-manager.d.ts +24 -2
  16. package/dist/core/knowledge-base-manager.js +204 -52
  17. package/dist/core/multi-project-manager.d.ts +10 -0
  18. package/dist/core/multi-project-manager.js +21 -2
  19. package/dist/core/project-config.d.ts +4 -0
  20. package/dist/core/project-config.js +4 -0
  21. package/dist/core/project-id.d.ts +5 -0
  22. package/dist/core/project-id.js +5 -0
  23. package/dist/core/project-registry.d.ts +1 -0
  24. package/dist/core/project-registry.js +1 -0
  25. package/dist/dedup/dedup-engine.d.ts +3 -0
  26. package/dist/dedup/dedup-engine.js +1 -0
  27. package/dist/dedup/relationship-detector.d.ts +7 -0
  28. package/dist/dedup/relationship-detector.js +7 -0
  29. package/dist/embedding/embedding-provider.d.ts +32 -0
  30. package/dist/embedding/embedding-provider.js +136 -2
  31. package/dist/extraction/content-extractor.d.ts +32 -2
  32. package/dist/extraction/content-extractor.js +524 -124
  33. package/dist/extraction/external-extractor.d.ts +10 -0
  34. package/dist/extraction/external-extractor.js +6 -0
  35. package/dist/extraction/module-resolver.js +2 -2
  36. package/dist/index.d.ts +2 -2
  37. package/dist/index.js +4 -4
  38. package/dist/search/federation-search.d.ts +8 -0
  39. package/dist/search/federation-search.js +2 -0
  40. package/dist/types.d.ts +10 -0
  41. package/dist/vector/collection-manager.d.ts +3 -0
  42. package/dist/vector/collection-manager.js +3 -0
  43. package/dist/vector/hnsw-vector-store.d.ts +21 -0
  44. package/dist/vector/hnsw-vector-store.js +108 -0
  45. package/dist/vector/types.d.ts +8 -0
  46. package/dist/vector/vector-indexer.d.ts +15 -1
  47. package/dist/vector/vector-indexer.js +34 -5
  48. package/models/bge-small-zh-v1.5/config.json +31 -0
  49. package/models/bge-small-zh-v1.5/onnx/model_quantized.onnx +0 -0
  50. package/models/bge-small-zh-v1.5/special_tokens_map.json +7 -0
  51. package/models/bge-small-zh-v1.5/tokenizer.json +21278 -0
  52. package/models/bge-small-zh-v1.5/tokenizer_config.json +15 -0
  53. package/package.json +16 -9
  54. package/scripts/install-hnsw.cjs +47 -0
  55. package/dist/vector/sqlite-vec-store.d.ts +0 -38
  56. package/dist/vector/sqlite-vec-store.js +0 -203
@@ -1,9 +1,11 @@
1
1
  import type { ClassifiedFile, FileCategory } from '../types.js';
2
+ /** 外部解析器提取结果 */
2
3
  export interface ExternalExtractionResult {
3
4
  text: string;
4
5
  metadata?: Record<string, unknown>;
5
6
  warnings?: string[];
6
7
  }
8
+ /** 外部解析器接口 */
7
9
  export interface ExternalExtractor {
8
10
  readonly id: string;
9
11
  readonly name: string;
@@ -15,6 +17,7 @@ export interface ExternalExtractor {
15
17
  extract(file: ClassifiedFile): ExternalExtractionResult;
16
18
  describe(): ExternalExtractorCapability;
17
19
  }
20
+ /** 外部解析器能力描述 */
18
21
  export interface ExternalExtractorCapability {
19
22
  id: string;
20
23
  name: string;
@@ -24,6 +27,7 @@ export interface ExternalExtractorCapability {
24
27
  available: boolean;
25
28
  kind: 'command';
26
29
  }
30
+ /** 命令行外部解析器配置选项 */
27
31
  export interface CommandExternalExtractorOptions {
28
32
  id: string;
29
33
  name: string;
@@ -34,6 +38,7 @@ export interface CommandExternalExtractorOptions {
34
38
  extensions?: string[];
35
39
  timeoutMs?: number;
36
40
  }
41
+ /** 命令行外部解析器,通过调用外部命令提取文件内容 */
37
42
  export declare class CommandExternalExtractor implements ExternalExtractor {
38
43
  readonly id: string;
39
44
  readonly name: string;
@@ -51,8 +56,13 @@ export declare class CommandExternalExtractor implements ExternalExtractor {
51
56
  private interpolate;
52
57
  private checkAvailable;
53
58
  }
59
+ /** 外部解析器注册表,管理所有已注册的外部解析器 */
54
60
  export declare class ExternalExtractorRegistry {
55
61
  private readonly extractors;
62
+ /**
63
+ * 根据环境变量自动注册可用解析器
64
+ * @param env 环境变量(默认 process.env)
65
+ */
56
66
  static fromEnvironment(env?: NodeJS.ProcessEnv): ExternalExtractorRegistry;
57
67
  register(extractor: ExternalExtractor): void;
58
68
  findAll(file: ClassifiedFile): ExternalExtractor[];
@@ -1,5 +1,6 @@
1
1
  import { spawnSync } from 'node:child_process';
2
2
  import * as path from 'node:path';
3
+ /** 命令行外部解析器,通过调用外部命令提取文件内容 */
3
4
  export class CommandExternalExtractor {
4
5
  id;
5
6
  name;
@@ -87,8 +88,13 @@ export class CommandExternalExtractor {
87
88
  return !result.error;
88
89
  }
89
90
  }
91
+ /** 外部解析器注册表,管理所有已注册的外部解析器 */
90
92
  export class ExternalExtractorRegistry {
91
93
  extractors = [];
94
+ /**
95
+ * 根据环境变量自动注册可用解析器
96
+ * @param env 环境变量(默认 process.env)
97
+ */
92
98
  static fromEnvironment(env = process.env) {
93
99
  const registry = new ExternalExtractorRegistry();
94
100
  registry.registerConfiguredOrAuto('cad-dwg', 'DWG Advanced Parser', env.CUSTOMIZE_AGENT_DWG_PARSER, [{ command: 'dwg-parser' }, { command: 'oda-dwg-parser' }], { category: 'cad', formats: ['autocad'], extensions: ['.dwg', '.dwt'] });
@@ -37,7 +37,7 @@ function findInPnpm(packageName) {
37
37
  }
38
38
  }
39
39
  }
40
- catch { /* continue upward */ }
40
+ catch { /* 继续向上遍历 */ }
41
41
  }
42
42
  const parent = path.dirname(dir);
43
43
  if (parent === dir)
@@ -54,7 +54,7 @@ export function resolvePackage(specifier) {
54
54
  try {
55
55
  return localRequire.resolve(specifier);
56
56
  }
57
- catch { /* fall through */ }
57
+ catch { /* 继续尝试后续回退方式 */ }
58
58
  const parts = specifier.split('/');
59
59
  let packageName;
60
60
  if (parts[0]?.startsWith('@')) {
package/dist/index.d.ts CHANGED
@@ -2,7 +2,7 @@ export { TextChunker, type ChunkConfig, type TextChunk } from './chunking/text-c
2
2
  export { FileClassifier } from './classification/classifier.js';
3
3
  export { DedupEngine, type MinHashSignature, type SimilarityMatch } from './dedup/dedup-engine.js';
4
4
  export { RelationshipDetector } from './dedup/relationship-detector.js';
5
- export { HashEmbeddingProvider, OpenAICompatibleEmbeddingProvider, createEmbeddingProviderFromEnvironment, type EmbeddingProvider, type OpenAICompatibleEmbeddingOptions } from './embedding/embedding-provider.js';
5
+ export { HashEmbeddingProvider, LocalTransformersEmbeddingProvider, OpenAICompatibleEmbeddingProvider, createEmbeddingProviderFromEnvironment, type EmbeddingProvider, type LocalTransformersEmbeddingOptions, type OpenAICompatibleEmbeddingOptions } from './embedding/embedding-provider.js';
6
6
  export { ContentExtractor, type ExtractionResult } from './extraction/content-extractor.js';
7
7
  export { CommandExternalExtractor, ExternalExtractorRegistry, type CommandExternalExtractorOptions, type ExternalExtractionResult, type ExternalExtractor, type ExternalExtractorCapability } from './extraction/external-extractor.js';
8
8
  export { ChangeTracker } from './core/change-tracker.js';
@@ -13,7 +13,7 @@ export { MultiProjectManager } from './core/multi-project-manager.js';
13
13
  export { computeProjectId } from './core/project-id.js';
14
14
  export { ensureProjectCustomizeFile, getProjectConfigPath, getProjectKbPath, ProjectConfigManager } from './core/project-config.js';
15
15
  export { ProjectRegistry } from './core/project-registry.js';
16
- export { SQLiteVecClient, SQLiteVecVectorStore, type SQLiteVecClientOptions } from './vector/sqlite-vec-store.js';
16
+ export { HNSWVectorStore } from './vector/hnsw-vector-store.js';
17
17
  export { CollectionManager, globalCollectionName, projectCollectionName } from './vector/collection-manager.js';
18
18
  export type { CollectionClient, VectorCollectionInfo, VectorDocument, VectorSearchQuery, VectorSearchResult, VectorStoreInterface } from './vector/types.js';
19
19
  export { VectorIndexer, type VectorIndexResult } from './vector/vector-indexer.js';
package/dist/index.js CHANGED
@@ -1,10 +1,10 @@
1
- // Types and constants from types.ts and constants.ts are not exported
2
- // as they are only used internally within the knowledge package.
1
+ // types.ts constants.ts 中的类型和常量不对外导出
2
+ // 它们仅在 knowledge 包内部使用。
3
3
  export { TextChunker } from './chunking/text-chunker.js';
4
4
  export { FileClassifier } from './classification/classifier.js';
5
5
  export { DedupEngine } from './dedup/dedup-engine.js';
6
6
  export { RelationshipDetector } from './dedup/relationship-detector.js';
7
- export { HashEmbeddingProvider, OpenAICompatibleEmbeddingProvider, createEmbeddingProviderFromEnvironment } from './embedding/embedding-provider.js';
7
+ export { HashEmbeddingProvider, LocalTransformersEmbeddingProvider, OpenAICompatibleEmbeddingProvider, createEmbeddingProviderFromEnvironment } from './embedding/embedding-provider.js';
8
8
  export { ContentExtractor } from './extraction/content-extractor.js';
9
9
  export { CommandExternalExtractor, ExternalExtractorRegistry } from './extraction/external-extractor.js';
10
10
  export { ChangeTracker } from './core/change-tracker.js';
@@ -15,7 +15,7 @@ export { MultiProjectManager } from './core/multi-project-manager.js';
15
15
  export { computeProjectId } from './core/project-id.js';
16
16
  export { ensureProjectCustomizeFile, getProjectConfigPath, getProjectKbPath, ProjectConfigManager } from './core/project-config.js';
17
17
  export { ProjectRegistry } from './core/project-registry.js';
18
- export { SQLiteVecClient, SQLiteVecVectorStore } from './vector/sqlite-vec-store.js';
18
+ export { HNSWVectorStore } from './vector/hnsw-vector-store.js';
19
19
  export { CollectionManager, globalCollectionName, projectCollectionName } from './vector/collection-manager.js';
20
20
  export { VectorIndexer } from './vector/vector-indexer.js';
21
21
  export { FederationSearch } from './search/federation-search.js';
@@ -1,7 +1,10 @@
1
1
  import type { VectorStoreInterface } from '../vector/types.js';
2
+ /** 搜索范围:项目级、全局级或全部 */
2
3
  export type SearchScope = 'project' | 'global' | 'all';
4
+ /** 联合搜索结果项 */
3
5
  export interface FederatedSearchItem {
4
6
  id: string;
7
+ rowid?: number;
5
8
  content: string;
6
9
  filePath: string;
7
10
  scope: 'project' | 'global';
@@ -25,6 +28,7 @@ export interface FederatedSearchItem {
25
28
  };
26
29
  facets?: Record<string, string | number | string[]>;
27
30
  }
31
+ /** 联合搜索结果 */
28
32
  export interface FederatedResult {
29
33
  results: FederatedSearchItem[];
30
34
  scopesSearched: Array<'project' | 'global'>;
@@ -37,16 +41,19 @@ export interface FederatedResult {
37
41
  reranker?: string;
38
42
  };
39
43
  }
44
+ /** 搜索过滤器 */
40
45
  export interface SearchFilters {
41
46
  category?: string;
42
47
  filePath?: string;
43
48
  }
49
+ /** 各检索方式权重配置 */
44
50
  export interface RetrievalWeights {
45
51
  keyword?: number;
46
52
  vector?: number;
47
53
  rewrite?: number;
48
54
  hybridBonus?: number;
49
55
  }
56
+ /** 联合搜索查询参数 */
50
57
  export interface FederatedQuery {
51
58
  query: string;
52
59
  queryEmbedding: number[];
@@ -56,6 +63,7 @@ export interface FederatedQuery {
56
63
  collections?: string[];
57
64
  filters?: SearchFilters;
58
65
  }
66
+ /** 联合搜索器,支持跨向量存储的分布式搜索和结果合并 */
59
67
  export declare class FederationSearch {
60
68
  private readonly vectorStores;
61
69
  static readonly SCOPE_WEIGHTS: Record<'project' | 'global', number>;
@@ -1,5 +1,6 @@
1
1
  import { ALL_CATEGORIES } from '../constants.js';
2
2
  import { globalCollectionName, projectCollectionName } from '../vector/collection-manager.js';
3
+ /** 联合搜索器,支持跨向量存储的分布式搜索和结果合并 */
3
4
  export class FederationSearch {
4
5
  vectorStores;
5
6
  static SCOPE_WEIGHTS = {
@@ -73,6 +74,7 @@ export class FederationSearch {
73
74
  toFederatedItem(result, scope) {
74
75
  return {
75
76
  id: result.document.id,
77
+ rowid: typeof result.document.metadata.sqlite_rowid === 'number' ? result.document.metadata.sqlite_rowid : undefined,
76
78
  content: result.document.content,
77
79
  filePath: String(result.document.metadata.file_path ?? ''),
78
80
  scope,
package/dist/types.d.ts CHANGED
@@ -1,6 +1,10 @@
1
+ /** 知识库作用域:项目级、全局级或会话级 */
1
2
  export type KBScope = 'project' | 'global' | 'session';
3
+ /** 文件分类枚举 */
2
4
  export type FileCategory = 'document' | 'spreadsheet' | 'image' | 'cad' | 'code' | 'data' | 'web' | 'diagram' | 'archive' | 'other';
5
+ /** 项目状态:活跃、空闲或错误 */
3
6
  export type ProjectStatus = 'active' | 'idle' | 'error';
7
+ /** 项目信息 */
4
8
  export interface ProjectInfo {
5
9
  projectId: string;
6
10
  projectRoot: string;
@@ -13,6 +17,7 @@ export interface ProjectInfo {
13
17
  lastOpenedAt: number;
14
18
  status: ProjectStatus;
15
19
  }
20
+ /** 项目配置 */
16
21
  export interface ProjectConfig {
17
22
  projectId: string;
18
23
  projectName?: string;
@@ -27,6 +32,7 @@ export interface ProjectConfig {
27
32
  createdAt: number;
28
33
  lastOpenedAt: number;
29
34
  }
35
+ /** 已分类的文件信息 */
30
36
  export interface ClassifiedFile {
31
37
  absolutePath: string;
32
38
  relativePath: string;
@@ -36,6 +42,7 @@ export interface ClassifiedFile {
36
42
  mtime: number;
37
43
  mimeType: string;
38
44
  }
45
+ /** 索引状态记录 */
39
46
  export interface IndexStateRecord {
40
47
  relativePath: string;
41
48
  category: FileCategory;
@@ -51,6 +58,7 @@ export interface IndexStateRecord {
51
58
  errorMessage?: string;
52
59
  metadataJson?: string;
53
60
  }
61
+ /** 文件差异对比结果 */
54
62
  export interface DiffResult {
55
63
  newFiles: ClassifiedFile[];
56
64
  modifiedFiles: ClassifiedFile[];
@@ -64,6 +72,7 @@ export interface DiffResult {
64
72
  hasChanges: boolean;
65
73
  diffTimeMs: number;
66
74
  }
75
+ /** 知识库统计信息 */
67
76
  export interface KnowledgeBaseStats {
68
77
  scope: Exclude<KBScope, 'session'>;
69
78
  projectId?: string;
@@ -72,6 +81,7 @@ export interface KnowledgeBaseStats {
72
81
  totalSizeBytes: number;
73
82
  lastIndexedAt: number;
74
83
  }
84
+ /** 跨项目重复文件检测结果 */
75
85
  export interface CrossProjectDuplicate {
76
86
  contentHash: string;
77
87
  files: Array<{
@@ -1,7 +1,10 @@
1
1
  import type { FileCategory } from '../types.js';
2
2
  import type { CollectionClient } from './types.js';
3
+ /** 生成项目级 Collection 名称 */
3
4
  export declare function projectCollectionName(projectId: string, category: FileCategory): string;
5
+ /** 生成全局 Collection 名称 */
4
6
  export declare function globalCollectionName(category: FileCategory): string;
7
+ /** Collection 管理器,负责 Vector Collection 的创建和管理 */
5
8
  export declare class CollectionManager {
6
9
  private readonly client?;
7
10
  constructor(client?: CollectionClient | undefined);
@@ -1,10 +1,13 @@
1
1
  import { ALL_CATEGORIES, COLLECTION_CATEGORY_NAMES } from '../constants.js';
2
+ /** 生成项目级 Collection 名称 */
2
3
  export function projectCollectionName(projectId, category) {
3
4
  return `proj_${projectId}_kb_${COLLECTION_CATEGORY_NAMES[category]}`;
4
5
  }
6
+ /** 生成全局 Collection 名称 */
5
7
  export function globalCollectionName(category) {
6
8
  return `global_kb_${COLLECTION_CATEGORY_NAMES[category]}`;
7
9
  }
10
+ /** Collection 管理器,负责 Vector Collection 的创建和管理 */
8
11
  export class CollectionManager {
9
12
  client;
10
13
  constructor(client) {
@@ -0,0 +1,21 @@
1
+ import type { VectorDocument, VectorSearchQuery, VectorSearchResult, VectorStoreInterface } from './types.js';
2
+ /** HNSW(分层可导航小世界图)向量存储,基于 hnswlib-node 实现的高效近似最近邻搜索 */
3
+ export declare class HNSWVectorStore implements VectorStoreInterface {
4
+ readonly collectionName: string;
5
+ private readonly indexPath;
6
+ private readonly dimensions;
7
+ private readonly maxElements;
8
+ private index?;
9
+ private deletedSinceRebuild;
10
+ private readonly documents;
11
+ constructor(collectionName: string, indexPath: string, dimensions?: number, maxElements?: number);
12
+ ensureCollection(): Promise<void>;
13
+ upsert(documents: VectorDocument[]): Promise<void>;
14
+ clearCollection(): Promise<void>;
15
+ deleteByFilePath(filePath: string): Promise<void>;
16
+ needsRebuild(): boolean;
17
+ search(query: VectorSearchQuery): Promise<VectorSearchResult[]>;
18
+ private persist;
19
+ private loadDocuments;
20
+ private metadataPath;
21
+ }
@@ -0,0 +1,108 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+ import { createRequire } from 'node:module';
4
+ const require = createRequire(import.meta.url);
5
+ /** HNSW(分层可导航小世界图)向量存储,基于 hnswlib-node 实现的高效近似最近邻搜索 */
6
+ export class HNSWVectorStore {
7
+ collectionName;
8
+ indexPath;
9
+ dimensions;
10
+ maxElements;
11
+ index;
12
+ deletedSinceRebuild = 0;
13
+ documents = new Map();
14
+ constructor(collectionName, indexPath, dimensions = 512, maxElements = 500_000) {
15
+ this.collectionName = collectionName;
16
+ this.indexPath = indexPath;
17
+ this.dimensions = dimensions;
18
+ this.maxElements = maxElements;
19
+ }
20
+ async ensureCollection() {
21
+ if (this.index)
22
+ return;
23
+ fs.mkdirSync(path.dirname(this.indexPath), { recursive: true });
24
+ this.loadDocuments();
25
+ const mod = require('hnswlib-node');
26
+ this.index = new mod.HierarchicalNSW('cosine', this.dimensions);
27
+ if (fs.existsSync(this.indexPath))
28
+ this.index.readIndexSync(this.indexPath, true);
29
+ else
30
+ this.index.initIndex(this.maxElements, 16, 200, 100, true);
31
+ }
32
+ async upsert(documents) {
33
+ await this.ensureCollection();
34
+ for (const document of documents) {
35
+ const rowid = Number(document.metadata.sqlite_rowid);
36
+ if (!Number.isFinite(rowid) || rowid <= 0)
37
+ throw new Error(`HNSW 向量写入缺少有效 sqlite_rowid: ${document.id}`);
38
+ this.index.addPoint(document.embedding, rowid, true);
39
+ this.documents.set(rowid, document);
40
+ }
41
+ this.persist();
42
+ }
43
+ async clearCollection() {
44
+ if (fs.existsSync(this.indexPath))
45
+ fs.rmSync(this.indexPath, { force: true });
46
+ if (fs.existsSync(this.metadataPath()))
47
+ fs.rmSync(this.metadataPath(), { force: true });
48
+ this.documents.clear();
49
+ this.deletedSinceRebuild = 0;
50
+ this.index = undefined;
51
+ await this.ensureCollection();
52
+ }
53
+ async deleteByFilePath(filePath) {
54
+ await this.ensureCollection();
55
+ for (const [rowid, document] of this.documents.entries()) {
56
+ if (document.metadata.file_path === filePath) {
57
+ try {
58
+ this.index.markDelete(rowid);
59
+ this.deletedSinceRebuild += 1;
60
+ }
61
+ catch { /* 忽略缺失的标签 */ }
62
+ this.documents.delete(rowid);
63
+ }
64
+ }
65
+ this.persist();
66
+ }
67
+ needsRebuild() {
68
+ const total = this.documents.size + this.deletedSinceRebuild;
69
+ return total >= 1000 && this.deletedSinceRebuild / total > 0.25;
70
+ }
71
+ async search(query) {
72
+ await this.ensureCollection();
73
+ const result = this.index.searchKnn(query.queryEmbedding, query.topK);
74
+ return result.neighbors.flatMap((rowid, index) => {
75
+ const document = this.documents.get(rowid);
76
+ if (!document)
77
+ return [];
78
+ if (typeof query.where?.file_path === 'string' && document.metadata.file_path !== query.where.file_path)
79
+ return [];
80
+ const { embedding: _embedding, ...stored } = document;
81
+ const distance = result.distances[index] ?? 0;
82
+ return [{ collection: this.collectionName, document: stored, score: 1 / (1 + distance) }];
83
+ });
84
+ }
85
+ persist() {
86
+ this.index.writeIndexSync(this.indexPath);
87
+ fs.writeFileSync(this.metadataPath(), JSON.stringify({ deletedSinceRebuild: this.deletedSinceRebuild, documents: [...this.documents.entries()] }), 'utf8');
88
+ }
89
+ loadDocuments() {
90
+ const file = this.metadataPath();
91
+ if (!fs.existsSync(file))
92
+ return;
93
+ try {
94
+ const parsed = JSON.parse(fs.readFileSync(file, 'utf8'));
95
+ const entries = Array.isArray(parsed) ? parsed : parsed.documents ?? [];
96
+ this.deletedSinceRebuild = Array.isArray(parsed) ? 0 : Number(parsed.deletedSinceRebuild ?? 0);
97
+ this.documents.clear();
98
+ for (const [rowid, document] of entries)
99
+ this.documents.set(Number(rowid), document);
100
+ }
101
+ catch {
102
+ this.documents.clear();
103
+ }
104
+ }
105
+ metadataPath() {
106
+ return `${this.indexPath}.documents.json`;
107
+ }
108
+ }
@@ -1,31 +1,39 @@
1
+ /** 向量文档,包含文本内容、嵌入向量和元数据 */
1
2
  export interface VectorDocument {
2
3
  id: string;
3
4
  content: string;
4
5
  embedding: number[];
5
6
  metadata: Record<string, string | number | boolean | null>;
6
7
  }
8
+ /** 向量搜索查询参数 */
7
9
  export interface VectorSearchQuery {
8
10
  queryEmbedding: number[];
9
11
  topK: number;
10
12
  where?: Record<string, string | number | boolean>;
11
13
  }
14
+ /** 向量搜索结果 */
12
15
  export interface VectorSearchResult {
13
16
  document: Omit<VectorDocument, 'embedding'>;
14
17
  score: number;
15
18
  collection: string;
16
19
  }
20
+ /** Vector Collection 信息 */
17
21
  export interface VectorCollectionInfo {
18
22
  id?: string;
19
23
  name: string;
20
24
  metadata?: Record<string, unknown>;
21
25
  }
26
+ /** 向量存储接口,定义所有向量存储实现必须支持的方法 */
22
27
  export interface VectorStoreInterface {
23
28
  readonly collectionName: string;
24
29
  ensureCollection(metadata?: Record<string, unknown>): Promise<void>;
25
30
  upsert(documents: VectorDocument[]): Promise<void>;
26
31
  deleteByFilePath(filePath: string): Promise<void>;
32
+ clearCollection?(): Promise<void>;
33
+ needsRebuild?(): boolean;
27
34
  search(query: VectorSearchQuery): Promise<VectorSearchResult[]>;
28
35
  }
36
+ /** Collection Client 接口,用于管理远程 Vector Collection */
29
37
  export interface CollectionClient {
30
38
  getOrCreateCollection(name: string, metadata?: Record<string, unknown>): Promise<VectorCollectionInfo>;
31
39
  listCollections(): Promise<VectorCollectionInfo[]>;
@@ -1,18 +1,32 @@
1
1
  import type { EmbeddingProvider } from '../embedding/embedding-provider.js';
2
2
  import type { StoredChunk } from '../core/index-state-store.js';
3
3
  import type { VectorStoreInterface } from './types.js';
4
+ /** 向量索引结果 */
4
5
  export interface VectorIndexResult {
5
6
  collectionName: string;
6
7
  chunkCount: number;
7
8
  embeddingModel: string;
8
9
  embeddingDimension: number;
9
10
  }
11
+ export interface VectorIndexProgress {
12
+ collectionName: string;
13
+ processedChunks: number;
14
+ totalChunks: number;
15
+ batchSize: number;
16
+ }
17
+ export interface VectorIndexOptions {
18
+ batchSize?: number;
19
+ onProgress?: (progress: VectorIndexProgress) => void;
20
+ }
21
+ /** 向量索引器,负责将文本切片生成 Embedding 并写入向量存储 */
10
22
  export declare class VectorIndexer {
11
23
  private readonly embeddingProvider;
12
24
  private readonly vectorStores;
13
25
  constructor(embeddingProvider: EmbeddingProvider, vectorStores: Map<string, VectorStoreInterface>);
14
- indexChunks(chunks: StoredChunk[]): Promise<VectorIndexResult[]>;
26
+ indexChunks(chunks: StoredChunk[], options?: VectorIndexOptions): Promise<VectorIndexResult[]>;
15
27
  deleteFile(collectionName: string, filePath: string): Promise<void>;
28
+ private embedDocuments;
29
+ private isValidEmbeddings;
16
30
  private groupByCollection;
17
31
  private toVectorDocument;
18
32
  private parseMetadata;
@@ -1,3 +1,10 @@
1
+ function resolveVectorIndexBatchSize(configured) {
2
+ const raw = configured ?? Number(process.env.CUSTOMIZE_VECTOR_INDEX_BATCH_SIZE ?? process.env.KB_VECTOR_INDEX_BATCH_SIZE);
3
+ if (!Number.isFinite(raw) || raw <= 0)
4
+ return 32;
5
+ return Math.max(1, Math.min(256, Math.floor(raw)));
6
+ }
7
+ /** 向量索引器,负责将文本切片生成 Embedding 并写入向量存储 */
1
8
  export class VectorIndexer {
2
9
  embeddingProvider;
3
10
  vectorStores;
@@ -5,9 +12,11 @@ export class VectorIndexer {
5
12
  this.embeddingProvider = embeddingProvider;
6
13
  this.vectorStores = vectorStores;
7
14
  }
8
- async indexChunks(chunks) {
15
+ async indexChunks(chunks, options = {}) {
9
16
  const byCollection = this.groupByCollection(chunks);
10
17
  const results = [];
18
+ const totalChunks = chunks.length;
19
+ let processedTotalChunks = 0;
11
20
  for (const [collectionName, collectionChunks] of byCollection) {
12
21
  const store = this.vectorStores.get(collectionName);
13
22
  if (!store)
@@ -16,12 +25,21 @@ export class VectorIndexer {
16
25
  embedding_model: this.embeddingProvider.model,
17
26
  embedding_dimension: this.embeddingProvider.dimensions,
18
27
  });
19
- const embeddings = await this.embeddingProvider.embedDocuments(collectionChunks.map(chunk => chunk.content));
20
- const documents = collectionChunks.map((chunk, index) => this.toVectorDocument(chunk, embeddings[index] ?? []));
21
- await store.upsert(documents);
28
+ const batchSize = resolveVectorIndexBatchSize(options.batchSize);
29
+ let processedChunks = 0;
30
+ for (let offset = 0; offset < collectionChunks.length; offset += batchSize) {
31
+ const batchChunks = collectionChunks.slice(offset, offset + batchSize);
32
+ const texts = batchChunks.map(chunk => chunk.content);
33
+ const embeddings = await this.embedDocuments(texts);
34
+ const documents = batchChunks.map((chunk, index) => this.toVectorDocument(chunk, embeddings[index] ?? []));
35
+ await store.upsert(documents);
36
+ processedChunks += documents.length;
37
+ processedTotalChunks += documents.length;
38
+ options.onProgress?.({ collectionName, processedChunks: processedTotalChunks, totalChunks, batchSize: documents.length });
39
+ }
22
40
  results.push({
23
41
  collectionName,
24
- chunkCount: documents.length,
42
+ chunkCount: processedChunks,
25
43
  embeddingModel: this.embeddingProvider.model,
26
44
  embeddingDimension: this.embeddingProvider.dimensions,
27
45
  });
@@ -34,6 +52,16 @@ export class VectorIndexer {
34
52
  return;
35
53
  await store.deleteByFilePath(filePath);
36
54
  }
55
+ async embedDocuments(texts) {
56
+ const embeddings = await this.embeddingProvider.embedDocuments(texts);
57
+ if (!this.isValidEmbeddings(embeddings, texts.length, this.embeddingProvider.dimensions)) {
58
+ throw new Error(`Embedding 结果异常:期望 ${texts.length} 条 ${this.embeddingProvider.dimensions} 维向量,实际返回 ${embeddings.length} 条`);
59
+ }
60
+ return embeddings;
61
+ }
62
+ isValidEmbeddings(embeddings, count, dimensions) {
63
+ return embeddings.length === count && embeddings.every(vector => vector.length === dimensions && vector.every(value => Number.isFinite(value)));
64
+ }
37
65
  groupByCollection(chunks) {
38
66
  const grouped = new Map();
39
67
  for (const chunk of chunks) {
@@ -50,6 +78,7 @@ export class VectorIndexer {
50
78
  content: chunk.content,
51
79
  embedding,
52
80
  metadata: {
81
+ sqlite_rowid: chunk.rowid,
53
82
  file_path: chunk.relativePath,
54
83
  chunk_index: chunk.chunkIndex,
55
84
  category: chunk.category,
@@ -0,0 +1,31 @@
1
+ {
2
+ "_name_or_path": "BAAI/bge-small-zh-v1.5",
3
+ "architectures": [
4
+ "BertModel"
5
+ ],
6
+ "attention_probs_dropout_prob": 0.1,
7
+ "classifier_dropout": null,
8
+ "gradient_checkpointing": false,
9
+ "hidden_act": "gelu",
10
+ "hidden_dropout_prob": 0.1,
11
+ "hidden_size": 512,
12
+ "id2label": {
13
+ "0": "LABEL_0"
14
+ },
15
+ "initializer_range": 0.02,
16
+ "intermediate_size": 2048,
17
+ "label2id": {
18
+ "LABEL_0": 0
19
+ },
20
+ "layer_norm_eps": 1e-12,
21
+ "max_position_embeddings": 512,
22
+ "model_type": "bert",
23
+ "num_attention_heads": 8,
24
+ "num_hidden_layers": 4,
25
+ "pad_token_id": 0,
26
+ "position_embedding_type": "absolute",
27
+ "transformers_version": "4.34.0.dev0",
28
+ "type_vocab_size": 2,
29
+ "use_cache": true,
30
+ "vocab_size": 21128
31
+ }
@@ -0,0 +1,7 @@
1
+ {
2
+ "cls_token": "[CLS]",
3
+ "mask_token": "[MASK]",
4
+ "pad_token": "[PAD]",
5
+ "sep_token": "[SEP]",
6
+ "unk_token": "[UNK]"
7
+ }