向量
简单定义:向量就是一个有序的数字列表。
可以把向量想象成“事物的特征指纹”。
任何事物——无论是文字、图片、声音还是视频——都可以通过一系列的数字来描述其特征,这一串数字就构成了该事物的向量。
例如:如何用一个三维向量来描述水果?
假设我们只用三个特征来描述水果:甜度、酸度和脆度。
- 一个红苹果:甜度很高(9分),酸度适中(3分),非常脆(8分)。它的向量就是 [9, 3, 8]。
- 一个柠檬:甜度很低(1分),酸度极高(9分),基本不脆(1分)。它的向量就是 [1, 9, 1]。
- 一根香蕉:甜度很高(8分),酸度很低(2分),不脆(0分)。它的向量就是 [8, 2, 0]。
你看,我们只用三个数字,就为每种水果创建了一个独一无二的“数字ID”或“指纹”。在实际的AI模型中,向量的维度(即数字的个数)会大得多,通常是几百甚至几千维。这些维度捕捉的是更复杂、更抽象的特征,比如词语的“词性”、“情感色彩”、“在句子中的角色”,或者图片的“纹理”、“形状”、“颜色分布”等。
向量的几个用途:
- 度量相似相
- 语义搜索
- 等...
为此 LangChain4j 提供了将事物向量化的功能。主要的几个概念有:
- Embedding
- Embedding Model
- Embedding Store
- EmbeddingSearchRequest
这些概念和 API 比较长,我放在了最后,可以简单的看一下各个的“简介”,然后就去阅读“示例”中的代码即可。
如果你知道什么是向量,那么直接阅读“示例”即可。我这里也只是将官方文档简单翻译(机翻)并整理了一下。
Tips:文中的 Embedding(嵌入),也可以说成是 Vector(向量),这样可能更加好理解一些。
Qdrant
Qdrant 向量数据库的安装方式和运行方式这里不再赘述,自行百度。
$ docker run -p 6333:6333 -p 6334:6334 225e903e65c3
- 6336 用户 HTTP API,WEB 管理后台
- 6334 用于 gRPC API
引入 qdrant 客户端依赖
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-qdrant</artifactId>
</dependency>
示例
模型配置
@Configuration
public class LLMConfig {
@Bean
public EmbeddingModel embeddingModel() {
return OpenAiEmbeddingModel.builder()
.apiKey(System.getenv("ALI_QWEN_API_KEY"))
.modelName("text-embedding-v4")
.baseUrl("https://dashscope.aliyuncs.com/compatible-mode/v1")
.build();
}
@Bean
public EmbeddingStore<TextSegment> embeddingStore() {
return QdrantEmbeddingStore.builder()
.host("10.24.26.46")
.port(6334)
.collectionName("test-qdrant")
.build();
}
}
qdrant 客户端配置
@Configuration
public class QDrantConfig {
/**
* 创建 Qdrant 客户端
*/
@Bean
public QdrantClient qdrantClient() {
QdrantGrpcClient.Builder builder
= QdrantGrpcClient.newBuilder("10.24.26.46", 6334, false);
return new QdrantClient(builder.build());
}
}
创建向量数据库
新增向量数据库实例和创建索引:test-qdrant
@Test
public void createCollection() {
Collections.VectorParams vectorParams = Collections.VectorParams.newBuilder()
.setDistance(Collections.Distance.Cosine)
.setSize(1024)
.build();
qdrantClient.createCollectionAsync("test-qdrant", vectorParams);
}
插入数据
往向量数据库中新增文本记录,将 prompt 向量化后插入数据库。
@Test
public void add() {
String prompt = "超级无敌大帅哥说:我超级无敌帅!";
TextSegment segment = TextSegment.from(prompt);
segment.metadata().put("author", "wwj");
Embedding embedding = embeddingModel.embed(segment).content(); // 向量化
String res = embeddingStore.add(embedding, segment); // 插入数据库
System.out.println(res);
}
查询数据
@Test
public void query1() {
Embedding queryEmbedding = embeddingModel.embed("超级无敌大帅哥说的是什么?").content();
EmbeddingSearchRequest req = EmbeddingSearchRequest.builder()
.queryEmbedding(queryEmbedding) // 查询向量
.maxResults(1) // 最大返回 1 条
.build();
EmbeddingSearchResult<TextSegment> result = embeddingStore.search(req);
System.out.println(result.matches().get(0).embedded().text());
}
filter 查询
查询 metadata 元数据中 author=wwj 的向量数据。
@Test
public void query2() {
Embedding embedding = embeddingModel.embed("超级无敌").content();
EmbeddingSearchRequest req = EmbeddingSearchRequest.builder()
.queryEmbedding(embedding)
.filter(MetadataFilterBuilder.metadataKey("author").isEqualTo("wwj")) // here
.maxResults(1)
.build();
EmbeddingSearchResult<TextSegment> result = embeddingStore.search(req);
List<EmbeddingMatch<TextSegment>> list = result.matches();
if(list.isEmpty()) {
System.out.println("啥都没有");
} else {
System.out.println(list.get(0).embedded().text());
}
}
API 查询
Embedding
Embedding 类封装了一个数值向量,表示嵌入内容的"语义意义"(通常是文本,如 TextSegment )。
了解更多关于向量嵌入的内容:
通常的用法:
Embedding.dimension()返回嵌入向量的维度(即其长度)CosineSimilarity.between(Embedding, Embedding)计算 2 个Embedding之间的余弦相似度Embedding.normalize()将嵌入向量进行归一化(原地操作)
Embedding Model
EmbeddingModel 接口代表一种特殊的模型,可以将文本转换为 Embedding 。(将给定文本向量化)
通常的用法:
EmbeddingModel.embed(String)将给定文本向量化EmbeddingModel.embed(TextSegment)将给定的TextSegment向量化EmbeddingModel.embedAll(List<TextSegment>)向量化所有给定的TextSegmentEmbeddingModel.dimension()返回此模型生成的Embedding的维度
Embedding Store
EmbeddingStore 接口表示一个用于 Embedding 的存储,也称为向量数据库。它允许存储和高效搜索相似的(在向量空间中接近的) Embedding 。
EmbeddingStore 可以单独存储 Embedding 或与相应的 TextSegment 一起存储:
- 它可以通过 ID 存储
Embedding。原始向量数据可以存储在其他地方,并使用 ID 进行关联。 - 它可以存储
Embedding和向量化后的原始数据(通常是TextSegment)。
添加一个:
EmbeddingStore.add(Embedding)将给定的Embedding添加到存储中并返回一个随机 IDEmbeddingStore.add(String id, Embedding)将指定的Embedding添加到存储中,并分配一个 IDEmbeddingStore.add(Embedding, TextSegment)将指定的Embedding添加到存储中,并关联一个TextSegment,然后返回一个随机 ID
添加多个:
EmbeddingStore.addAll(List<Embedding>)将一系列给定的Embedding添加到存储中,并返回一系列随机 IDEmbeddingStore.addAll(List<Embedding>, List<TextSegment>)将一系列给定的Embedding与关联的TextSegment添加到存储中,并返回一系列随机 IDEmbeddingStore.addAll(List<String> ids, List<Embedding>, List<TextSegment>)添加具有相关 ID 和TextSegment的Embedding列表到存储中
搜索:
EmbeddingStore.search(EmbeddingSearchRequest)搜索最相似的Embedding
删除:
EmbeddingStore.remove(String id)通过 ID 从存储中删除单个EmbeddingEmbeddingStore.removeAll(Collection<String> ids)从存储中删除所有 ID 存在于给定集合中的EmbeddingEmbeddingStore.removeAll(Filter)删除存储中所有与指定的Filter匹配的EmbeddingEmbeddingStore.removeAll()从存储中删除所有Embedding
EmbeddingSearchRequest
EmbeddingSearchRequest 表示在 EmbeddingStore 中进行搜索的请求。它具有以下属性:
Embedding queryEmbedding: 用作参考的嵌入。int maxResults: 返回的最大结果数量。这是一个可选参数。默认值:3。double minScore: 最小分数,范围从 0 到 1(包含两端)。只有分数 >=minScore的嵌入结果才会被返回。这是一个可选参数。默认值:0。Filter filter: 在搜索过程中应用于Metadata的过滤器。只有TextSegment的Metadata与Filter匹配的结果才会被返回。
Filter
Filter 允许在执行向量搜索时通过 Metadata 条目进行过滤。
目前,支持以下 Filter 类型/操作:
IsEqualToIsNotEqualToIsGreaterThanIsGreaterThanOrEqualToIsLessThanIsLessThanOrEqualToIsInIsNotInContainsStringAndNotOr
并非所有嵌入存储都支持通过
Metadata进行过滤,请参考模型比较表的“按元数据过滤(Filtering by Metadata)”列。 【模型比较表】docs.langchain4j.dev/integration…
支持通过
Metadata进行过滤的某些存储并不支持所有可能的Filter类型/操作。例如ContainsString操作目前仅由 Milvus、PgVector 和 Qdrant 支持。 【其他资料】github.com/langchain4j…