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]);
}
}