在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]前缀,方便前端识别出来这个是引用相关内容,这样就可以不在输出的正文中展示,也可以通过特殊的引用样式显示。