From fd94a1772f4b56555b7ed17d0270335d27ba01d2 Mon Sep 17 00:00:00 2001 From: Yzm Date: Mon, 30 Jun 2025 22:03:07 +0800 Subject: [PATCH] =?UTF-8?q?feat(chat):=20=E9=9B=86=E6=88=90=20FastGPT=20?= =?UTF-8?q?=E8=81=8A=E5=A4=A9=E6=A8=A1=E5=9E=8B-=20=E5=9C=A8=20ChatModeTyp?= =?UTF-8?q?e=20=E6=9E=9A=E4=B8=BE=E4=B8=AD=E6=B7=BB=E5=8A=A0=20FASTGPT=20?= =?UTF-8?q?=E9=80=89=E9=A1=B9-=20=E6=96=B0=E5=A2=9E=20FastGPT=20=E7=9B=B8?= =?UTF-8?q?=E5=85=B3=E7=9A=84=E5=AE=9E=E4=BD=93=E7=B1=BB=E5=92=8C=E8=AF=B7?= =?UTF-8?q?=E6=B1=82=E5=93=8D=E5=BA=94=E7=B1=BB=20-=20=E5=AE=9E=E7=8E=B0?= =?UTF-8?q?=20FastGPT=E8=81=8A=E5=A4=A9=E6=9C=8D=E5=8A=A1=E6=8E=A5?= =?UTF-8?q?=E5=8F=A3=20-=20=E6=B7=BB=E5=8A=A0=20FastGPT=20SSE=20=E4=BA=8B?= =?UTF-8?q?=E4=BB=B6=E7=9B=91=E5=90=AC=E5=99=A8?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../entity/chat/FastGPTAnswerResponse.java | 15 ++++ .../chat/entity/chat/FastGPTChatChoice.java | 25 +++++++ .../entity/chat/FastGPTChatCompletion.java | 41 +++++++++++ .../common/chat/entity/chat/Variables.java | 20 ++++++ .../org/ruoyi/chat/enums/ChatModeType.java | 4 +- .../FastGPTSSEEventSourceListener.java | 70 +++++++++++++++++++ .../service/chat/impl/FastGPTServiceImpl.java | 52 ++++++++++++++ 7 files changed, 226 insertions(+), 1 deletion(-) create mode 100644 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTAnswerResponse.java create mode 100644 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTChatChoice.java create mode 100644 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTChatCompletion.java create mode 100644 ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Variables.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/listener/FastGPTSSEEventSourceListener.java create mode 100644 ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/FastGPTServiceImpl.java diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTAnswerResponse.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTAnswerResponse.java new file mode 100644 index 00000000..856bec81 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTAnswerResponse.java @@ -0,0 +1,15 @@ +package org.ruoyi.common.chat.entity.chat; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import lombok.Data; + +import java.util.List; +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FastGPTAnswerResponse { + private String id; + private String object; + private long created; + private String model; + private List choices; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTChatChoice.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTChatChoice.java new file mode 100644 index 00000000..11e9280b --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTChatChoice.java @@ -0,0 +1,25 @@ +package org.ruoyi.common.chat.entity.chat; + +import com.fasterxml.jackson.annotation.JsonIgnoreProperties; +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.Data; + +import java.io.Serializable; + +@Data +@JsonIgnoreProperties(ignoreUnknown = true) +public class FastGPTChatChoice implements Serializable { + private long index; + /** + * 请求参数stream为true返回是delta + */ + @JsonProperty("delta") + private Message delta; + /** + * 请求参数stream为false返回是message + */ + @JsonProperty("message") + private Message message; + @JsonProperty("finish_reason") + private String finishReason; +} \ No newline at end of file diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTChatCompletion.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTChatCompletion.java new file mode 100644 index 00000000..8e8dc933 --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/FastGPTChatCompletion.java @@ -0,0 +1,41 @@ +package org.ruoyi.common.chat.entity.chat; + +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.NoArgsConstructor; +import lombok.experimental.SuperBuilder; + +import java.io.Serializable; + +@Data +@SuperBuilder +@AllArgsConstructor +@NoArgsConstructor +public class FastGPTChatCompletion extends ChatCompletion implements Serializable { + + /** + * 是否使用FastGPT提供的上下文 + */ + private String chatId; + + + /** + * 是否返回详细信息;stream模式下会通过event进行区分,非stream模式结果保存在responseData中. + */ + private boolean detail; + + + /** + * 运行时变量 + * 模块变量,一个对象,会替换模块中,输入fastgpt框内容里的{{key}} + */ + private Variables variables; + + /** + * responseChatItemId: string | undefined 。 + * 如果传入,则会将该值作为本次对话的响应消息的 ID, + * FastGPT 会自动将该 ID 存入数据库。请确保, + * 在当前chatId下,responseChatItemId是唯一的。 + */ + private String responseChatItemId; +} diff --git a/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Variables.java b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Variables.java new file mode 100644 index 00000000..3af364ac --- /dev/null +++ b/ruoyi-common/ruoyi-common-chat/src/main/java/org/ruoyi/common/chat/entity/chat/Variables.java @@ -0,0 +1,20 @@ +package org.ruoyi.common.chat.entity.chat; + + +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +import java.io.Serializable; + +@Data +@Builder +@AllArgsConstructor +@NoArgsConstructor +public class Variables implements Serializable { + + private String uid; + + private String name; +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/enums/ChatModeType.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/enums/ChatModeType.java index 13a39d90..aa09f1eb 100644 --- a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/enums/ChatModeType.java +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/enums/ChatModeType.java @@ -17,7 +17,9 @@ public enum ChatModeType { VECTOR("vector", "知识库向量模型"), - IMAGE("image", "图片识别模型"); + IMAGE("image", "图片识别模型"), + + FASTGPT("fastgpt", "FASTGPT"); private final String code; private final String description; diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/listener/FastGPTSSEEventSourceListener.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/listener/FastGPTSSEEventSourceListener.java new file mode 100644 index 00000000..0d58176d --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/listener/FastGPTSSEEventSourceListener.java @@ -0,0 +1,70 @@ +package org.ruoyi.chat.listener; + +import com.fasterxml.jackson.databind.ObjectMapper; +import lombok.RequiredArgsConstructor; +import lombok.SneakyThrows; +import lombok.extern.slf4j.Slf4j; +import okhttp3.Response; +import okhttp3.ResponseBody; +import okhttp3.sse.EventSource; +import okhttp3.sse.EventSourceListener; +import org.jetbrains.annotations.NotNull; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Component; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.Objects; + +@Slf4j +@Component +@RequiredArgsConstructor +public class FastGPTSSEEventSourceListener extends EventSourceListener { + + private SseEmitter emitter; + + @Autowired(required = false) + public FastGPTSSEEventSourceListener(SseEmitter emitter) { + this.emitter = emitter; + } + + @Override + public void onOpen(EventSource eventSource, Response response) { + log.info("FastGPT sse连接成功"); + } + + @Override + public void onEvent(@NotNull EventSource eventSource, String id, String type, @NotNull String data) { + try { + log.debug("事件类型为: {}", type); + log.debug("事件数据为: {}", data); + if ("flowResponses".equals(type)){ + emitter.send(data); + emitter.complete(); + } else { + emitter.send(data); + } + } catch (Exception e) { + throw new RuntimeException(e); + } + } + + @Override + public void onClosed(EventSource eventSource) { + log.info("FastGPT sse连接关闭"); + } + + @Override + @SneakyThrows + public void onFailure(EventSource eventSource, Throwable t, Response response) { + if (Objects.isNull(response)) { + return; + } + ResponseBody body = response.body(); + if (Objects.nonNull(body)) { + log.error("FastGPT sse连接异常data:{},异常:{}", body.string(), t); + } else { + log.error("FastGPT sse连接异常data:{},异常:{}", response, t); + } + eventSource.cancel(); + } +} diff --git a/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/FastGPTServiceImpl.java b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/FastGPTServiceImpl.java new file mode 100644 index 00000000..15acf6f2 --- /dev/null +++ b/ruoyi-modules/ruoyi-chat/src/main/java/org/ruoyi/chat/service/chat/impl/FastGPTServiceImpl.java @@ -0,0 +1,52 @@ +package org.ruoyi.chat.service.chat.impl; + +import org.ruoyi.chat.config.ChatConfig; +import org.ruoyi.chat.enums.ChatModeType; +import org.ruoyi.chat.listener.FastGPTSSEEventSourceListener; +import org.ruoyi.chat.service.chat.IChatService; +import org.ruoyi.common.chat.entity.chat.FastGPTChatCompletion; +import org.ruoyi.common.chat.entity.chat.Message; +import org.ruoyi.common.chat.openai.OpenAiStreamClient; +import org.ruoyi.common.chat.request.ChatRequest; +import org.ruoyi.domain.vo.ChatModelVo; +import org.ruoyi.service.IChatModelService; +import org.springframework.beans.factory.annotation.Autowired; +import org.springframework.stereotype.Service; +import org.springframework.web.servlet.mvc.method.annotation.SseEmitter; + +import java.util.List; + +/** + * FastGpt 聊天管理 + * 项目整体沿用Openai接口范式,根据FastGPT文档增加相应的参数 + * + * @author yzm + */ +@Service +public class FastGPTServiceImpl implements IChatService { + + @Autowired + private IChatModelService chatModelService; + + @Override + public SseEmitter chat(ChatRequest chatRequest, SseEmitter emitter) { + ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel()); + OpenAiStreamClient openAiStreamClient = ChatConfig.createOpenAiStreamClient(chatModelVo.getApiHost(), chatModelVo.getApiKey()); + List messages = chatRequest.getMessages(); + FastGPTSSEEventSourceListener listener = new FastGPTSSEEventSourceListener(emitter); + FastGPTChatCompletion completion = FastGPTChatCompletion + .builder() + .messages(messages) + // 开启后sse会返回event值 + .detail(true) + .stream(true) + .build(); + openAiStreamClient.streamChatCompletion(completion, listener); + return emitter; + } + + @Override + public String getCategory() { + return ChatModeType.FASTGPT.getCode(); + } +}