ai对话修复
continuous-integration/drone Build is passing
Details
continuous-integration/drone Build is passing
Details
parent
accbc9e869
commit
1c61edb659
@ -0,0 +1,39 @@
|
|||||||
|
package com.example.venue_reservation_service.controller;
|
||||||
|
|
||||||
|
|
||||||
|
import com.example.venue_reservation_service.entity.ChatRequest;
|
||||||
|
import com.example.venue_reservation_service.service.ChatService;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.servlet.ServletOutputStream;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import jakarta.servlet.http.HttpServletResponse;
|
||||||
|
import org.springframework.http.MediaType;
|
||||||
|
import org.springframework.web.bind.annotation.*;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
|
||||||
|
|
||||||
|
@RestController
|
||||||
|
@RequestMapping("/chat")
|
||||||
|
@CrossOrigin
|
||||||
|
public class ChatController {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private ChatService chatService;
|
||||||
|
|
||||||
|
@PostMapping(value = "/ask", produces = MediaType.TEXT_PLAIN_VALUE)
|
||||||
|
public void chat(@RequestBody ChatRequest request, HttpServletRequest httpRequest, HttpServletResponse response) throws IOException {
|
||||||
|
|
||||||
|
// 设置流式响应头
|
||||||
|
response.setContentType("text/event-stream");
|
||||||
|
response.setCharacterEncoding(StandardCharsets.UTF_8.name());
|
||||||
|
response.setHeader("Cache-Control", "no-cache");
|
||||||
|
|
||||||
|
// 获取输出流
|
||||||
|
ServletOutputStream outputStream = response.getOutputStream();
|
||||||
|
|
||||||
|
// 处理流式响应
|
||||||
|
chatService.streamChatResponse(request, outputStream, httpRequest);
|
||||||
|
}
|
||||||
|
}
|
@ -1,66 +0,0 @@
|
|||||||
package com.example.venue_reservation_service.controller;
|
|
||||||
|
|
||||||
import com.example.venue_reservation_service.domain.VeMessage;
|
|
||||||
import com.example.venue_reservation_service.domain.VeSession;
|
|
||||||
import com.example.venue_reservation_service.service.VeMessageService;
|
|
||||||
import com.example.venue_reservation_service.service.VeSessionService;
|
|
||||||
import io.swagger.annotations.Api;
|
|
||||||
import io.swagger.annotations.ApiOperation;
|
|
||||||
import jakarta.annotation.Resource;
|
|
||||||
import org.springframework.http.HttpStatus;
|
|
||||||
import org.springframework.http.ResponseEntity;
|
|
||||||
import org.springframework.web.bind.annotation.*;
|
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
@RestController
|
|
||||||
@RequestMapping("/session")
|
|
||||||
@Api("ai会话管理模块")
|
|
||||||
@CrossOrigin
|
|
||||||
public class SessionController {
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private VeSessionService sessionService;
|
|
||||||
|
|
||||||
@Resource
|
|
||||||
private VeMessageService messageService;
|
|
||||||
|
|
||||||
@ApiOperation("创建新会话")
|
|
||||||
@PostMapping("/create")
|
|
||||||
public ResponseEntity<VeSession> createSession(@RequestBody VeSession session) {
|
|
||||||
VeSession savedSession = sessionService.saveSession(session);
|
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(savedSession);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOperation("获取用户会话历史")
|
|
||||||
@GetMapping("/list")
|
|
||||||
public ResponseEntity<List<VeSession>> getUserSessions(@RequestParam(name = "userId") Integer userId) {
|
|
||||||
List<VeSession> sessions = sessionService.getSessionsByUserId(userId);
|
|
||||||
return ResponseEntity.ok(sessions);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOperation("获取会话详情")
|
|
||||||
@GetMapping("/{sessionId}")
|
|
||||||
public ResponseEntity<VeSession> getSession(@PathVariable("sessionId") Integer sessionId) {
|
|
||||||
VeSession session = sessionService.getById(sessionId);
|
|
||||||
if (session != null) {
|
|
||||||
return ResponseEntity.ok(session);
|
|
||||||
} else {
|
|
||||||
return ResponseEntity.status(HttpStatus.NOT_FOUND).build();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOperation("获取指定会话的所有消息")
|
|
||||||
@GetMapping("/message/{sessionId}")
|
|
||||||
public ResponseEntity<List<VeMessage>> getSessionMessages(@PathVariable("sessionId") Integer sessionId) {
|
|
||||||
List<VeMessage> messages = messageService.getMessagesBySessionId(sessionId);
|
|
||||||
return ResponseEntity.ok(messages);
|
|
||||||
}
|
|
||||||
|
|
||||||
@ApiOperation("创建新消息")
|
|
||||||
@PostMapping("/message/create")
|
|
||||||
public ResponseEntity<VeMessage> createMessage(@RequestBody VeMessage message) {
|
|
||||||
VeMessage savedMessage = messageService.saveMessage(message);
|
|
||||||
return ResponseEntity.status(HttpStatus.CREATED).body(savedMessage);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,11 @@
|
|||||||
|
package com.example.venue_reservation_service.entity;
|
||||||
|
|
||||||
|
import lombok.Data;
|
||||||
|
@Data
|
||||||
|
public class ChatRequest {
|
||||||
|
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
private String sessionToken;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,14 @@
|
|||||||
|
package com.example.venue_reservation_service.entity;
|
||||||
|
|
||||||
|
import lombok.AllArgsConstructor;
|
||||||
|
import lombok.Data;
|
||||||
|
|
||||||
|
@Data
|
||||||
|
@AllArgsConstructor
|
||||||
|
public class ChatResponse {
|
||||||
|
|
||||||
|
private String content;
|
||||||
|
|
||||||
|
private String sessionToken;
|
||||||
|
|
||||||
|
}
|
@ -0,0 +1,10 @@
|
|||||||
|
package com.example.venue_reservation_service.service;
|
||||||
|
|
||||||
|
import com.example.venue_reservation_service.entity.ChatRequest;
|
||||||
|
import jakarta.servlet.ServletOutputStream;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
|
||||||
|
// ChatService.java
|
||||||
|
public interface ChatService {
|
||||||
|
void streamChatResponse(ChatRequest request, ServletOutputStream outputStream, HttpServletRequest httpRequest);
|
||||||
|
}
|
@ -0,0 +1,169 @@
|
|||||||
|
package com.example.venue_reservation_service.service.impl;
|
||||||
|
|
||||||
|
import com.example.venue_reservation_service.domain.VeMessage;
|
||||||
|
import com.example.venue_reservation_service.domain.VeSession;
|
||||||
|
import com.example.venue_reservation_service.entity.ChatRequest;
|
||||||
|
import com.example.venue_reservation_service.mapper.VeMessageMapper;
|
||||||
|
import com.example.venue_reservation_service.mapper.VeSessionMapper;
|
||||||
|
import com.example.venue_reservation_service.service.ChatService;
|
||||||
|
import com.fasterxml.jackson.databind.JsonNode;
|
||||||
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
||||||
|
import jakarta.annotation.Resource;
|
||||||
|
import jakarta.servlet.ServletOutputStream;
|
||||||
|
import jakarta.servlet.http.HttpServletRequest;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import org.apache.http.client.methods.CloseableHttpResponse;
|
||||||
|
import org.apache.http.client.methods.HttpPost;
|
||||||
|
import org.apache.http.entity.StringEntity;
|
||||||
|
import org.apache.http.impl.client.CloseableHttpClient;
|
||||||
|
import org.apache.http.impl.client.HttpClients;
|
||||||
|
import org.springframework.beans.factory.annotation.Value;
|
||||||
|
import org.springframework.stereotype.Service;
|
||||||
|
import org.springframework.transaction.annotation.Transactional;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.time.LocalDateTime;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.UUID;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
|
@Service
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class ChatServiceImpl implements ChatService {
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private VeSessionMapper sessionMapper;
|
||||||
|
|
||||||
|
@Resource
|
||||||
|
private VeMessageMapper messageMapper;
|
||||||
|
@Value("${internai.api-url}")
|
||||||
|
private String apiUrl;
|
||||||
|
@Value("${internai.api-key}")
|
||||||
|
private String apiKey;
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Transactional
|
||||||
|
public void streamChatResponse(ChatRequest request, ServletOutputStream outputStream, HttpServletRequest httpRequest) {
|
||||||
|
// 获取或创建会话
|
||||||
|
VeSession session = getOrCreateSession(request.getSessionToken(), httpRequest);
|
||||||
|
String sessionToken = session.getSessionToken();
|
||||||
|
|
||||||
|
// 保存用户消息
|
||||||
|
saveMessage(request.getContent(), "user", session.getId());
|
||||||
|
|
||||||
|
// 构建完整对话历史
|
||||||
|
List<Map<String, String>> messages = buildMessages(session.getId());
|
||||||
|
|
||||||
|
// 调用第三方API
|
||||||
|
streamApiResponse(messages, outputStream, sessionToken, session.getId());
|
||||||
|
|
||||||
|
// 更新会话结束时间
|
||||||
|
updateSessionEndTime(session);
|
||||||
|
}
|
||||||
|
|
||||||
|
private VeSession getOrCreateSession(String sessionToken, HttpServletRequest request) {
|
||||||
|
if (sessionToken != null && !sessionToken.isEmpty()) {
|
||||||
|
VeSession session = sessionMapper.selectBySessionToken(sessionToken);
|
||||||
|
if (session != null) {
|
||||||
|
return session;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 创建新会话
|
||||||
|
VeSession newSession = new VeSession();
|
||||||
|
newSession.setUserIp(request.getRemoteAddr());
|
||||||
|
newSession.setStartTime(LocalDateTime.now());
|
||||||
|
newSession.setSessionToken(UUID.randomUUID().toString());
|
||||||
|
newSession.setTitle("New Chat");
|
||||||
|
newSession.setUserId(1);
|
||||||
|
sessionMapper.insert(newSession);
|
||||||
|
return newSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void saveMessage(String content, String sender, Integer sessionId) {
|
||||||
|
VeMessage message = new VeMessage();
|
||||||
|
message.setSessionId(sessionId);
|
||||||
|
message.setContent(content);
|
||||||
|
message.setSender(sender);
|
||||||
|
message.setCreatedAt(LocalDateTime.now());
|
||||||
|
|
||||||
|
messageMapper.insert(message);
|
||||||
|
}
|
||||||
|
|
||||||
|
private List<Map<String, String>> buildMessages(Integer sessionId) {
|
||||||
|
List<VeMessage> history = messageMapper.selectMessagesBySessionId(sessionId);
|
||||||
|
|
||||||
|
return history.stream().map(msg -> {
|
||||||
|
Map<String, String> messageMap = new HashMap<>();
|
||||||
|
messageMap.put("role", msg.getSender().equals("user") ? "user" : "assistant");
|
||||||
|
messageMap.put("content", msg.getContent());
|
||||||
|
return messageMap;
|
||||||
|
}).collect(Collectors.toList());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void streamApiResponse(List<Map<String, String>> messages,
|
||||||
|
ServletOutputStream outputStream,
|
||||||
|
String sessionToken,
|
||||||
|
Integer sessionId) {
|
||||||
|
|
||||||
|
StringBuilder assistantResponse = new StringBuilder();
|
||||||
|
|
||||||
|
try (CloseableHttpClient httpClient = HttpClients.createDefault()) {
|
||||||
|
HttpPost httpPost = new HttpPost(apiUrl);
|
||||||
|
httpPost.setHeader("Authorization", "Bearer " + apiKey);
|
||||||
|
httpPost.setHeader("Content-Type", "application/json");
|
||||||
|
|
||||||
|
// 构建请求体
|
||||||
|
Map<String, Object> requestBody = new HashMap<>();
|
||||||
|
requestBody.put("model", "internlm3-latest");
|
||||||
|
requestBody.put("messages", messages);
|
||||||
|
requestBody.put("temperature", 0.8);
|
||||||
|
requestBody.put("top_p", 0.9);
|
||||||
|
requestBody.put("stream", true);
|
||||||
|
|
||||||
|
httpPost.setEntity(new StringEntity(new ObjectMapper().writeValueAsString(requestBody)));
|
||||||
|
|
||||||
|
try (CloseableHttpResponse response = httpClient.execute(httpPost)) {
|
||||||
|
InputStream contentStream = response.getEntity().getContent();
|
||||||
|
try (BufferedReader reader = new BufferedReader(new InputStreamReader(contentStream))) {
|
||||||
|
String line;
|
||||||
|
while ((line = reader.readLine()) != null) {
|
||||||
|
if (line.startsWith("data: ")) {
|
||||||
|
String json = line.substring(6).trim();
|
||||||
|
if (json.equals("[DONE]")) break;
|
||||||
|
|
||||||
|
JsonNode data = new ObjectMapper().readTree(json);
|
||||||
|
JsonNode choices = data.path("choices");
|
||||||
|
if (!choices.isEmpty()) {
|
||||||
|
String chunk = choices.get(0).path("delta").path("content").asText();
|
||||||
|
if (!chunk.isEmpty()) {
|
||||||
|
assistantResponse.append(chunk);
|
||||||
|
outputStream.write(chunk.getBytes(StandardCharsets.UTF_8));
|
||||||
|
outputStream.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 保存助手回复
|
||||||
|
if (!assistantResponse.isEmpty()) {
|
||||||
|
saveMessage(assistantResponse.toString(), "assistant", sessionId);
|
||||||
|
}
|
||||||
|
|
||||||
|
} catch (Exception e) {
|
||||||
|
throw new RuntimeException("API call failed", e);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateSessionEndTime(VeSession session) {
|
||||||
|
session.setEndTime(LocalDateTime.now());
|
||||||
|
sessionMapper.updateById(session);
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue