LangChain4j实战-嵌入(向量)存储Embedding(Vector)Stores
向量相关的概念
向量的基本定义
- 数学定义:
向量是一个有序的数字列表,通常表示为[v1,v2,...,vn],其中每个数字称为分量,对应一个维度。向量既可以看作空间中的点,也可以看作带方向和大小的箭头。
- 在AI中的意义
在人工智能中,向量常被用来表示复杂的数据,比如图像、文本、声音等。通过将现实世界的对象"向量化",AI模型能够处理和理解这些数据。向量承载了数据的特征和语义信息,是机器学习、深度学习等算法的"通用语言"。
向量在AI中的核心作用
- 数据表示与特征提取
非结构化数据向量化,文本、图像、音频等原始数据通常是非结构化的,AI模型无法直接处理。通过embedding(嵌入)技术,这些数据被转换为高维稠密向量,每个维度代表一种抽象特征。
eg:
文本:单词或句子通过Word2Vec、BERT等模型转化为向量,向量间距离表示语义相似性。
图像:通过卷积神经网络(CNN)提取特征,生成表示图像内容的向量。
- 相似性度量与搜索
向量之间的距离(如欧式距离、余弦相似度)可以用来衡量数据间的相似性。例如,推荐系统中,通过比较用户和商品的向量实现个性化推荐。
向量化的定义
向量化是将非数值数据(如文本、图像、音频等)映射为高维数值向量(即一组有序数字)的过程。这些向量能够捕捉数据的语义、特征或上下文信息,使计算机能够通过数学运算(如相似度计算、聚类、分类等)处理和理解非结构化内容。
向量化与嵌入(Embedding)的关系
"嵌入"(Embedding)是向量化的一种主流技术,通常由深度学习模型生成。嵌入模型经过专门训练,能够高效地将数据映射为低维稠密向量,捕捉复杂的语义关系。"向量嵌入"(Vector Embedding)是一种将单词、句子和其他数据转换成数字的方法,可以捕捉它们的含义和关系。它们将不同的数据类型表示多维空间中的点,其中相似的数据点聚集在一起。这些数值表示帮助机器更有效地理解和处理这些数据。
LangChain4j中嵌入(向量)存储相关的类
Embedding(嵌入)
Embedding类封装了一个表示数值向量,表示嵌入内容的"语义"(通常是文本,比如TextSegment)。
Embedding Model(嵌入模型)
EmbeddingModel接口表示一种特殊类型的模型,它将文本转换为Embedding类。常用的方法:
EmbeddingModel.embed(String)
嵌入给定的文本EmbeddingModel.embed(TextSegment)
嵌入给定的TextSegmentEmbeddingModel.embedAll(List<TextSegment>)
嵌入给定的所有TextSegmentEmbeddingModel.dimension()
返回该模型产生的Embedding的维度

Embedding Store(嵌入存储)
EmbeddingStore接口表示存储Embedding,也称为向量数据库。它允许存储和高效搜索相似的Embedding
当前Embedding支持的嵌入存储可以在官网中找到:
https://docs.langchain4j.dev/integrations/embedding-stores/
EmbeddingStore可以单独存储Embeddding或者与相应的TextSegment一起存储:
- 只能按ID存储Embedding。原始嵌入数据可以存储在其他地方,并使用ID进行关联。
- 既可以存储Embedding,也可以存储已嵌入的原始数据(通常为TextSegment)。
Embedding Store的常用方法

