1、为什么要让它们结合?
大语言模型虽然强大,但有两个天生的“短板”:
知识是“停滞”的:模型的知识截止于其训练数据的日期,对于新发生的事件或最新信息一无所知。
缺乏“私域知识”:它不掌握你企业内部文档、特定产品手册等非公开信息,回答这类问题时容易“一本正经地胡说八道”(即产生“幻觉”)。
而向量数据库正好能弥补这些不足。它能把海量的文本、图片等非结构化数据转换成“向量”——也就是数据的语义数字指纹。向量之间的“距离”反映了它们在含义上的相近程度,这使得向量数据库能进行语义搜索,而不仅仅是关键词匹配。
2、核心工作流程:从建库到问答
向量数据库和大模型最经典的结合方式,就是构建RAG(检索增强生成)系统。可以这样理解:向量数据库充当大模型的“外接大脑”或“长期记忆体”,按需为模型提供它本身不具备的、最新的、特定领域的专业知识,从而让模型的回答更精准、更可靠。
核心工作流程
这个结合过程通常分为两个阶段,我整理了一个流程图来帮你快速理解它的核心工作流程:
它们的结合使用可以拆解为“离线建库”和“在线问答”两个核心环节。
阶段一:离线建库(准备知识库)
这个阶段的目标是将你的专属知识“搬进”向量数据库。
数据准备:收集你拥有的各种文档(如PDF、Word、网页等)。
文本分块:将长文档切分成语义完整的、长度适中的小块(Chunk)。通常会设置一定的重叠区域,防止切断关键信息。
向量化:调用一个嵌入模型,将每个文本块转换为一个固定维度的数值向量(例如,OpenAI的
text-embedding-ada-002模型会生成1536维的向量)。存储入库:将这些文本块和它们对应的向量,以及一些元数据(如标题、来源),一并存入向量数据库。
阶段二:在线问答(实时生成回答)
当用户提问时,系统实时运作:
问题向量化:使用与建库阶段完全相同的嵌入模型,将用户的问题也转化为一个向量。
相似度检索:向量数据库在已存储的海量向量中,通过特定的索引算法(如HNSW、IVF)快速找到与问题向量最相似的Top-K个向量,并返回它们对应的原始文本块。
上下文增强:将检索到的这些相关文本块作为“参考资料”,与用户的原始问题组合成一个内容丰富的“增强提示词”。
大模型生成:将这个增强后的提示词输入给大语言模型。模型不再凭空想象,而是基于提供的参考资料来生成最终的回答,这大大提升了回答的准确性和可靠性。
3、进阶技巧与趋势
除了基础流程,为了让结合效果更好,业界也有一些进阶玩法:
混合检索 (Hybrid Search):将“向量检索”(语义)和“关键词检索”(精确匹配)结合,再通过重排序(Rerank)模型综合打分,可以得到更全面的结果。
持久化聊天记忆:利用向量数据库存储对话历史,让大模型在长对话中也能“记住”之前聊了什么,实现有上下文关联的连续对话。
处理多模态数据:将图片、音视频也转化为向量,构建能处理多模态内容的RAG系统,实现“以图搜图”或图文混合问答。
图与向量的深度融合:对于复杂的逻辑推理(如“A公司的实控人和B供应商有什么关系?”),更先进的做法是将向量数据库与图数据库结合,让AI既能理解语义,又能进行关系推理。
4、spring ai示例
服务实现
package com.ybw.service; import jakarta.annotation.Resource; import lombok.extern.slf4j.Slf4j; import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.document.Document; import org.springframework.ai.reader.TextReader; import org.springframework.ai.transformer.splitter.TokenTextSplitter; import org.springframework.ai.vectorstore.SearchRequest; import org.springframework.ai.vectorstore.VectorStore; import org.springframework.ai.vectorstore.filter.Filter; import org.springframework.stereotype.Service; import java.util.List; import java.util.Map; @Service @Slf4j public class RagService { // 自动注入Ollama ChatClient(qwen3.5:4b) @Resource(name = "chatClient") private ChatClient chatClient; // 自动注入Qdrant向量存储 @Resource private VectorStore vectorStore; // 文本切分器,自动分块 private final TokenTextSplitter textSplitter = TokenTextSplitter.builder() // 最大分块数量,默认300 .withMaxNumChunks(300) // 最小分块字符数,默认80 .withMinChunkSizeChars(80) .build(); /** * 加载本地文本文档,向量化存入Qdrant * * @param resource 文本文件 * @param metadata 元信息 */ public void loadDocumentToVectorDB(org.springframework.core.io.Resource resource, Map<String, Object> metadata) { //1、读取txt文件 TextReader reader = new TextReader(resource); List<Document> rawDocs = reader.get(); //2、为文档打上标签 rawDocs.forEach(doc -> { doc.getMetadata().putAll(metadata); }); //2、文本分片 List<Document> chunks = textSplitter.transform(rawDocs); //3、批量写入向量库,自动调用nomic-embed-text向量化 vectorStore.add(chunks); log.info("文档入库完成,分片数量:{}", chunks.size()); } /** * RAG问答:检索向量库+大模型生成答案 * * @param question 用户问题 * @param expression 向量数据库-过滤条件 * @methodName: ragChat * @return: java.lang.String * @author: ybw * @date: 2026/7/18 **/ public String ragChat(String question, Filter.Expression expression) { // 1. 根据问题语义检索Top4相似文档片段 List<Document> similarDocs = vectorStore.similaritySearch( SearchRequest.builder() .query(question) // 设置 topK 为 3,即只返回相似度最高的 3 条文档 .topK(3) // 设置相似度阈值为 0.3,即只返回相似度大于 0.3 的文档 .similarityThreshold(0.3) // 设置过滤条件,只返回 category=programming 的文档 .filterExpression(expression) .build() ); // 2. 拼接检索上下文 StringBuilder context = new StringBuilder(); for (Document doc : similarDocs) { context.append(doc.getText()).append("\n\n"); } // 3. 构造RAG提示词,约束大模型仅使用检索内容作答 String promptTemplate = """ 你是专业知识库助手,请严格根据下面提供的参考资料回答用户问题, 如果参考资料中没有相关内容,直接回复“暂无相关资料”,禁止编造信息。 参考资料: {context} 用户问题:{question} """; // 4. 调用Qwen3.5:4b生成回答 log.info("context:{}", context); log.info("promptTemplate:{}", promptTemplate); return chatClient.prompt() .user(u -> u.text(promptTemplate) .param("context", context.toString()) .param("question", question)) .call() .content(); } }测试
package com.ybw.service; import lombok.extern.slf4j.Slf4j; import org.junit.jupiter.api.Test; import org.springframework.ai.vectorstore.filter.Filter; import org.springframework.ai.vectorstore.filter.FilterExpressionBuilder; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.boot.test.context.SpringBootTest; import org.springframework.core.io.ClassPathResource; import org.springframework.core.io.Resource; import java.io.IOException; import java.util.Map; @SpringBootTest @Slf4j public class RagServiceTest { @Autowired private RagService ragService; @Test public void loadDocumentToVectorDB() throws IOException { Resource resource = new ClassPathResource("file/a.txt"); Map<String, Object> metadata = Map.of("category", "database", "type", "test"); log.info("file是否存在:{},路径:{}", resource.exists(), resource.getFilePath()); ragService.loadDocumentToVectorDB(resource, metadata); } @Test public void ragChat() { String question = "小松鼠做了什么?"; Filter.Expression expression = new FilterExpressionBuilder().eq("category", "database").build(); String answer = ragService.ragChat(question,expression); log.info("question:{}, answer:{}", question, answer); } }