前言
之前我们介绍了使用 Spring AI 加上嵌入模型将数据向量化,并且存入了内存当中。在用户进行对话时,根据用户的问题去内存中进行相似度匹配,从而可以找得到我们预置好的答案来回答用户。本章将使用关系型数据库结合实际项目来实现向量的动态更新与维护。
预置的知识库在加载的时候向量化进入内存,如果后期这个知识库进行变更,怎么做到向量的同步更新?
RAG
这里只做关键性代码的说明,如果对基本的大模型集成还不是很了解的可以看下我之前发布的文章。
构建知识库表单
这里使用 MySQL 进行数据存储,包含基本的标题和内容。
CREATE TABLE `ai_knowledge` (
`id` bigint NOT NULL AUTO_INCREMENT COMMENT '主键ID',
`title` varchar(200) NOT NULL COMMENT '知识库标题',
`content` text NOT NULL COMMENT '知识库内容',
`create_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP COMMENT '创建时间',
`update_time` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP COMMENT '更新时间',
`create_by` varchar(50) DEFAULT NULL COMMENT '创建人',
`update_by` varchar(50) DEFAULT NULL COMMENT '更新人',
`deleted` tinyint(1) NOT NULL DEFAULT '0' COMMENT '是否删除(0-未删除,1-已删除)',
PRIMARY KEY (`id`)
) ENGINE=InnoDB AUTO_INCREMENT=6 DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci COMMENT='AI知识库表';
实体以及数据的增删改查本章就直接省略了,如果需要可以直接查看后面内容,会提供完整代码。
数据向量化
通过实现 CommandLineRunner达到在启动的时候将数据库中的知识库内容向量化。
/**
* 向量数据加载
*
* @author lanjii
*/
@Slf4j
@Component
@RequiredArgsConstructor
public class VectorStoreRunner implements CommandLineRunner {
private final VectorStore vectorStore;
private final AiKnowledgeService aiKnowledgeService;
@Override
public void run(String... args) throws Exception {
List<AiKnowledge> list = aiKnowledgeService.list();
List<Document> documents = list.stream().map(aiKnowledge -> {
return new Document(String.valueOf(aiKnowledge.getId()), aiKnowledge.toString(), new HashMap<>());
}).collect(Collectors.toList());
vectorStore.add(documents);
}
}
我们在进行 Document创建的时候使用的如下方式
new Document(String.valueOf(aiKnowledge.getId()), aiKnowledge.toString(), new HashMap<>())
第一个参数就是向量的 id,这里直接使用知识库 id,方便后期的动态维护,第二个参数为需要向量化的数据,第三个参数则是元数据,元数据可以实现类似于 sql 的精准查找,这里暂不阐述。
知识库维护
在进行数据库的时候使用 vectorStore 同步进行向量的操作,这里就使用到了之前自己定义的向量 id。
private final VectorStore vectorStore;
@Override
@Transactional(rollbackFor = Exception.class)
public void saveNew(AiKnowledgeDTO dto) {
AiKnowledge entity = AiKnowledge.INSTANCE.toEntity(dto);
save(entity);
// 向量化和存储
vectorStore.add(Collections.singletonList(new Document(String.valueOf(entity.getId()), entity.toString(), new HashMap<>())));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void updateByIdNew(Long id, AiKnowledgeDTO dto) {
AiKnowledge existEntity = getById(id);
if (existEntity == null) {
throw new BizException(ResultCode.NOT_FOUND, "数据不存在");
}
// 删除向量
vectorStore.delete(List.of(String.valueOf(id)));
AiKnowledge entity = AiKnowledge.INSTANCE.toEntity(dto);
entity.setId(id);
updateById(entity);
// 向量化和存储
vectorStore.add(List.of(new Document(String.valueOf(entity.getId()), entity.toString(), new HashMap<>())));
}
@Override
@Transactional(rollbackFor = Exception.class)
public void removeByIdNew(Long id) {
AiKnowledge entity = getById(id);
if (entity == null) {
throw new BizException(ResultCode.NOT_FOUND, "数据不存在");
}
removeById(id);
// 删除向量
vectorStore.delete(List.of(String.valueOf(id)));
}
接口
最关键的还是要在聊天的时候使用我们的向量,在接口创建时申明使用 VectorStore
@RequestMapping("/chat")
@RequiredArgsConstructor
public class ChatBotController {
private final ChatClient chatClient;
private final VectorStore vectorStore;
/**
* 流式问答
*/
@GetMapping(value = "/stream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public Flux<String> chatStream(String message, String conversationId) {
return chatClient.prompt()
.advisors(
a -> a.param(ChatMemory.CONVERSATION_ID, conversationId)
)
.advisors(QuestionAnswerAdvisor.builder(vectorStore).build())
.user(message)
.stream()
.content();
}
}
效果
## 完整代码
不定期同步至 Gitee:gitee.com/leven2018/l…
小结
至此一个依托于数据库知识库构建的 RAG 功能就建设好了,但实际场景可能不仅仅知识数据库,也可能需要解析文件,Spring AI 也提供了很好的支持,放在之后的章节再说。