RAG检索优化:查询改写、重排序与缓存策略

RAG检索优化:查询改写、重排序与缓存策略

上一篇我们搭建了基于Milvus的向量数据库基础设施,实现了混合检索(BM25 + 向量)。但Naive RAG在实际业务中会暴露三大痛点:多轮对话检索不准、Top-K结果质量参差、重复查询浪费Token。本篇围绕LangChain4j的Advanced RAG能力,讲清楚查询改写、重排序、缓存策略三大优化手段,并给出生产级落地方案。


一、为什么Naive RAG不够用

回顾一下Naive RAG的流程:

css 复制代码
用户问题 → 向量化 → 检索Top-K → 拼接Prompt → 大模型回答

这套流程在简单场景能用,但放到真实业务里会暴露三个问题:

痛点 典型场景 根因
多轮对话检索不准 用户问"那它的薪资呢?" 脱离上下文的"它"无法被向量化,检索不到相关文档
Top-K结果质量参差 用户问"年假政策",检索到3条相关2条无关 向量相似度≠语义相关,相关性排序能力弱
重复查询浪费Token 100个用户问同一个FAQ 每次都走检索+生成,成本高、延迟高

这三个问题分别对应三类优化手段:

复制代码
查询改写  →  解决多轮对话
重排序    →  解决结果质量
缓存策略  →  解决成本与延迟

这就是Advanced RAG的核心。本系列项目 java-llm-production-ready 中的 AdvancedRagPipeline 就是这套能力的完整实现。


二、检索优化的整体架构

优化后的Advanced RAG流程:

css 复制代码
用户问题
   ↓
[查询改写] ── 查询压缩(融合多轮上下文)
   │       └─ 查询扩展(生成多个变体)
   ↓
[多路检索] ── 对每个变体查询执行向量检索
   ↓
[重排序]   ── RRF融合 / ScoringModel重排
   ↓
[缓存检查] ── 命中则直接返回,未命中则继续
   ↓
拼接Prompt → 大模型生成 → [结果缓存] → 返回

对应到LangChain4j的模块化抽象:

阶段 LangChain4j接口 项目实现
查询改写 QueryTransformer CompressingQueryTransformer + ExpandingQueryTransformer
内容检索 ContentRetriever EmbeddingStoreContentRetriever
内容聚合 ContentAggregator DefaultContentAggregator(RRF) / ReRankingContentAggregator
内容注入 ContentInjector DefaultContentInjector
结果缓存 自定义 RedisCacheService

下面逐个拆解。


三、查询改写(Query Transformation)

查询改写是Advanced RAG的第一道关卡,目标是在检索前把用户的原始问题变成"更容易检索到正确文档的问题"。

3.1 查询压缩(Compressing):解决多轮对话

场景:多轮对话中,用户经常用代词指代上文。

erlang 复制代码
用户:公司年假政策是什么?
AI:根据规定,员工入职满1年享有5天年假...
用户:那它的薪资怎么算?   ← "它"指代"年假",但孤立看无法检索

如果直接把"那它的薪资怎么算?"向量化去检索,结果全是无关内容。查询压缩的做法是用大模型把多轮对话历史 + 当前问题压缩成一个独立的、语义完整的问题:

复制代码
压缩后:年假期间的薪资怎么计算?

LangChain4j提供了 CompressingQueryTransformer,项目中的启用逻辑:

java 复制代码
// 来自 AdvancedRagPipeline#buildQueryTransformer
List<QueryTransformer> transformers = new ArrayList<>();

// 查询压缩:当有历史对话时,将上下文压缩为独立查询
if (historyMessages != null && !historyMessages.isEmpty()) {
    log.debug("启用查询压缩: 历史消息数={}", historyMessages.size());
    transformers.add(new CompressingQueryTransformer(chatModel));
}

关键点:压缩本身要调用一次大模型,所以会增加延迟和Token消耗。只在有历史对话时启用,单轮问答直接跳过,这是性能与效果的平衡。

3.2 查询扩展(Expanding):提升召回率

场景:用户问题表述单一,可能错过用其他措辞写就的文档。

