知识库问答源码解析

整体架构

flowchart LR
    A[用户提问] --> B{指定知识库?}
    B -->|否| C[多知识库自动匹配]
    B -->|是| D[获取知识库配置]
    C --> D
    D --> E[问题向量化]
    E --> F{知识库模式?}
    F -->|标准化/客服| G[标准问答策略]
    F -->|普通| H[向量检索策略]
    G --> I{匹配成功?}
    I -->|是| J[返回预定义答案]
    I -->|否| H
    H --> K[向量检索 + 全文检索]
    K --> L[重排序]
    L --> M[生成答案]
    M --> N[相似问题推荐]
    N --> O[返回完整响应]

多知识库自动匹配

当用户未指定知识库时,系统会根据用户问题语义自动匹配最相关的知识库。

flowchart LR
    A[用户提问] --> B{datasetId 为空?}
    B -->|否| C[直接使用指定知识库]
    B -->|是| D[调用 autoChoice]
    D --> E[EmbeddingProvider.search]
    E --> F[用户问题向量化]
    F --> G[在业务向量库中搜索]
    G --> H{找到匹配?}
    H -->|否| I[返回提示选择知识库]
    H -->|是| J[获取最高分知识库ID]
    J --> C

自动匹配逻辑

位置: support/rule/VectorChatRule.java

private static Flux<AiMessageResultDTO> autoChoice(ChatMessageDTO chatMessageDTO) {
    // 在业务向量库中搜索,过滤类型为 DATASET(知识库描述)
    EmbeddingSearchResult<TextSegment> searchResult = EmbeddingProvider.search(
        chatMessageDTO.getContent(),
        metadataKey(EmbedBizTypeEnums.Fields.type).isEqualTo(EmbedBizTypeEnums.DATASET.getType())
    );

    // 未找到匹配的知识库
    if (Objects.isNull(searchResult) || searchResult.matches().isEmpty()) {
        return Flux.just(new AiMessageResultDTO("未找到相关知识库,请点击下方+按钮选择目标知识库"));
    }

    // 获取匹配度最高的知识库ID,设置到上下文
    Long dataId = searchResult.matches().get(0).embedded().metadata().getLong(TEMP_ID);
    chatMessageDTO.setDatasetId(dataId);
    ...
}

RAG 搜索流程

flowchart LR
    A[开始检索] --> B[构建搜索请求]
    B --> C[向量相似度搜索]
    C --> D{Milvus?}
    D -->|是| E[BM25 全文检索]
    D -->|否| F[合并结果]
    E --> F
    F --> G{结果为空?}
    G -->|是| H[空结果处理]
    G -->|否| I{配置 Reranker?}
    I -->|是| J[重排序优化]
    I -->|否| K[生成答案]
    J --> K
    K --> L[并行任务]
    L --> M[返回 SSE 流]

向量检索增强生成策略

位置: support/handler/rag/strategy/impl/VectorRetrievalAugmentedGenerationStrategy.java

@Override
public Flux<AiMessageResultDTO> processChat(Embedding queryEmbedding, AiDatasetEntity dataset,
                                            ChatMessageDTO chatMessageDTO) {
    // 1. 执行向量检索
    List<EmbeddingMatch<TextSegment>> vectorMatches = performVectorSearch(queryEmbedding, dataset);

    // 2. 执行全文检索(仅 Milvus 支持)
    List<EmbeddingMatch<TextSegment>> fullTextMatches = performFullTextSearch(dataset, chatMessageDTO);

    // 3. 合并检索结果
    List<EmbeddingMatch<TextSegment>> allMatches = RagHelper.mergeSearchResults(vectorMatches, fullTextMatches);

    // 4. 空结果处理
    if (RagHelper.isEmpty(allMatches)) {
        return RagHelper.handleEmptyResult(dataset, chatMessageDTO, recordId);
    }

    // 5. 重排序搜索结果
    List<Content> rerankedContent = RagHelper.rerankSearchResults(dataset, chatMessageDTO.getContent(), allMatches);

    // 6. 生成答案并附加参考资料
    return generateAnswerWithReferences(dataset, chatMessageDTO, rerankedContent, allMatches);
}

搜索参数说明

参数说明示例
queryEmbedding用户问题的向量表示Vector[0.23, -0.45, ...]
maxResults返回结果数量(TopK)5
minScore相似度阈值标准问答: 0.9,向量检索: 0.75
datasetId知识库ID过滤123
documentType文档类型"0"=ANSWER,"1"=QUESTION

向量搜索请求构建

位置: support/handler/rag/strategy/RagHelper.java

public static EmbeddingSearchRequest buildSearchRequest(Embedding queryEmbedding, AiDatasetEntity dataset,
                                                        String documentType, Double minScore) {
    // 计算相似度阈值(将百分比转换为小数,如 75 → 0.75)
    double finalMinScore = Objects.nonNull(minScore) ? minScore :
        NumberUtil.div(Double.parseDouble(dataset.getScore().toString()), 100.0, 2);

    return new EmbeddingSearchRequest(
        queryEmbedding,           // 查询向量
        dataset.getTopK(),        // 返回数量
        finalMinScore,            // 相似度阈值
        metadataKey(AiDocumentEntity.Fields.datasetId).isEqualTo(dataset.getId().toString())
            .and(metadataKey(DocumentTypeEnums.Fields.type).isEqualTo(documentType))
    );
}

重排序(Rerank)

位置: support/handler/rag/strategy/RagHelper.java

