diff --git a/pom.xml b/pom.xml
index d93520b..ca6086c 100644
--- a/pom.xml
+++ b/pom.xml
@@ -16,6 +16,7 @@
21
1.0.3
3.19.0
+ 1.18.40
@@ -46,6 +47,11 @@
commons-lang3
${commons-lang3.version}
+
+ org.projectlombok
+ lombok
+ ${lombok.version}
+
diff --git a/src/main/java/com/hanserwei/airobot/advisor/MyLoggerAdvisor.java b/src/main/java/com/hanserwei/airobot/advisor/MyLoggerAdvisor.java
new file mode 100644
index 0000000..dd85e1d
--- /dev/null
+++ b/src/main/java/com/hanserwei/airobot/advisor/MyLoggerAdvisor.java
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/hanserwei/airobot/config/ChatClientConfig.java b/src/main/java/com/hanserwei/airobot/config/ChatClientConfig.java
new file mode 100644
index 0000000..5c10c5c
--- /dev/null
+++ b/src/main/java/com/hanserwei/airobot/config/ChatClientConfig.java
@@ -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();
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/hanserwei/airobot/controller/ChatClientController.java b/src/main/java/com/hanserwei/airobot/controller/ChatClientController.java
new file mode 100644
index 0000000..602d7b7
--- /dev/null
+++ b/src/main/java/com/hanserwei/airobot/controller/ChatClientController.java
@@ -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 generateStream(@RequestParam(value = "message", defaultValue = "你是谁?") String message) {
+ return chatClient.prompt()
+ .user(message) // 提示词
+ .stream() // 流式输出
+ .content();
+
+ }
+}
\ No newline at end of file
diff --git a/src/main/java/com/hanserwei/airobot/controller/DeepSeekR1ChatController.java b/src/main/java/com/hanserwei/airobot/controller/DeepSeekR1ChatController.java
index ff4ced5..8fdce1e 100644
--- a/src/main/java/com/hanserwei/airobot/controller/DeepSeekR1ChatController.java
+++ b/src/main/java/com/hanserwei/airobot/controller/DeepSeekR1ChatController.java
@@ -12,6 +12,9 @@ import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.RestController;
import reactor.core.publisher.Flux;
+import java.util.Objects;
+import java.util.concurrent.atomic.AtomicBoolean;
+
@RestController
@RequestMapping("/v1/ai")
public class DeepSeekR1ChatController {
@@ -29,6 +32,9 @@ public class DeepSeekR1ChatController {
// 构建提示词
Prompt prompt = new Prompt(new UserMessage(message));
+ // 使用原子布尔值跟踪分隔线状态(每个请求独立)
+ AtomicBoolean needSeparator = new AtomicBoolean(true);
+
// 流式输出
return chatModel.stream(prompt)
.mapNotNull(chatResponse -> {
@@ -39,11 +45,27 @@ public class DeepSeekR1ChatController {
// 推理结束后的正式回答
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", "
") : rawContent;
+
+ // 在正式回答内容之前,添加一个分隔线
+ if (isTextResponse
+ && needSeparator.compareAndSet(true, false)) {
+ processed = "
" + processed; // 使用 HTML 的
标签实现
+ }
+
+ return processed;
});
-
-
-
}
}
\ No newline at end of file
diff --git a/src/main/resources/application.yml b/src/main/resources/application.yml
index 3be31b2..c3f06da 100644
--- a/src/main/resources/application.yml
+++ b/src/main/resources/application.yml
@@ -8,10 +8,13 @@ spring:
base-url: https://api.deepseek.com # DeepSeek 的请求 URL, 可不填,默认值为 api.deepseek.com
chat:
options:
- model: deepseek-reasoner # 使用哪个模型
+ model: deepseek-chat # 使用哪个模型
temperature: 0.8 # 温度值
jasypt:
encryptor:
password: ${jasypt.encryptor.password}
algorithm: PBEWithHMACSHA512AndAES_256
- iv-generator-classname: org.jasypt.iv.RandomIvGenerator
\ No newline at end of file
+ iv-generator-classname: org.jasypt.iv.RandomIvGenerator
+logging:
+ level:
+ org.springframework.ai.chat.client.advisor: debug
\ No newline at end of file