共计 1455 个字符,预计需要花费 4 分钟才能阅读完成。
当 Agent 系统遇上检索瓶颈
最近在重构公司客服 Agent 系统时,我们遇到了典型的检索性能天花板:当并发量突破 8 万 QPS 后,原有 Elasticsearch 集群的 P99 延迟从 35ms 飙升至 210ms。更棘手的是,用户开始抱怨 ” 找相似工单 ” 功能总返回不相关结果——这是传统关键词检索在语义理解上的先天缺陷。

技术选型:倒排索引与向量空间的博弈
- Elasticsearch 的强项与短板
- 倒排索引像字典目录,适合精确匹配 ” 错误代码 404″ 这类查询
-
但处理 ” 支付失败 ” 和 ” 交易被拒 ” 这类语义相似查询时,BM25 算法会给相同词频的文档打相似分
-
向量数据库的破局思路
- Milvus 的 HNSW 算法将文本转换为 768 维向量(采用 BERT-base 编码)
-
通过计算余弦相似度,” 账户余额不足 ” 和 ” 银行卡没钱 ” 这类表述能得到 90%+ 匹配度
-
混合架构设计
graph LR A[查询请求] --> B{包含业务 ID?} B -->| 是 | C[ES 精确查询] B -->| 否 | D[向量语义检索] D --> E[结果融合排序]
Spring Boot 集成实战
关键代码片段(精简版):
// 向量检索服务层
@Async
public CompletableFuture<List<AgentDoc>> semanticSearch(String query, int topK) {float[] vector = bertClient.encode(query); // JNI 加速的 BERT 编码
SearchParam param = SearchParam.create(
COLLECTION_NAME,
VECTOR_FIELD_NAME,
vector
).withTopK(topK);
return milvusClient.searchAsync(param)
.thenApply(res -> convertToAgentDocs(res));
}
// 索引预热技巧
@PostConstruct
public void warmUpIndex() {new Thread(() -> {milvusClient.loadCollection(COLLECTION_NAME);
faissIndex.precompute(2048); // 预计算聚类中心
}).start();}
性能优化七武器
- PQ 量化参数调优
- 将 768 维向量切分为 12 个子空间(m=12),每个子空间用 8bits 表示(nbits=8)
-
内存占用从 2.4GB 降至 600MB,召回率仅下降 2.3%
-
分层缓存策略
- L1: 本地 Caffeine 缓存高频查询向量
- L2: Redis 集群缓存原始文档
-
通过 Redisson 的 RReadWriteLock 保证缓存一致性
-
向量维度灾难解法
- 先用 PCA 降维至 512 维
- 对分类变量(如错误类型)单独建立倒排索引
压测数据说话
| 方案 | QPS | P99 延迟 | 内存占用 | 语义召回率 |
|---|---|---|---|---|
| 纯 ES | 82k | 143ms | 48GB | 61% |
| 纯 Milvus | 65k | 89ms | 32GB | 93% |
| 混合架构 | 107k | 53ms | 56GB | 88% |
踩过的坑
- 冷启动问题 :首次查询延迟高达 2s
- 解决方案:在系统启动时预加载 10 万条高频问答向量
- 维度不一致 :业务部门新增特征导致向量长度变化
- 现在要求所有特征变更必须走 Schema 评审
待解难题
- 当语义检索返回 ” 转账失败 ” 但业务规则要求必须显示 ” 支付异常 ” 时,该如何设计权重融合算法?
- 在工单状态实时变化的场景下,如何平衡索引重建频率(目前我们采用每小时增量更新)
这次架构升级让平均响应时间从 78ms 降至 41ms,但更让我兴奋的是用户反馈:” 现在系统好像真的懂我在问什么 ”。或许这就是向量魔法带来的质变吧。
正文完
发表至: 技术分享
近一天内
