for first push
This commit is contained in:
86
services/voiceprint/src/indexer/FAISSIndex.ts
Normal file
86
services/voiceprint/src/indexer/FAISSIndex.ts
Normal file
@@ -0,0 +1,86 @@
|
||||
export class FAISSIndex {
|
||||
private store: Map<string, number[]> = new Map();
|
||||
private readonly dimension = 192;
|
||||
private initialized = false;
|
||||
|
||||
async initialize(): Promise<void> {
|
||||
if (this.initialized) return;
|
||||
this.initialized = true;
|
||||
}
|
||||
|
||||
async add(id: string, vector: number[]): Promise<void> {
|
||||
this.normalizeInPlace(vector);
|
||||
this.store.set(id, [...vector]);
|
||||
}
|
||||
|
||||
async remove(id: string): Promise<void> {
|
||||
this.store.delete(id);
|
||||
}
|
||||
|
||||
async search(
|
||||
queryVector: number[],
|
||||
topK: number = 5
|
||||
): Promise<SearchResult[]> {
|
||||
const normalized = [...queryVector];
|
||||
this.normalizeInPlace(normalized);
|
||||
|
||||
const scores: Array<{ id: string; similarity: number }> = [];
|
||||
|
||||
for (const [id, vector] of this.store.entries()) {
|
||||
const similarity = this.cosineSimilarity(normalized, vector);
|
||||
scores.push({ id, similarity });
|
||||
}
|
||||
|
||||
scores.sort((a, b) => b.similarity - a.similarity);
|
||||
return scores.slice(0, topK).map((s, i) => ({ rank: i + 1, id: s.id, similarity: s.similarity }));
|
||||
}
|
||||
|
||||
async count(): Promise<number> {
|
||||
return this.store.size;
|
||||
}
|
||||
|
||||
async clear(): Promise<void> {
|
||||
this.store.clear();
|
||||
}
|
||||
|
||||
async loadFromDatabase(): Promise<void> {
|
||||
const prisma = (await import("@shieldai/db")).default;
|
||||
const enrollments = await prisma.voiceEnrollment.findMany({
|
||||
select: { id: true, embeddingVector: true },
|
||||
});
|
||||
|
||||
for (const enrollment of enrollments) {
|
||||
this.store.set(enrollment.id, enrollment.embeddingVector);
|
||||
}
|
||||
}
|
||||
|
||||
private cosineSimilarity(a: number[], b: number[]): number {
|
||||
let dotProduct = 0;
|
||||
let normA = 0;
|
||||
let normB = 0;
|
||||
|
||||
for (let i = 0; i < a.length; i++) {
|
||||
dotProduct += a[i] * b[i];
|
||||
normA += a[i] * a[i];
|
||||
normB += b[i] * b[i];
|
||||
}
|
||||
|
||||
const denominator = Math.sqrt(normA) * Math.sqrt(normB);
|
||||
return denominator > 0 ? dotProduct / denominator : 0;
|
||||
}
|
||||
|
||||
private normalizeInPlace(vector: number[]): void {
|
||||
const norm = Math.sqrt(vector.reduce((s, v) => s + v * v, 0));
|
||||
if (norm > 0) {
|
||||
for (let i = 0; i < vector.length; i++) {
|
||||
vector[i] /= norm;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
export interface SearchResult {
|
||||
rank: number;
|
||||
id: string;
|
||||
similarity: number;
|
||||
}
|
||||
Reference in New Issue
Block a user