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(); + } +}