feat(ai): 新增 ChatClient 配置与控制器
- 添加 ChatClientConfig 配置类,初始化 ChatClient 并配置系统提示和顾问 - 创建 ChatClientController 控制器,支持普通对话与流式对话接口- 引入 lombok依赖并添加 MyLoggerAdvisor 日志顾问实现 - 调整 DeepSeekR1ChatController,优化流式输出内容处理逻辑 - 更新 application.yml 中默认模型名称及日志级别配置
This commit is contained in:
6
pom.xml
6
pom.xml
@@ -16,6 +16,7 @@
|
|||||||
<java.version>21</java.version>
|
<java.version>21</java.version>
|
||||||
<spring-ai.version>1.0.3</spring-ai.version>
|
<spring-ai.version>1.0.3</spring-ai.version>
|
||||||
<commons-lang3.version>3.19.0</commons-lang3.version>
|
<commons-lang3.version>3.19.0</commons-lang3.version>
|
||||||
|
<lombok.version>1.18.40</lombok.version>
|
||||||
</properties>
|
</properties>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
<dependency>
|
<dependency>
|
||||||
@@ -46,6 +47,11 @@
|
|||||||
<artifactId>commons-lang3</artifactId>
|
<artifactId>commons-lang3</artifactId>
|
||||||
<version>${commons-lang3.version}</version>
|
<version>${commons-lang3.version}</version>
|
||||||
</dependency>
|
</dependency>
|
||||||
|
<dependency>
|
||||||
|
<groupId>org.projectlombok</groupId>
|
||||||
|
<artifactId>lombok</artifactId>
|
||||||
|
<version>${lombok.version}</version>
|
||||||
|
</dependency>
|
||||||
</dependencies>
|
</dependencies>
|
||||||
<dependencyManagement>
|
<dependencyManagement>
|
||||||
<dependencies>
|
<dependencies>
|
||||||
|
|||||||
@@ -0,0 +1,30 @@
|
|||||||
|
package com.hanserwei.airobot.advisor;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.springframework.ai.chat.client.ChatClientRequest;
|
||||||
|
import org.springframework.ai.chat.client.ChatClientResponse;
|
||||||
|
import org.springframework.ai.chat.client.advisor.api.CallAdvisor;
|
||||||
|
import org.springframework.ai.chat.client.advisor.api.CallAdvisorChain;
|
||||||
|
|
||||||
|
@Slf4j
|
||||||
|
public class MyLoggerAdvisor implements CallAdvisor {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ChatClientResponse adviseCall(ChatClientRequest chatClientRequest, CallAdvisorChain callAdvisorChain) {
|
||||||
|
log.info("## 请求入参: {}", chatClientRequest);
|
||||||
|
ChatClientResponse chatClientResponse = callAdvisorChain.nextCall(chatClientRequest);
|
||||||
|
log.info("## 请求出参: {}", chatClientResponse);
|
||||||
|
return chatClientResponse;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getOrder() {
|
||||||
|
return 1; // order 值越小,越先执行
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
// 获取类名称
|
||||||
|
return this.getClass().getSimpleName();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,27 @@
|
|||||||
|
package com.hanserwei.airobot.config;
|
||||||
|
|
||||||
|
import com.hanserwei.airobot.advisor.MyLoggerAdvisor;
|
||||||
|
import org.springframework.ai.chat.client.ChatClient;
|
||||||
|
import org.springframework.ai.chat.client.advisor.SimpleLoggerAdvisor;
|
||||||
|
import org.springframework.ai.deepseek.DeepSeekChatModel;
|
||||||
|
import org.springframework.context.annotation.Bean;
|
||||||
|
import org.springframework.context.annotation.Configuration;
|
||||||
|
|
||||||
|
@Configuration
|
||||||
|
public class ChatClientConfig {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 初始化 ChatClient 客户端
|
||||||
|
*
|
||||||
|
* @param chatModel 模型
|
||||||
|
* @return ChatClient
|
||||||
|
*/
|
||||||
|
@Bean
|
||||||
|
public ChatClient chatClient(DeepSeekChatModel chatModel) {
|
||||||
|
return ChatClient.builder(chatModel)
|
||||||
|
.defaultSystem("请你扮演一名犬小哈 Java 项目实战专栏的客服人员")
|
||||||
|
.defaultAdvisors(new SimpleLoggerAdvisor(),
|
||||||
|
new MyLoggerAdvisor())
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
package com.hanserwei.airobot.controller;
|
||||||
|
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import org.springframework.ai.chat.client.ChatClient;
|
||||||
|
import org.springframework.web.bind.annotation.GetMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestMapping;
|
||||||
|
import org.springframework.web.bind.annotation.RequestParam;
|
||||||
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/v2/ai")
|
||||||
|
public class ChatClientController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ChatClient chatClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 普通对话
|
||||||
|
*
|
||||||
|
* @param message 对话输入内容
|
||||||
|
* @return 对话结果
|
||||||
|
*/
|
||||||
|
@GetMapping("/generate")
|
||||||
|
public String generate(@RequestParam(value = "message", defaultValue = "你是谁?") String message) {
|
||||||
|
// 一次性返回结果
|
||||||
|
return chatClient.prompt()
|
||||||
|
.user(message)
|
||||||
|
.call()
|
||||||
|
.content();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* 流式对话
|
||||||
|
* @param message 对话输入内容
|
||||||
|
* @return 对话结果
|
||||||
|
*/
|
||||||
|
@GetMapping(value = "/generateStream", produces = "text/html;charset=utf-8")
|
||||||
|
public Flux<String> generateStream(@RequestParam(value = "message", defaultValue = "你是谁?") String message) {
|
||||||
|
return chatClient.prompt()
|
||||||
|
.user(message) // 提示词
|
||||||
|
.stream() // 流式输出
|
||||||
|
.content();
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -12,6 +12,9 @@ import org.springframework.web.bind.annotation.RequestParam;
|
|||||||
import org.springframework.web.bind.annotation.RestController;
|
import org.springframework.web.bind.annotation.RestController;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
|
||||||
|
import java.util.Objects;
|
||||||
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
|
||||||
@RestController
|
@RestController
|
||||||
@RequestMapping("/v1/ai")
|
@RequestMapping("/v1/ai")
|
||||||
public class DeepSeekR1ChatController {
|
public class DeepSeekR1ChatController {
|
||||||
@@ -29,6 +32,9 @@ public class DeepSeekR1ChatController {
|
|||||||
// 构建提示词
|
// 构建提示词
|
||||||
Prompt prompt = new Prompt(new UserMessage(message));
|
Prompt prompt = new Prompt(new UserMessage(message));
|
||||||
|
|
||||||
|
// 使用原子布尔值跟踪分隔线状态(每个请求独立)
|
||||||
|
AtomicBoolean needSeparator = new AtomicBoolean(true);
|
||||||
|
|
||||||
// 流式输出
|
// 流式输出
|
||||||
return chatModel.stream(prompt)
|
return chatModel.stream(prompt)
|
||||||
.mapNotNull(chatResponse -> {
|
.mapNotNull(chatResponse -> {
|
||||||
@@ -39,11 +45,27 @@ public class DeepSeekR1ChatController {
|
|||||||
// 推理结束后的正式回答
|
// 推理结束后的正式回答
|
||||||
String text = deepSeekAssistantMessage.getText();
|
String text = deepSeekAssistantMessage.getText();
|
||||||
|
|
||||||
|
// 是否是正式回答
|
||||||
|
boolean isTextResponse = false;
|
||||||
// 若推理内容有值,则响应推理内容,否则,说明推理结束了,响应正式回答
|
// 若推理内容有值,则响应推理内容,否则,说明推理结束了,响应正式回答
|
||||||
return StringUtils.isNotBlank(reasoningContent) ? reasoningContent : text;
|
String rawContent;
|
||||||
|
if (Objects.isNull(text)) {
|
||||||
|
rawContent = reasoningContent;
|
||||||
|
} else {
|
||||||
|
rawContent = text;
|
||||||
|
isTextResponse = true; // 标记为正式回答
|
||||||
|
}
|
||||||
|
|
||||||
|
// 处理换行
|
||||||
|
String processed = StringUtils.isNotBlank(rawContent) ? rawContent.replace("\n", "<br>") : rawContent;
|
||||||
|
|
||||||
|
// 在正式回答内容之前,添加一个分隔线
|
||||||
|
if (isTextResponse
|
||||||
|
&& needSeparator.compareAndSet(true, false)) {
|
||||||
|
processed = "<hr>" + processed; // 使用 HTML 的 <hr> 标签实现
|
||||||
|
}
|
||||||
|
|
||||||
|
return processed;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -8,10 +8,13 @@ spring:
|
|||||||
base-url: https://api.deepseek.com # DeepSeek 的请求 URL, 可不填,默认值为 api.deepseek.com
|
base-url: https://api.deepseek.com # DeepSeek 的请求 URL, 可不填,默认值为 api.deepseek.com
|
||||||
chat:
|
chat:
|
||||||
options:
|
options:
|
||||||
model: deepseek-reasoner # 使用哪个模型
|
model: deepseek-chat # 使用哪个模型
|
||||||
temperature: 0.8 # 温度值
|
temperature: 0.8 # 温度值
|
||||||
jasypt:
|
jasypt:
|
||||||
encryptor:
|
encryptor:
|
||||||
password: ${jasypt.encryptor.password}
|
password: ${jasypt.encryptor.password}
|
||||||
algorithm: PBEWithHMACSHA512AndAES_256
|
algorithm: PBEWithHMACSHA512AndAES_256
|
||||||
iv-generator-classname: org.jasypt.iv.RandomIvGenerator
|
iv-generator-classname: org.jasypt.iv.RandomIvGenerator
|
||||||
|
logging:
|
||||||
|
level:
|
||||||
|
org.springframework.ai.chat.client.advisor: debug
|
||||||
Reference in New Issue
Block a user