mirror of
https://gitcode.com/ageerle/ruoyi-ai.git
synced 2026-04-04 23:37:32 +00:00
feat: 支持coze,dify,派欧云等三方模型调用
This commit is contained in:
@@ -32,12 +32,11 @@ public class ChatConfig {
|
||||
public OpenAiStreamClient openAiStreamClient() {
|
||||
String apiHost = configService.getConfigValue("chat", "apiHost");
|
||||
String apiKey = configService.getConfigValue("chat", "apiKey");
|
||||
String url = configService.getConfigValue("chat", "apiUrl");
|
||||
openAiStreamClient = createOpenAiStreamClient(apiHost,apiKey,url);
|
||||
openAiStreamClient = createOpenAiStreamClient(apiHost,apiKey);
|
||||
return openAiStreamClient;
|
||||
}
|
||||
|
||||
public static OpenAiStreamClient createOpenAiStreamClient(String apiHost, String apiKey,String url) {
|
||||
public static OpenAiStreamClient createOpenAiStreamClient(String apiHost, String apiKey) {
|
||||
HttpLoggingInterceptor httpLoggingInterceptor = new HttpLoggingInterceptor(new OpenAILogger());
|
||||
httpLoggingInterceptor.setLevel(HttpLoggingInterceptor.Level.HEADERS);
|
||||
OkHttpClient okHttpClient = new OkHttpClient.Builder()
|
||||
@@ -48,7 +47,6 @@ public class ChatConfig {
|
||||
.build();
|
||||
return OpenAiStreamClient.builder()
|
||||
.apiHost(apiHost)
|
||||
.apiUrl(url)
|
||||
.apiKey(Collections.singletonList(apiKey))
|
||||
.keyStrategy(new KeyRandomStrategy())
|
||||
.okHttpClient(okHttpClient)
|
||||
|
||||
@@ -28,7 +28,7 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
|
||||
|
||||
/**
|
||||
* 描述:聊天管理
|
||||
* 聊天管理
|
||||
*
|
||||
* @author ageerle@163.com
|
||||
* @date 2023-03-01
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.ruoyi.chat.domain.bo;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 描述:文生视频请求对象
|
||||
* 文生视频请求对象
|
||||
*
|
||||
* @author ageerle@163.com
|
||||
* date 2024/6/27
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.ruoyi.chat.domain.bo;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* 描述:生成歌词
|
||||
* 生成歌词
|
||||
*
|
||||
* @author ageerle@163.com
|
||||
* date 2024/6/27
|
||||
|
||||
@@ -6,6 +6,8 @@ import lombok.Getter;
|
||||
public enum ChatModeType {
|
||||
OLLAMA("ollama", "本地部署模型"),
|
||||
CHAT("chat", "中转模型"),
|
||||
DIFY("dify", "DIFY"),
|
||||
COZE("coze", "扣子"),
|
||||
VECTOR("vector", "知识库向量模型");
|
||||
|
||||
private final String code;
|
||||
|
||||
@@ -3,7 +3,7 @@ package org.ruoyi.chat.enums;
|
||||
import lombok.Getter;
|
||||
|
||||
/**
|
||||
* 描述:是否显示
|
||||
* 是否显示
|
||||
*
|
||||
* @author ageerle@163.com
|
||||
* date 2025/4/10
|
||||
|
||||
@@ -0,0 +1,43 @@
|
||||
package org.ruoyi.chat.factory;
|
||||
|
||||
import org.ruoyi.chat.service.chat.IChatService;
|
||||
import org.springframework.beans.BeansException;
|
||||
import org.springframework.context.ApplicationContext;
|
||||
import org.springframework.context.ApplicationContextAware;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
|
||||
/**
|
||||
* 聊天服务工厂类
|
||||
*
|
||||
* @author ageerle@163.com
|
||||
* date 2025/5/10
|
||||
*/
|
||||
@Component
|
||||
public class ChatServiceFactory implements ApplicationContextAware {
|
||||
private final Map<String, IChatService> chatServiceMap = new ConcurrentHashMap<>();
|
||||
|
||||
@Override
|
||||
public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
|
||||
// 初始化时收集所有IChatService的实现
|
||||
Map<String, IChatService> serviceMap = applicationContext.getBeansOfType(IChatService.class);
|
||||
for (IChatService service : serviceMap.values()) {
|
||||
if (service != null) {
|
||||
chatServiceMap.put(service.getCategory(), service);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据模型类别获取对应的聊天服务实现
|
||||
*/
|
||||
public IChatService getChatService(String category) {
|
||||
IChatService service = chatServiceMap.get(category);
|
||||
if (service == null) {
|
||||
throw new IllegalArgumentException("不支持的模型类别: " + category);
|
||||
}
|
||||
return service;
|
||||
}
|
||||
}
|
||||
@@ -24,7 +24,7 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import java.util.Objects;
|
||||
|
||||
/**
|
||||
* 描述:OpenAIEventSourceListener
|
||||
* OpenAIEventSourceListener
|
||||
*
|
||||
* @author https:www.unfbx.com
|
||||
* @date 2023-02-22
|
||||
|
||||
@@ -16,6 +16,8 @@ public interface IChatService {
|
||||
* @param chatRequest 请求对象
|
||||
*/
|
||||
SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter);
|
||||
|
||||
|
||||
/**
|
||||
* 获取此服务支持的模型类别
|
||||
*/
|
||||
String getCategory();
|
||||
}
|
||||
|
||||
@@ -1,26 +1,16 @@
|
||||
package org.ruoyi.chat.service.chat;
|
||||
|
||||
import cn.dev33.satoken.stp.StpUtil;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import org.ruoyi.chat.enums.DisplayType;
|
||||
import org.ruoyi.chat.enums.UserGradeType;
|
||||
import org.ruoyi.common.satoken.utils.LoginHelper;
|
||||
import org.ruoyi.domain.bo.ChatModelBo;
|
||||
import org.ruoyi.domain.bo.ChatPackagePlanBo;
|
||||
import org.ruoyi.domain.vo.ChatModelVo;
|
||||
import org.ruoyi.domain.vo.ChatPackagePlanVo;
|
||||
import org.ruoyi.service.IChatModelService;
|
||||
import org.ruoyi.service.IChatPackagePlanService;
|
||||
import org.ruoyi.system.domain.vo.SysUserVo;
|
||||
import org.ruoyi.system.service.ISysUserService;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* 描述:用户模型信息
|
||||
* 用户模型信息
|
||||
*
|
||||
* @author ageerle@163.com
|
||||
* date 2025/4/10
|
||||
@@ -32,30 +22,9 @@ public class UserModelService {
|
||||
|
||||
private final IChatModelService chatModelService;
|
||||
|
||||
private final ISysUserService userService;
|
||||
|
||||
private final IChatPackagePlanService packagePlanService;
|
||||
|
||||
public List<ChatModelVo> modelList(ChatModelBo bo) {
|
||||
bo.setModelShow(DisplayType.VISIBLE.getCode());
|
||||
List<ChatModelVo> chatModelList = chatModelService.queryList(bo);
|
||||
ChatPackagePlanBo sysPackagePlanBo = new ChatPackagePlanBo();
|
||||
if (StpUtil.isLogin()) {
|
||||
Long userId = LoginHelper.getLoginUser().getUserId();
|
||||
SysUserVo sysUserVo = userService.selectUserById(userId);
|
||||
if (UserGradeType.UNPAID.getCode().equals(sysUserVo.getUserGrade())){
|
||||
sysPackagePlanBo.setName("Free");
|
||||
ChatPackagePlanVo chatPackagePlanVo = packagePlanService.queryList(sysPackagePlanBo).get(0);
|
||||
List<String> array = new ArrayList<>(Arrays.asList(chatPackagePlanVo.getPlanDetail().split(",")));
|
||||
chatModelList.removeIf(model -> !array.contains(model.getModelName()));
|
||||
}
|
||||
}else {
|
||||
sysPackagePlanBo.setName("Visitor");
|
||||
ChatPackagePlanVo sysPackagePlanVo = packagePlanService.queryList(sysPackagePlanBo).get(0);
|
||||
List<String> array = new ArrayList<>(Arrays.asList(sysPackagePlanVo.getPlanDetail().split(",")));
|
||||
chatModelList.removeIf(model -> !array.contains(model.getModelName()));
|
||||
}
|
||||
return new ArrayList<>(chatModelList);
|
||||
return chatModelService.queryList(bo);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
package org.ruoyi.chat.service.chat.impl;
|
||||
|
||||
import com.coze.openapi.client.chat.CreateChatReq;
|
||||
import com.coze.openapi.client.chat.model.ChatEvent;
|
||||
import com.coze.openapi.client.chat.model.ChatEventType;
|
||||
import com.coze.openapi.client.connversations.message.model.Message;
|
||||
import com.coze.openapi.service.auth.TokenAuth;
|
||||
import com.coze.openapi.service.config.Consts;
|
||||
import com.coze.openapi.service.service.CozeAPI;
|
||||
import io.reactivex.Flowable;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.chat.enums.ChatModeType;
|
||||
import org.ruoyi.chat.service.chat.IChatService;
|
||||
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.Collections;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/**
|
||||
* 扣子聊天管理
|
||||
*
|
||||
* @author ageer
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class CozeServiceImpl implements IChatService {
|
||||
|
||||
@Autowired
|
||||
private IChatModelService chatModelService;
|
||||
|
||||
@Override
|
||||
public SseEmitter chat(ChatRequest chatRequest, SseEmitter emitter) {
|
||||
ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel());
|
||||
|
||||
TokenAuth authCli = new TokenAuth(chatModelVo.getApiKey());
|
||||
CozeAPI coze =
|
||||
new CozeAPI.Builder()
|
||||
.baseURL(chatModelVo.getApiHost())
|
||||
.auth(authCli)
|
||||
.readTimeout(10000)
|
||||
.build();
|
||||
CreateChatReq req =
|
||||
CreateChatReq.builder()
|
||||
.botID(chatModelVo.getModelName())
|
||||
.userID(chatRequest.getUserId().toString())
|
||||
.messages(Collections.singletonList(Message.buildUserQuestionText("What can you do?")))
|
||||
.build();
|
||||
|
||||
Flowable<ChatEvent> resp = coze.chat().stream(req);
|
||||
ExecutorService executor = Executors.newFixedThreadPool(10);
|
||||
executor.submit(() -> {
|
||||
resp.blockingForEach(
|
||||
event -> {
|
||||
if (ChatEventType.CONVERSATION_MESSAGE_DELTA.equals(event.getEvent())) {
|
||||
emitter.send(event.getMessage().getContent());
|
||||
log.info("coze: {}", event.getMessage().getContent());
|
||||
}
|
||||
if (ChatEventType.CONVERSATION_CHAT_COMPLETED.equals(event.getEvent())) {
|
||||
emitter.complete();
|
||||
log.info("Token usage: {}", event.getChat().getUsage().getTokenCount());
|
||||
}
|
||||
}
|
||||
);
|
||||
coze.shutdownExecutor();
|
||||
});
|
||||
|
||||
|
||||
return emitter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return ChatModeType.COZE.getCode();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,96 @@
|
||||
package org.ruoyi.chat.service.chat.impl;
|
||||
|
||||
import io.github.imfangs.dify.client.DifyClient;
|
||||
import io.github.imfangs.dify.client.DifyClientFactory;
|
||||
import io.github.imfangs.dify.client.callback.ChatStreamCallback;
|
||||
import io.github.imfangs.dify.client.enums.ResponseMode;
|
||||
import io.github.imfangs.dify.client.event.ErrorEvent;
|
||||
import io.github.imfangs.dify.client.event.MessageEndEvent;
|
||||
import io.github.imfangs.dify.client.event.MessageEvent;
|
||||
import io.github.imfangs.dify.client.model.DifyConfig;
|
||||
import io.github.imfangs.dify.client.model.chat.ChatMessage;
|
||||
|
||||
import lombok.SneakyThrows;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
||||
import org.ruoyi.chat.enums.ChatModeType;
|
||||
import org.ruoyi.chat.service.chat.IChatService;
|
||||
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;
|
||||
|
||||
/**
|
||||
* dify 聊天管理
|
||||
*
|
||||
* @author ageer
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class DifyServiceImpl implements IChatService {
|
||||
|
||||
@Autowired
|
||||
private IChatModelService chatModelService;
|
||||
|
||||
@Override
|
||||
public SseEmitter chat(ChatRequest chatRequest, SseEmitter emitter) {
|
||||
ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel());
|
||||
|
||||
// 使用自定义配置创建客户端
|
||||
DifyConfig config = DifyConfig.builder()
|
||||
.baseUrl(chatModelVo.getApiHost())
|
||||
.apiKey(chatModelVo.getApiKey())
|
||||
.connectTimeout(5000)
|
||||
.readTimeout(60000)
|
||||
.writeTimeout(30000)
|
||||
.build();
|
||||
DifyClient chatClient = DifyClientFactory.createClient(config);
|
||||
|
||||
// 创建聊天消息
|
||||
ChatMessage message = ChatMessage.builder()
|
||||
.query(chatRequest.getPrompt())
|
||||
.user(chatRequest.getUserId().toString())
|
||||
.responseMode(ResponseMode.STREAMING)
|
||||
.build();
|
||||
|
||||
// 发送流式消息
|
||||
try {
|
||||
chatClient.sendChatMessageStream(message, new ChatStreamCallback() {
|
||||
@SneakyThrows
|
||||
@Override
|
||||
public void onMessage(MessageEvent event) {
|
||||
emitter.send(event.getAnswer());
|
||||
log.info("收到消息片段: {}", event.getAnswer());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onMessageEnd(MessageEndEvent event) {
|
||||
emitter.complete();
|
||||
log.info("消息结束,完整消息ID: {}", event.getMessageId());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onError(ErrorEvent event) {
|
||||
System.err.println("错误: " + event.getMessage());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onException(Throwable throwable) {
|
||||
System.err.println("异常: " + throwable.getMessage());
|
||||
}
|
||||
});
|
||||
} catch (Exception e) {
|
||||
log.error("dify请求失败:{}", e.getMessage());
|
||||
}
|
||||
|
||||
return emitter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return ChatModeType.DIFY.getCode();
|
||||
}
|
||||
|
||||
}
|
||||
@@ -7,6 +7,8 @@ import io.github.ollama4j.models.chat.OllamaChatRequestBuilder;
|
||||
import io.github.ollama4j.models.chat.OllamaChatRequestModel;
|
||||
import io.github.ollama4j.models.generate.OllamaStreamHandler;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.chat.enums.ChatModeType;
|
||||
import org.ruoyi.chat.service.chat.IChatService;
|
||||
import org.ruoyi.chat.util.SSEUtil;
|
||||
import org.ruoyi.common.chat.entity.chat.Message;
|
||||
import org.ruoyi.common.chat.request.ChatRequest;
|
||||
@@ -22,14 +24,18 @@ import java.util.List;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
|
||||
|
||||
/**
|
||||
* @author ageer
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class OllamaServiceImpl {
|
||||
public class OllamaServiceImpl implements IChatService {
|
||||
|
||||
@Autowired
|
||||
private IChatModelService chatModelService;
|
||||
@Autowired
|
||||
private IChatModelService chatModelService;
|
||||
|
||||
public SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter) {
|
||||
@Override
|
||||
public SseEmitter chat(ChatRequest chatRequest, SseEmitter emitter) {
|
||||
ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel());
|
||||
String host = chatModelVo.getApiHost();
|
||||
List<Message> msgList = chatRequest.getMessages();
|
||||
@@ -73,4 +79,8 @@ public class OllamaServiceImpl {
|
||||
return emitter;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return ChatModeType.OLLAMA.getCode();
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,6 +3,7 @@ package org.ruoyi.chat.service.chat.impl;
|
||||
import io.modelcontextprotocol.client.McpSyncClient;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.ruoyi.chat.config.ChatConfig;
|
||||
import org.ruoyi.chat.enums.ChatModeType;
|
||||
import org.ruoyi.chat.listener.SSEEventSourceListener;
|
||||
import org.ruoyi.chat.service.chat.IChatService;
|
||||
import org.ruoyi.common.chat.entity.chat.ChatCompletion;
|
||||
@@ -21,6 +22,9 @@ import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
|
||||
import java.util.List;
|
||||
|
||||
|
||||
/**
|
||||
* @author ageer
|
||||
*/
|
||||
@Service
|
||||
@Slf4j
|
||||
public class OpenAIServiceImpl implements IChatService {
|
||||
@@ -28,9 +32,6 @@ public class OpenAIServiceImpl implements IChatService {
|
||||
@Autowired
|
||||
private IChatModelService chatModelService;
|
||||
|
||||
private OpenAiStreamClient openAiStreamClient;
|
||||
|
||||
|
||||
@Value("${spring.ai.mcp.client.enabled}")
|
||||
private Boolean enabled;
|
||||
|
||||
@@ -47,7 +48,7 @@ public class OpenAIServiceImpl implements IChatService {
|
||||
@Override
|
||||
public SseEmitter chat(ChatRequest chatRequest,SseEmitter emitter) {
|
||||
ChatModelVo chatModelVo = chatModelService.selectModelByName(chatRequest.getModel());
|
||||
openAiStreamClient = ChatConfig.createOpenAiStreamClient(chatModelVo.getApiHost(), chatModelVo.getApiKey(),chatModelVo.getApiUrl());
|
||||
OpenAiStreamClient openAiStreamClient = ChatConfig.createOpenAiStreamClient(chatModelVo.getApiHost(), chatModelVo.getApiKey());
|
||||
List<Message> messages = chatRequest.getMessages();
|
||||
if (enabled) {
|
||||
String toolString = mcpChat(chatRequest.getPrompt());
|
||||
@@ -69,4 +70,9 @@ public class OpenAIServiceImpl implements IChatService {
|
||||
return this.chatClient.prompt(prompt).call().content();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCategory() {
|
||||
return ChatModeType.CHAT.getCode();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -8,7 +8,9 @@ import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import okhttp3.ResponseBody;
|
||||
import org.ruoyi.chat.enums.ChatModeType;
|
||||
import org.ruoyi.chat.factory.ChatServiceFactory;
|
||||
import org.ruoyi.chat.service.chat.IChatCostService;
|
||||
import org.ruoyi.chat.service.chat.IChatService;
|
||||
import org.ruoyi.chat.service.chat.ISseService;
|
||||
import org.ruoyi.chat.util.IpUtil;
|
||||
import org.ruoyi.chat.util.SSEUtil;
|
||||
@@ -61,9 +63,7 @@ public class SseServiceImpl implements ISseService {
|
||||
|
||||
private final IChatModelService chatModelService;
|
||||
|
||||
private final OpenAIServiceImpl openAIService;
|
||||
|
||||
private final OllamaServiceImpl ollamaService;
|
||||
private final ChatServiceFactory chatServiceFactory;
|
||||
|
||||
private final IChatSessionService chatSessionService;
|
||||
|
||||
@@ -95,7 +95,8 @@ public class SseServiceImpl implements ISseService {
|
||||
chatCostService.deductToken(chatRequest);
|
||||
}
|
||||
// 根据模型分类调用不同的处理逻辑
|
||||
switchModelAndHandle(chatRequest,sseEmitter);
|
||||
IChatService chatService = chatServiceFactory.getChatService(chatModelVo.getCategory());
|
||||
chatService.chat(chatRequest, sseEmitter);
|
||||
} catch (Exception e) {
|
||||
log.error(e.getMessage(),e);
|
||||
SSEUtil.sendErrorEvent(sseEmitter,e.getMessage());
|
||||
@@ -147,17 +148,6 @@ public class SseServiceImpl implements ISseService {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 根据模型名称前缀调用不同的处理逻辑
|
||||
*/
|
||||
private void switchModelAndHandle(ChatRequest chatRequest,SseEmitter emitter) {
|
||||
// 调用ollama中部署的本地模型
|
||||
if (ChatModeType.OLLAMA.getCode().equals(chatModelVo.getCategory())) {
|
||||
ollamaService.chat(chatRequest,emitter);
|
||||
} else {
|
||||
openAIService.chat(chatRequest,emitter);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* 构建消息列表
|
||||
|
||||
Reference in New Issue
Block a user