当前位置: 首页 > news >正文

LangChain4j-RAG基础

RAG是什么

简而言之,RAG 是一种在将数据发送到 LLM 之前从数据中查找相关信息并将其注入到提示中的方法。这样LLM将获得(希望)相关信息,并能够使用这些信息进行回复,这应该会减少产生幻觉的可能性。

实现方法:

  • 全文(关键字)搜索。该方法使用 TF-IDF 和 BM25 等技术,通过将查询中的关键字(例如,用户询问的内容)与文档数据库进行匹配来搜索文档。它根据每个文档中这些关键字频率相关性对结果进行排名。
  • 矢量搜索,也称为“语义搜索”。使用嵌入模型将文本文档转换为数字向量。然后,它根据查询向量和文档向量之间的余弦相似度或其他相似度/距离度量来查找文档并对其进行排名,从而捕获更深层次的语义。
  • 结合多种搜索方法(例如全文+向量)通常可以提高搜索的效率。

RAG的两个步骤

RAG 过程分为 2 个不同的阶段:索引(indexing)检索(retrieval)

索引(Indexing)

此过程可能会根据所使用的信息检索方法而有所不同。对于矢量搜索,这通常涉及清理文档,用额外的数据和元数据丰富它们,将它们分成更小的片段(也称为分块),嵌入这些片段,最后将它们存储在嵌入存储(又称为矢量数据库)中。

索引阶段通常离线进行,这意味着它不需要最终用户等待其完成。例如,这可以通过 cron 定时任务来实现,该定时任务每周在周末重新索引一次公司内部文档。负责索引的代码也可以是仅处理索引任务的单独应用程序。

但是,在某些情况下,最终用户可能希望上传其自定义文档,以便 LLM 可以访问它们。在这种情况下,索引应该在线执行并成为主应用程序的一部分。

检索(Retrieval)

检索过程通常发生在用户提交文档使用索引回答用户问题时。

此过程可能会根据所使用的信息检索方法而有所不同。对于向量搜索,这通常涉及嵌入用户的查询(问题)并在嵌入存储中执行相似性搜索。然后相关片段(原始文档的片段)被注入到提示中并发送到LLM。

Easy RAG

LangChain4j 有一个“Easy RAG”功能,可以让 RAG 上手变得尽可能简单。不必了解嵌入、选择向量存储、找到正确的嵌入模型、弄清楚如何解析和分割文档等。只需指向您的文档,LangChain4j 就会发挥其魔力。

导入angchain4j-easy-rag依赖

<dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-easy-rag</artifactId><version>0.33.0</version>
</dependency>

官方示例:

public class Easy_RAG_Example {/*** This example demonstrates how to implement an "Easy RAG" (Retrieval-Augmented Generation) application.* By "easy" we mean that we won't dive into all the details about parsing, splitting, embedding, etc.* All the "magic" is hidden inside the "langchain4j-easy-rag" module.* <p>* If you want to learn how to do RAG without the "magic" of an "Easy RAG", see {@link Naive_RAG_Example}.*/public static void main(String[] args) {// First, let's load documents that we want to use for RAGList<Document> documents = loadDocuments(toPath("documents/"), glob("*.txt"));// Second, let's create an assistant that will have access to our documentsAssistant assistant = AiServices.builder(Assistant.class).chatLanguageModel(OpenAiChatModel.builder().baseUrl(OPENAI_API_URL).apiKey(OPENAI_API_KEY).build()) // it should use OpenAI LLM.chatMemory(MessageWindowChatMemory.withMaxMessages(10)) // it should remember 10 latest messages.contentRetriever(createContentRetriever(documents)) // it should have access to our documents.build();// Lastly, let's start the conversation with the assistant. We can ask questions like:// - Can I cancel my reservation?// - I had an accident, should I pay extra?startConversationWith(assistant);}private static ContentRetriever createContentRetriever(List<Document> documents) {// Here, we create and empty in-memory store for our documents and their embeddings.InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();// Here, we are ingesting our documents into the store.// Under the hood, a lot of "magic" is happening, but we can ignore it for now.EmbeddingStoreIngestor.ingest(documents, embeddingStore);// Lastly, let's create a content retriever from an embedding store.return EmbeddingStoreContentRetriever.from(embeddingStore);}
}
List<Document> documents = FileSystemDocumentLoader.loadDocuments("/home/langchain4j/documentation");

LangChain4j支持了15种向量存储的方式, 为了简单起见, 这里的Easy RAG就是用了内存存储。

InMemoryEmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();
EmbeddingStoreIngestor.ingest(documents, embeddingStore);

最后一步: 创建一个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();

现在就可以正常聊天了

String answer = assistant.chat("How to do Easy RAG with LangChain4j?");

Accessing Sources

如果想要获取访问源(检索到的 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 APIs

LangChain4j 提供了一组丰富的 API,可以轻松构建自定义 RAG 管道,从简单的管道到高级的管道。

Document 文件

Document 类代表整个文档,例如单个 PDF 文件或网页。目前, Document 只能表示文本信息,但未来的更新将使其能够支持图像和表格

使用方法:

  • Document.text()返回 Document 的文本
  • Document.metadata() 返回 DocumentMetadata (见下文)
  • Document.toTextSegment()Document 转换为 TextSegment (见下文), 主要是为了更好的将文档分段向量化,在上下文窗口限制比较小的时候比较适用。Text Segment 文本段
  • Document.from(String, Metadata) 从文本和 Metadata 创建 Document
  • Document.from(String) 从带有空 Metadata 的文本创建 Document

Metadata 元数据

每个 Document 包含 Metadata 。它存储有关 Document 的元信息,例如其名称、来源、上次更新日期、所有者或任何其他相关详细信息

Metadata 存储为键值映射,其中键为 String 类型,值可以为以下类型之一: StringIntegerLongFloatDouble

Metadata 很有用,有几个原因:

  • 当在 LLM 的提示中包含 Document 的内容时,还可以包含元数据条目,为 LLM 提供要考虑的附加信息。例如,提供 Document 名称和来源可以帮助提高LLM对内容的理解。
  • 当搜索要包含在提示中的相关内容时,可以按 Metadata 条目进行过滤。例如,您可以将语义搜索范围缩小到仅属于特定所有者的 Document
  • 当 Document 的来源更新时(例如,文档的特定页面),可以通过其元数据条目(例如“id”、 “源”等)并在 EmbeddingStore 中更新它以保持同步。

这里需要结合官方的示例学习, Metadata算是一个很重要的东西, 可以按照我们想要的方式把不同的文档数据进行隔离和过滤, 这样可以实现私有知识库的隔离。会对特定的回答达到更精确的效果。

静态过滤使用示例

void Static_Metadata_Filter_Example() {// givenTextSegment dogsSegment = TextSegment.from("Article about dogs ...", metadata("animal", "dog"));TextSegment birdsSegment = TextSegment.from("Article about birds ...", metadata("animal", "bird"));EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();embeddingStore.add(embeddingModel.embed(dogsSegment).content(), dogsSegment);embeddingStore.add(embeddingModel.embed(birdsSegment).content(), birdsSegment);// embeddingStore contains segments about both dogs and birdsFilter onlyDogs = metadataKey("animal").isEqualTo("dog");ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder().embeddingStore(embeddingStore).embeddingModel(embeddingModel).filter(onlyDogs) // by specifying the static filter, we limit the search to segments only about dogs.build();Assistant assistant = AiServices.builder(Assistant.class).chatLanguageModel(chatLanguageModel).contentRetriever(contentRetriever).build();// whenString answer = assistant.answer("Which animal?");// thenassertThat(answer).containsIgnoringCase("dog").doesNotContainIgnoringCase("bird");
}

按用户id动态过滤示例

interface PersonalizedAssistant {String chat(@MemoryId String userId, @dev.langchain4j.service.UserMessage String userMessage);
}@Test
void Dynamic_Metadata_Filter_Example() {// 这里就是将文本设置Meta的userId, 区分所属用户TextSegment user1Info = TextSegment.from("My favorite color is green", metadata("userId", "1"));TextSegment user2Info = TextSegment.from("My favorite color is red", metadata("userId", "2"));EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();embeddingStore.add(embeddingModel.embed(user1Info).content(), user1Info);embeddingStore.add(embeddingModel.embed(user2Info).content(), user2Info);// embeddingStore contains information about both first and second userFunction<Query, Filter> filterByUserId =(query) -> metadataKey("userId").isEqualTo(query.metadata().chatMemoryId().toString());ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder().embeddingStore(embeddingStore).embeddingModel(embeddingModel)// 动态过滤, 只检索MemoryId等于当前用户id的文档数据.dynamicFilter(filterByUserId).build();PersonalizedAssistant personalizedAssistant = AiServices.builder(PersonalizedAssistant.class).chatLanguageModel(chatLanguageModel).contentRetriever(contentRetriever).build();// whenString answer1 = personalizedAssistant.chat("1", "Which color would be best for a dress?");// thenassertThat(answer1).containsIgnoringCase("green").doesNotContainIgnoringCase("red");// whenString answer2 = personalizedAssistant.chat("2", "Which color would be best for a dress?");// thenassertThat(answer2).containsIgnoringCase("red").doesNotContainIgnoringCase("green");
}

动态字段过滤实现一个简单的推荐系统

@Test
void LLM_generated_Metadata_Filter_Example() {// givenTextSegment forrestGump = TextSegment.from("Forrest Gump", metadata("genre", "drama").put("year", 1994));TextSegment groundhogDay = TextSegment.from("Groundhog Day", metadata("genre", "comedy").put("year", 1993));TextSegment dieHard = TextSegment.from("Die Hard", metadata("genre", "action").put("year", 1998));// 将元数据键描述为SQL表中的字段, 模拟sql表的结构TableDefinition tableDefinition = TableDefinition.builder().name("movies").addColumn("genre", "VARCHAR", "one of: [comedy, drama, action]").addColumn("year", "INT").build();LanguageModelSqlFilterBuilder sqlFilterBuilder = new LanguageModelSqlFilterBuilder(chatLanguageModel, tableDefinition);EmbeddingStore<TextSegment> embeddingStore = new InMemoryEmbeddingStore<>();embeddingStore.add(embeddingModel.embed(forrestGump).content(), forrestGump);embeddingStore.add(embeddingModel.embed(groundhogDay).content(), groundhogDay);embeddingStore.add(embeddingModel.embed(dieHard).content(), dieHard);ContentRetriever contentRetriever = EmbeddingStoreContentRetriever.builder().embeddingStore(embeddingStore).embeddingModel(embeddingModel).dynamicFilter(query -> sqlFilterBuilder.build(query)) // LLM will generate the filter dynamically.build();Assistant assistant = AiServices.builder(Assistant.class).chatLanguageModel(chatLanguageModel).contentRetriever(contentRetriever).build();// whenString answer = assistant.answer("Recommend me a good drama from 90s");System.out.println(answer);// thenassertThat(answer).containsIgnoringCase("Forrest Gump").doesNotContainIgnoringCase("Groundhog Day").doesNotContainIgnoringCase("Die Hard");
}

核心使用方法

Metadata底层实际上就是一个HashMap

  • Metadata.from(Map)Map 创建 Metadata
  • Metadata.put(String key, String value) / put(String, int) /等等,向 Metadata 添加一个条目
  • Metadata.getString(String key) / getInteger(String key) /等等,返回 Metadata 条目的值,将其转换为所需的类型
  • Metadata.containsKey(String key) 检查 Metadata 是否包含具有指定key
  • Metadata.remove(String key) 通过键从 Metadata 中删除对应键值对
  • Metadata.copy() 返回 Metadata 的副本
  • Metadata.toMap()Metadata 转换为 Map

Document Loader 文档加载器

可以从 String 创建 Document ,但更简单的方法是使用库中包含的文档加载器之一:

  • langchain4j 模块下的 FileSystemDocumentLoader: 根据本地文件路径加载文档
  • langchain4j 模块下的 UrlDocumentLoader: 读取可以url访问下载的文档
  • langchain4j-document-loader-amazon-s3 模块下的 AmazonS3DocumentLoader
  • langchain4j-document-loader-azure-storage-blob 模块的 AzureBlobStorageDocumentLoader
  • langchain4j-document-loader-github 模块的 GitHubDocumentLoader: 读取github上文档的工具, 可以选择仓库,分支,和文件路径
  • langchain4j-document-loader-tencent-cos 模块的 TencentCosDocumentLoader: 根据腾云cos读取文档

Document Parser 文档解析器

Document 可以表示各种格式的文件,例如 PDF、DOC、TXT 等。为了解析这些格式中的每一种,库中有一个 DocumentParser 接口,其中包含多个实现:

  • TextDocumentParser 来自 langchain4j 模块,它可以解析纯文本格式的文件(例如TXT、HTML、MD等)
  • ApachePdfBoxDocumentParser 来自 langchain4j-document-parser-apache-pdfbox 模块,可以解析PDF文件
  • 来自 langchain4j-document-parser-apache-poi 模块的 ApachePoiDocumentParser ,它可以解析MS Office文件格式(例如DOC,DOCX,PPT,PPTX,XLS,XLSX等)
  • ApacheTikaDocumentParser 来自 langchain4j-document-parser-apache-tika 模块,可以自动检测和解析几乎所有现有的文件格式
// 加载单个文档
Document document = FileSystemDocumentLoader.loadDocument("/home/langchain4j/file.txt", new TextDocumentParser());// 加载目录下所有文档
List<Document> documents = FileSystemDocumentLoader.loadDocuments("/home/langchain4j", new TextDocumentParser());// 加载目录下所有".txt"结尾的文档
PathMatcher pathMatcher = FileSystems.getDefault().getPathMatcher("glob:*.txt");
List<Document> documents = FileSystemDocumentLoader.loadDocuments("/home/langchain4j", pathMatcher, new TextDocumentParser());// 加载目录下和子目录下的所有文档
List<Document> documents = FileSystemDocumentLoader.loadDocumentsRecursively("/home/langchain4j", new TextDocumentParser());

Document Transformer 文档转换器

DocumentTransformer 实现可以执行各种文档转换,例如:

  • cleaning:这涉及从 Document 文本中删除不必要的噪音,这可以节省 tokens 并减少干扰。
  • Filtering:从搜索中完全排除特定的 Document 。(这里就可以结合前面提到的Metadata来进行条件过滤)
  • Enriching: 可以将附加信息添加到 Document 中,以潜在地增强搜索结果。
  • Summarizing: Document 可以被总结,它的简短总结可以存储在 Metadata 中,稍后包含在每个 TextSegment 中(我们将在下面介绍)以潜在地改进搜索。

官方只提供了一个实现类: HtmlTextExtractor实现了对原始HTML文件提取文本内容和Metadata元数据。

后续可能官方会有更多的实现工具,官方建议可以根据自己的需求来实现DocumentTransformer满足自己的文档解析的需求。

官方示例:

@Test
public void test() {List<Document> docs = new ArrayList<>();docs.add(Document.document("abc xyz", Metadata.metadata("lang", "en")));docs.add(Document.document("jkl 123", Metadata.metadata("lang", "en")));docs.add(Document.document("mno qrs", Metadata.metadata("lang", "fr")));List<Document> results = ((DocumentTransformer) document -> {if (document.metadata().get("lang").equals("en")) {return Document.document(document.text().toUpperCase(Locale.ROOT),document.metadata());} else {return null;}}).transformAll(docs);assertThat(results).containsOnly(Document.document("ABC XYZ", Metadata.metadata("lang", "en")),Document.document("JKL 123", Metadata.metadata("lang", "en")));
}

这里就是一个实例,DocumentTransformer实现一个如果是Metadata中的lang标识为en的英文文档,则将文本内容进行大写的转换。

Text Segment 文本段

一旦 Document 被加载,就可以将它们分割(块)成更小的段(片)。 LangChain4j 的域模型包含一个 TextSegment 类,它表示 Document 的一部分。顾名思义, TextSegment 只能表示文本信息。

有时候可能只想在提示中包含几个相关部分而不是整个知识库,原因有多种:

  • LLMs 的上下文窗口有限,因此整个知识库可能不适合
  • 在提示中提供的信息越多,LLM 处理该信息并做出响应的时间就越长
  • 提示中不相关的信息可能会分散 LLM 的注意力并增加产生幻觉的机会
  • 在提示中提供的信息越多,就越难根据 LLM 响应的信息进行解释

我们可以通过将知识库分成更小、更容易理解的部分来解决这些问题。

目前有两种广泛使用的方法

  1. 每个文档(例如 PDF 文件、网页等)都是原子且不可分割的。在 RAG 管道中检索期间,将检索 N 个最相关的文档并将其注入到提示中。在这种情况下,您很可能需要使用长上下文 LLM,因为文档可能会很长。如果检索完整文档很重要(不能错过某些细节时),则适合使用此方法。
  • 优点:不会丢失上下文。
  • 缺点:
    • 消耗更多的tokens。
    • 有时,文档可以包含多个部分/主题,并且并非所有部分/主题都与查询相关。
    • 矢量搜索质量会受到影响,因为各种大小的完整文档被压缩为单个固定长度的矢量。
  1. 文档被分成更小的部分,例如章节、段落,有时甚至是句子。在 RAG 管道中检索期间,将检索 N 个最相关的段并将其注入到提示中。挑战在于确保每个细分都为 LLM 提供足够的上下文/信息来理解它。缺少上下文可能会导致 LLM 误解给定的片段并产生幻觉。一种常见的策略是将文档分割成重叠的片段,但这并不能完全解决问题。一些先进的技术可以在这里提供帮助,例如“句子窗口检索”、“自动合并检索”和“父文档检索”。但本质上,这些方法有助于获取有关检索到的段的更多上下文,为 LLM 提供检索到的段之前和之后的附加信息。
  • 优点:
    • 更好的矢量搜索质量。
    • 减少代币消耗。
  • 缺点:一些上下文可能仍然丢失。

核心使用方法

  • TextSegment.text() 返回 TextSegment 的文本
  • TextSegment.metadata() 返回 TextSegmentMetadata
  • TextSegment.from(String, Metadata) 从文本创建 TextSegmentMetadata
  • TextSegment.from(String) 从带有空 Metadata 的文本创建 TextSegment

Document Splitter 文档分割器

原理:

  • DocumentSplitter ,指定 TextSegment 的所需大小,以及字符或tokens的重叠(可选)。
  • 调用DocumentSplittersplit(Document)splitAll(List<Document>) 方法。
  • DocumentSplitter 将给定的 Document 分割成更小的单元,其性质随分割器的不同而变化。例如:
    • DocumentByParagraphSplitter 将文档分割为段落(由两个或多个连续换行符定义)
    • DocumentBySentenceSplitter 使用OpenNLP库的句子检测器将文档分割为句子,等等。
  • DocumentSplitter 将这些较小的单元(段落、句子、单词等)组合成 TextSegment ,尝试在单个 TextSegment 不超过步骤 1 中设置的限制。如果某些单元仍然太大而无法放入 TextSegment 中,则会调用子拆分器。这是另一个能够将不适合更细化单元的单元拆分的 DocumentSplitter 。所有 Metadata 条目都从 Document 复制到每个 TextSegment 。唯一的元数据条目“索引”被添加到每个文本段。第一个 TextSegment 将包含 index=0 ,第二个 index=1 ,依此类推。

Text Segment Transformer 文本段转换器

官方只是提供了定义,仍然建议我们自行去根据业务去实现自定义的转换器。

使用场景: 一种对于提高检索效果非常有效的技术是在每个 TextSegment 中包含 Document 标题或简短摘要。

官方的使用示例:

class TextSegmentTransformerTest implements WithAssertions {public static class LowercaseFnordTransformer implements TextSegmentTransformer {@Overridepublic TextSegment transform(TextSegment segment) {//对文本进行小写转换String result = segment.text().toLowerCase();//对包含指定字符的文本段进行过滤, 可以考虑安全隐患的一些数据过滤场景if (result.contains("fnord")) {return null;}return TextSegment.from(result, segment.metadata());}}@Testpublic void test_transformAll() {TextSegmentTransformer transformer = new LowercaseFnordTransformer();TextSegment ts1 = TextSegment.from("Text");ts1.metadata().put("abc", "123"); // metadata is copied over (not transformedTextSegment ts2 = TextSegment.from("Segment");TextSegment ts3 = TextSegment.from("Fnord will be filtered out");TextSegment ts4 = TextSegment.from("Transformer");List<TextSegment> segmentList = new ArrayList<>();segmentList.add(ts1);segmentList.add(ts2);segmentList.add(ts3);segmentList.add(ts4);assertThat(transformer.transformAll(segmentList)).containsExactly(TextSegment.from("text", ts1.metadata()),TextSegment.from("segment"),TextSegment.from("transformer"));}}

Embedding 嵌入

Embedding 类封装了一个数值向量,表示已嵌入内容(通常是文本,例如 TextSegment )的“语义含义”。

使用方法:

  • Embedding.dimension() 返回嵌入向量的维度(其长度)
  • CosineSimilarity.between(Embedding, Embedding) 计算 2 个 Embedding 之间的余弦相似度
  • Embedding.normalize() 标准化嵌入向量

Embedding Model 嵌入模型

使用方法:

  • EmbeddingModel.embed(String) 嵌入给定的文本
  • EmbeddingModel.embed(TextSegment) 嵌入给定的 TextSegment
  • EmbeddingModel.embedAll(List<TextSegment>) 嵌入所有给定的 TextSegment
  • EmbeddingModel.dimension() 返回此模型生成的 Embedding 的尺寸

Embedding Store 嵌入存储

EmbeddingStore 接口代表 Embedding 的存储,也称为矢量数据库。它允许存储和有效搜索相似的(在嵌入空间中接近的) Embedding

EmbeddingStore 可以单独存储 Embedding ,也可以与相应的 TextSegment 一起存储:

  • 它只能通过 ID 存储 Embedding 。原始嵌入数据可以存储在其他地方并使用 ID 进行关联。
  • 它可以存储 Embedding 和已嵌入的原始数据(通常是 TextSegment )。

使用方法:

  • EmbeddingStore.add(Embedding) 将给定的 Embedding 添加到存储并返回随机 ID
  • EmbeddingStore.add(String id, Embedding) 将具有指定 ID 的给定 Embedding 添加到存储中
  • EmbeddingStore.add(Embedding, TextSegment) 将给定的 Embedding 以及关联的 TextSegment 添加到存储中并返回随机 ID
  • EmbeddingStore.addAll(List<Embedding>) 将给定 Embedding 的列表添加到存储中并返回随机 ID 的列表
  • EmbeddingStore.addAll(List<Embedding>, List<TextSegment>) 将给定的 Embedding 列表以及关联的 TextSegment 列表添加到存储中并返回随机 ID 列表
  • EmbeddingStore.search(EmbeddingSearchRequest) 搜索最相似的 Embedding
  • EmbeddingStore.remove(String id) 根据 ID 从存储中删除单个 Embedding
  • EmbeddingStore.removeAll(Collection<String> ids) 通过 ID 从存储中删除多个 Embedding
  • EmbeddingStore.removeAll(Filter) 从存储中删除与指定 Filter 匹配的所有 Embedding
  • EmbeddingStore.removeAll()从存储中删除所有 Embedding

官方 ElasticsearchEmbeddingStore 的存储使用示例:

public class ElasticsearchEmbeddingStoreExample {public static void main(String[] args) throws InterruptedException {try (ElasticsearchContainer elastic = new ElasticsearchContainer("docker.elastic.co/elasticsearch/elasticsearch:8.9.0").withEnv("xpack.security.enabled", "false")) {elastic.start();EmbeddingStore<TextSegment> embeddingStore = ElasticsearchEmbeddingStore.builder().serverUrl("http://" + elastic.getHttpHostAddress()).dimension(384).build();EmbeddingModel embeddingModel = new AllMiniLmL6V2EmbeddingModel();TextSegment segment1 = TextSegment.from("I like football.");Embedding embedding1 = embeddingModel.embed(segment1).content();embeddingStore.add(embedding1, segment1);TextSegment segment2 = TextSegment.from("The weather is good today.");Embedding embedding2 = embeddingModel.embed(segment2).content();embeddingStore.add(embedding2, segment2);Thread.sleep(1000); // to be sure that embeddings were persistedEmbedding queryEmbedding = embeddingModel.embed("What is your favourite sport?").content();List<EmbeddingMatch<TextSegment>> relevant = embeddingStore.findRelevant(queryEmbedding, 1);EmbeddingMatch<TextSegment> embeddingMatch = relevant.get(0);System.out.println(embeddingMatch.score()); // 0.81442887System.out.println(embeddingMatch.embedded().text()); // I like football.}}
}

Embedding Store Ingestor 嵌入存储摄取器

EmbeddingStoreIngestor 表示摄取管道,负责将 Document 摄取到 EmbeddingStore 中。

在最简单的配置中, EmbeddingStoreIngestor 使用指定的 EmbeddingModel 嵌入提供的 Document ,并将它们及其 Embedding 存储在指定的 EmbeddingStore

EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder().embeddingModel(embeddingModel).embeddingStore(embeddingStore).build();ingestor.ingest(document1);
ingestor.ingest(document2, document3);
ingestor.ingest(List.of(document4, document5, document6));

一般在使用 EmbeddingStoreIngestor 之前会使用 DocumentTransformerDocument 进行清理、丰富或格式化,有助于后续在向量提取的时候效果更好。

官方示例:

EmbeddingStoreIngestor ingestor = EmbeddingStoreIngestor.builder()// adding userId metadata entry to each Document to be able to filter by it later.documentTransformer(document -> {document.metadata().put("userId", "12345");return document;})// splitting each Document into TextSegments of 1000 tokens each, with a 200-token overlap.documentSplitter(DocumentSplitters.recursive(1000, 200, new OpenAiTokenizer()))// adding a name of the Document to each TextSegment to improve the quality of search.textSegmentTransformer(textSegment -> TextSegment.from(textSegment.metadata("file_name") + "\n" + textSegment.text(),textSegment.metadata())).embeddingModel(embeddingModel).embeddingStore(embeddingStore).build();
  • documentTransformer: 它为每个文档添加了一个userId的元数据项,值为"12345"。这意味着无论处理多少文档,所有文档都将被标记为属于同一个用户ID。这在需要按用户过滤结果时非常有用。
  • documentSplitter: 分割器以1000个令牌为单位进行分割,但每个片段之间会有200个令牌的重叠。这有助于确保在搜索时,即使查询文本落在片段边界附近,也能检索到相关的文本片段。
  • textSegmentTransformer: 它将每个文本片段的原始文本与其file_name元数据项的值(文件名)相结合,中间用换行符分隔。这样做的目的是在搜索时,不仅考虑文本片段的内容,还考虑它所属的文档(或文件)的名称,这可能会提高搜索的相关性。

相关文章:

LangChain4j-RAG基础

RAG是什么 简而言之&#xff0c;RAG 是一种在将数据发送到 LLM 之前从数据中查找相关信息并将其注入到提示中的方法。这样LLM将获得&#xff08;希望&#xff09;相关信息&#xff0c;并能够使用这些信息进行回复&#xff0c;这应该会减少产生幻觉的可能性。 实现方法: 全文…...

git--本地仓库修改同步到远程仓库

尝试将本地分支推送到远程仓库时&#xff0c;出现一个非快速前进的错误。通常是因为远程仓库中的分支包含本地分支没有的提交。在推送之前&#xff0c;需要将远程仓库的更改合并到本地分支。 解决步骤如下&#xff1a; 切换到你的本地分支&#xff1a; 确保处于想要推送的分支…...

剑和沙盒 3 - 深度使用和解析Windows Sandbox

介绍 两年前&#xff0c;微软作为Insiders build 18305的一部分发布了一项新功能- Windows Sandbox。 该沙箱具有一些有用的规格&#xff1a; Windows 10&#xff08;Pro/Enterprise&#xff09;的集成部分。在 Hyper-V 虚拟化上运行。原始且可抛弃 – 每次运行时都干净地开…...

深度学习loss

pytorch模型训练demo代码 在PyTorch中&#xff0c;模型训练通常涉及几个关键步骤&#xff1a;定义模型、定义损失函数、选择优化器、准备数据加载器、编写训练循环。以下是一个简单的PyTorch模型训练演示代码&#xff0c;该代码实现了一个用于手写数字识别&#xff08;使用MNIS…...

编写一个Chrome插件,网页选择文字后,右键出现菜单“search with bing”,选择菜单后用bing搜索文字

kimi ai 生成&#xff0c;测试可用&#xff0c;需要自行准备图标文件 创建一个简单的Chrome插件来实现选择文本后的搜索功能&#xff0c;你需要完成以下几个步骤&#xff1a; 创建插件的基础文件夹和文件&#xff1a; 创建一个文件夹用于存放插件的所有文件。在该文件夹中创建以…...

【算法】分割回文串

难度:中等 题目: 给你一个字符串 s,请你将 s 分割成一些子串,使每个子串都是 回文串。返回 s 所有可能的分割方案。 示例 1: 输入:s = “aab” 输出:[[“a”,“a”,“b”],[“aa”,“b”]] 示例 2: 输入:s = “a” 输出:[[“a”]] 提示: 1 <= s.length <…...

lua 游戏架构 之 游戏 AI (三)ai_attack

这段Lua脚本定义了一个名为 ai_attack 的类&#xff0c;继承自 ai_base 类。 lua 游戏架构 之 游戏 AI &#xff08;一&#xff09;ai_base-CSDN博客文章浏览阅读119次。定义了一套接口和属性&#xff0c;可以基于这个基础类派生出具有特定行为的AI组件。例如&#xff0c;可以…...

大数据之Oracle同步Doris数据不一致问题

数据同步架构如下&#xff1a; 出现的问题&#xff1a; doris中的数据条数 源库中的数据条数 总数完全不一致。 出现问题的原因&#xff1a; 在Dinky中建立表结构时&#xff0c;缺少对主键属性的限制 primary key(ID) not enforced 加上如上语句&#xff0c;数据条数解决一致 …...

visual studio 问题总结

一. Visual Studio: 使用简体中文&#xff08;GB2312&#xff09;编码加载文件, 有些字节已用Unicode替换字符更换 解决方法&#xff1a;vs 工具-》选项-》文本编辑器...

go-错误码的最佳实践

一、背景 在工程开发中&#xff0c;我们有以下场景可以用错误码解决 我们不太方便直接将内部的错误原因暴露给外部&#xff0c;可以根据错误码得到对应的外部暴露消息通过设定错误码判断是客户端或者服务端的问题&#xff0c;避免不必要的排障浪费方便查找日志&#xff0c;定…...

Python面试题:使用Matplotlib和Seaborn进行数据可视化

使用Matplotlib和Seaborn进行数据可视化是数据分析中非常重要的一部分。以下示例展示了如何使用这两个库来创建各种图表&#xff0c;包括基本的线图、柱状图、散点图和高级的分类数据可视化图表。 安装 Matplotlib 和 Seaborn 如果你还没有安装这两个库&#xff0c;可以使用以…...

模拟实现c++中的vector模版

目录 一vector简述&#xff1a; 二vector的一些接口函数&#xff1a; 1初始化&#xff1a; 2.vector增长&#xff1a; 3vector增删查改&#xff1a; 三vector模拟实现部分主要函数&#xff1a; 1.size,capacity,empty,clear接口&#xff1a; 2.reverse的实现&#xff1…...

uniapp安卓通过绝对路径获取文件

uniapp安卓通过绝对路径获取文件 在uniapp中&#xff0c;如果你想要访问安卓设备上的文件&#xff0c;你需要使用uniapp提供的plus.io API。这个API允许你在应用内访问设备的文件系统。 以下是一个示例代码&#xff0c;展示了如何使用plus.io API来获取文件&#xff1a; fun…...

Known框架实战演练——进销存业务单据

本文介绍如何实现进销存管理系统的业务单据模块&#xff0c;业务单据模块包括采购进货单、采购退货单、销售出货单、销售退货单4个菜单页面。由于进销单据字段大同小异&#xff0c;因此设计共用一个页面组件类。 项目代码&#xff1a;JxcLite开源地址&#xff1a; https://git…...

解决npm依赖树冲突的方法以及npm ERR! code ERESOLVE错误的解决方案

一、问题描述 在使用ng new myapp --skip-install 构建Angular 项目后&#xff0c;尝试用npm install 安装依赖的时候报了以下错误。 (base) PS C:\Users\Administrator\Desktop\agtest\myapp> npm i npm ERR! code ERESOLVE npm ERR! ERESOLVE unable to resolve dependenc…...

Spring Boot + Spring Batch + Quartz 整合定时批量任务

​ 博客主页: 南来_北往 系列专栏&#xff1a;Spring Boot实战 前言 最近一周&#xff0c;被借调到其他部门&#xff0c;赶一个紧急需求&#xff0c;需求内容如下&#xff1a; PC网页触发一条设备升级记录&#xff08;下图&#xff09;&#xff0c;后台要定时批量设备更…...

C++STL简介(二)

目录 1.模拟实现string 1.string基本属性和大体框架 2.基本函数 2.1size&#xff08;&#xff09; 2.2 [] 2.3 begin() 和end() 2.4capacity&#xff08;&#xff09; 2.5 reserve 2.6push_back 2.7 append 2.8 2.9insert 2.10find 2.11substr 2.12 2.12 < …...

嵌入式高频面试题100道及参考答案(3万字长文)

目录 解释嵌入式系统的定义和主要特点 描述微处理器与微控制器的主要区别 什么是ARM体系结构?它在嵌入式系统中有哪些优势? 解释GPIO(通用输入输出)的工作原理 什么是ADC和DAC?它们在嵌入式系统中的作用是什么? 解释中断的概念及其在实时系统中的重要性 描述SPI(串…...

python爬虫-事件触发机制

今天想爬取一些政策&#xff0c;从政策服务 (smejs.cn) 这个网址爬取&#xff0c;html源码找不到链接地址&#xff0c;通过浏览器的开发者工具&#xff0c;点击以下红框 分析预览可知想要的链接地址的id有了&#xff0c;进行地址拼接就行 点击标头可以看到请求后端服务器的api地…...

LeetCode-day27-3106. 满足距离约束且字典序最小的字符串

LeetCode-day27-3106. 满足距离约束且字典序最小的字符串 题目描述示例示例1&#xff1a;示例2&#xff1a;示例3&#xff1a; 思路代码 题目描述 给你一个字符串 s 和一个整数 k 。 定义函数 distance(s1, s2) &#xff0c;用于衡量两个长度为 n 的字符串 s1 和 s2 之间的距…...

C++中的static_cast函数

static_cast 是 C 中的一个类型转换操作符&#xff0c;用于在编译时进行类型转换。它主要用于基本数据类型之间的转换&#xff0c;以及类的指针或引用之间的向上转换&#xff08;将派生类指针或引用转换为基类指针或引用&#xff09;和某些情况下的向下转换&#xff08;将基类指…...

从零开始学习网络安全渗透测试之基础入门篇——(二)Web架构前后端分离站Docker容器站OSS存储负载均衡CDN加速反向代理WAF防护

Web架构 Web架构是指构建和管理Web应用程序的方法和模式。随着技术的发展&#xff0c;Web架构也在不断演进。当前&#xff0c;最常用的Web架构包括以下几种&#xff1a; 单页面应用&#xff08;SPA&#xff09;&#xff1a; 特点&#xff1a;所有用户界面逻辑和数据处理都包含…...

2679. 矩阵中的和

两种方法&#xff1a; 第一种&#xff1a;先对二维列表的每一列进行排序&#xff0c;然后对每一列的数据进行逐个比较&#xff0c;找出最大值。 class Solution:def matrixSum(self, nums: list[list[int]]) -> int:result0mlen(nums)nlen(nums[0])for i in range(m):nums…...

Unity Playables:下一代动画与音频序列

Unity的Playables API是一种灵活的系统&#xff0c;用于创建和控制动画、音频以及其他形式的连续媒体序列。它为开发者提供了一种全新的方法来处理游戏中的时间序列&#xff0c;包括动画、音频、特效等。本文将探讨Playables的基本概念、如何使用Playables API实现动画&#xf…...

matlab仿真 模拟调制(下)

&#xff08;内容源自详解MATLAB&#xff0f;SIMULINK 通信系统建模与仿真 刘学勇编著第五章内容&#xff0c;有兴趣的读者请阅读原书&#xff09; clear all ts0.001; t0:ts:10-ts; fs1/ts; dffs/length(t); msgrandi([-3 3],100,1); msg1msg*ones(1,fs/10); msg2reshape(ms…...

RabbitMQ是什么?

RabbitMQ是一个开源的消息代理软件&#xff08;Message Broker&#xff09;&#xff0c;它实现了高级消息队列协议&#xff08;AMQP&#xff0c;Advanced Message Queuing Protocol&#xff09;&#xff0c;并支持多种消息传递协议。它最初由英国的Rabbit Technologies开发&…...

追问试面试系列:分布式id

hi 大家好,欢迎来到追问试面试系列:分布式id 面试中可能面试官不会直接问你分布式id问题,基本上都是因为你在某些面试题回答中提到了,所以就开始追问分布式id相关问题。 先看面试题 ● 面试官:什么是分布式id? ● 面试官:举个例子说说 ● 面试官:什么叫分库分表? ●…...

护网紧急情况应对指南:Linux 应急响应手册

继上一篇&#xff1a;护网紧急情况应对指南&#xff1a;Windows版v1.2全新升级版 之后 收到小伙伴后台要Linux应急手册&#xff0c;今天给大家安排上。 《Linux应急手册》是一本为Linux系统管理员和运维工程师量身打造的实用指南&#xff0c;旨在帮助他们快速应对各种突发状况…...

WEB攻防-通用漏洞-SQL 读写注入-MYSQLMSSQLPostgreSQL

什么是高权限注入 高权限注入指的是攻击者通过SQL注入漏洞&#xff0c;利用具有高级权限的数据库账户&#xff08;如MYSQL的root用户、MSSQL的sa用户、PostgreSQL的dba用户&#xff09;执行恶意SQL语句。这些高级权限账户能够访问和修改数据库中的所有数据&#xff0c;甚至执行…...

【前端学习笔记】CSS基础一

一、什么是CSS 1.CSS 介绍 CSS&#xff08;Cascading Style Sheets&#xff0c;层叠样式表&#xff09;是一种用来控制网页布局和设计外观的样式语言。它使得开发者可以分离网页的内容&#xff08;HTML&#xff09;和表现形式&#xff08;样式&#xff09;&#xff0c;提高了…...

Github遇到的问题解决方法总结(持续更新...)

1.github每次push都需要输入用户名和token的解决方法 push前&#xff0c;执行下面命令 &#xff1a; git config --global credential.helper store 之后再输入一次用户名和token之后&#xff0c;就不用再输入了。 2.git push时遇到“fatal: unable to access https://githu…...

数字信封+数字签名工具类测试样例(Java实现)

加解密过程 加密&#xff1a; 生成加密方SM2密钥对用于签名使用生成的SM2私钥生成数字签名生成SM4对称密钥对明文进行对称加密使用与解密方提前约定好的SM2公钥对第三步中的SM4对称密钥进行非对称加密把【加密方SM2公钥】、【数字签名】、【SM4对称加密后的密文】和【SM2非对…...

The Schematic workflow failed. See above.

在使用 ng new 新建Angular项目的时候会报一个错误&#xff1a;The Schematic workflow failed. See above. 解决办法&#xff1a; 只需要在后面加上 --skip-install 参数&#xff0c;就不会报错了。 ng new myapp --skip-install...

操作系统面试知识点总结4

#来自ウルトラマンメビウス&#xff08;梦比优斯&#xff09; 1 文件系统基础 1.1 文件的相关概念 文件是以计算机硬盘为载体的存储在计算机上的信息集合&#xff0c;可以是文本文档、图片、程序。 文件的结构&#xff1a;数据项、记录、文件&#xff08;有结构文件、无结构式…...

Lua实现面向对象以及类的继承

0.简单前言 1、面向对象主要四个特征&#xff1a;封装&#xff0c;继承&#xff0c;多态&#xff0c;抽象 2、Lua是种简单精致小巧的语言&#xff0c;其本质是个表&#xff08;table&#xff09;&#xff0c;变量和方法皆可看作为该表的元素。 P.S. 该博客和代码为个人编写习…...

机器学习课程学习周报五

机器学习课程学习周报五 文章目录 机器学习课程学习周报五摘要Abstract一、机器学习部分1.1 向量序列作为模型输入1.1.1 文字的向量表达1.1.2 语音的向量表达 1.2 自注意力机制原理1.2.1 自注意力机制理论1.2.2 矩阵运算自注意力机制 1.3 多头自注意力1.4 位置编码1.5 截断自注…...

vue3.0学习笔记(二)——生命周期与响应式数据(ref,reactive,toRef,toRefs函数)

1. 组合API-setup函数 使用细节&#xff1a; setup 是一个新的组件选项&#xff0c;作为组件中使用组合API的起点。从组件生命周期来看&#xff0c;它的执行在组件实例创建之前vue2.x的beforeCreate执行。这就意味着在setup函数中 this 还不是组件实例&#xff0c;this 此时是…...

C++——QT:保姆级教程,从下载到安装到用QT写出第一个程序

登录官网&#xff0c;在官网选择合适的qt版本进行下载 这里选择5.12.9版本 点击exe文件下载&#xff0c;因为服务器在国外&#xff0c;国内不支持&#xff0c;所以可以从我的网盘下载 链接: https://pan.baidu.com/s/1XMILFS1uHTenH3mH_VlPLw 提取码: 1567 --来自百度网盘超级…...

掌握互联网路由选择协议:从基础入门到实战

文章目录 路由选择协议的基本概念路由选择算法的分类分层次的路由选择协议路由信息协议&#xff08;RIP&#xff09;内部网关协议&#xff1a;OSPF外部网关协议&#xff1a;BGP互联网中的实际应用总结 互联网的路由选择协议是网络通信的核心&#xff0c;它决定了数据包如何在网…...

[笔记]ONVIF服务端实现[进行中...]

1.文档搜索&#xff1a; 从&#xff1a;https://www.cnblogs.com/liwen01/p/17337916.html 跳转到了&#xff1a;ONVIF协议网络摄像机&#xff08;IPC&#xff09;客户端程序开发&#xff08;1&#xff09;&#xff1a;专栏开篇_onvif 许振坪-CSDN博客 1.1原生代码支持&…...

深度强化学习 ②(DRL)

参考视频&#xff1a;&#x1f4fa;王树森教授深度强化学习 前言&#xff1a; 最近在学习深度强化学习&#xff0c;学的一知半解&#x1f622;&#x1f622;&#x1f622;&#xff0c;这是我的笔记&#xff0c;欢迎和我一起学习交流~ 这篇博客目前还相对比较乱&#xff0c;后面…...

线性代数重要知识点和理论(下)

奇异值分解 奇异值分解非常重要且有趣。首先对于 n n n\times n nn对称矩阵 A A A&#xff0c;可以通过对角化得到其对角化形式 A P D P − 1 APDP^{-1} APDP−1&#xff0c;但是如果 A A A不是对称矩阵或者不是方阵&#xff0c;则不能进行对角化&#xff0c;但是可以通过奇…...

独立开发者系列(35)——python环境的理解

新手阶段&#xff0c;为了快速入门&#xff0c;基本都是直接开始写python代码实现自己想要的效果&#xff0c;类似搭建博客&#xff0c;写个web服务器&#xff0c;搭建简易聊天室&#xff0c;偶尔也写些爬虫&#xff0c;或者使用pygame写个简单小游戏&#xff0c;也有tk库做点简…...

中小企业常见的网络安全问题及防范措施

在数字化浪潮的推动下&#xff0c;我国中小企业的信息化建设取得了显著成就。然而&#xff0c;随着网络安全形势的日益严峻&#xff0c;中小企业在网络安全方面的短板逐渐暴露出来。本文将从中小企业网络安全现状出发&#xff0c;深入剖析其存在的问题&#xff0c;并提出针对性…...

Android 线程并发:线程通信:Handler机制

文章目录 API源码分析操作总结 API Handler相关 Handler对象.sendMessage(Message) 发送消息 Handler对象.handleMessage()空方法 自定义Handler重写handleMessage方法&#xff0c;处理Message Looper相关 Looper.getMainLooper() 获取App的UI线程的Looper对象 Looper…...

搭建自己的金融数据源和量化分析平台(三):读取深交所股票列表

深交所的股票信息读取比较简单&#xff1a; 看上图&#xff0c;爬虫读取到下载按钮的链接之后发起请求&#xff0c;得到XLS文件后直接解析就可以了。 这里放出深交所爬虫模块的代码&#xff1a; # -*- coding: utf-8 -*- # 深圳交易所爬虫 import osimport pandas as pd imp…...

企业级视频拍摄与编辑SDK的全面解决方案

视频已成为企业传播信息、展示品牌、连接用户的重要桥梁&#xff0c;如何高效、专业地制作高质量视频内容&#xff0c;成为众多企业面临的共同挑战。美摄科技&#xff0c;作为视音频技术领域的创新先锋&#xff0c;以其强大的视频拍摄与编辑SDK&#xff0c;为企业量身打造了一站…...

后端返回列表中包含图片id,如何将列表中的图片id转化成url

问题描述 如果我有一个列表数据&#xff0c;列表中每个对象都包含一个图片id&#xff0c;现在我需要将列表中的图片id转化成图片&#xff0c;然后再页面上显示出来 如果你有一个列表数据&#xff0c;列表中每个对象都包含一个图片 ID&#xff0c;并且你想将这些图片 ID 转化为…...

Python学习笔记44:游戏篇之外星人入侵(五)

前言 上一篇文章中&#xff0c;我们成功的设置好了游戏窗口的背景颜色&#xff0c;并且在窗口底部中间位置将飞船加载出来了。 今天&#xff0c;我们将通过代码让飞船移动。 移动飞船 想要移动飞船&#xff0c;先要明白飞船位置变化的本质是什么。 通过上一篇文章&#xff0…...

export在linux中的作用

在某些项目中常常使用export命令。该命令的作用是设置环境变量&#xff0c;并且该环境变量为当前shell进程与其启动的子进程共享。 export MODEL_NAME"stable-diffusion-v1-4"比如以上命令&#xff0c;如果不采用export&#xff0c;设置的变量仅在当前shell命令/进程…...