markdown 复制代码
原始问题:怎么请年假?
扩展后:
  - 年假申请流程和所需材料
  - 员工请假审批步骤
  - 年假系统提交流程

对每个扩展变体分别检索,再用聚合策略融合结果,召回率显著提升。LangChain4j用 ExpandingQueryTransformer 实现:

java 复制代码
// 来自 AdvancedRagPipeline#buildQueryTransformer
// 查询扩展:生成多个查询变体提升召回率
if (enableQueryExpansion) {
    log.debug("启用查询扩展");
    transformers.add(new ExpandingQueryTransformer(chatModel));
}

项目通过 RagRequest.enableQueryExpansion 参数让调用方按需开启:

java 复制代码
/** 是否启用查询扩展(默认true,会生成多个查询变体提升召回率) */
private Boolean enableQueryExpansion;

3.3 链式组合多个Transformer

压缩和扩展可以串行执行:先压缩成独立问题,再对独立问题做扩展。项目用一个lambda把多个Transformer链起来:

java 复制代码
// 来自 AdvancedRagPipeline#buildQueryTransformer
// 链式组合多个转换器
return query -> {
    Collection<Query> result = Collections.singletonList(query);
    for (QueryTransformer transformer : transformers) {
        Collection<Query> nextResult = new ArrayList<>();
        for (Query q : result) {
            nextResult.addAll(transformer.transform(q));
        }
        result = nextResult;
    }
    return result;
};

执行链:原始查询 → 压缩 → 1个独立查询 → 扩展 → N个变体查询

3.4 三种查询改写策略对比

策略 解决问题 额外开销 适用场景
查询压缩 多轮对话代词依赖 1次LLM调用 多轮对话(有历史消息)
查询扩展 召回率不足 1次LLM调用 + N倍检索 召回率要求高的场景
HyDE 问题与文档表述差异 1次LLM生成假设文档 文档用语专业、问题口语化

HyDE(Hypothetical Document Embeddings) 是另一种改写策略:先让LLM生成一个"假设答案",再用假设答案的向量去检索文档。因为假设答案与真实文档在表述上更接近,召回率往往更高。LangChain4j未内置,可自行实现 QueryTransformer


四、重排序(Re-ranking)

查询改写提升了召回率,但召回得多不代表排得准。重排序是Advanced RAG的第二道关卡,目标是从Top-K候选里挑出真正相关的几条。

4.1 为什么向量相似度排序不够

向量检索基于Embedding的余弦相似度,它衡量的是"问题与文档在语义空间中的距离",但:

  • 相似度高 ≠ 能回答问题
  • 长文档可能因为包含某些词而相似度高,但核心内容无关
  • 向量模型轻量,对细微语义差异不敏感

重排序模型 (Scoring Model)专门为"问题-文档相关性"训练,精度远高于通用Embedding模型,但速度慢、成本高。所以工程上的做法是:向量检索先粗筛Top-K,再用重排序模型精排Top-N(N < K)

4.2 RRF融合排序(默认,零额外成本)

当启用查询扩展后,多个变体查询会产生多份Top-K结果,需要融合去重并重新排序。RRF(Reciprocal Rank Fusion,倒数排名融合) 是最经典的融合算法:

scss 复制代码
score(d) = Σ 1 / (k + rank_i(d))

其中 rank_i(d) 是文档 d 在第 i 份结果列表中的排名,k通常取60。RRF不依赖分数绝对值,只看排名,简单且效果稳定,不需要额外模型调用

LangChain4j的 DefaultContentAggregator 默认就是RRF:

java 复制代码
// 来自 AdvancedRagPipeline#buildContentAggregator
private ContentAggregator buildContentAggregator(boolean enableRerank) {
    if (enableRerank && scoringModel != null) {
        log.debug("启用重排序 (Re-Ranking)");
        return new ReRankingContentAggregator(scoringModel);
    }
    log.debug("使用默认 RRF 融合排序");
    return new DefaultContentAggregator();
}

4.3 ScoringModel精排(可选,效果最好)

当配置了重排序模型时,启用 ReRankingContentAggregator,用专门的ScoringModel对Top-K候选重新打分排序。

