langchain4j [FEATURE] - Reuse embeddings

szqfcxe2  于 5个月前  发布在  其他
关注(0)|答案(4)|浏览(88)

您的功能请求是否与问题相关?请描述。

能够跨应用程序示例重启重用嵌入是非常好的,尤其是在内部循环周期中。例如,如果应用程序在启动时计算嵌入,我可能只想在处理一个功能时执行一次计算,因为有时候计算可能会耗费时间。目前唯一的解决方案是切换到支持此功能的 InMemoryEmbeddingStore

但是如果我在使用其他嵌入存储时想要这个功能呢?目前这是不可能的。也许对于所有存储实现来说都不可能,那么也没关系。但每个存储应该被允许提供这种功能。

描述您希望的解决方案

目前 InMemoryEmbeddingStore 实现了一些方法来解决这个问题。有几个 serializeTo... 方法将嵌入存储的内容写出来。

我希望将这些方法“提升”到 EmbeddingStore 接口。我会为它们提供默认实现,这些实现会抛出一些不支持的异常,以保持向后兼容性。通过这个特定的更改,只有 InMemoryEmbeddingStore 将继续实现该功能。

但是未来的增强/ PR 可以为其他嵌入存储实现该功能。

不过我看到的挑战是 from... 方法,它们从其他来源填充嵌入存储,在 InMemoryEmbeddingStore 中声明为 static 。我们可能需要废弃这些方法并引入示例级别的方法,或者引入某种知道如何示例化正确实现类型的工厂。

然而,一切都应该以向后兼容的方式进行,并且作为此更改的一部分,不会触及任何其他嵌入存储提供程序。

附加上下文

如果我们都同意,我很乐意接受这个任务并开始工作。只是想在投入时间之前提出它以收集反馈。

xiozqbni

xiozqbni1#

感谢您的建议!Embeddings 确实计算量较大,通常在应用程序生命周期中只计算一次,然后在源文档更改时更新。
我是否理解正确,您希望有一种方法可以在示例重启后持久化 EmbeddingStores?我相信除了 InMemoryEmbeddingStore 之外的所有 EmbeddingStores 都是自动持久化的,并且大部分行为类似于普通数据库。
我认为为其他类型的 EmbeddingStores 添加 serializeTo...from... 方法的主要优势是,让 Embeddings 从一个 EmbeddingStore 实现到另一个实现之间有一个通用的中间格式。这是您想要的吗?

46scxncf

46scxncf2#

感谢你的留言@LizeRaes!
这并不是我正在寻找的,但这是一个好的想法!
我同意,在生产环境中,这个功能很可能不会被使用,因为一个“真正的”嵌入存储会在应用程序重启之间维护嵌入。
我正在解决的开发周期内循环的使用案例是用户在使用testcontainers/docker compose等工具进行开发时,建立一个“真正的”嵌入存储。每次testcontainers/docker compose关闭时,计算出的嵌入就会消失。这个功能将允许将嵌入写入文件系统,这样就不需要重新计算了。我的初步想法是,写入的内容格式应该是特定于嵌入存储供应商的,但它不一定是这样。老实说,我没有深入研究过这个问题。
我也有点自私,因为我的主要目的是为quarkus LangChain4j扩展提供这个功能,该扩展将在开发模式/实时编码中使用这个功能。
目前,如果我使用一个真实的嵌入存储,每次我做出代码更改时,都需要重新计算嵌入并重新存储它们。
我已经为内存嵌入存储实现了这个功能,因为那个存储已经暴露了这个能力。我想能够将其扩展到其他嵌入存储,尽管如果有人决定实际上为其他嵌入存储实现这些功能,那么应该在单独的PR中进行。我不会在这个更改中解决这个问题。我现在只想有一个一致的接口。
请参阅https://docs.quarkiverse.io/quarkus-langchain4j/dev/easy-rag.html#_reusing_embeddings
这有道理吗?再次强调,这可能不会在生产环境中广泛使用,但肯定会节省内部循环开发的时间。

bqucvtff

bqucvtff3#

每次我进行代码更改时,都需要重新计算嵌入并重新存储它们。这听起来很奇怪,为什么呢?有两个原因需要重新计算嵌入:

  • 你选择了不同的嵌入模型(从不同模型生成的嵌入是不兼容的)。
  • 你更改了分块算法。

每次testcontainers/docker compose关闭时,计算出的嵌入都会消失。
抱歉,我不是Docker方面的Maven,但我看到了N种解决方法:

  • 你应该使用一个真正的矢量数据库。
  • 然后,查找数据如何在磁盘上持久化。
  • 当Docker容器关闭之前,先进行备份。

