know-engine

如何把引用文档内容返回给用户?

在RAG中,给问题回答增加参考来源是比较常见的做法,一方面可以增加回答的可信度,另外也能让用户直接查看完整文档,获取更多信息,如: 具体实现方式分两步,首先在分段时,需要在每一个分段中记录下来当前分段所属的文档的名称、以及对应的地址…

TL;DR

在RAG中,给问题回答增加参考来源是比较常见的做法,一方面可以增加回答的可信度,另外也能让用户直接查看完整文档,获取更多信息,如: 具体实现方式分两步,首先在分段时,需要在每一个分段中记录下来当前分段所属的文档的名称、以及对应的地址…

在RAG中,给问题回答增加参考来源是比较常见的做法,一方面可以增加回答的可信度,另外也能让用户直接查看完整文档,获取更多信息,如: 具体实现方式分两步,首先在分段时,需要在每一个分段中记录下来当前分段所属的文档的名称、以及对应的地址。 在DocumentProcessServiceImpl#split方法中 我们统一做了这个处理:

Metadata metadata = segment.metadata();
metadata.put(MetadataKeyConstant.DOC_ID, document.getDocId());
metadata.put(MetadataKeyConstant.FILE_NAME, document.getDocTitle());
metadata.put(MetadataKeyConstant.URL, document.getDocUrl());

//todo metadata统一处理(权限相关、多版本相关)
knowledgeSegment.setMetadata(JSON.toJSONString(metadata.toMap()));

也就是说,在分段之后,我们会统一给每一个分段都增加一些metadata的信息,包括DOC_ID、FILE_NAME以及URL。 然后在检索文档时,我们也需要针对检索结果做处理,我们在重排序精筛后做这个处理即可,所以代码实现在ProgressAwareContentAggregator#aggregate中:

try {
    List<ChatMessage.RagReference> ragReferences = results.stream()
            .collect(Collectors.toMap(
                    content -> content.textSegment().metadata().getInteger(DOC_ID),
                    content -> content,
                    (existing, replacement) -> existing
            )).values().stream().map(content -> {
                ChatMessage.RagReference reference = new ChatMessage.RagReference();
                reference.setDocumentId(content.textSegment().metadata().getInteger(DOC_ID) + "");
                reference.setChunkId(content.textSegment().metadata().getString(CHUNK_ID));
                reference.setUrl(content.textSegment().metadata().getString(URL));
                reference.setDocumentTitle(content.textSegment().metadata().getString(FILE_NAME));
                reference.setChunkContent(content.textSegment().text());
                reference.setRerankScore((double) content.metadata().get(RERANKED_SCORE));
                return reference;
            }).collect(Collectors.toList());

    if (!CollectionUtils.isEmpty(ragReferences) && chatMessageService != null && chatMessageId != null) {
        chatMessageService.updateRagReferences(chatMessageId, ragReferences);
    }

    if (progressCallback != null) {
        progressCallback.accept("[REFERENCE]:" + JSON.toJSONString(ragReferences));
        System.out.println("[REFERENCE]:" + JSON.toJSONString(ragReferences));
    }
} catch (Exception e) {
    log.warn("RAG引用信息回写失败: assistantMsgId={}", chatMessageId, e);
}

针对精排后的文档内容(results),我们先根据DOC_ID做去重,然后再从metadata中构造RagReference。 构造成功后,把这个信息保存在chatMessage中,所以这个类需要在构造时传入chatMessageId和chatMessageService。

ContentAggregator contentAggregator = new ProgressAwareContentAggregator(
        ReRankingContentAggregator.builder()
                .scoringModel(scoringModel)
                .maxResults(5)
                .querySelector(queryToContents -> queryToContents.keySet().iterator().next())
                .build(),
        callback, assistantMessageId, chatMessageService
);

注意:这里的messageId,我们需要传入assistantMessage的 ,即模型返回的那条消息的,而不是用户提问的消息的。 并且,内容保存下来之后,我们还需要把这个应用信息返回给前端,所以我们借助这里的progressCallback,把内容返回:

    if (progressCallback != null) {
        progressCallback.accept("[REFERENCE]:" + JSON.toJSONString(ragReferences));
        System.out.println("[REFERENCE]:" + JSON.toJSONString(ragReferences));
    }

这里,我们特殊的用一个[REFERENCE]前缀,方便前端识别出来这个是引用相关内容,这样就可以不在输出的正文中展示,也可以通过特殊的引用样式显示。

版本提示

模型、框架与接口会持续变化。涉及版本号、参数与生产配置时,请在实践前对照对应官方文档。

LLMentor系统化学习大模型应用工程

内容来自个人课程知识库备份,并经过结构化整理。技术版本持续演进,生产使用前请结合官方文档验证。