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 内容注入元数据

步骤4中 DefaultContentInjector 配置了注入哪些元数据到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等)的落地实践。


参考资料

相关推荐
软件技术NINI3 小时前
基于JavaWeb 汽车租聘
java·vue.js·spring boot·后端·python·毕业设计·毕设
yz_aiks3 小时前
RestTemplate 废弃后迁移 RestClient 实战:Spring Boot 4.2 升级指南
java·spring boot·后端·微服务·restclient
金銀銅鐵12 小时前
[Java] 用 GUI 展示 EnumSet 增减元素时的内部变化(上)
java·后端·ai编程
念何架构之路13 小时前
go-grpc设计思想
开发语言·后端·golang
system_error_x13 小时前
SpringBoot 集成 Thymeleaf 简洁入门
java·spring boot·后端
步行cgn15 小时前
Spring MVC 视图机制的核心接口与类
java·开发语言·后端·spring·mvc
码事漫谈15 小时前
AI 读你的提示词,其实很没耐心
后端
用户83562907805116 小时前
如何使用 Python 格式化 Excel 单元格中的长文本
后端·python
我命由我1234516 小时前
Python 开发 - Python 的 pathlib
java·开发语言·后端·python·学习·java-ee·学习方法
IT_陈寒16 小时前
Java性能优化完全指南
前端·人工智能·后端