EmbeddingStore.add(Embedding)
将给定的Embedding添加到存储并返回一个随机IDEmbeddingStore.add(String id,Embedding)
将指定ID的Embedding添加到存储中EmbeddingStore.add(Embedding, TextSegment)
将给定的Embedding与关联的TextSegment添加到存储并返回一个随机IDEmbeddingStore.addAll(List<Embedding>)
向存储中添加一个给定Embedding的列表,并返回一个随机ID列表EmbeddingStore.addAll(List<Embedding>, List<TextSegment>)
将给定的Embedding与关联的TextSegment的列表添加到存储中,并返回一个随机id列表EmbeddingStore.addAll(List<String> ids, List<Embedding>, List<TextSegment>)
将给定的Embedding与关联id和TextSegment的列表添加到存储中EmbeddingStore.search(EmbeddingSearchRequest)
查找最相似的EmbeddingEmbeddingStore.remove(String id)
按ID从存储中删除单个EmbeddingEmbeddingStore.removeAll(Collection<String> ids)
从存储中删除id存在于给定集合中的所有EmbeddingEmbeddingStore.removeAll(Filter)
从存储中删除所有与指定的Filter匹配的EmbeddingEmbeddingStore.removeAll()
从存储中删除所有的Embedding
EmbeddingSearchRequest类
EmbeddingSearchRequest表示在EmbeddingStore中搜索的请求。它具有以下属性:
- Embedding queryEmbedding:作为引用的Embedding。
- int maxResults:返回结果的最大数目。可选的参数,默认值为3
- double minScore:最小得分,取值范围0-1(含1)。只有分数>=minScore的Embedding才会被返回。这是一个可选参数。默认值为0
- Filter filter:搜索时应用与Metadata的过滤器。只有TextSegment的Metadata匹配Filter的才会返回。
Filter类
Filter允许在执行矢量搜索时按Metadata项进行过滤。目前支持的Filter类型/操作如下:
- IsEqualTo
- IsNotEqualTo
- IsGreaterThan
- IsGreaterThanOrEqualTo
- IsLessThan
- IsLessThanOrEqualTo
- IsIn
- IsNotIn
- ContainsString
- And
- Not
- Or
EmbeddingSearchResult类
EmbeddingSearchResult表示在EmbeddingStore中的搜索结果。它包含EmbeddingMatch的列表。
EmbeddingMatch类
EmbeddingMatch表示匹配的Embedding及其关联评分、ID和原始嵌入数据
向量数据库-Qdrant
Qdrant是什么
Qdrant是一个向量相似度搜索引擎,它提供了一个生产就绪的服务,并配备了便捷的API,用于存储、搜索和管理带有附加有效载荷的点(即向量)。你可以将有效负载视为额外的信息片段,这些信息可以帮助你精确搜索,同时也能获取到可以提供给用户的有用信息。
什么是向量数据库

向量数据库是一种旨在高效存储和查询高纬度向量的数据。在传统的OLTP和OLAP数据库中,数据以行和列的形式组织(并称为表),查询则根据这些列中的值进行。然后,在图像识别、自然语言处理和推荐系统等特定应用中,数据通常被表示为高维空间的向量,而这些向量,加上一个id和一个playload,我们称之为"点"。这些"点"正式我们存储在Qdrant这类向量数据库的"集合"中的元素。在此语境下,向量是对象或数据点的一种数字表示,其向量中的元素会隐式或显式地对应于该对象的特定特征或属性。例如,在图像识别系统中,一个向量可以代表一张图片,其中向量的每个元素代表一个像素值或该像素的描述符/特征。在音乐推荐系统中,每个向量可以代表一首歌,而向量的元素则捕捉了歌曲的特征,如节奏、流派、歌词等等。
Qdrant架构的高级概念