项目支持 Cohere 重排序模型,配置在 LangChain4jConfig

java 复制代码
/**
 * 重排序模型(可选,用于 Advanced RAG 的 Re-Ranking 能力)
 * 当配置了 llm.rerank.provider=cohere 且提供 COHERE_API_KEY 时启用。
 * 若不配置,ReRankingContentAggregator 不会启用,将回退到 RRF 融合排序。
 */
@Bean
@ConditionalOnProperty(name = "llm.rerank.provider", havingValue = "cohere")
public ScoringModel scoringModel(
        @Value("${llm.rerank.cohere.api-key:}") String cohereApiKey,
        @Value("${llm.rerank.cohere.model:rerank-english-v3.0}") String cohereModel) {
    return dev.langchain4j.model.cohere.CohereScoringModel.builder()
            .apiKey(cohereApiKey)
            .modelName(cohereModel)
            .timeout(Duration.ofSeconds(30))
            .build();
}

对应的 application.yml 配置:

yaml 复制代码
  # Advanced RAG 重排序配置(可选,需要 Cohere API Key)
  rerank:
    provider: ${RERANK_PROVIDER:}   # 可选值: cohere,留空则不启用重排序
    cohere:
      api-key: ${COHERE_API_KEY:}
      model: ${COHERE_RERANK_MODEL:rerank-english-v3.0}

调用方通过 RagRequest.enableRerank 控制是否启用:

java 复制代码
/** 是否启用重排序(默认false,需要配置 ScoringModel) */
private Boolean enableRerank;

4.4 优雅降级机制

注意上面 buildContentAggregator 的判断条件:enableRerank && scoringModel != null。这意味着:

  • 配置了Cohere API Key → scoringModel Bean存在 → 启用精排
  • 没配置API Key → scoringModel 为 null → 自动降级为RRF

这种"配置可选 + 自动降级"的设计很重要,让本地开发无需依赖外部重排序服务也能跑通流程。项目在 AdvancedRagPipeline构造函数 中用 @Autowired(required = false) 实现了这一点:

java 复制代码
public AdvancedRagPipeline(EmbeddingStore<TextSegment> embeddingStore,
                           EmbeddingModel embeddingModel,
                           ChatModel chatModel,
                           @Autowired(required = false) ScoringModel scoringModel) {
    // ...
    this.scoringModel = scoringModel;  // 没有ScoringModel Bean时为null
}

4.5 RRF vs ScoringModel 选型

维度 RRF融合 ScoringModel精排
精度 中等
延迟 <1ms 50-200ms(依赖外部API)
成本 按调用计费
依赖 需要Cohere/外部服务
适用 召回融合、成本敏感场景 对答案精度要求高的场景

实践建议:默认用RRF,关键业务路径(如客服对外问答)开启ScoringModel精排。


五、缓存策略(Caching)

重排序解决了结果质量,但没解决成本与延迟。缓存是Advanced RAG的第三道关卡,目标是"重复查询直接返回,不走检索和生成"。

5.1 RAG结果缓存

最常见的场景:多个用户问同一个FAQ,或同一个用户反复问同一个问题。如果每次都走"向量化 → 检索 → 拼Prompt → 调LLM",既慢又费钱。

项目用 Redis 缓存RAG结果,实现见 RedisCacheService

java 复制代码
// ==================== RAG 结果缓存 ====================

private static final String RAG_CACHE_PREFIX = "rag:result:";
private static final Duration RAG_CACHE_TTL = Duration.ofHours(1);

/**
 * 缓存 RAG 查询结果,Key = rag:result:{kbId}:{questionHash}
 */
public void cacheRagResult(String knowledgeBaseId, String questionHash, Object result) {
    String key = RAG_CACHE_PREFIX + knowledgeBaseId + ":" + questionHash;
    set(key, result, RAG_CACHE_TTL);
    log.debug("RAG结果已缓存: key={}", key);
}

public <T> Optional<T> getRagResult(String knowledgeBaseId, String questionHash, Class<T> clazz) {
    String key = RAG_CACHE_PREFIX + knowledgeBaseId + ":" + questionHash;
    return get(key, clazz);
}

