Ollama 应用实践:基于 Ollama + LangChain4j 的 RAG 实现
LangChain4j 提供了一个可以让我们快速了解RAG 实现过程的
简易
LangChain4j 具有“Easy RAG”功能,可让您尽可能轻松地开始使用 RAG。您无需了解嵌入、选择向量存储、找到正确的嵌入模型、弄清楚如何解析和拆分文档等。只需指向您的文档,LangChain4j 就会发挥其魔力。
如果您需要可定制的 RAG,请跳至下一部分。
1、导入langchain4j-easy-rag依赖项:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-easy-rag</artifactId>
<version>0.33.0</version>
</dependency>
2、让我们加载您的文档:
List<Document> documents = FileSystemDocumentLoader.loadDocuments("/home/langchain4j/documentation");
3、现在,我们需要预处理文档并将其存储在专门的嵌入存储(也称为矢量数据库)中。当用户提出问题时,这对于快速找到相关信息是必要的。我们可以使用我们支持的 15 多个嵌入存储中的任何一个,但为了简单起见,我们将使用内存中的嵌入存储:
InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
EmbeddingStoreIngestor.ingest(documents, embeddingStore);
4、最后一步是创建一个AI 服务,它将作为我们的 LLM API:
interface Assistant {
String chat(String userMessage);
}
Assistant assistant = AiServices.builder(Assistant.class)
.chatLanguageModel(OpenAiChatModel.withApiKey(OPENAI_API_KEY))
.chatMemory(MessageWindowChatMemory.withMaxMessages(10))
.contentRetriever(EmbeddingStoreContentRetriever.from(embeddingStore))
.build();
在这里,我们配置Assistant使用 OpenAI LLM 来回答用户问题,记住对话中最新的 10 条消息,并从EmbeddingStore包含我们文档的中检索相关内容。
5、现在我们就可以与它聊天了!
String answer = assistant.chat("How to do Easy RAG with LangChain4j?");
访问
如果您希望访问源(Content用于扩充消息的检索),您可以通过在Result类中包装返回类型轻松地实现此目的:
interface Assistant {
Result<String> chat(String userMessage);
}
Result<String> result = assistant.chat("How to do Easy RAG with LangChain4j?");
String answer = result.content();
List<Content> sources = result.sources();
RAG 阶段
LangChain4j 提供了一组丰富的 API,让您可以轻松构建自定义 RAG 管道,从简单到高级。在本节中,我们将介绍主要的领域类和 API。
类Document代表整个文档,例如单个 PDF 文件或网页。目前,它Document只能表示文本信息,但未来的更新将使其能够支持图像和表格。
有用的方法
- Document.text()返回文本Document
- Document.metadata()返回Metadata的Document(见下文)
- Document.toTextSegment()将其转换Document为TextSegment(见下文)
- Document.from(String, Metadata)创建一个Document来自文本并Metadata
- Document.from(String)创建Document带有空文本的Metadata
每个都Document包含Metadata。它存储有关 的元信息Document,例如其名称、来源、上次更新日期、所有者或任何其他相关详细信息。
Metadata以键值对的形式存储,其中键为 类型,String值可以是以下类型之一:String,Integer,Long,Float,Double。
Metadata 很有用,原因如下:
- Document 在向 LLM 提交提示时,还可以添加元数据条目,为 LLM 提供需要考虑的其他信息。例如,提供Document名称和来源可以帮助 LLM 更好地理解内容。
- 在搜索要包含在提示中的相关内容时,可以按Metadata条目进行过滤。例如,您可以将语义搜索范围缩小到仅Document属于特定所有者的条目。
- 当的来源Document更新时(例如,文档的特定页面),可以Document通过其元数据条目(例如,“id”,“source”等)轻松找到相应的内容,并在中更新它EmbeddingStore以保持同步。
文档加载器
您可以Document从创建一个String,但更简单的方法是使用库中包含的文档加载器之一:
- FileSystemDocumentLoader从langchain4j模块
- UrlDocumentLoader从langchain4j模块
- AmazonS3DocumentLoader从langchain4j-document-loader-amazon-s3模块
- AzureBlobStorageDocumentLoader从langchain4j-document-loader-azure-storage-blob模块
- GitHubDocumentLoader从langchain4j-document-loader-github模块
- TencentCosDocumentLoader从langchain4j-document-loader-tencent-cos模块
文档解析器
Documents 可以表示各种格式的文件,例如 PDF、DOC、TXT 等。为了解析每一种格式,DocumentParser库中包含一个具有多种实现的接口:
- TextDocumentParser来自langchain4j模块,可以解析纯文本格式的文件(例如TXT,HTML,MD等)
- ApachePdfBoxDocumentParser来自langchain4j-document-parser-apache-pdfbox可以解析 PDF 文件的模块
- ApachePoiDocumentParser来自langchain4j-document-parser-apache-poi模块,它可以解析 MS Office 文件格式(例如 DOC、DOCX、PPT、PPTX、XLS、XLSX 等)
- ApacheTikaDocumentParser该langchain4j-document-parser-apache-tika模块可以自动检测和解析几乎所有现有的文件格式
Document下面是如何从文件系统加载一个或多个的示例:
// Load a single document
Document document = FileSystemDocumentLoader.loadDocument("/home/langchain4j/file.txt", new TextDocumentParser());
// Load all documents from a directory
List<Document> documents = FileSystemDocumentLoader.loadDocuments("/home/langchain4j", new TextDocumentParser());
// Load all *.txt documents from a directory
PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:*.txt");
List<Document> documents = FileSystemDocumentLoader.loadDocuments("/home/langchain4j", pathMatcher, new TextDocumentParser());
// Load all documents from a directory and its subdirectories
List<Document> documents = FileSystemDocumentLoader.loadDocumentsRecursively("/home/langchain4j", new TextDocumentParser());
您也可以加载文档而不明确指定DocumentParser。在这种情况下,DocumentParser将使用默认值。默认值是通过 SPI 加载的(例如来自langchain4j-document-parser-apache-tika或langchain4j-easy-rag)。如果DocumentParser通过 SPI 未找到 ,TextDocumentParser则将使用 a 作为后备。
文档转换器
DocumentTransformer实现可以执行各种文档转换,例如:
清理:这涉及从Document文本中删除不必要的噪音,这可以节省标记并减少干扰。
Document过滤:从搜索中完全排除特定的内容。
丰富:可以添加附加信息Document以潜在地增强搜索结果。
总结:Document可以进行总结,并且它的简短摘要可以存储在中,Metadata 以便稍后包含在每个中TextSegment(我们将在下面介绍),以潜在地改进搜索。
ETC。
Metadata在此阶段还可以添加、修改或删除条目。
目前,唯一现成提供的实现是HtmlTextExtractor在langchain4j模块中,它可以从原始 HTML 中提取所需的文本内容和元数据条目。
由于没有一刀切的解决方案,我们建议您DocumentTransformer根据自己的独特数据量身定制解决方案。
文本片段
一旦Document加载了 ,就该将它们拆分(分块)成更小的段(块)。 LangChain4j 的域模型包括一个TextSegment表示 的片段的类Document。顾名思义,TextSegment只能表示文本信息。
分裂还是不分裂?
出于多种原因,您可能只想在提示中包含几个相关段而不是整个知识库:
- LLM 的上下文窗口有限,因此整个知识库可能不适合
- 你在提示中提供的信息越多,LLM 处理和回复所需的时间就越长
- 提示中提供的信息越多,支付的费用就越高
- 提示中的不相关信息可能会分散法学硕士的注意力,并增加出现幻觉的可能性
- 提示中提供的信息越多,LLM 的回复基于哪些信息就越难解释
- 我们可以通过将知识库拆分成更小、更易于理解的部分来解决这些问题。这些部分应该有多大?这是一个好问题。一如既往,这取决于具体情况。
目前有两种广泛使用的方法:
1、每个文档(例如 PDF 文件、网页等)都是原子且不可分割的。在 RAG 管道中进行检索时,将检索 N 个最相关的文档并将其注入提示中。在这种情况下,您很可能需要使用长上下文 LLM,因为文档可能很长。如果检索完整文档很重要,例如当您不能错过某些细节时,这种方法是合适的。
- 优点:没有丢失任何背景信息。
- 缺点:
- 消耗了更多代币。
- 有时,文档可能包含多个部分/主题,但并非所有部分/主题都与查询相关。
- 由于各种大小的完整文档被压缩成单个固定长度的向量,因此向量搜索质量会受到影响。
2、文档被分成更小的片段,例如章节、段落,有时甚至是句子。在 RAG 管道中进行检索时,会检索 N 个最相关的片段并将其注入提示中。挑战在于确保每个片段都提供足够的上下文/信息,以便 LLM 理解它。缺少上下文可能会导致 LLM 误解给定的片段并产生幻觉。一种常见的策略是将文档分成重叠的片段,但这并不能完全解决问题。几种高级技术可以提供帮助,例如“句子窗口检索”、“自动合并检索”和“父文档检索”。我们不会在这里详细介绍,但本质上,这些方法有助于获取检索到的片段周围的更多上下文,为 LLM 提供检索到的片段之前和之后的附加信息。
- 优点:
- 更好的矢量搜索质量。
- 减少代币消耗。
- 缺点:某些上下文可能仍然会丢失。
有用的方法
TextSegment.text()返回文本TextSegment
TextSegment.metadata()返回Metadata的TextSegment
TextSegment.from(String, Metadata)创建一个TextSegment来自文本并Metadata
TextSegment.from(String)创建TextSegment带有空文本的Metadata
- 简易 RAG
- 天真的 RAG
- 具有查询压缩功能的高级 RAG
- 具有查询路由的高级 RAG
- 具有重新排序功能的高级 RAG
- 包含元数据的高级 RAG
- 具有元数据过滤功能的高级 RAG
- 配备多只猎犬的高级 RAG
- 具有 Web 搜索功能的高级 RAG
- 带有 SQL 数据库的高级 RAG
- 跳过检索
- RAG +工具
- 加载文档
最后编辑:Jeebiz 更新时间:2024-11-21 01:00