Skip to content

4. 语义检索

4.1 向量化存储

typescript
interface MemoryEntry {
  id: string;
  content: string;
  embedding: number[];
  metadata: {
    topic: string;
    timestamp: Date;
    accessCount: number;
    lastAccessed: Date;
  };
}

class VectorMemoryStore {
  private entries: MemoryEntry[] = [];
  private index: VectorIndex;

  // 添加记忆
  async add(content: string, metadata: any) {
    const embedding = await this.embed(content);

    const entry: MemoryEntry = {
      id: crypto.randomUUID(),
      content,
      embedding,
      metadata: {
        ...metadata,
        timestamp: new Date(),
        accessCount: 0,
        lastAccessed: new Date()
      }
    };

    this.entries.push(entry);
    await this.index.insert(entry.id, embedding);
  }

  // 语义搜索
  async search(query: string, limit = 5): Promise<MemoryEntry[]> {
    const queryEmbedding = await this.embed(query);

    // 使用向量索引搜索
    const results = await this.index.search(queryEmbedding, limit * 2);

    // 重排序:结合相似度和访问频率
    const ranked = results.map(r => {
      const entry = this.entries.find(e => e.id === r.id)!;
      const recencyScore = this.calculateRecency(entry.metadata.lastAccessed);
      const frequencyScore = Math.log(entry.metadata.accessCount + 1) / 10;

      return {
        entry,
        score: r.similarity * 0.7 + recencyScore * 0.2 + frequencyScore * 0.1
      };
    });

    return ranked
      .sort((a, b) => b.score - a.score)
      .slice(0, limit)
      .map(r => {
        // 更新访问统计
        r.entry.metadata.accessCount++;
        r.entry.metadata.lastAccessed = new Date();
        return r.entry;
      });
  }

  // 计算时间衰减
  private calculateRecency(lastAccessed: Date): number {
    const daysSince = (Date.now() - lastAccessed.getTime()) / (1000 * 60 * 60 * 24);
    return Math.exp(-daysSince / 30);  // 30 天半衰期
  }

  // 生成嵌入向量
  private async embed(text: string): Promise<number[]> {
    // 使用 Claude 的 embedding API
    const response = await anthropic.embeddings.create({
      model: 'claude-3-embedding',
      input: text
    });
    return response.embedding;
  }
}

4.2 混合检索策略

typescript
class HybridMemoryRetriever {
  private vectorStore: VectorMemoryStore;
  private keywordIndex: KeywordIndex;

  // 混合检索:向量 + 关键词
  async retrieve(query: string, limit = 5): Promise<MemoryEntry[]> {
    // 1. 向量检索
    const vectorResults = await this.vectorStore.search(query, limit);

    // 2. 关键词检索
    const keywordResults = await this.keywordIndex.search(query, limit);

    // 3. 合并结果(去重)
    const combined = this.mergeResults(vectorResults, keywordResults);

    // 4. 重排序
    return this.rerank(query, combined, limit);
  }

  // 合并结果
  private mergeResults(
    vectorResults: MemoryEntry[],
    keywordResults: MemoryEntry[]
  ): MemoryEntry[] {
    const seen = new Set<string>();
    const merged: MemoryEntry[] = [];

    for (const entry of [...vectorResults, ...keywordResults]) {
      if (!seen.has(entry.id)) {
        seen.add(entry.id);
        merged.push(entry);
      }
    }

    return merged;
  }

  // 重排序
  private async rerank(
    query: string,
    candidates: MemoryEntry[],
    limit: number
  ): Promise<MemoryEntry[]> {
    // 使用 LLM 重排序
    const prompt = `
Given the query: "${query}"

Rank these memory entries by relevance (most relevant first):

${candidates.map((c, i) => `${i + 1}. ${c.content.slice(0, 200)}...`).join('\n\n')}

Return a JSON array of indices in order of relevance.
`;

    const response = await llm.complete(prompt);
    const ranking = JSON.parse(response);

    return ranking.slice(0, limit).map((i: number) => candidates[i]);
  }
}

前端面试知识库