当前问题:
// 每次查询都要检索 Qdrant
async fn retrieve_memory(user_id: &str, query: &str) {
// 1. 生成 embedding (1 次 LLM 调用)
let embedding = generate_embedding(query).await;
// 2. 检索短期记忆 (1 次 Redis 查询)
let stm = redis.get_short_term(user_id).await;
// 3. 检索中期记忆 (1 次 Qdrant 查询)
let mtm = qdrant.search(embedding, limit=10).await;
// 4. 检索长期记忆 (1 次 Qdrant 查询)
let ltm = qdrant.search_profile(user_id).await;
// 总计: 1 LLM + 1 Redis + 2 Qdrant = 4 次请求
}优化方案 1: 批量检索
// 合并 Qdrant 查询
async fn retrieve_memory_optimized(user_id: &str, query: &str) {
let embedding = generate_embedding(query).await;
// 并行执行
let (stm, memories) = tokio::join!(
redis.get_short_term(user_id),
qdrant.search_with_filter(
embedding,
filter = {
"should": [
{"key": "type", "match": {"value": "mtm"}},
{"key": "type", "match": {"value": "ltm"}},
{"key": "user_id", "match": {"value": user_id}}
]
},
limit = 20
)
);
// 总计: 1 LLM + 1 Redis + 1 Qdrant = 3 次请求
// 减少 25% 请求!
}优化方案 2: 缓存 Embedding
// 缓存常见查询的 embedding
pub struct EmbeddingCache {
cache: Arc<RwLock<LruCache<String, Vec<f32>>>>,
}
async fn retrieve_with_cache(query: &str) {
// 检查缓存
if let Some(embedding) = cache.get(query) {
// 跳过 LLM 调用!
return qdrant.search(embedding).await;
}
// 缓存未命中才调用 LLM
let embedding = generate_embedding(query).await;
cache.put(query.to_string(), embedding.clone());
// 对于高频查询,节省 100% LLM 调用!
}当前问题:
// 每次都要检索 + LLM
async fn chat(query: &str) {
// 1. 检索记忆 (3-4 次请求)
let context = retrieve_memory(query).await;
// 2. 调用 LLM (1 次,耗时 1-3 秒)
let response = llm.complete(query, context).await;
// 总耗时: ~2 秒
}优化方案: Bloom Filter + 精确匹配
pub struct FaqMatcher {
// Bloom Filter 快速判断是否可能是 FAQ
bloom: BloomFilter,
// 精确匹配的 FAQ 缓存
exact_cache: HashMap<u64, String>,
}
async fn chat_optimized(query: &str) {
// 1. 计算查询哈希 (< 1μs)
let hash = hash_query(query);
// 2. Bloom Filter 检查 (< 1μs)
if !bloom.contains(&hash) {
// 肯定不是 FAQ,走正常流程
return normal_chat(query).await;
}
// 3. 精确匹配检查 (< 1μs)
if let Some(answer) = exact_cache.get(&hash) {
// 直接返回!跳过所有检索和 LLM
return answer.clone();
}
// 4. 向量检索确认 (仅对可能的 FAQ)
let similar = qdrant.search(query, filter={"type": "faq"}, limit=1).await;
if similar.score > 0.98 {
// 缓存并返回
exact_cache.insert(hash, similar.content.clone());
return similar.content;
}
// 5. 不是 FAQ,走正常流程
normal_chat(query).await
}
// 性能提升:
// - FAQ 命中: 0 次请求,< 1ms (vs 原来 4 次请求 + LLM,2 秒)
// - 非 FAQ: 1 次 Qdrant (vs 原来 4 次)当前问题:
// 逐个生成 embedding
for message in messages {
let embedding = llm.embed(message.content).await; // N 次调用
segments.push(MidTermSegment { embedding, .. });
}
// 10 条消息 = 10 次 API 调用优化方案: 批量 API
// 一次调用生成多个 embedding
let contents: Vec<String> = messages.iter()
.map(|m| m.content.clone())
.collect();
let embeddings = llm.embed_batch(contents).await; // 1 次调用!
for (message, embedding) in messages.iter().zip(embeddings) {
segments.push(MidTermSegment { embedding, .. });
}
// 10 条消息 = 1 次 API 调用
// 减少 90% 请求!当前问题:
// 每次访问都更新 Qdrant
async fn record_access(segment_id: Uuid) {
// 1. 读取 (1 次 Qdrant)
let mut segment = qdrant.get(segment_id).await;
// 2. 更新
segment.access_count += 1;
segment.heat_score = calculate_heat(&segment);
// 3. 写回 (1 次 Qdrant)
qdrant.update(segment).await;
}
// 100 次访问 = 200 次 Qdrant 请求优化方案: 批量更新 + Redis 缓冲
pub struct HeatBuffer {
// Redis 缓冲访问计数
buffer: Arc<RwLock<HashMap<Uuid, u32>>>,
}
async fn record_access_optimized(segment_id: Uuid) {
// 1. 只更新 Redis 计数器 (< 1ms)
redis.incr(format!("heat:{}", segment_id)).await;
// 不立即写 Qdrant!
}
// 后台任务:每 5 分钟批量同步
async fn sync_heat_scores() {
loop {
tokio::time::sleep(Duration::from_secs(300)).await;
// 1. 从 Redis 读取所有计数 (1 次)
let counts = redis.hgetall("heat:*").await;
// 2. 批量读取 Qdrant (1 次)
let segments = qdrant.get_batch(counts.keys()).await;
// 3. 批量更新 (1 次)
let updates = segments.iter().map(|s| {
s.access_count += counts[&s.id];
s.heat_score = calculate_heat(s);
s
}).collect();
qdrant.update_batch(updates).await;
// 4. 清空 Redis 缓冲
redis.del("heat:*").await;
}
}
// 100 次访问 = 100 次 Redis (快) + 3 次 Qdrant (批量)
// vs 原来 200 次 Qdrant
// 减少 98.5% Qdrant 请求!当前问题:
// 每次都计算完整的向量相似度
fn find_similar(query_embedding: &[f32], segments: &[Segment]) -> Vec<Segment> {
segments.iter()
.map(|s| {
let score = cosine_similarity(query_embedding, &s.embedding);
(s, score)
})
.filter(|(_, score)| *score > 0.7)
.collect()
}
// 1000 个 segment = 1000 次完整计算优化方案: 分层过滤
fn find_similar_optimized(query: &[f32], segments: &[Segment]) -> Vec<Segment> {
// 1. 快速过滤:只比较前 64 维 (< 10% 计算量)
let candidates: Vec<_> = segments.iter()
.filter(|s| {
let quick_score = dot_product(&query[..64], &s.embedding[..64]);
quick_score > 0.5 // 粗筛
})
.collect();
// 2. 精确计算:只对候选计算完整相似度
candidates.iter()
.map(|s| {
let score = cosine_similarity(query, &s.embedding);
(s, score)
})
.filter(|(_, score)| *score > 0.7)
.collect()
}
// 假设粗筛过滤掉 80%
// 1000 个 segment = 1000 次快速计算 + 200 次完整计算
// vs 原来 1000 次完整计算
// 减少 ~70% 计算量!当前问题:
// 每次都调用 LLM 总结
async fn consolidate_memory(messages: Vec<Message>) {
for chunk in messages.chunks(10) {
// 每 10 条消息调用一次 LLM
let summary = llm.summarize(chunk).await;
store_summary(summary).await;
}
}
// 100 条消息 = 10 次 LLM 调用优化方案: 增量合并
pub struct IncrementalSummarizer {
current_summary: String,
message_count: usize,
}
async fn consolidate_incremental(new_messages: Vec<Message>) {
// 只在累积到阈值时才调用 LLM
self.message_count += new_messages.len();
if self.message_count < 20 {
// 简单拼接,不调用 LLM
self.current_summary.push_str(&format_messages(new_messages));
return;
}
// 达到阈值,调用 LLM 压缩
self.current_summary = llm.summarize(&self.current_summary).await;
self.message_count = 0;
}
// 100 条消息 = 5 次 LLM 调用 (vs 原来 10 次)
// 减少 50% LLM 调用!| 优化项 | 原始请求数 | 优化后请求数 | 减少比例 | 优先级 |
|---|---|---|---|---|
| 向量检索合并 | 4 | 3 | 25% | ⭐⭐⭐⭐⭐ |
| FAQ Bloom Filter | 4 + LLM | 0 | 100% | ⭐⭐⭐⭐⭐ |
| 批量 Embedding | N | 1 | 90%+ | ⭐⭐⭐⭐ |
| 热度批量更新 | 200 | 3 | 98.5% | ⭐⭐⭐ |
| 相似度分层 | 1000 计算 | 300 计算 | 70% | ⭐⭐⭐⭐ |
| 增量合并 | 10 LLM | 5 LLM | 50% | ⭐⭐⭐ |
- ✅ FAQ Bloom Filter - 最高 ROI
- ✅ 向量检索合并 - 简单有效
- ✅ Embedding 缓存 - 低成本高收益
- 批量 Embedding 生成
- 热度批量更新
- 相似度分层过滤
- 增量记忆合并
- 预测性预加载
- 智能缓存淘汰