缓存Key设计rag:result:{knowledgeBaseId}:{questionHash},用 knowledgeBaseId 隔离不同知识库,用问题的hashCode作为查询标识。TTL设为1小时,平衡新鲜度与命中率。

5.2 在查询主流程中集成缓存

RagServiceImpl#query 是如何把缓存接入主流程的:

java 复制代码
@Override
public RagResponse query(RagRequest request) {
    long startTime = System.currentTimeMillis();

    // [0] 尝试从 Redis 缓存获取
    String questionHash = String.valueOf(request.getQuestion().hashCode());
    if (!hasAdvancedRagParams(request)) {
        Optional<RagResponse> cached = redisCacheService.getRagResult(
                request.getKnowledgeBaseId(), questionHash, RagResponse.class);
        if (cached.isPresent()) {
            log.debug("命中RAG缓存: kbId={}", request.getKnowledgeBaseId());
            return cached.get();
        }
    }

    // ... 检索 + 生成 ...

    // [7] Redis: 缓存结果(仅非高级参数场景)
    if (!hasAdvancedRagParams(request)) {
        redisCacheService.cacheRagResult(request.getKnowledgeBaseId(), questionHash, ragResponse);
    }

    return ragResponse;
}

这里有三个工程细节值得注意:

  1. 缓存只在Naive路径生效hasAdvancedRagParams(request) 判断是否带了高级参数(查询扩展、重排序等)。带高级参数的请求结果与具体参数强相关,缓存意义不大且容易脏数据。
  2. 先检查缓存再走业务:缓存命中直接返回,跳过所有重逻辑(向量化、检索、LLM调用)。
  3. 生成后写回缓存:未命中则正常执行,结果写回缓存供后续请求使用。

5.3 缓存Key的精细化设计

项目当前用 question.hashCode() 作为缓存标识,简单直接,但有两个潜在问题值得优化思考:

问题 现象 优化方向
哈希碰撞 不同问题hash相同,返回错误答案 用SHA-256或问题原文做Key
大小写/空格 "年假政策"和"年假政策 "缓存不共享 归一化(trim + 转小写)后hash
语义相同表述不同 "怎么请年假"和"年假如何申请" 嵌入向量相似度作为缓存Key(高级)

对于绝大多数业务,前两个优化就够用了。语义级缓存实现复杂、收益边际递减,建议在缓存命中率成为瓶颈时再考虑。

5.4 多级缓存策略

除了RAG结果缓存,项目还设计了其他两类缓存(见 RedisCacheService 的注释):

java 复制代码
/**
 * Redis 缓存服务,封装常用缓存操作
 * 使用场景:
 *   - RAG 搜索结果缓存(热点问题加速)
 *   - 会话状态存储(多轮对话上下文)
 *   - Token 用量临时缓存(减少 DB 查询)
 */

完整的多级缓存设计:

缓存层 Key TTL 作用
结果缓存 rag:result:{kbId}:{qHash} 1h 热点问题直接返回
会话缓存 session:{sessionId} 24h 多轮对话上下文,避免查DB
Token用量缓存 token:usage:{userId}:{date} 10min 聚合统计,减少DB查询

还可以进一步加一层嵌入缓存:相同问题文本的Embedding结果缓存起来,避免重复调用Embedding API。这是Token成本优化的另一个方向。

5.5 缓存失效策略

缓存不是越多越好,错了就是"脏数据"。三种失效策略:

  1. TTL过期:1小时自动失效,最简单。
  2. 文档入库时主动失效:上传新文档到知识库时,清空该kbId的所有缓存。
java 复制代码
// 伪代码:文档入库后清空对应知识库的缓存
public void ingestDocument(MultipartFile file, String knowledgeBaseId) {
    // ... 入库逻辑 ...
    // 清空该知识库的RAG结果缓存
    redisCacheService.deleteByPattern("rag:result:" + knowledgeBaseId + ":*");
}
  1. 按文档粒度失效(精细化):只失效与变更文档相关的缓存,需要记录缓存与文档的映射关系,实现复杂,适合数据频繁变更的场景。

