石家庄网站建设方案推广,盘锦网站建设报价,永久免费建站空间,大城网站制作前言
在上一篇文章中#xff0c;我们成功地将一篇长文档加载并分割成了一系列小的文本片段#xff08;TextSegment#xff09;。我们现在有了一堆“知识碎片”#xff0c;但面临一个新问题#xff1a;计算机如何理解这些碎片的内容#xff0c;并找出与用户问题最相关的片…前言
在上一篇文章中我们成功地将一篇长文档加载并分割成了一系列小的文本片段TextSegment。我们现在有了一堆“知识碎片”但面临一个新问题计算机如何理解这些碎片的内容并找出与用户问题最相关的片段呢
如果用户问“X-Wing的设置要求是什么”我们不能只用简单的关键词匹配比如搜索“setup”或“requirements”因为用户可能会用不同的词语提问“我该如何安装X-Wing”。我们需要一种能够理解**语义Semantic Meaning**的搜索方式。
这就是文本嵌入Text Embedding 和 向量数据库Vector Database 发挥作用的地方。今天我们将深入RAG技术的心脏地带学习如何将文本转化为向量并将其存储起来以便进行高效的语义搜索。
第一部分什么是文本嵌入Embedding语义的“指纹”
想象一下图书馆里的书。图书管理员不会记住每本书的每一个字但他们知道哪些书是关于“历史”的哪些是关于“科幻”的哪些是关于“烹饪”的。他们将书的内容“映射”到了一个类别体系中。
文本嵌入模型Embedding Model做的是类似但更精细的事情。它是一个专门的AI模型它的唯一工作就是读取一段文本然后输出一个由几百上千个数字组成的列表——向量Vector。 这个向量就是这段文本在多维语义空间中的“坐标”或“指纹”。 这个语义空间非常神奇。在其中意思相近的文本它们的向量在空间中的距离也相近。经典的例子是 vector(King) - vector(Man) vector(Woman) ≈ vector(Queen)
通过将所有文档片段都转换成向量我们就可以通过计算向量之间的“距离”来判断文本之间的语义相似度从而实现比关键词搜索高级得多的语义搜索。
在LangChain4j中这个功能由EmbeddingModel接口来抽象。
第二部分什么是向量数据库Vector Store向量的“家”
现在我们有能力将所有文本片段都转换成向量了但我们该把这些向量存放在哪里又如何高效地搜索它们呢
这就是向量数据库或称为向量存储EmbeddingStore的作用。 如果说嵌入模型是“翻译官”把文本翻译成向量那么向量数据库就是专门为这些向量建立的“高维空间索引系统”。 它允许我们
存储大量的向量及其关联的原始文本。当给定一个新的查询向量时能以极高的效率找出数据库中与它最相似的N个向量这个过程通常被称为“最近邻搜索”。
LangChain4j通过EmbeddingStore接口支持多种向量数据库从简单的内存存储到专业的分布式数据库
InMemoryEmbeddingStore: 完全在内存中运行非常适合快速原型开发和测试无需任何外部依赖。Chroma, Milvus, Pinecone, Weaviate: 生产级的、可独立部署的向量数据库支持海量数据和高并发查询。
在今天的教程中我们将从最简单的InMemoryEmbeddingStore开始。
第三部分实战 - 将文档片段嵌入并存储
我们将继续完善上一篇的DocumentService为其增加嵌入和存储的功能。
确认依赖和配置 好消息是我们之前添加的langchain4j-open-ai-spring-boot-starter已经包含了嵌入模型的功能。现在还需要修改application.prroperties增加embedding模型配置
langchain4j.open-ai.chat-model.api-key${OPENAI_API_KEY:your-api-key-here}
langchain4j.open-ai.chat-model.base-urlhttps://yibuapi.com/v1/
langchain4j.open-ai.chat-model.model-namegpt-4o-mini
langchain4j.open-ai.chat-model.temperature0.7
langchain4j.open-ai.chat-model.max-tokens1024langchain4j.open-ai.embedding-model.model-nametext-embedding-ada-002修改DocumentService 我们将注入EmbeddingModel和EmbeddingStoreIngestor并创建一个InMemoryEmbeddingStore的Bean。 第一步在config/LangChain4jConfig.java中创建EmbeddingModel Bean package com.example.aidemoapp.config;
// ... other imports
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.inmemory.InMemoryEmbeddingStore;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;Configuration
public class LangChain4jConfig {// ... chatLanguageModel 和 chatMemoryProvider Beans ...Beanpublic EmbeddingModel embeddingModel() {// 通常嵌入模型也使用相同的api-key和base-url// 注意OpenAI有专门的嵌入模型名称// 比如 text-embedding-ada-002 。// 如果不指定LangChain4j可能会使用一个默认值。// 为清晰起见最好在properties中也定义它。return OpenAiEmbeddingModel.builder().apiKey(apiKey).baseUrl(baseUrl)// 推荐在application.properties中添加:// langchain4j.open-ai.embedding-model.model-nametext-embedding-ada-002.modelName(embeddingModelName).build();}
}第二步改造DocumentService.java package com.example.aidemoapp.service;import dev.langchain4j.data.document.Document;
import dev.langchain4j.data.document.DocumentSplitter;
import dev.langchain4j.data.document.loader.FileSystemDocumentLoader;
import dev.langchain4j.data.document.parser.TextDocumentParser;
import dev.langchain4j.data.document.splitter.DocumentSplitters;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.store.embedding.EmbeddingStore;
import dev.langchain4j.store.embedding.EmbeddingStoreIngestor;
import lombok.RequiredArgsConstructor;
import org.springframework.stereotype.Service;import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.List;Service
RequiredArgsConstructor
public class DocumentService {// 注入由Starter自动创建的EmbeddingModelprivate final EmbeddingModel embeddingModel;// 注入我们自己创建的EmbeddingStore Beanprivate final EmbeddingStore embeddingStore;public void loadSplitAndEmbed() {Path documentPath Paths.get(src/main/resources/documents/product-info.txt);Document document FileSystemDocumentLoader.loadDocument(documentPath, new TextDocumentParser());// 2. 将文档分割成片段DocumentSplitter splitter DocumentSplitters.recursive(300, 10);ListTextSegment segments splitter.split(document);System.out.println(Document split into segments.size() segments.);// 3. 将片段嵌入并存储到向量数据库中// LangChain4j提供了一个方便的EmbeddingStoreIngestor来处理这个流程EmbeddingStoreIngestor ingestor EmbeddingStoreIngestor.builder().documentSplitter(splitter) // 可以在这里也指定分割器.embeddingModel(embeddingModel).embeddingStore(embeddingStore).build();// 开始摄入文档ingestor.ingest(document);System.out.println(Document ingested and stored in the embedding store.);}
}代码解析 我们注入了EmbeddingModel和EmbeddingStore。我们使用了EmbeddingStoreIngestor这是一个高级工具它将分割、嵌入和存储这三个步骤打包成了一个简单的.ingest()方法调用非常方便。运行loadSplitAndEmbed()方法后我们的InMemoryEmbeddingStore中就包含了文档所有片段的向量信息。
第四部分进行第一次语义搜索
光存储还不够我们需要验证一下检索效果。让我们添加一个搜索方法。
// 在 DocumentService.java 中继续添加
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.store.embedding.EmbeddingMatch;// ...public ListString search(String query) {System.out.println(\n--- Performing search for query: query ---);// 1. 将用户问题也进行嵌入得到查询向量ResponseEmbedding queryEmbedding embeddingModel.embed(query);// 2. 在向量存储中查找最相关的N个匹配项// 参数1: 查询向量// 参数2: 返回的最大结果数EmbeddingSearchRequest request EmbeddingSearchRequest.builder().queryEmbedding(queryEmbedding.content()).build();ListEmbeddingMatchTextSegment relevant embeddingStore.search(request).matches();// 3. 打印结果System.out.println(Found relevant.size() relevant segments:);relevant.forEach(match - {System.out.println(--------------------);System.out.println(Score: match.score()); // 相似度得分System.out.println(Text: match.embedded().text()); // 原始文本});return relevant.stream().map(match - match.embedded().text()).collect(Collectors.toList());}现在你可以创建一个测试端点来调用loadSplitAndEmbed()然后再调用search(What are the setup requirements for X-Wing?)。你会看到即使你的问题中没有“RAM”或“CPU”这些词返回的最相关的片段也正是包含“16GB RAM and a 4-core CPU”的那一段这就是语义搜索的威力。
总结
今天我们深入了RAG技术的核心腹地。我们学习了
**文本嵌入Embedding**如何将文字转换成代表其语义的数学向量。**向量数据库Vector Store**如何存储这些向量并进行高效的相似度搜索。如何使用LangChain4j的EmbeddingModel和EmbeddingStoreIngestor将文档片段向量化并存入内存向量库。如何执行一次真正的语义搜索并找到了与问题最相关的文档片段。
我们已经成功地“开卷”并“找到了答案所在的页面”。现在我们离终点只差最后一步将找到的这些“参考资料”连同原始问题一起交给大语言模型让它用自然、流畅的语言“总结”出最终的答案。 下一篇预告 《Java大模型开发入门 (10/15)连接外部世界(下) - 端到端构建完整的RAG问答系统》—— 我们将整合所有学过的知识打通RAG的“最后一公里”。我们将把检索到的文本片段与用户问题组合起来发送给ChatLanguageModel最终构建一个可以针对我们私有文档进行智能问答的完整端到端应用