例如,对于pgvector扩展和PostgreSQL扩展,以及sqlite-vss扩展和SQLite扩展。你可以非常容易地在.sql文件转储中备份结果。
你也可以不使用Docker容器,而是在你的计算机上本地启动它。
然而,最简单的方法是找到一个嵌入式向量数据库(就像SQLite是一个嵌入式数据库一样)。这就像是一个库,将所有内容存储在文件中。但不幸的是,每个向量数据库都需要一个专用服务器

wkftcu5l

wkftcu5l4#

每次我进行代码更改时,都需要重新计算嵌入并重新存储它们。
这听起来很奇怪,为什么呢?需要重新计算嵌入的原因只有两个:

  • 你选择了不同的嵌入模型(来自不同模型的嵌入是不兼容的)。
  • 你更改了分块算法。

我完全同意 在生产环境中 事情就是这样运作的。正如我所说,这个功能更适合于本地开发/内部循环。
每次testcontainers/docker compose关闭时,计算出的嵌入就会消失。
抱歉,我不是Docker方面的Maven,但我知道有N种解决方法:

  • 你应该使用一个真正的矢量数据库。
  • 然后,你可以查找数据是如何在磁盘上持久化的。
  • 当Docker容器关闭时,在此之前先进行备份。

这并不是现实世界的工作方式。当Docker容器关闭时,数据就会消失,特别是在使用临时文件系统时。像Testcontainers这样的应用程序框架使得在开发期间使用“真实”的东西(矢量数据库、关系数据库、消息代理等)变得非常简单。使它变得简单的部分是容器是临时的。
以关系型数据库为例(Postgres/MySQL/Oracle等)。在开发过程中,如果我启动一个数据库容器,我的应用程序将需要创建一个方案并可能预先加载一些数据。像Hibernate这样的框架支持这一点 - 我想要预先加载的数据可以在文件系统中找到,事情就这样顺利进行了。在生产环境中,“真实”的数据库已经在某个地方运行了,模式已经被创建了,数据也已经存在了。我在这里尝试做的是同样的事情,从概念上讲。我试图解决开发过程中耗时的一个用例。
例如,对于SQLite扩展和.sql扩展,我可以很容易地在import.sql文件转储中备份结果。
是的,这是真的,但这不是今天开发者工作的常见方式。如果可以的话,我希望我只运行Docker Compose或类似的东西(或者使用Testcontainers库),这样当我的应用启动时,它也会启动一个“真实”的矢量数据库(millvus/chroma/pgvector等),向其中填充一些数据,然后运行。当我的应用停止时,矢量数据库容器会被销毁。这是开发生命周期中非常常见的一种方式,也是如今与Docker或Testcontainers一起工作的方式。问题在于在这种场景下,每次应用启动时都需要重新计算嵌入(例如,Hibernate会重新创建模式/填充数据),但是计算嵌入可能非常耗时(而且昂贵($$),尤其是如果我使用的是远程嵌入模型并且必须为每个API请求付费)。
如果能在我的源代码树中有一些预计算好的嵌入样本来满足我在本地开发的需求,那就好了,就像我在src/main/resources中有EmbeddingStore文件一样,Hibernate知道该怎么处理一样,但只有在进行本地开发时才这样做。这样一来,当我启动本地开发服务器时,我可以传递一些配置信息告诉LangChain4J启动一个矢量数据库容器并在其中加载一些数据。这样我的应用程序就不需要重新计算嵌入了。否则现在当我的应用以开发模式启动时,它首先需要在Testcontainers启动的矢量数据库中计算一堆嵌入才能将其存储进去。
你也不必使用Docker容器,而是在你的计算机上本地启动它。
然而,最简单的方法是找到一个嵌入式矢量数据库(就像SQLite是一个嵌入式数据库)。这就像是一个库并将所有内容都存储在文件中。但不幸的是,每个矢量数据库都需要一个专用服务器
使用容器和像Testcontainers这样的框架在当今是非常普遍的,所以我不认为我应该需要在我自己的计算机上启动一个矢量数据库。许多开发者没有权限在其本地计算机上安装东西。
像Quarkus和Spring这样的Java框架支持这种开发模式,甚至可以从一开始就支持。这就是我通过这个增强功能要解决的问题。
使用Quarkus时,我们已经在内存矢量存储中实现了这一点(参见https://docs.quarkiverse.io/quarkiverse-langchain4j/dev/easy-rag.html#_reusing_embeddings)。我们希望能够通过通用API扩展到其他矢量数据库的能力。
如前所述,我提议的这个更改实际上不会为所有的 default 接口实现通用API。它只会为 EmbeddingStore 接口添加一些 NotYetImplementedException 方法或什么来表示特定实现尚不支持该功能。然后我们可以稍后为每个实现实现它。 InMemoryStore 已经实现了它。

相关问题