LangChain4j Embedding与向量数据库实战:使⽤Embedding实现⾃然语⾔搜索
1. 引言
在上一篇《LangChain4j AiServices 机制详解:快速构建智能体应用》中,我们探讨了如何利用AiServices快速构建智能体。然而,要让AI真正理解并回答特定领域的问题,仅仅依靠大模型的通用知识是不够的。我们需要一种机制,将企业内部的产品手册、常见问题等私有知识“喂”给AI,使其能够基于这些知识进行问答。
这正是RAG(检索增强生成)的核心思想。而实现RAG的第一步,就是文本向量化(Embedding)。本文将带你深入理解Embedding的原理,并手把手教你使用LangChain4j操作向量数据库,为构建智能客服系统打下坚实基础。
2. 什么是Embedding文本向量化
AI大模型之所以能“理解”人类语言,本质上是将语言数据化。计算机无法直接理解“我是谁”和“我叫什么名字”是同一个意思,但它可以将这些文本转换成一串串数字(即向量),然后通过计算这些数字之间的相似度来判断语义是否相近。
向量是一个有位置、有方向的变量。一个二维向量可以理解为平面坐标轴上的一个点 (x, y),在计算机中用 [x, y] 表示。同理,一个多维向量就是一个包含更多数字的数组。
文本向量化(Embedding) 就是通过机器学习模型,将一个文本(如一句话、一个段落)转换成一个多维向量的过程。这个向量能够捕捉文本的语义信息,使得语义相近的文本在向量空间中的距离也更近。
在LangChain4j中,EmbeddingModel 接口抽象了所有文本向量化模型的能力。例如,使用OpenAI的向量化模型,可以轻松将文本转换为一个1536维的向量。
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
import dev.langchain4j.model.output.Response;
public class EmbeddingDemo {
public static void main(String[] args) {
EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
.baseUrl("https://api.openai.com") // 替换为你的API地址
.apiKey("your-api-key") // 替换为你的API Key
.build();
Response<Embedding> embed = embeddingModel.embed("你好,我是楼兰");
System.out.println(embed.content().toString());
System.out.println("向量维度:" + embed.content().vector().length);
}
}
执行结果可以看到,这句话被转化成了一个包含1536个浮点数的向量。
3. 计算向量相似度
文本被转化为向量后,我们就可以通过数学公式计算两个向量之间的相似度,从而反映语义的相似性。最常用的方法是余弦相似度(Cosine Similarity),它通过计算两个向量夹角的余弦值来衡量相似度,值越接近1,表示语义越相似。
import java.util.*;
public class CosineSimilarity {
// 计算两个向量的点积
public static double dotProduct(double[] vectorA, double[] vectorB) {
double dotProduct = 0;
for (int i = 0; i < vectorA.length; i++) {
dotProduct += vectorA[i] * vectorB[i];
}
return dotProduct;
}
// 计算向量的模
public static double vectorMagnitude(double[] vector) {
double magnitude = 0;
for (double component : vector) {
magnitude += Math.pow(component, 2);
}
return Math.sqrt(magnitude);
}
// 计算余弦相似度
public static double cosineSimilarity(double[] vectorA, double[] vectorB) {
double dotProduct = dotProduct(vectorA, vectorB);
double magnitudeA = vectorMagnitude(vectorA);
double magnitudeB = vectorMagnitude(vectorB);
if (magnitudeA == 0 || magnitudeB == 0) {
return 0; // 避免除以零
} else {
return dotProduct / (magnitudeA * magnitudeB);
}
}
public static void main(String[] args) {
double[] vectorA = {1, 2, 3};
double[] vectorB = {3, 2, 1};
double similarity = cosineSimilarity(vectorA, vectorB);
System.out.println("Cosine Similarity: " + similarity);
}
}
当然,LangChain4j内部已经封装了大量计算向量相似度的工具类,我们在实际开发中无需手动实现。
4. 向量数据库持久化
有了向量模型,我们自然需要将文本的向量结果存储起来,以便后续进行大规模的相似度检索。向量数据库就是为此而生的专用数据库。
在LangChain4j中,EmbeddingStore 接口抽象了所有向量数据库的操作。它提供了20多个实现类,对接不同的数据库产品,如Redis、Milvus、Pinecone、Elasticsearch等。完整列表可参考官方文档:https://docs.langchain4j.dev/integrations/embedding-stores/
接下来,我们以Redis为例,演示如何使用LangChain4j操作向量数据库。
4.1 添加依赖
首先,在 pom.xml 中添加LangChain4j对Redis的支持:
<dependency>
<groupId>dev.langchain4j</groupId>
<artifactId>langchain4j-redis</artifactId>
<version>${langchain4j.version}</version>
</dependency>
4.2 部署Redis(启用Search模块)
Redis需要启用 redisearch 模块才能支持向量检索。推荐使用Docker部署官方提供的 redis-stack-server 镜像:
docker run -p 6379:6379 redis/redis-stack-server:latest
4.3 使用LangChain4j操作Redis
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.store.embedding.redis.RedisEmbeddingStore;
public class RedisEmbeddingDemo {
public static void main(String[] args) {
// 1. 初始化向量模型
EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
.baseUrl("https://api.openai.com")
.apiKey("your-api-key")
.build();
// 2. 初始化Redis向量存储
RedisEmbeddingStore embeddingStore = RedisEmbeddingStore.builder()
.host("127.0.0.1")
.port(6379)
.dimension(1536) // 维度需与模型输出一致
.build();
// 3. 生成向量
Response<Embedding> embed = embeddingModel.embed("你好,我是楼兰");
// 4. 存储向量到Redis(索引名默认为embedding-index)
embeddingStore.add("vec1", embed.content());
}
}
注意:
RedisEmbeddingStore每次会创建一个固定的索引(embedding-index)来保存向量数据。如果要重复实验,最好手动删除Redis上已有的数据再重新插入。删除索引的指令为:redis-cli FT.DROPINDEX embedding-index DD其中
DD表示删除索引的同时删除对应的数据。
5. 计算相似向量:实现语义搜索
EmbeddingStore 不仅提供了持久化功能,还封装了相似向量查找的能力。我们可以通过它快速找到与输入文本语义最相似的历史文本。
5.1 基础相似度搜索
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.EmbeddingSearchRequest;
import dev.langchain4j.store.embedding.EmbeddingSearchResult;
import dev.langchain4j.store.embedding.redis.RedisEmbeddingStore;
import java.util.List;
public class VectorSearchDemo {
public static void main(String[] args) {
EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
.baseUrl("https://api.openai.com")
.apiKey("your-api-key")
.build();
// 假设Redis中已存储了一些向量
Response<Embedding> embed1 = embeddingModel.embed("我的名字叫楼兰");
RedisEmbeddingStore embeddingStore = RedisEmbeddingStore.builder()
.host("127.0.0.1")
.port(6379)
.dimension(1536)
.build();
// 构建搜索请求,寻找最近的4条记录
EmbeddingSearchRequest embeddingSearchRequest = EmbeddingSearchRequest.builder()
.queryEmbedding(embed1.content())
.maxResults(4)
.build();
EmbeddingSearchResult<TextSegment> searchResult = embeddingStore.search(embeddingSearchRequest);
List<EmbeddingMatch<TextSegment>> matchedResult = searchResult.matches();
for (EmbeddingMatch<TextSegment> embeddingMatch : matchedResult) {
System.out.println("相似度:" + embeddingMatch.score());
}
}
}
执行结果会打印出与“我的名字叫楼兰”最相似的4条记录的相似度分数,分数越接近1,语义越相似。
5.2 构建智能客服雏形
下面,我们演示一个简单的智能客服场景:预设几条知识,然后根据用户的问题找到最接近的答案。
import dev.langchain4j.data.embedding.Embedding;
import dev.langchain4j.data.segment.TextSegment;
import dev.langchain4j.model.embedding.EmbeddingModel;
import dev.langchain4j.model.openai.OpenAiEmbeddingModel;
import dev.langchain4j.model.output.Response;
import dev.langchain4j.store.embedding.EmbeddingMatch;
import dev.langchain4j.store.embedding.redis.RedisEmbeddingStore;
import java.util.List;
public class QuestionSearchDemo {
public static void main(String[] args) {
EmbeddingModel embeddingModel = OpenAiEmbeddingModel.builder()
.baseUrl("https://api.openai.com")
.apiKey("your-api-key")
.build();
RedisEmbeddingStore embeddingStore = RedisEmbeddingStore.builder()
.host("127.0.0.1")
.port(6379)
.dimension(1536)
.build();
// 1. 预设几条知识,生成向量并存储
TextSegment textSegment1 = TextSegment.textSegment("客服电话是400-8558558");
TextSegment textSegment2 = TextSegment.textSegment("客服工作时间是周一到周五");
TextSegment textSegment3 = TextSegment.textSegment("客服投诉电话是400-8668668");
Response<Embedding> embed1 = embeddingModel.embed(textSegment1);
Response<Embedding> embed2 = embeddingModel.embed(textSegment2);
Response<Embedding> embed3 = embeddingModel.embed(textSegment3);
embeddingStore.add(embed1.content(), textSegment1);
embeddingStore.add(embed2.content(), textSegment2);
embeddingStore.add(embed3.content(), textSegment3);
// 2. 用户提问,生成向量并查询
Response<Embedding> embed = embeddingModel.embed("客服电话多少");
List<EmbeddingMatch<TextSegment>> result = embeddingStore.findRelevant(embed.content(), 5);
// 3. 输出结果
for (EmbeddingMatch<TextSegment> embeddingMatch : result) {
if (embeddingMatch.embedded() != null) {
System.out.println(embeddingMatch.embedded().text() + ", 分数为:" + embeddingMatch.score());
}
}
}
}
执行结果如下:
客服电话是400-8558558, 分数为:0.9530125260353
客服投诉电话是400-8668668, 分数为:0.9520850479603
客服工作时间是周一到周五, 分数为:0.930568754673
可以看到,系统成功找到了与“客服电话多少”语义最相关的三条知识,并按相似度从高到低排序。这就是一个智能客服系统的雏形。
6. 总结与展望
本文我们深入了解了Embedding文本向量化的原理,并学习了如何使用LangChain4j操作向量数据库Redis,实现了基于语义的文本检索。这为构建RAG(检索增强生成)应用打下了坚实的基础。
然而,这离一个完整的智能客服系统还有一点差距——我们目前只是找到了相关的知识片段,还没有让大模型基于这些片段生成一个流畅、准确的回答。这正是RAG要做的事情。在下一篇文章中,我们将结合LangChain4j的AiServices和ContentRetriever,构建一个完整的RAG智能客服应用,敬请期待!
更多推荐


所有评论(0)