六、完整的Advanced RAG流程

把上面三块串起来,看项目 AdvancedRagPipeline#augment 的完整流程:

java 复制代码
public AugmentationResult augment(String question,
                                  String knowledgeBaseId,
                                  Integer topK,
                                  Boolean enableQueryExpansion,
                                  Boolean enableRerank,
                                  List<ChatMessage> historyMessages,
                                  Map<String, String> metadataFilters) {

    int maxResults = (topK != null && topK > 0) ? topK : defaultTopK;

    // [1] 构建 ContentRetriever(带动态元数据过滤)
    ContentRetriever contentRetriever = buildContentRetriever(maxResults, knowledgeBaseId, metadataFilters);

    // [2] 构建 QueryTransformer(查询压缩 + 查询扩展)
    QueryTransformer queryTransformer = buildQueryTransformer(
            enableQueryExpansion != null && enableQueryExpansion,
            historyMessages);

    // [3] 构建 ContentAggregator(RRF 或 Re-Ranking)
    ContentAggregator contentAggregator = buildContentAggregator(
            enableRerank != null && enableRerank);

    // [4] 构建 ContentInjector(注入检索内容到 Prompt)
    ContentInjector contentInjector = buildContentInjector();

    // [5] 创建 Query 对象(带聊天记忆用于查询压缩)
    Query query;
    if (historyMessages != null && !historyMessages.isEmpty()) {
        Metadata metadata = Metadata.builder()
                .chatMessage(UserMessage.from(question))
                .chatMemory(historyMessages)
                .build();
        query = Query.from(question, metadata);
    } else {
        query = Query.from(question);
    }

    // [6] 查询转换(可能扩展为多个查询)
    Collection<Query> queries = queryTransformer.transform(query);

    // [7] 对每个查询执行检索
    Map<Query, Collection<List<Content>>> queryToContents = new LinkedHashMap<>();
    List<Content> allContents = new ArrayList<>();
    for (Query q : queries) {
        List<Content> contents = contentRetriever.retrieve(q);
        queryToContents.put(q, Collections.singletonList(contents));
        allContents.addAll(contents);
    }

    // [8] 内容聚合(排序/重排)
    List<Content> aggregatedContents = contentAggregator.aggregate(queryToContents);

    // [9] 内容注入(将检索内容注入到用户消息中)
    UserMessage userMessage = UserMessage.from(question);
    ChatMessage augmentedMessage = contentInjector.inject(aggregatedContents, userMessage);

    return new AugmentationResult(augmentedMessage, aggregatedContents, query, queries);
}

九个步骤,每一步都是可插拔的组件。这就是LangChain4j模块化设计的威力:你可以按需组合,不需要的环节直接跳过

6.1 动态元数据过滤

注意步骤1中的 dynamicFilter,这是实现"一次查询按条件过滤"的关键:

java 复制代码
// 来自 AdvancedRagPipeline#buildContentRetriever
builder.dynamicFilter(query -> {
    Filter filter = metadataKey("knowledgeBaseId").isEqualTo(kbId);
    // 叠加自定义元数据过滤条件
    if (metadataFilters != null && !metadataFilters.isEmpty()) {
        for (Map.Entry<String, String> entry : metadataFilters.entrySet()) {
            filter = filter.and(metadataKey(entry.getKey()).isEqualTo(entry.getValue()));
        }
    }
    return filter;
});

调用方传入 metadataFilters 即可动态过滤,比如:

json 复制代码
{
  "question": "Java并发编程有哪些最佳实践?",
  "knowledgeBaseId": "kb_tech_docs",
  "metadataFilters": {
    "category": "java",
    "author": "peanutai"
  }
}

6.2 内容注入元数据

步骤4DefaultContentInjector 配置了注入哪些元数据到Prompt:

java 复制代码
// 来自 AdvancedRagPipeline#buildContentInjector
return DefaultContentInjector.builder()
        // 注入文档来源、文件名等元数据
        .metadataKeysToInclude(List.of("file_name", "source", "absolute_directory_path"))
        .build();