public static List<Content> rerankSearchResults(AiDatasetEntity dataset, String queryContent,
                                                List<EmbeddingMatch<TextSegment>> embeddingMatches) {
    ...
    // 如果没有配置重排序模型,使用默认聚合器
    if (StrUtil.isBlank(dataset.getRerankerModel())) {
        return DEFAULT_AGGREGATOR.aggregate(queryToContents);
    }

    // 使用配置的重排序模型
    ScoringModel rerankerModel = modelProvider.getRerankerModel(dataset.getRerankerModel());
    ContentAggregator contentAggregator = ReRankingContentAggregator.builder()
        .scoringModel(rerankerModel)
        .build();
    return contentAggregator.aggregate(queryToContents);
}

相似问题推荐

flowchart LR
    A[检索完成] --> B{启用相似问题?}
    B -->|否| C[跳过]
    B -->|是| D{有检索内容?}
    D -->|否| C
    D -->|是| E[取前3条上下文]
    E --> F[构建 Prompt]
    F --> G[调用 LLM 生成]
    G --> H[JSON 解析]
    H --> I[过滤规则]
    I --> J[返回相似问题]

核心实现

位置: support/handler/rag/strategy/SimilarQuestionHelper.java

public static Mono<List<AiMessageResultDTO.SimilarQuestion>> generateSimilarQuestions(
    String userQuery, List<String> retrievedContents, ChatMessageDTO chatMessageDTO) {

    if (retrievedContents == null || retrievedContents.isEmpty()) {
        return Mono.empty();
    }

    // 构建 Prompt(取前3条检索内容作为上下文)
    String context = String.join("\n", retrievedContents.subList(0, Math.min(3, retrievedContents.size())));
    ...

    // 异步调用大模型生成
    return Mono.fromCallable(() -> {
        String result = streamAssistant.getValue().chat(systemPrompt, userPrompt)...;
        return parseAndFilterQuestions(result, userQuery);
    }).subscribeOn(Schedulers.boundedElastic());
}

过滤规则

private static List<AiMessageResultDTO.SimilarQuestion> parseAndFilterQuestions(
    String jsonResult, String originalQuery) {
    ...
    for (int i = 0; i < questions.size(); i++) {
        String text = questions.getStr(i).trim();

        // 长度过滤:10-40 字符
        if (text.length() < 10 || text.length() > 40) continue;

        // 相似度过滤:与原问题相似度 < 70%
        if (isSimilarToOriginal(text, originalQuery)) continue;

        result.add(new AiMessageResultDTO.SimilarQuestion(text, null));

        // 数量限制:最多3条
        if (result.size() >= 3) break;
    }
    return result;
}
规则条件说明
长度过滤10 ≤ 长度 ≤ 40过短或过长的问题被丢弃
去重过滤相似度 < 0.7与原问题相似度过高被丢弃
数量限制最多 3 条达到 3 条后停止添加

并行执行

位置: support/handler/rag/strategy/impl/VectorRetrievalAugmentedGenerationStrategy.java

private Flux<AiMessageResultDTO> generateAnswerWithReferences(...) {
    ...
    // 并行任务 1:相似问题生成(异步)
    Mono<List<SimilarQuestion>> similarQuestionsMono = needSimilarQuestions
        ? SimilarQuestionHelper.generateSimilarQuestions(...)
        : Mono.just(Collections.emptyList());

    // 并行任务 2:参考资料链接构建(异步)
    Mono<List<ExtLink>> extLinksMono = Mono
        .fromCallable(() -> RagHelper.buildReferenceLinks(embeddingMatches, aiDocumentService))
        .subscribeOn(Schedulers.boundedElastic());

    // 主回复流 + 合并最终结果
    return answerStream.concatWith(
        Mono.zip(similarQuestionsMono, extLinksMono).map(tuple -> {
            AiMessageResultDTO result = new AiMessageResultDTO();
            result.setSimilarQuestions(tuple.getT1());
            result.setExtLinks(tuple.getT2());
            result.setFinish(true);
            return result;
        })
    );
}

向量存储工厂

flowchart LR
    A[EmbeddingStoreFactory] --> B{向量库类型?}
    B -->|milvus| C[MilvusEmbeddingStoreFactory]
    B -->|chroma| D[ChromaEmbeddingStoreFactory]
    B -->|qdrant| E[QdrantEmbeddingStoreFactory]
    B -->|neo4j| F[Neo4jEmbeddingStoreFactory]
    B -->|pgvector| G[PgVectorEmbeddingStoreFactory]
实现类向量库特性
MilvusEmbeddingStoreFactoryMilvus支持 BM25 全文检索
ChromaEmbeddingStoreFactoryChroma轻量级向量库
QdrantEmbeddingStoreFactoryQdrant高性能向量搜索
Neo4jEmbeddingStoreFactoryNeo4j图数据库 + 向量
PgVectorEmbeddingStoreFactorypgvectorPostgreSQL 扩展

核心类总结

类名路径职责
VectorChatRulesupport/rule/RAG 入口,策略选择,多知识库自动匹配
StandardQuestionAnswerStrategysupport/handler/rag/strategy/impl/标准问答精确匹配(0.9 阈值)
VectorRetrievalAugmentedGenerationStrategysupport/handler/rag/strategy/impl/向量检索 + 全文检索 + 重排序 + 生成
RagHelpersupport/handler/rag/strategy/搜索请求构建、BM25、重排序、答案生成
SimilarQuestionHelpersupport/handler/rag/strategy/相似问题生成与过滤
EmbeddingProvidersupport/provider/业务向量存储,知识库自动匹配
EmbeddingStoreFactorysupport/handler/rag/向量存储工厂接口