Spring Cloud 接入阿里云大模型生成向量化数据,并通过 DeepSeek AI 实现 Elasticsearch 向量查询
·
1. 引言
本文介绍如何在 Spring Cloud 微服务架构中,使用**阿里云大模型(DashScope)**将文本转换为向量化数据,再通过 DeepSeek AI 的 Embedding 能力实现 Elasticsearch 向量查询,让系统具备语义搜索能力。
2. 技术架构
整体架构采用 Spring Cloud 微服务模式,核心组件包括:
- Spring Cloud Gateway:统一网关入口
- Nacos:服务注册与配置中心
- 阿里云大模型(DashScope):负责将文本转换为向量(Embedding)
- DeepSeek AI:负责将查询文本转换为向量(Embedding)
- Elasticsearch:存储向量数据并执行相似度检索
3. 环境准备
引入 Maven 依赖,在 pom.xml 中添加以下依赖:
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>8.18.8</version>
</dependency>
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.18.8</version>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.json</artifactId>
<version>2.0.1</version>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-actuator</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-discovery</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-sentinel</artifactId>
</dependency>
<dependency>
<groupId>com.alibaba.cloud</groupId>
<artifactId>spring-cloud-starter-alibaba-nacos-config</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-amqp</artifactId>
</dependency>
4. 接入阿里云大模型生成向量化数据
4.1 配置阿里云客户端
在 application.yml 中配置自定义阿里云API 信息:
embedding:
# 指定调用的嵌入模型名称
model: qwen3.7-text-embedding
# OpenAI接口规范的向量生成服务调用地址
url: https://dashscope.aliyuncs.com/compatible-mode/v1/embeddings
# 调用该服务的身份认证凭证
api-key: 平台生成apikey
# 指定模型输出的稠密向量维度
dimensions: 768
4.2 创建阿里云 Embedding 调用服务
@Service
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.http.HttpEntity;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.stereotype.Service;
import org.springframework.web.client.RestTemplate;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.Stream;
//提供向量化功能
@Service
public class EmbeddingService {
//配置文件中的内容读取
@Value("${embedding.model}")
private String model;
@Value("${embedding.dimensions}")
private Integer dimensions;
@Value("${embedding.api-key}")
private String apiKey;
@Value("${embedding.url}")
private String embeddingUrl;
@Autowired
private RestTemplate restTemplate;
public List<Float> getEmbedding(String text) {
//组装请求头
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON);
headers.setAccept(Stream.of(MediaType.APPLICATION_JSON).collect(Collectors.toList()));
headers.setBearerAuth(apiKey);
//组装请求体
EmbeddingReq req = new EmbeddingReq();
req.setDimensions(dimensions);
req.setInput(text);
req.setModel(model);
//生成entity对象
HttpEntity<EmbeddingReq> request = new HttpEntity<>(req, headers);
//发送请求 获取结果
EmbeddingResp resp = restTemplate.postForObject(embeddingUrl, request, EmbeddingResp.class);
if (resp == null || resp.getData() == null || resp.getData().isEmpty()) {
throw new ServiceException("Embedding API returned null or empty=---向量化失败");
}
List<EmbeddingResp.EmbeddingData> data = resp.getData();
if (data.get(0) == null || data.get(0).getEmbedding() == null) {
throw new ServiceException("向量化失败");
}
return data.get(0).getEmbedding();
}
}
4.3 定义响应实体
import lombok.AllArgsConstructor;
import lombok.Data;
import lombok.NoArgsConstructor;
import java.util.List;
//做向量化的返回对象
@Data
@AllArgsConstructor
@NoArgsConstructor
public class EmbeddingResp {
//向量模型允许一次发送多个字段,形成对应的向量
private List<EmbeddingData> data;
//真正生成向量的结构
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class EmbeddingData {
private List<Float> embedding;
}
}
5. 接入 DeepSeek AI 生成查询向量
5.1 配置 DeepSeek 客户端
在 application.yml 中配置 DeepSeek API 信息:
deepseek:
# 设置为DeepSeek官方的API接入根地址
baseUrl: https://api.deepseek.com
# 指定本次调用的模型
model: deepseek-v4-pro
# 取值为1,代表模型生成内容时的随机程度处于中等偏高水平
temperature: 1
# 配置为enabled即开启模型的深度思考模式
thinking: enabled
# 取值为1,代表模型在生成采样过程中会覆盖全部概率总和的候选token池,不会做内容裁剪,完整保留所有合法生成选项
top_p: 1
apiKey: 平台生成apikey
5.2 创建 DeepSeek 调用方法
返回ai生成内容
@Value("${deepseek.baseUrl}")
private String baseUrl;
@Value("${deepseek.apiKey}")
private String apiKey;
@Value("${deepseek.model}")
private String model;
@Value("${deepseek.temperature}")
private Double temperature;
@Value("${deepseek.thinking}")
private String thinking;
@Value("${deepseek.top_p}")
private Double top_p;
@Autowired
private ElasticsearchClient client;
@Autowired
private RestTemplate restTemplate;
@Autowired
private EmbeddingService embeddingService;
private String askAI(String question, List<StrategyEs> strategyEsList) {
//将私库数据进行拼接
StringJoiner joiner = new StringJoiner("\n\n");
strategyEsList.forEach(strategyEs -> {joiner.add(JSON.toJSONString(strategyEs));});
//攻略的字符串数据
String strategyStr = joiner.toString();
/*
message role 分四种类型
① system :系统信息,存放前置规则和角色设定
② user : 用户信息,存放客户端输入的信息
③ assistant : 大模型数据自身生成的返回结果
④ tool : 存放外部工具,存放外部工具生成的返回结果
*/
//私库数据
OpenAIMessage ragMessage = new OpenAIMessage("system", strategyStr);
//系统消息(角色定位)
OpenAIMessage sysMessage = new OpenAIMessage("system", "你是一名资深导游,请根据用户问题,结合私库数据,并根据互联网信息,给出旅行建议,规划出路线图、避坑指南及旅行推荐。回答不多于200字");
//用户消息
OpenAIMessage userMessage = new OpenAIMessage("user", question);
List<OpenAIMessage> messages = new ArrayList<>();
messages.add(ragMessage);
messages.add(sysMessage);
messages.add(userMessage);
OpenAIChatRequest requestBody = new OpenAIChatRequest();
requestBody.setMessages(messages);
requestBody.setModel(model);
requestBody.setTemperature(temperature);
requestBody.setTop_p(top_p);
Map<String,Object> map = new HashMap<>();
map.put("type",thinking);
requestBody.setThinking(map);
//header
HttpHeaders headers = new HttpHeaders();
//设置请求格式
headers.setContentType(MediaType.APPLICATION_JSON);
//设置相应格式
List<MediaType> mediaTypes = new ArrayList<>();
mediaTypes.add(MediaType.APPLICATION_JSON);
//接收的是json类型
headers.setAccept(mediaTypes);
//设置 Bearer Token
headers.setBearerAuth(apiKey);
HttpEntity<OpenAIChatRequest> request = new HttpEntity<>(requestBody, headers);
String url = baseUrl + "/chat/completions";
//发送请求
OpenAIChatResponse response = restTemplate.postForObject(url, request, OpenAIChatResponse.class);
if (response == null || response.getChoices() == null || response.getChoices().isEmpty()) {
return "AI没有返回合适的结果";
}
OpenAIChatResponse.Choice choice = response.getChoices().get(0);
if (choice == null || choice.getMessage() == null || choice.getMessage().getContent() == null) {
return "AI返回内容无结果";
}
return choice.getMessage().getContent();
}
5.3 定义响应实体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OpenAIChatRequest {
private String model;
//对话的消息列表 有四种类型,可以传递前置数据和规则
private List<OpenAIMessage> messages;
//采样温度,介于 0 和 2 之间
// 更高的值会使输出更随机,而更低的值,如 0.2,会使其更加集中和确定
// 我们通常建议可以更改这个值或者更改 top_p,但不建议同时对两者进行修改
private Double temperature;
//Possible values: [enabled, disabled]
//Default value: enabled
//如果设为 enabled,则使用思考模式。如果设为 disabled,则使用非思考模式
private Map<String, Object> thinking;
//作为调节采样温度的替代方案,默认值1,模型会考虑前 top_p 概率的 token 的结果。所以 0.1 就意味着只有包括在最高 10% 概率中的 token 会被考虑
private Double top_p;
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OpenAIChatResponse {
private String id;
private List<Choice> choices;
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Choice{
private OpenAIMessage message;
}
}
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OpenAIMessage {
private String role;
private String content;
}
6. Elasticsearch 向量索引设计并实现向量写入
@Service
public class XxxxEsServiceImpl implements IXxxxEsService {
@Autowired
private RemoteXxxxService remoteXxxxService;
@Autowired
private ElasticsearchClient client;
public static final String INDEX_NAME = "Xxxx";
@Autowired
private EmbeddingService embeddingService;
@Value("${embedding.dimensions}")
private Integer dimensions;
@Override
public void initXxxxEs() throws IOException {
//删库
boolean value = client.indices().exists(e -> e.index(INDEX_NAME)).value();
//存在该库 则删除
if (value){
client.indices().delete(d->d.index(INDEX_NAME));
}
//建库 numberOfShards-分片数 建立映射
CreateIndexRequest request = CreateIndexRequest.of(r -> r.index(INDEX_NAME)
.settings(s -> s.numberOfShards("1").numberOfReplicas("1"))
.mappings(m -> m.properties("id", p -> p.long_(l -> l))
.properties("title", p -> p.text(t -> t.analyzer("ik_max_word")))
.properties("subTitle", p -> p.text(t -> t.analyzer("ik_max_word")))
.properties("summary", p -> p.text(t -> t.analyzer("ik_max_word")))
.properties("embedding", p -> p.denseVector(
t -> t.dims(dimensions)
.index(true)
.similarity(DenseVectorSimilarity.Cosine))))
);
client.indices().create(request);
//查询mysql
List<Xxxx> Xxxxs = remoteXxxxService.list("inner").getData();
//同步mysql数据到es
for (Xxxx xxx : Xxxxs) {
XxxxEs xxxEs = new XxxxEs();
//相同名字的属性进行拷贝
BeanUtils.copyProperties(xxx, xxxEs);
//向量化
StringJoiner sj = new StringJoiner(" ");
sj.add(xxx.getTitle()).add(xxx.getSubTitle()).add(xxx.getSummary());
List<Float> embedding = embeddingService.getEmbedding(sj.toString());
strategyEs.setEmbedding(embedding);
client.index(i->i.index(INDEX_NAME)
.id(xxxEs.getId().toString())
.document(xxxEs));
}
}
}
6.1 定义实体类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class XxxxEs {
private Long id;
private String title;
private String subTitle;
private String summary;
private List<Float> embedding;
}
7. 查询服务
7.1 向量相似度查询服务(使用 DeepSeek AI 生成查询向量)
@Autowired
private ElasticsearchClient client;
@Autowired
private RestTemplate restTemplate;
@Autowired
private EmbeddingService embeddingService;
@RequestMapping("/chat")
public Object getAnswer(@RequestBody ChatRequest request) throws IOException {
//精准匹配模式::查询es数据 根据关键字和每页显示条数 需要设置分词器和查询的字段
/*SearchResponse<StrategyEs> resp = client.search(sh -> sh.index("xxx")
.from(0)
.size(request.getTopK())
.query(q -> q.multiMatch(
m -> m.query(request.getQuestion())
.fields("title", "subTitle", "summary")
.analyzer("ik_max_word"))), XxxxEs.class);*/
//向量模式查询
//用户问题向量化
List<Float> embedding = embeddingService.getEmbedding(request.getQuestion());
//使用向量方式查询es
SearchResponse<XxxxEs> resp = client.search(sh -> sh.index("xxx")
.knn(k -> k.field("embedding")
.queryVector(embedding).k(request.getTopK())), XxxxEs.class);
//私库数据
HitsMetadata<XxxxEs> hits = resp.hits();
List<Hit<XxxxEs>> hitList = hits.hits();
//存储返回的攻略数据
List<XxxxEs> xxxEsList = new ArrayList<>();
//遍历将查询到的数据存储到list中
hitList.forEach(hit -> xxxEsList.add(hit.source()));
//发送请求到deepseek 通过askAI()方法实现
String answer = askAI(request.getQuestion(),xxxEsList);
//返回json格式数据 包含ai生成回答和私库数据
return new ChatResponse(answer, xxxEsList);
}
7.2 请求实体
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ChatRequest {
//查询关键字
private String question;
//每页显示条数
private Integer topK;
}
更多推荐
所有评论(0)