AI回答结果流式返回给前台。

This commit is contained in:
chenxudong 2025-04-01 15:41:54 +08:00
parent bd40c34382
commit 3ef43b36e9
5 changed files with 98 additions and 17 deletions

View File

@ -46,12 +46,19 @@
<groupId>org.springframework.boot</groupId> <groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId> <artifactId>spring-boot-starter-test</artifactId>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-webflux</artifactId>
</dependency>
<dependency> <dependency>
<groupId>cn.hutool</groupId> <groupId>cn.hutool</groupId>
<artifactId>hutool-all</artifactId> <artifactId>hutool-all</artifactId>
<version>5.8.22</version> <version>5.8.22</version>
</dependency> </dependency>
<dependency>
<groupId>org.springframework.ai</groupId>
<artifactId>spring-ai-pdf-document-reader</artifactId>
</dependency>
</dependencies> </dependencies>
<build> <build>

View File

@ -2,13 +2,16 @@ package cn.szsd.ai.chat.controller;
import cn.szsd.ai.chat.pojo.ElectromagneticResult; import cn.szsd.ai.chat.pojo.ElectromagneticResult;
import cn.szsd.ai.chat.pojo.QueryDTO; import cn.szsd.ai.chat.pojo.QueryDTO;
import cn.szsd.ai.chat.pojo.UploadDTO;
import cn.szsd.ai.chat.service.ChatService; import cn.szsd.ai.chat.service.ChatService;
import cn.szsd.ai.chat.service.ElectromagneticResultUtil; import cn.szsd.ai.chat.service.ElectromagneticResultUtil;
import cn.szsd.ai.chat.utils.ChatTaskThread; import cn.szsd.ai.chat.utils.ChatTaskThread;
import cn.szsd.ai.chat.utils.ChatTaskThread1;
import cn.szsd.ai.chat.utils.ThreadUtil; import cn.szsd.ai.chat.utils.ThreadUtil;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import org.springframework.http.MediaType;
import org.springframework.web.bind.annotation.*; import org.springframework.web.bind.annotation.*;
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Flux;
import java.util.concurrent.*; import java.util.concurrent.*;
import java.util.logging.Logger; import java.util.logging.Logger;
@ -18,12 +21,11 @@ import java.util.logging.Logger;
public class ChatController { public class ChatController {
Logger log = Logger.getLogger(ChatController.class.getName()); Logger log = Logger.getLogger(ChatController.class.getName());
@Resource @Resource
private ChatService chatService; private ChatService chatService;
@PostMapping("/chat") @PostMapping("/chat")
public ElectromagneticResult<String> test(@RequestBody QueryDTO queryDTO) throws Exception { public ElectromagneticResult<String> chat(@RequestBody QueryDTO queryDTO) throws Exception {
log.info("question is --->" + queryDTO.getMsg()); log.info("question is --->" + queryDTO.getMsg());
ChatTaskThread chatTaskThread = new ChatTaskThread(chatService, queryDTO); ChatTaskThread chatTaskThread = new ChatTaskThread(chatService, queryDTO);
Future<String> future = ThreadUtil.getThreadPool().submit(chatTaskThread); Future<String> future = ThreadUtil.getThreadPool().submit(chatTaskThread);
@ -32,10 +34,16 @@ public class ChatController {
return ElectromagneticResultUtil.success(res); return ElectromagneticResultUtil.success(res);
} }
@PostMapping("/upload") @PostMapping(path = "/chatStream", produces = MediaType.TEXT_EVENT_STREAM_VALUE)
public ElectromagneticResult<?> upload(@RequestBody UploadDTO uploadDTO) { public Flux<String> chatStream(@RequestBody QueryDTO queryDTO) throws ExecutionException, InterruptedException {
chatService.add(uploadDTO.getContent()); ChatTaskThread1 chatTaskThread = new ChatTaskThread1(chatService, queryDTO);
return ElectromagneticResultUtil.success(""); Future<Flux<String>> future = ThreadUtil.getThreadPool().submit(chatTaskThread);
return future.get();
}
@RequestMapping("/upload")
public ElectromagneticResult<?> upload(@RequestParam("file") MultipartFile file) throws Exception {
return chatService.addFromUpload(file);
} }
} }

View File

@ -1,19 +1,27 @@
package cn.szsd.ai.chat.service; package cn.szsd.ai.chat.service;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.util.IdUtil;
import cn.hutool.core.util.StrUtil;
import cn.hutool.crypto.digest.DigestUtil;
import cn.szsd.ai.chat.pojo.ElectromagneticResult;
import cn.szsd.ai.chat.pojo.QueryDTO; import cn.szsd.ai.chat.pojo.QueryDTO;
import jakarta.annotation.Resource; import jakarta.annotation.Resource;
import lombok.extern.slf4j.Slf4j; import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.client.ChatClient; import org.springframework.ai.chat.client.ChatClient;
import org.springframework.ai.chat.client.advisor.*; import org.springframework.ai.chat.client.advisor.*;
import org.springframework.ai.chat.messages.UserMessage;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.document.Document; import org.springframework.ai.document.Document;
import org.springframework.ai.ollama.OllamaChatModel; import org.springframework.ai.ollama.OllamaChatModel;
import org.springframework.ai.reader.pdf.PagePdfDocumentReader;
import org.springframework.ai.vectorstore.VectorStore; import org.springframework.ai.vectorstore.VectorStore;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.stereotype.Service; import org.springframework.stereotype.Service;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.multipart.MultipartFile;
import reactor.core.publisher.Flux; import reactor.core.publisher.Flux;
import java.io.File;
import java.nio.charset.Charset;
import java.util.List; import java.util.List;
import java.util.stream.Collectors; import java.util.stream.Collectors;
import java.util.stream.Stream; import java.util.stream.Stream;
@ -34,11 +42,44 @@ public class ChatService {
@Resource @Resource
private QuestionAnswerAdvisor questionAnswerAdvisor; private QuestionAnswerAdvisor questionAnswerAdvisor;
@Value("file.md5RecordPath")
private String uploadFileMd5RecordPath;
@PostMapping
public void init() {
if (!FileUtil.exist(uploadFileMd5RecordPath)) {
FileUtil.touch(uploadFileMd5RecordPath);
}
}
public void add(String content) { public void add(String content) {
List<Document> documents = Stream.of(content).map(Document::new).collect(Collectors.toList()); List<Document> documents = Stream.of(content).map(Document::new).collect(Collectors.toList());
vectorStore.write(documents); vectorStore.write(documents);
} }
public ElectromagneticResult<?> addFromUpload(MultipartFile file) throws Exception{
String fileType = FileUtil.extName(file.getOriginalFilename());
if (!StrUtil.equals(fileType, "pdf")) {
return ElectromagneticResultUtil.fail("当前仅支持pdf格式文件");
}
String fileMd5 = DigestUtil.md5Hex(file.getInputStream());
List<String> lines = FileUtil.readLines(uploadFileMd5RecordPath, Charset.defaultCharset());
if (lines.contains(fileMd5)) {
return ElectromagneticResultUtil.success(fileMd5);
}
String pdfTmpPath = FileUtil.getParent(uploadFileMd5RecordPath, 1) + File.separator + IdUtil.fastSimpleUUID() + "." + fileType;
FileUtil.writeFromStream(file.getInputStream(), pdfTmpPath);
PagePdfDocumentReader pagePdfDocumentReader = new PagePdfDocumentReader(pdfTmpPath);
List<Document> documents = pagePdfDocumentReader.read();
vectorStore.write(documents);
lines.add(fileMd5);
FileUtil.writeLines(lines, uploadFileMd5RecordPath, Charset.defaultCharset());
FileUtil.del(pdfTmpPath);
return ElectromagneticResultUtil.success(fileMd5);
}
public String chat(QueryDTO queryDTO) { public String chat(QueryDTO queryDTO) {
log.info("Start call model to answer"); log.info("Start call model to answer");
@ -52,8 +93,7 @@ public class ChatService {
.content(); .content();
} }
public Flux<ChatResponse> chat1(String msg, String userId) { public Flux<String> chatStream(String msg) {
Prompt prompt = new Prompt(new UserMessage(msg)); return ChatClient.builder(model).defaultAdvisors(messageChatMemoryAdvisor, questionAnswerAdvisor).build().prompt(msg).stream().content();
return this.model.stream(prompt);
} }
} }

View File

@ -0,0 +1,23 @@
package cn.szsd.ai.chat.utils;
import cn.szsd.ai.chat.pojo.QueryDTO;
import cn.szsd.ai.chat.service.ChatService;
import lombok.AllArgsConstructor;
import lombok.NoArgsConstructor;
import reactor.core.publisher.Flux;
import java.util.concurrent.Callable;
@AllArgsConstructor
@NoArgsConstructor
public class ChatTaskThread1 implements Callable<Flux<String>> {
private ChatService chatService;
private QueryDTO queryDTO;
@Override
public Flux<String> call() throws Exception {
return chatService.chatStream(queryDTO.getMsg());
}
}

View File

@ -1,8 +1,8 @@
spring: spring:
elasticsearch: elasticsearch:
password: LCFLr9iQx*mKtwfjJj40 password: _oO*of_l-b+4mrzXo6B0
username: elastic username: elastic
uris: http://127.0.0.1:9200 uris: http://139.196.179.195:9200
ai: ai:
ollama: ollama:
@ -28,4 +28,7 @@ spring:
request-timeout: 3600000 request-timeout: 3600000
server: server:
port: 8186 port: 8186
file:
md5RecordPath: /workspace/app/ai/ele-ai/record.txt