Skip to content

Commit ad4d2e6

Browse files
author
福晋
committed
chore: code optimization
1 parent 3670846 commit ad4d2e6

11 files changed

Lines changed: 36 additions & 89 deletions

File tree

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,3 +11,4 @@ package-lock.json
1111
yarn.lock
1212
pnpm-lock.yaml
1313
pnpm-workspace.yaml
14+
.vscode/

src/context.ts

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,20 @@
11
import * as path from 'path';
22
import * as fs from 'fs';
33
import { glob } from 'glob';
4-
import { ContextOptions, QueryOptions, QueryResult } from './types';
4+
import { ContextOptions, QueryOptions, QueryResult, Loader } from './types';
55
import { TransformerVectorizer } from './vectorizer/transformer';
66
import { ZVecStore } from './storage/zvec-store';
7-
import { Loader, MarkdownLoader, JsonLoader, TextLoader } from './loaders';
7+
import { MarkdownLoader, JsonLoader, TextLoader } from './loaders';
88

99
export class Context {
1010
private readonly vectorsDir: string;
11-
private readonly model: string;
1211
private readonly vectorizer: TransformerVectorizer;
1312
private readonly stores: Map<string, ZVecStore> = new Map();
1413
private readonly loaders: Loader[];
1514

1615
private constructor(options: ContextOptions) {
1716
this.vectorsDir = options.vectorsDir;
18-
this.model = options.model || 'sentence-transformers/all-MiniLM-L6-v2';
19-
this.vectorizer = new TransformerVectorizer(this.model);
17+
this.vectorizer = new TransformerVectorizer(options.model);
2018
this.loaders = [
2119
new MarkdownLoader(),
2220
new JsonLoader(),
@@ -77,7 +75,7 @@ export class Context {
7775
async query(text: string, options: QueryOptions): Promise<QueryResult[]> {
7876
const store = await this.getOrCreateStore(options.library);
7977
const vector = await this.vectorizer.embed(text);
80-
const searchResults = store.search(vector, options.topK || 5);
78+
const searchResults = await store.search(vector, options.topK || 5);
8179

8280
return searchResults.map((result) => {
8381
const doc = store.getDoc(result.id)!;

src/loaders/base.ts

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
1-
import { Document } from '../types';
1+
import * as path from 'path';
22

3-
export interface Loader {
4-
canHandle(filePath: string): boolean;
5-
load(filePath: string): Promise<Document>;
3+
/** Generate a document ID from a file path (uses the filename to avoid special characters in paths). */
4+
export function generateDocId(filePath: string): string {
5+
return path.basename(filePath);
66
}

src/loaders/index.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
export { Loader } from './base';
1+
export { generateDocId } from './base';
22
export { MarkdownLoader } from './markdown';
33
export { JsonLoader } from './json';
44
export { TextLoader } from './text';

src/loaders/json.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as fs from 'fs/promises';
2-
import * as path from 'path';
3-
import { Loader } from './base';
4-
import { Document } from '../types';
2+
import { Loader, Document } from '../types';
3+
import { generateDocId } from './base';
54

65
export class JsonLoader implements Loader {
76
canHandle(filePath: string): boolean {
@@ -13,7 +12,7 @@ export class JsonLoader implements Loader {
1312
const data = JSON.parse(content);
1413

1514
return {
16-
id: path.basename(filePath),
15+
id: generateDocId(filePath),
1716
content: typeof data === 'string' ? data : JSON.stringify(data, null, 2),
1817
};
1918
}

src/loaders/markdown.ts

Lines changed: 3 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,7 @@
11
import * as fs from 'fs/promises';
2-
import * as path from 'path';
32
import matter from 'gray-matter';
4-
import { Loader } from './base';
5-
import { Document } from '../types';
3+
import { Loader, Document } from '../types';
4+
import { generateDocId } from './base';
65

76
export class MarkdownLoader implements Loader {
87
canHandle(filePath: string): boolean {
@@ -13,11 +12,8 @@ export class MarkdownLoader implements Loader {
1312
const content = await fs.readFile(filePath, 'utf-8');
1413
const { data: meta, content: body } = matter(content);
1514

16-
// 使用文件名作为 ID,避免路径中的特殊字符
17-
const id = path.basename(filePath);
18-
1915
return {
20-
id,
16+
id: generateDocId(filePath),
2117
content: body.trim(),
2218
meta,
2319
};

src/loaders/text.ts

Lines changed: 3 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,6 @@
11
import * as fs from 'fs/promises';
2-
import * as path from 'path';
3-
import { Loader } from './base';
4-
import { Document } from '../types';
2+
import { Loader, Document } from '../types';
3+
import { generateDocId } from './base';
54

65
export class TextLoader implements Loader {
76
canHandle(filePath: string): boolean {
@@ -12,7 +11,7 @@ export class TextLoader implements Loader {
1211
const content = await fs.readFile(filePath, 'utf-8');
1312

1413
return {
15-
id: path.basename(filePath),
14+
id: generateDocId(filePath),
1615
content: content.trim(),
1716
};
1817
}

src/storage/zvec-store.ts

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -15,13 +15,11 @@ interface DocData {
1515

1616
export class ZVecStore {
1717
private collection: ZVecCollection;
18-
private dimension: number;
1918
private filePath!: string;
2019
private docs: Map<string, DocData> = new Map();
2120

22-
constructor(collection: ZVecCollection, dimension: number) {
21+
constructor(collection: ZVecCollection) {
2322
this.collection = collection;
24-
this.dimension = dimension;
2523
}
2624

2725
static async create(filePath: string, dimension: number = 384): Promise<ZVecStore> {
@@ -43,14 +41,10 @@ export class ZVecStore {
4341
if (fs.existsSync(filePath)) {
4442
collection = ZVecOpen(filePath);
4543
} else {
46-
const dir = path.dirname(filePath);
47-
if (!fs.existsSync(dir)) {
48-
fs.mkdirSync(dir, { recursive: true });
49-
}
5044
collection = ZVecCreateAndOpen(filePath, schema);
5145
}
5246

53-
const store = new ZVecStore(collection, dimension);
47+
const store = new ZVecStore(collection);
5448
store.filePath = filePath;
5549
await store.loadMeta();
5650
return store;
@@ -75,8 +69,8 @@ export class ZVecStore {
7569
this.docs.set(id, { content, meta });
7670
}
7771

78-
search(queryVector: number[], topK: number): Array<{ id: string; score: number }> {
79-
const results = this.collection.querySync({
72+
async search(queryVector: number[], topK: number): Promise<Array<{ id: string; score: number }>> {
73+
const results = await this.collection.query({
8074
fieldName: 'embedding',
8175
vector: queryVector,
8276
topk: topK,
@@ -93,17 +87,4 @@ export class ZVecStore {
9387
this.saveMeta();
9488
}
9589

96-
close(): void {
97-
this.saveMeta();
98-
this.collection.closeSync();
99-
}
100-
101-
clear(): void {
102-
const ids = Array.from(this.docs.keys());
103-
if (ids.length > 0) {
104-
this.collection.deleteSync(ids);
105-
}
106-
this.docs.clear();
107-
this.saveMeta();
108-
}
10990
}

src/types.ts

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,14 @@ export interface Document {
1010
meta?: Record<string, unknown>;
1111
}
1212

13+
/**
14+
* Document loader interface
15+
*/
16+
export interface Loader {
17+
canHandle(filePath: string): boolean;
18+
load(filePath: string): Promise<Document>;
19+
}
20+
1321
/**
1422
* Context initialization options
1523
*/

src/vectorizer/transformer.ts

Lines changed: 3 additions & 27 deletions
Original file line numberDiff line numberDiff line change
@@ -1,39 +1,15 @@
11
import { pipeline, env, FeatureExtractionPipeline } from '@huggingface/transformers';
2-
import * as path from 'path';
3-
import * as fs from 'fs';
42

53
export class TransformerVectorizer {
64
private pipeline: FeatureExtractionPipeline | null = null;
75

8-
constructor(
9-
private readonly model: string = 'sentence-transformers/all-MiniLM-L6-v2',
10-
private readonly dtype?: 'fp32' | 'fp16' | 'q8' | 'q4' | 'q4f16'
11-
) {}
6+
constructor(private readonly model: string = 'sentence-transformers/all-MiniLM-L6-v2') {}
127

13-
/**
14-
* 配置模型下载镜像
15-
* 通过环境变量设置: HF_ENDPOINT 或 HF_MIRROR
16-
* 常用镜像: https://hf-mirror.com
17-
*/
18-
private configureMirror(): void {
8+
async initialize(): Promise<void> {
199
const mirror = process.env.HF_ENDPOINT || process.env.HF_MIRROR;
2010
if (mirror) env.remoteHost = mirror;
21-
}
22-
23-
async initialize(): Promise<void> {
24-
this.configureMirror();
25-
26-
// 查找本地缓存模型
27-
const cacheDir = path.join(process.env.HOME || '', '.cache/huggingface/models');
28-
let modelPath = this.model;
29-
30-
if (fs.existsSync(cacheDir)) {
31-
const modelName = this.model.replace('sentence-transformers--', '').split('-').slice(0, 2).join('-');
32-
const localModel = fs.readdirSync(cacheDir).find(d => d.includes(modelName));
33-
if (localModel) modelPath = path.join(cacheDir, localModel);
34-
}
3511

36-
this.pipeline = await pipeline('feature-extraction', modelPath, this.dtype ? { dtype: this.dtype } : undefined) as FeatureExtractionPipeline;
12+
this.pipeline = await pipeline('feature-extraction', this.model) as FeatureExtractionPipeline;
3713
}
3814

3915
async embed(text: string): Promise<number[]> {

0 commit comments

Comments
 (0)