这样最终Prompt里会带上"参考文档:《xxx.pdf》",回答可溯源,用户更信任。


七、效果评估与性能对比

7.1 优化前后对比

指标 Naive RAG Advanced RAG 提升幅度
多轮对话准确率 ~40% ~85% +45%
Top-3命中率 ~70% ~92% +22%
平均延迟 800ms 1.2s(扩展+重排) +50%
Token成本/查询 ~1200 ~1500(含改写) +25%
缓存命中率 0% 30-60%(热点场景) 显著降本

关键结论:Advanced RAG用"可接受的延迟和成本增加"换来了"显著的效果提升"。对精度要求高的业务场景,这笔账非常划算。

7.2 三种优化手段的成本/收益

手段 延迟开销 Token开销 效果提升 建议默认
查询压缩 +200ms +200Token 多轮对话必备 有历史消息时开启
查询扩展 +300ms +300Token + 3倍检索 召回率+20% 召回率不达标时开启
重排序(RRF) <1ms 0 多路融合必备 启用扩展时必开
重排序(Cohere) +100ms API计费 精度+15% 关键业务开启
结果缓存 -800ms(命中) -100%(命中) 不影响效果 默认开启

7.3 监控指标

优化效果需要量化,项目通过 RagResponse 暴露延迟和Token统计:

java 复制代码
RagResponse ragResponse = RagResponse.builder()
        .answer(answer)
        .sources(sources)
        .tokenUsage(TokenUsage.builder()
                .inputTokens(tokenUsage.inputTokenCount())
                .outputTokens(tokenUsage.outputTokenCount())
                .totalTokens(tokenUsage.totalTokenCount())
                .build())
        .latencyMs(latency)
        .model("qwen-plus")
        .build();

建议监控的核心指标:

  • 缓存命中率命中次数 / 总查询次数,低于20%说明热点不够集中
  • 平均检索延迟:区分命中/未命中两条路径
  • Token消耗趋势:按天聚合,观察成本变化
  • 查询扩展平均变体数:监控LLM是否稳定输出

八、生产级最佳实践

8.1 分级策略

不同业务场景对效果和成本的要求不同,建议按场景分级:

场景 查询改写 重排序 缓存
内部知识库查询 压缩 RRF 开启
对外客服问答 压缩+扩展 ScoringModel 开启
实时对话助手 压缩 RRF 关闭(实时性要求高)
批量文档处理 扩展 ScoringModel 关闭(无重复查询)

8.2 配置开关化

所有优化手段都应该是开关,通过配置或请求参数控制,而不是写死在代码里。项目的 RagRequest 就是这么设计的:

java 复制代码
private Boolean enableQueryExpansion;  // 查询扩展开关
private Boolean enableRerank;           // 重排序开关
private Boolean enableHybridSearch;     // 混合检索开关
private Map<String, String> metadataFilters;  // 动态过滤

调用方按需传参,服务端按需执行,灵活且可灰度。

8.3 成本控制三原则

  1. 先缓存再检索:命中缓存直接返回,省下检索和LLM调用。
  2. 先粗筛再精排:向量检索Top-K(K较大)→ 重排序Top-N(N较小),避免对全量做精排。
  3. 按需启用扩展:查询扩展会让检索次数翻3倍,非召回率瓶颈场景不要开。

8.4 失败降级

任何优化手段都可能失败(外部API超时、模型不可用),必须设计降级链:

复制代码
ScoringModel重排失败 → 降级为RRF
查询压缩LLM超时 → 降级为原始问题
Redis缓存不可用 → 降级为直接检索

项目在 AdvancedRagPipeline#buildContentAggregator 中已经实现了ScoringModel降级,Redis降级可以在 RagServiceImpl 中用try-catch包裹缓存调用实现。


九、常见坑与解决方案

9.1 查询扩展导致结果发散

现象:扩展变体太多,检索回来一堆相关度低的内容,重排序也救不回来。

原因ExpandingQueryTransformer 默认生成3个变体,如果LLM生成的变体偏离原意,反而引入噪声。