上面的图片显示了Qdrant的一些主要组件的高层次概念,以下是你应该熟悉的一些术语。
- Collections(集合):
集合是一个命名的点(带有有效载荷的向量)集合,你可以在其中进行搜索。在同一个集合中,每个点的向量必须具有相同的维度,并通过单一的度量标准进行比较。命名向量可以用于在单个点中包含多个向量,其中每个向量都可以有自己的维度和度量要求。
- Distance Metrics(距离度量):
这些度量标准用于衡量向量之间的相似度,并且必须在创建集合时进行选择。度量标准的选择取决于向量的获取方式,尤其取决于将用于编码新查询的神经网络。
- Points(点):点是Qdrant操作的核心实体,它由一个向量(vector)和一个可选的id和payload组成。
- id:向量的唯一标识符。
- Vector(向量):数据的高维表示,例如图像、声音、文档、视频等。
- Payload(有效载荷):有效载荷是一个JSON对象,你可以将其附加数据添加到向量中。
- Storage(c存储):
Qdrant提供两种存储选项:一种是内存存储,它将所有向量保存在RAM中,仅在持久化时才访问磁盘,因为速度最快;另一种是内存映射存储,它会创建一个与磁盘文件相关联的虚拟地址空间。
- Clients(客户端):
可以用来连接到Qdrant的编程语言
快速本地使用Qdrant
使用docker下载并运行Qdrant
从Dockerhub下载最新的Qdrant镜像
docker pull qdrant/qdrant
直接运行服务
docker run -p 6333:6333 -p 6334:6334 \-v "$(pwd)/qdrant_storage:/qdrant/storage:z" \qdrant/qdrant
运行之后,默认配置下,所有数据将存储在./qdrant_storage目录下。
- REST API: localhost:6333
- Web UI: localhost:6333/dashboard
- GRPC API: localhost:6334
使用Embedding模型向量化存储到向量数据库并进行相似性搜索的示例
bom物料清单
<properties><maven.compiler.source>21</maven.compiler.source><maven.compiler.target>21</maven.compiler.target><project.build.sourceEncoding>UTF-8</project.build.sourceEncoding><project.reporting.outputEncoding>UTF-8</project.reporting.outputEncoding><java.version>21</java.version><!--Spring Boot--><spring-boot.version>3.5.3</spring-boot.version><!--LangChain4J--><langchain4j.version>1.7.1</langchain4j.version><!--LangChain4J community--><langchain4j-community.version>1.7.1-beta14</langchain4j-community.version></properties><!-- <dependencyManagement> 是一个声明和集中管理依赖版本和配置的机制(物料清单)。它本身并不引入实际的依赖,而是为依赖提供一个“模板”或“蓝图”--><dependencyManagement><dependencies><!--Spring Boot--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-dependencies</artifactId><version>${spring-boot.version}</version><type>pom</type><scope>import</scope></dependency><!--LangChain4J--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-bom</artifactId><version>${langchain4j.version}</version><type>pom</type><scope>import</scope></dependency><!--langchain4j-community--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-community-bom</artifactId><version>${langchain4j-community.version}</version><type>pom</type><scope>import</scope></dependency></dependencies></dependencyManagement>
pom依赖
<dependencies><!--快速构建一个基于 Spring MVC 的 Web 应用程序而预置的一组依赖项的集合--><dependency><groupId>org.springframework.boot</groupId><artifactId>spring-boot-starter-web</artifactId></dependency><!--一个通过注解在编译时自动生成 Java 样板代码的库--><dependency><groupId>org.projectlombok</groupId><artifactId>lombok</artifactId></dependency><!--LangChain4J openAI集成依赖(低级API依赖)--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-open-ai</artifactId></dependency><!--LangChain4J 高级AI服务API依赖--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j</artifactId></dependency><!--LangChain4J 响应式编程依赖(AI服务使用Flux)--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-reactor</artifactId></dependency><!--hutool Java工具库--><dependency><groupId>cn.hutool</groupId><artifactId>hutool-all</artifactId><version>5.8.40</version></dependency><!--langchain4j 依赖qdrant向量数据库的依赖--><dependency><groupId>dev.langchain4j</groupId><artifactId>langchain4j-qdrant</artifactId></dependency></dependencies>
大模型配置类
@Configuration
public class LLMConfig {/*** 嵌入(向量化)模型** @return 嵌入模型*/@Beanpublic EmbeddingModel embeddingModel() {return OpenAiEmbeddingModel.builder().apiKey(System.getenv("aliyunQwen-apiKey")).modelName("text-embedding-v4").baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1").build();}/*** qdrant客户端** @return qdrant客户端*/@Beanpublic QdrantClient qdrantClient() {QdrantGrpcClient.Builder clientBuilder = QdrantGrpcClient.newBuilder("127.0.0.1", 6334, false);return new QdrantClient(clientBuilder.build());}/*** 向量数据库** @return 向量数据库*/@Beanpublic EmbeddingStore<TextSegment> embeddingStore() {return QdrantEmbeddingStore.builder().host("127.0.0.1").port(6334).collectionName("test-qdrant").build();}
}
大模型控制层接口
@Slf4j
@RestController
@RequestMapping("/embedding")
public class ChatEmbeddingController {//在Java 15中,文本块现在是官方的,可以使用。public static final String POETRY = """定风波·莫听穿林打叶声(宋)苏轼三月七日,沙湖道中遇雨。雨具先去,同行皆狼狈,余独不觉。已而遂晴,故作此词。莫听穿林打叶声,何妨吟啸且徐行。竹杖芒鞋轻胜马,谁怕?一蓑烟雨任平生。料峭春风吹酒醒,微冷,山头斜照却相迎。回首向来萧瑟处,归去,也无风雨也无晴。""";@Autowiredprivate EmbeddingModel embeddingModel;@Autowiredprivate QdrantClient qdrantClient;@Autowiredprivate EmbeddingStore<TextSegment> embeddingStore;/*** 通过文本向量化模型进行文本向量化** @return 向量化文本*/@GetMapping("/textEmbedding")public String textEmbedding() {Response<Embedding> embeddingResponse = embeddingModel.embed(POETRY);log.info("embeddingResponse: {}", embeddingResponse);return embeddingResponse.content().toString();}/*** 新建向量数据库实例和创建索引*/@GetMapping("/createCollection")public void createCollection() {var vectorParmas = Collections.VectorParams.newBuilder().setDistance(Collections.Distance.Cosine).setSize(1024).build();qdrantClient.createCollectionAsync("test-qdrant", vectorParmas);}/*** 嵌入存储** @return 存储结果*/@GetMapping("/add")public String add() {TextSegment textSegment = TextSegment.from(POETRY);textSegment.metadata().put("author", "苏轼");Embedding embedding = embeddingModel.embed(textSegment).content();String result = embeddingStore.add(embedding, textSegment);log.info("embedding add result: {}", result);return result;}/*** 查找最相似的Embedding** @return 最相似的Embedding*/@GetMapping("queryOne")public String queryOne() {Embedding queryEmbedding = embeddingModel.embed("莫听穿林打叶声").content();EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder().queryEmbedding(queryEmbedding).maxResults(1).build();EmbeddingSearchResult<TextSegment> searchResult = embeddingStore.search(embeddingSearchRequest);List<EmbeddingMatch<TextSegment>> matchList = searchResult.matches();if (CollUtil.isEmpty(matchList)) {return "未找到相似的Embedding";}String resultText = searchResult.matches().getFirst().embedded().text();log.info("searchResult: {}", resultText);return resultText;}/*** 查找最相似的Embedding(带过滤器Filter)** @return 最相似的Embedding*/@GetMapping("queryTwo")public String queryTwo() {Embedding queryEmbedding = embeddingModel.embed("莫听穿林打叶声").content();EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder().queryEmbedding(queryEmbedding).filter(new IsEqualTo("author", "王维")).maxResults(1).build();EmbeddingSearchResult<TextSegment> searchResult = embeddingStore.search(embeddingSearchRequest);List<EmbeddingMatch<TextSegment>> matchList = searchResult.matches();if (CollUtil.isEmpty(matchList)) {return "未找到相似的Embedding";}String resultText = searchResult.matches().getFirst().embedded().text();log.info("searchResult: {}", resultText);return resultText;}
}
参考资料
https://www.elastic.co/what-is/vector-embedding
https://docs.langchain4j.dev/tutorials/embedding-stores
https://docs.langchain4j.dev/tutorials/rag/#embedding-store
https://qdrant.tech/documentation/