调用DeepSeek模型集成项目(8.24)
一、Java代码测试deepseek访问
新建travel-modules-ai模块(Java项目)
(1)添加依赖
<dependencies>
<dependency>
<groupId>com.mashape.unirest</groupId>
<artifactId>unirest-java</artifactId>
<version>1.4.9</version>
</dependency>
</dependencies>
(2)新建TestDeepseek测试类
public class TestDeepseek {
public static void main(String[] args) throws UnirestException {
Unirest.setTimeouts(0, 0);
HttpResponse<String> response = Unirest.post("https://api.deepseek.com/chat/completions")
.header("Content-Type", "application/json")
.header("Accept", "application/json")
.header("Authorization", "Bearer 你的Apikey")
.body("{\n \"messages\": [\n {\n \"content\": \"You are a helpful assistant\",\n \"role\": \"system\"\n },\n {\n \"content\": \"今天天气如何?\",\n \"role\": \"user\"\n }\n ],\n \"model\": \"deepseek-v4-pro\",\n \"thinking\": {\n \"type\": \"enabled\"\n },\n \"reasoning_effort\": \"low\",\n \"max_tokens\": 4096,\n \"response_format\": {\n \"type\": \"text\"\n },\n \"stop\": null,\n \"stream\": false,\n \"stream_options\": null,\n \"temperature\": 1,\n \"top_p\": 1,\n \"tools\": null,\n \"tool_choice\": \"none\",\n \"logprobs\": false,\n \"top_logprobs\": null\n}")
.asString();
System.err.println(response.getBody());
}
}
二、推荐页功能实现
1、推荐页 RAG 流程简述
- 接收输入:用户在推荐页输入自然语言问题。
- ES 检索:将问题作为查询条件,从 Elasticsearch 中召回 Top-K 条相关数据。
- 构建上下文:将 ES 返回的数据整理拼接为字符串,作为私有知识库上下文,与用户问题一起组装成 Prompt。
- 调用 DeepSeek 生成:将 Prompt 发送给 DeepSeek,模型基于私库数据整合生成推荐结果返回给用户。
2、AI程序搭建
(1)前端页面
将static文件夹拷贝到resources下
(2)添加依赖
<dependencies>
<!-- HTTP 客户端库,用于发送 HTTP 请求(如调用 DeepSeek API) -->
<dependency>
<groupId>com.mashape.unirest</groupId>
<artifactId>unirest-java</artifactId>
<version>1.4.9</version>
</dependency>
<!-- Spring Boot 测试依赖,提供 JUnit、Mockito 等测试工具 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
</dependency>
<!-- Spring Boot Web 依赖,提供内嵌 Tomcat 和 Spring MVC 支持 -->
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<!-- Jackson JSON 序列化/反序列化工具,用于处理 JSON 数据 -->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
</dependency>
<!-- Jakarta JSON API,提供 JSON 处理的标准接口 -->
<dependency>
<groupId>jakarta.json</groupId>
<artifactId>jakarta.json-api</artifactId>
<version>2.0.1</version>
</dependency>
<!-- Jakarta JSON 实现(Glassfish),为上面的 API 提供具体实现 -->
<dependency>
<groupId>org.glassfish</groupId>
<artifactId>jakarta.json</artifactId>
<version>2.0.1</version>
</dependency>
<!-- Elasticsearch 低级 REST 客户端,用于与 ES 进行底层通信 -->
<dependency>
<groupId>org.elasticsearch.client</groupId>
<artifactId>elasticsearch-rest-client</artifactId>
<version>8.18.8</version>
</dependency>
<!-- 项目公共模块(travel-common-core),提供通用工具类和公共配置 -->
<dependency>
<groupId>cn.wolfcode.wolf2w</groupId>
<artifactId>travel-common-core</artifactId>
</dependency>
<!-- 搜索服务 API 模块,提供 ES 检索相关的接口和实体类 -->
<dependency>
<groupId>cn.wolfcode.wolf2w</groupId>
<artifactId>travel-api-search</artifactId>
<version>3.6.2</version>
</dependency>
<!-- Elasticsearch 高级 Java 客户端,提供类型安全的 ES 操作 API -->
<dependency>
<groupId>co.elastic.clients</groupId>
<artifactId>elasticsearch-java</artifactId>
<version>8.18.8</version>
</dependency>
</dependencies>
(3)添加application.yml配置文件
server:
port: 9096
spring:
application:
name: travel-ai
elasticsearch:
url: localhost
port: 9200
deepseek:
baseUrl: https://api.deepseek.com
model: deepseek-v4-pro
temperature: 1
thinking: enabled
top_p: 1
apiKey: 你的Apikey
(4)编写config.EsConfig配置类
@Configuration // 标记为 Spring 配置类,容器启动时会自动加载
public class EsConfig {
@Value("${elasticsearch.url}") // 从配置文件中读取 ES 的地址(如 localhost)
private String url;
@Value("${elasticsearch.port}") // 从配置文件中读取 ES 的端口号(如 9200)
private int port;
@Bean // 将该方法的返回值注册为 Spring 容器中的 Bean,供其他地方注入使用
public ElasticsearchClient elasticsearchClient() {
// 1. 创建 ES 低级 REST 客户端,配置连接地址、端口和协议(http)
RestClient restClient = RestClient.builder(
new HttpHost(url, port, "http")
).build();
// 2. 将低级客户端包装为高级传输层,并指定 Jackson 作为 JSON 序列化/反序列化工具
RestClientTransport transport = new RestClientTransport(restClient, new JacksonJsonpMapper());
// 3. 基于传输层创建 ES 高级 Java 客户端并返回
return new ElasticsearchClient(transport);
}
}
(5)编写AiApplication启动类
@SpringBootApplication(exclude = {DataSourceAutoConfiguration.class})
public class AiApplication {
public static void main(String[] args) {
SpringApplication.run(AiApplication.class, args);
}
@Bean
public RestTemplate restTemplate() {
return new RestTemplate();
}
}
3、私库数据查询
(1)新建dto.ChatRequest类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ChatRequest {
/**
* 用户输入的原始问题文本。
* 用于在 Elasticsearch 中进行语义检索,并作为 Prompt 的一部分发送给大模型生成回答。
*/
private String question;
/**
* 检索召回的文档数量(Top K)。
* 控制从私库中查询并返回给大模型作为参考上下文的条数。
* 数值越大参考信息越丰富,但可能引入噪声且消耗更多 Token。
*/
private Integer topK;
}
(2)新建dto.ChatResponse类
@Data
@AllArgsConstructor
@NoArgsConstructor
public class ChatResponse {
/**
* 大模型生成的最终回答文本。
* 后端结合检索到的私库内容(Context)和用户问题,通过大模型总结生成的自然语言回复。
*/
private String answer;
/**
* 检索到的参考策略/攻略列表(数据来源)。
* 对应 Elasticsearch 中查出的原始文档对象列表,用于前端展示“参考来源”或实现详情跳转。
*/
private List<StrategyEs> strategies;
}
(3)新建controller.AIController类
@RestController
public class AIController {
// 注入 Elasticsearch 客户端,用于连接和操作 ES 数据库
@Autowired
private ElasticsearchClient client;
/**
* 聊天接口:处理用户的提问请求
* @param request 包含用户问题(question)和检索数量(topK)的请求对象
* @return ChatResponse 包含AI回答(answer)和参考来源(strategies)的响应对象
*/
@RequestMapping("/chat")
public Object getAnswer(@RequestBody ChatRequest request) throws IOException {
// --- 第一步:从私库(ES)中检索相关数据 ---
// 执行搜索请求:
// 1. index("strategy"): 指定查询 "strategy" 索引(攻略库)
// 2. from(0).size(request.getTopK()): 从第0条开始,取前 TopK 条数据
// 3. query(...): 构建查询条件
SearchResponse<StrategyEs> resp = client.search(sh -> sh.index("strategy")
.from(0).size(request.getTopK())
.query(q -> q.multiMatch(m -> m.query(request.getQuestion())
// multiMatch: 多字段匹配查询
.fields("title", "subtitle", "summary") // 在标题、副标题、摘要中搜索
.analyzer("ik_max_word"))), // 使用 ik_max_word 分词器进行细粒度中文分词
StrategyEs.class);
// --- 第二步:解析 ES 返回结果 ---
// 获取命中的元数据信息
HitsMetadata<StrategyEs> hits = resp.hits();
// 获取命中总数(当前代码未使用,可用于日志或分页)
TotalHits total = hits.total();
// 获取具体的命中列表
List<Hit<StrategyEs>> hits2 = hits.hits();
// 初始化私库数据列表,用于存储提取出的攻略对象
List<StrategyEs> strategies = new ArrayList<>();
// 遍历命中结果,提取 _source 原始数据
for (Hit<StrategyEs> strategyEsHit : hits2) {
StrategyEs es = strategyEsHit.source(); // 获取文档源对象
strategies.add(es); // 添加到列表中
}
// --- 第三步:调用大模型生成回答 (待实现) ---
// todo 发送请求到deepseek处理
// 提示:此处应将 request.getQuestion() 和 strategies 拼接成 Prompt,
// 调用 DeepSeek API 获取生成的 answer 字符串。
// 封装响应对象
// 目前 answer 为占位符,strategies 为刚才检索到的参考数据
ChatResponse chatResponse = new ChatResponse("deepseek无结果", strategies);
return chatResponse;
}
}
4、实现deepseek的rag访问
(1)新建dto.OpenAIMessage类
/**
* 大模型对话消息对象,对应 OpenAI 兼容接口中 messages 数组的单个元素。
* 用于封装发送给大模型(如 DeepSeek)的每一条对话消息。
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OpenAIMessage {
/**
* 消息角色,用于标识这条消息的发送方。
* 常见取值:
* - "system":系统消息,用于设定 AI 的角色定位和行为规则
* - "user":用户消息,即用户输入的原始问题
* - "assistant":助手消息,即 AI 生成的回答
*/
private String role;
/**
* 消息内容,即该角色对应的具体文本。
* 例如:角色为 "user" 时,content 就是用户的问题;
* 角色为 "system" 时,content 就是私库数据或角色设定指令。
*/
private String content;
}
(2)新建dto.OpenAIChatRequest类
/**
* 大模型对话请求体对象,封装发送给 DeepSeek(OpenAI 兼容接口)的全部参数。
* 对应 /chat/completions 接口的 JSON 请求体结构。
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OpenAIChatRequest {
/**
* 模型名称,指定调用哪个大模型。
* 例如:"deepseek-chat"、"deepseek-reasoner" 等。
*/
private String model;
/**
* 对话消息列表,按顺序包含 system、user、assistant 等角色的消息。
* 大模型会根据消息列表的上下文来生成回答。
* 在 RAG 场景中,通常包含:私库数据(system) + 角色设定(system) + 用户问题(user)。
*/
private List<OpenAIMessage> messages;
/**
* 温度参数,控制模型输出的随机性,取值范围通常为 0~2。
* - 值越低(如 0.1):输出越确定、越保守,适合事实性问答
* - 值越高(如 0.9):输出越随机、越有创造性,适合创意写作
*/
private Double temperature;
/**
* 思考模式配置,DeepSeek R1 系列模型特有的参数。
* 通过 Map 传入,例如 {"type": "enabled"} 开启深度思考模式,
* 模型会先进行内部推理再输出最终回答,提升复杂问题的回答质量。
*/
private Map<String, Object> thinking;
/**
* Top-p(核采样)参数,控制模型从概率最高的前 p 比例的词中采样。
* 取值范围 0~1,例如 0.9 表示只从累计概率达到 90% 的候选词中选择。
* 与 temperature 配合使用,进一步控制输出的多样性和质量。
*/
private Double top_p;
}
(3)新建dto.OpenAIChatResponse类
/**
* 大模型对话响应体对象,封装 DeepSeek(OpenAI 兼容接口)返回的 JSON 响应。
* 对应 /chat/completions 接口的响应结构。
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public class OpenAIChatResponse {
/**
* 候选回答列表。
* 大模型可能返回多个候选结果(由请求参数 n 控制),通常只取第一个(index=0)。
* 每个 Choice 包含一条完整的助手回复消息。
*/
private List<Choice> choices;
/**
* 单个候选回答,对应 choices 数组中的一个元素。
* 内部包含大模型生成的具体消息内容。
*/
@Data
@AllArgsConstructor
@NoArgsConstructor
public static class Choice {
/**
* 大模型生成的回复消息。
* 其中 role 通常为 "assistant",content 就是 AI 生成的最终回答文本。
* 在 Controller 中通过 choice.getMessage().getContent() 提取回答内容。
*/
private OpenAIMessage message;
}
}
(4)实现发送请求到deepseek处理
/**
* AI 聊天控制器,实现 RAG(检索增强生成)流程。
* 核心逻辑:先从 ES 私库检索相关攻略 → 组装 Prompt → 调用 DeepSeek 生成回答 → 返回结果。
*/
@RestController
public class AIController {
// ========== 从配置文件中读取 DeepSeek 相关参数 ==========
@Value("${deepseek.baseUrl}")
private String baseUrl; // DeepSeek API 基础地址
@Value("${deepseek.model}")
private String model; // 模型名称,如 deepseek-chat
@Value("${deepseek.temperature}")
private Double temperature; // 温度参数,控制输出随机性
@Value("${deepseek.thinking}")
private String thinking; // 思考模式开关(enabled/disabled)
@Value("${deepseek.top_p}")
private Double top_p; // 核采样参数,控制候选词范围
@Value("${deepseek.apiKey}")
private String apiKey; // API 密钥,用于鉴权
// HTTP 客户端,用于发送请求到 DeepSeek 接口
@Autowired
private RestTemplate restTemplate;
// Elasticsearch 客户端,用于从私库中检索攻略数据
@Autowired
private ElasticsearchClient client;
/**
* 聊天主接口:接收用户提问,执行 RAG 流程并返回 AI 回答。
* @param request 包含用户问题(question)和检索数量(topK)的请求对象
* @return ChatResponse 包含 AI 回答(answer)和参考攻略列表(strategies)
*/
@RequestMapping("/chat")
public Object getAnswer(@RequestBody ChatRequest request) throws IOException {
// --- 第一步:从 ES 私库中检索与用户问题相关的攻略数据 ---
// 使用 multiMatch 多字段匹配查询,在 title、subtitle、summary 中搜索用户问题
// ik_max_word 分词器:对中文进行最细粒度切分,提高召回率
// from(0).size(topK):从第 0 条开始,取前 topK 条最相关的结果
SearchResponse<StrategyEs> resp = client.search(sh -> sh.index("strategy")
.from(0).size(request.getTopK())
.query(q -> q.multiMatch(m -> m.query(request.getQuestion())
.fields("title", "subtitle", "summary")
.analyzer("ik_max_word"))), StrategyEs.class);
// 解析 ES 返回的搜索结果
HitsMetadata<StrategyEs> hits = resp.hits(); // 命中元数据
TotalHits total = hits.total(); // 总命中数(可用于日志记录)
List<Hit<StrategyEs>> hits2 = hits.hits(); // 具体的命中列表
// 提取私库数据:遍历命中结果,取出每个文档的 _source 原始数据
List<StrategyEs> strategies = new ArrayList<>();
for (Hit<StrategyEs> strategyEsHit : hits2) {
StrategyEs es = strategyEsHit.source(); // 获取文档源对象
strategies.add(es); // 添加到攻略列表中
}
// --- 第二步:将用户问题和私库数据发送给 DeepSeek 生成回答 ---
String answer = askAI(request.getQuestion(), strategies);
// --- 第三步:封装响应并返回 ---
// answer:AI 生成的回答文本
// strategies:参考来源列表,供前端展示"引用了哪些攻略"
ChatResponse chatResponse = new ChatResponse(answer, strategies);
return chatResponse;
}
/**
* 调用 DeepSeek 大模型生成回答。
* 负责组装 Prompt(私库数据 + 角色设定 + 用户问题)并发送 HTTP 请求。
* @param question 用户的原始问题
* @param strategies 从 ES 检索到的攻略列表,作为 AI 的参考上下文
* @return AI 生成的回答文本
*/
private String askAI(String question, List<StrategyEs> strategies) {
// --- 1. 将私库攻略列表拼接为 JSON 字符串,作为 RAG 上下文 ---
StringJoiner joiner = new StringJoiner("\n\n"); // 用双换行分隔每条攻略
for (StrategyEs strategy : strategies) {
String strategyJson = JSON.toJSONString(strategy); // 将攻略对象序列化为 JSON
joiner.add(strategyJson);
}
String strategiesStr = joiner.toString(); // 拼接后的完整私库数据字符串
// --- 2. 组装对话消息列表(Prompt 的核心) ---
// 第一条 system 消息:注入私库数据,让 AI 基于这些参考资料回答
OpenAIMessage ragMessage = new OpenAIMessage("system", strategiesStr);
// 第二条 system 消息:设定 AI 的角色和行为规则
// 要求 AI 扮演资深导游,结合私库数据和互联网信息给出建议,回答控制在 200 字以内
OpenAIMessage sysMessage = new OpenAIMessage("system",
"你是一名资深导游,请根据用户问题结合私库数据,并根据互联网信息给出旅行建议。规划路线避坑指南及旅行推荐,回答小于200字");
// 第三条 user 消息:用户的原始问题
OpenAIMessage userMessage = new OpenAIMessage("user", question);
// 按顺序添加到消息列表中(顺序很重要,大模型会按顺序理解上下文)
List<OpenAIMessage> messages = new ArrayList<>();
messages.add(ragMessage); // 私库数据
messages.add(sysMessage); // 角色设定
messages.add(userMessage); // 用户提问
// --- 3. 构建请求体,设置模型参数 ---
OpenAIChatRequest requestBody = new OpenAIChatRequest();
requestBody.setMessages(messages); // 设置对话消息列表
requestBody.setModel(model); // 设置模型名称
requestBody.setTemperature(temperature); // 设置温度参数
// 设置思考模式(DeepSeek R1 特有)
Map<String, Object> map = new HashMap<>();
map.put("type", thinking); // "enabled" 开启 / "disabled" 关闭
requestBody.setThinking(map);
requestBody.setTop_p(top_p); // 设置核采样参数
// --- 4. 设置 HTTP 请求头 ---
HttpHeaders headers = new HttpHeaders();
headers.setContentType(MediaType.APPLICATION_JSON); // 请求体格式为 JSON
List<MediaType> mediaTypeList = new ArrayList<>();
mediaTypeList.add(MediaType.APPLICATION_JSON);
headers.setAccept(mediaTypeList); // 期望响应格式为 JSON
headers.setBearerAuth(apiKey); // 设置 API Key 鉴权
// --- 5. 发送 POST 请求到 DeepSeek 接口 ---
HttpEntity<OpenAIChatRequest> httpEntity = new HttpEntity<>(requestBody, headers);
String url = baseUrl + "/chat/completions"; // 拼接完整的 API 地址
// 发送请求并接收响应
OpenAIChatResponse response = restTemplate.postForObject(url, httpEntity, OpenAIChatResponse.class);
// --- 6. 解析响应,提取 AI 回答内容(防御性判空) ---
// 第一层判空:响应体、choices 列表是否为空
if (response == null || response.getChoices() == null || response.getChoices().isEmpty()) {
return "AI没有返回合适的结果";
}
// 取第一个候选回答(通常只有一个)
OpenAIChatResponse.Choice choice = response.getChoices().get(0);
// 第二层判空:choice、message、content 是否为空
if (choice == null || choice.getMessage() == null || choice.getMessage().getContent() == null) {
return "AI没有返回合适的结果";
}
// 返回 AI 生成的最终回答文本
return choice.getMessage().getContent();
}
}
浙公网安备 33010602011771号