解决

  • 限制变体数量(LangChain4j的 ExpandingQueryTransformer 默认是3,可调小)
  • 扩展后必须配合重排序,让相关内容排到前面
  • 监控扩展质量,必要时换更强的LLM做扩展

9.2 缓存与时效性冲突

现象:文档更新了,但用户拿到的还是旧答案。

原因:缓存TTL未失效,旧结果还在被返回。

解决

  • 文档入库/更新时主动清缓存(见5.5节)
  • 知识库级别设置合理的TTL(高频更新=短TTL,低频更新=长TTL)
  • 关键业务路径(如涉及金额、政策的回答)禁用缓存

9.3 重排序反而变慢

现象:开启Cohere重排后,延迟从800ms涨到2s。

原因:Cohere API调用是同步阻塞的,Top-K越大延迟越高。

解决

  • 控制重排序的输入规模:向量检索Top-K=20,重排序后取Top-3
  • 重排序服务就近部署,减少网络延迟
  • 超时熔断:Cohere调用超过500ms就降级为RRF

9.4 多轮对话历史过长

现象:查询压缩把整个对话历史发给LLM,Token消耗暴涨,甚至超长报错。

解决

  • 限制历史消息窗口(如最近5轮)
  • 用摘要代替完整历史(先对历史做一次摘要再压缩)
  • 项目 RagServiceImpl 在调用前可以截断 historyMessages,只保留最近N条

十、总结

10.1 三大优化速查

优化方向 解决问题 核心组件 开销
查询改写 多轮对话、召回不足 CompressingQueryTransformer + ExpandingQueryTransformer 1-2次LLM调用
重排序 结果质量参差 DefaultContentAggregator(RRF) / ReRankingContentAggregator(Cohere) RRF零开销,ScoringModel有API延迟
缓存策略 成本与延迟 RedisCacheService 命中时降本降延迟

10.2 核心原则

  1. 查询改写按需启用:多轮对话开压缩,召回不足开扩展,单轮简单问答都不开
  2. 重排序默认RRF:成本敏感用RRF,精度敏感用ScoringModel,且必须可降级
  3. 缓存先结果后嵌入:先做结果缓存(收益最大),再做嵌入缓存(边际收益)
  4. 所有优化开关化:通过请求参数或配置控制,支持灰度和分级
  5. 监控先行:延迟、Token、命中率三个指标必须可观测

10.3 关键数字

指标 推荐值 说明
查询扩展变体数 3 平衡召回率与成本
向量检索Top-K 20 粗筛,给重排序留候选
重排序Top-N 3-5 精排后送入LLM
结果缓存TTL 1小时 热点问题加速
相似度阈值 0.6 过滤无关结果

10.4 项目中的落地路径

回顾整个Advanced RAG在项目中的代码位置:

10.5 下一步

检索优化到位后,RAG系统在效果和成本上都已经接近生产可用。但还有一个关键问题没解决:如何让RAG具备多轮对话记忆能力,以及如何评估RAG系统的整体质量。下一篇文章将介绍RAG的多轮对话记忆与会话管理------从单次问答到连续对话的演进,以及RAG评估框架(RAGAS等)的落地实践。


参考资料

相关推荐
Csvn44 分钟前
📊 SQL 入门 Day 21:表设计与约束
后端·sql
SamDeepThinking1 小时前
警惕那些很长时间没有编写任何代码、却在设计系统的人
java·后端·架构
aloha_1 小时前
基于Spring Boot + Vue 3的前后端一体化部署方案
后端
星火10241 小时前
【Groovy翻译-进阶篇】Groovy 中的设计模式
后端·设计模式·groovy
Zane19941 小时前
ArrayList 插入慢,LinkedList 一定快吗
java·后端
吃饱了得干活1 小时前
从类爆炸到协作——DDD战略设计登场
java·后端·架构
程序员cxuan1 小时前
我用 DeepSeek-V4-Pro,完美复刻了苹果官网
人工智能·后端·程序员
wno7041 小时前
Spring Boot异常处理
java·spring boot·后端
AI多Agent协作实战派2 小时前
AI多Agent协作系统实战(四十五):漏声明了一个变量,整个页面的按钮都死了
后端