Revert "FIX #1035 And FIX #980 Dify Chat API Support"

This commit is contained in:
robinji0
2023-12-28 14:23:19 +08:00
committed by GitHub
parent a3de4f8b0f
commit 01ab0ad304
13 changed files with 55 additions and 590 deletions

View File

@ -14,7 +14,6 @@ const AITypeName = {
[AIType.TONGYIQIANWENAI]: i18n('setting.tab.aiType.tongyiqianwen'),
[AIType.OPENAI]: 'Open AI',
[AIType.AZUREAI]: 'Azure AI',
[AIType.DIFYCHAT]: 'Dify AI',
[AIType.RESTAI]: i18n('setting.tab.custom'),
};
@ -54,10 +53,6 @@ const AIFormConfig: Record<AIType, IAiConfigBooleans> = {
apiHost: true,
model: true,
},
[AIType.DIFYCHAT]: {
apiKey: true,
apiHost: true,
},
[AIType.RESTAI]: {
apiKey: true,
apiHost: true,

View File

@ -7,8 +7,6 @@ export enum AIType {
OPENAI = 'OPENAI',
AZUREAI = 'AZUREAI',
RESTAI = 'RESTAI',
DIFYCHAT = 'DIFYCHAT',
}
export interface IRemainingUse {

View File

@ -62,11 +62,6 @@ public enum AiSqlSourceEnum implements BaseEnum<String> {
*/
FASTCHATAI("FAST CHAT AI"),
/**
* FAST CHAT AI
*/
DIFYCHAT("DIFY CHAT AI"),
;
final String description;

View File

@ -27,9 +27,6 @@ import ai.chat2db.server.web.api.controller.ai.claude.model.ClaudeChatCompletion
import ai.chat2db.server.web.api.controller.ai.claude.model.ClaudeChatMessage;
import ai.chat2db.server.web.api.controller.ai.config.LocalCache;
import ai.chat2db.server.web.api.controller.ai.converter.ChatConverter;
import ai.chat2db.server.web.api.controller.ai.dify.client.DifyChatAIClient;
import ai.chat2db.server.web.api.controller.ai.dify.listener.DifyChatAIEventSourceListener;
import ai.chat2db.server.web.api.controller.ai.dify.model.DifyChatConstant;
import ai.chat2db.server.web.api.controller.ai.enums.PromptType;
import ai.chat2db.server.web.api.controller.ai.fastchat.client.FastChatAIClient;
import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse;
@ -252,35 +249,10 @@ public class ChatController {
return chatWithTongyiChatAi(queryRequest, sseEmitter, uid);
case ZHIPUAI:
return chatWithZhipuChatAi(queryRequest, sseEmitter, uid);
case DIFYCHAT:
return chatWithDifyChatAi(queryRequest, sseEmitter, uid);
}
return chatWithOpenAi(queryRequest, sseEmitter, uid);
}
private SseEmitter chatWithDifyChatAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException {
String prompt = buildPrompt(queryRequest);
if (prompt.length() / TOKEN_CONVERT_CHAR_LENGTH > MAX_PROMPT_LENGTH) {
log.error("exceed max token length:{}input length:{}", MAX_PROMPT_LENGTH,
prompt.length() / TOKEN_CONVERT_CHAR_LENGTH);
throw new ParamBusinessException();
}
prompt = prompt.replaceAll("#", "");
log.info(prompt);
Message currentMessage = Message.builder().content(prompt).role(Message.Role.USER).build();
List<Message> messages = new ArrayList<>();
messages.add(currentMessage);
buildSseEmitter(sseEmitter, uid);
DifyChatAIEventSourceListener eventSourceListener = new DifyChatAIEventSourceListener(sseEmitter, uid);
String conversationId = (String) LocalCache.CACHE.get(DifyChatConstant.CONVERSATION_CACHE_PREFIX + uid);
DifyChatAIClient.getInstance().streamCompletions(messages, eventSourceListener, uid, conversationId);
LocalCache.CACHE.put(uid, JSONUtil.toJsonStr(messages), LocalCache.TIMEOUT);
return sseEmitter;
}
/**
* 使用自定义AI接口进行聊天
*
@ -391,7 +363,6 @@ public class ChatController {
return sseEmitter;
}
/**
* chat with fast chat openai
*

View File

@ -1,40 +0,0 @@
package ai.chat2db.server.web.api.controller.ai.chat2db.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import java.util.Map;
@Data
public class DifyChatCompletionsOptions {
/**
* 选填以键值对方式提供用户输入字段与提示词编排中的变量对应。Key 为变量名称Value 是参数值。
* 如果字段类型为 Select传入的 Value 需为预设选项之一。
*/
private Map<String, String> inputs;
/**
* 用户输入/提问内容
*/
private String query;
/**
* blocking 阻塞型,等待执行完毕后返回结果。(请求若流程较长可能会被中断)
* streaming 流式返回。基于 SSEServer-Sent Events实现流式返回。
*/
@JsonProperty("response_mode")
private String responseMode = "blocking";
/**
* (必填)‼️ 会话标识符,首次对话为 conversation_id: "" ‼️,如果要继续对话请传入上下文返回的 conversation_id
*/
@JsonProperty("conversation_id")
private String conversationId;
/**
* 用户标识,由开发者定义规则,需保证用户标识在应用内唯一。
*/
private String user;
}

View File

@ -1,90 +0,0 @@
package ai.chat2db.server.web.api.controller.ai.dify.client;
import ai.chat2db.server.domain.api.model.Config;
import ai.chat2db.server.domain.api.service.ConfigService;
import ai.chat2db.server.web.api.controller.ai.azure.client.AzureOpenAiStreamClient;
import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2DBAIStreamClient;
import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient;
import ai.chat2db.server.web.api.controller.ai.dify.listener.DifyChatAIEventSourceListener;
import ai.chat2db.server.web.api.util.ApplicationContextUtil;
import com.unfbx.chatgpt.constant.OpenAIConst;
import com.unfbx.chatgpt.entity.chat.Message;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.StringUtils;
import java.util.List;
@Slf4j
public class DifyChatAIClient {
/**
* ZHIPU OPENAI KEY
*/
public static final String DIFYCHAT_API_KEY = "difychat.apiKey";
/**
* ZHIPU OPENAI HOST
*/
public static final String DIFYCHAT_HOST = "difychat.host";
private static DifyChatAiStreamClient DIFY_CHAT_STREAM_CLIENT;
public static DifyChatAiStreamClient getInstance() {
if (DIFY_CHAT_STREAM_CLIENT != null) {
return DIFY_CHAT_STREAM_CLIENT;
} else {
return singleton();
}
}
private static DifyChatAiStreamClient singleton() {
if (DIFY_CHAT_STREAM_CLIENT == null) {
synchronized (DifyChatAIClient.class) {
if (DIFY_CHAT_STREAM_CLIENT == null) {
refresh();
}
}
}
return DIFY_CHAT_STREAM_CLIENT;
}
public static void refresh() {
String apikey;
String apiHost = ApplicationContextUtil.getProperty(DIFYCHAT_HOST);
if (StringUtils.isBlank(apiHost)) {
apiHost = OpenAIConst.OPENAI_HOST;
}
ConfigService configService = ApplicationContextUtil.getBean(ConfigService.class);
Config apiHostConfig = configService.find(DIFYCHAT_HOST).getData();
if (apiHostConfig != null) {
apiHost = apiHostConfig.getContent();
}
Config config = configService.find(DIFYCHAT_API_KEY).getData();
if (config != null) {
apikey = config.getContent();
} else {
apikey = ApplicationContextUtil.getProperty(DIFYCHAT_API_KEY);
}
log.info("refresh dify chat apiHost:{} apikey:{}", apiHost, maskApiKey(apikey));
DIFY_CHAT_STREAM_CLIENT = DifyChatAiStreamClient.builder().apiHost(apiHost).apiKey(apikey).build();
}
private static String maskApiKey(String input) {
if (input == null) {
return input;
}
StringBuilder maskedString = new StringBuilder(input);
for (int i = input.length() / 4; i < input.length() / 2; i++) {
maskedString.setCharAt(i, '*');
}
return maskedString.toString();
}
}

View File

@ -1,163 +0,0 @@
package ai.chat2db.server.web.api.controller.ai.dify.client;
import ai.chat2db.server.tools.common.exception.ParamBusinessException;
import ai.chat2db.server.web.api.controller.ai.azure.interceptor.AzureHeaderAuthorizationInterceptor;
import ai.chat2db.server.web.api.controller.ai.chat2db.model.DifyChatCompletionsOptions;
import ai.chat2db.server.web.api.controller.ai.dify.listener.DifyChatAIEventSourceListener;
import cn.hutool.http.ContentType;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.unfbx.chatgpt.entity.chat.Message;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSources;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.poi.hpsf.GUID;
import org.jetbrains.annotations.NotNull;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
@Slf4j
public class DifyChatAiStreamClient {
/**
* apikey
*/
@Getter
@NotNull
private String apiKey;
/**
* apiHost
*/
@Getter
@NotNull
private String apiHost;
@Getter
private OkHttpClient okHttpClient;
private DifyChatAiStreamClient(DifyChatAiStreamClient.Builder builder) {
this.apiKey = builder.apiKey;
this.apiHost = builder.apiHost;
if (Objects.isNull(builder.okHttpClient)) {
builder.okHttpClient = this.okHttpClient();
}
okHttpClient = builder.okHttpClient;
}
/**
* 构造
*
* @return
*/
public static DifyChatAiStreamClient.Builder builder() {
return new DifyChatAiStreamClient.Builder();
}
/**
* okhttpclient
*/
private OkHttpClient okHttpClient() {
OkHttpClient okHttpClient = new OkHttpClient
.Builder()
.addInterceptor(new AzureHeaderAuthorizationInterceptor(this.apiKey))
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(50, TimeUnit.SECONDS)
.readTimeout(50, TimeUnit.SECONDS)
.build();
return okHttpClient;
}
public static final class Builder {
private String apiKey;
private String apiHost;
/**
* 自定义OkhttpClient
*/
private OkHttpClient okHttpClient;
public Builder() {
}
public DifyChatAiStreamClient.Builder apiKey(String apiKeyValue) {
this.apiKey = apiKeyValue;
return this;
}
/**
* @param apiHost
* @return
*/
public DifyChatAiStreamClient.Builder apiHost(String apiHost) {
this.apiHost = apiHost;
return this;
}
public DifyChatAiStreamClient.Builder okHttpClient(OkHttpClient val) {
this.okHttpClient = val;
return this;
}
public DifyChatAiStreamClient build() {
return new DifyChatAiStreamClient(this);
}
}
public void streamCompletions(List<Message> messages, DifyChatAIEventSourceListener eventSourceListener,
String uid, String conversationId) {
if (CollectionUtils.isEmpty(messages)) {
log.error("param errorDify Chat Prompt cannot be empty");
throw new ParamBusinessException("prompt");
}
if (Objects.isNull(eventSourceListener)) {
log.error("param errorDifyChatAIEventSourceListener cannot be empty");
throw new ParamBusinessException();
}
String lastMessage = messages.get(messages.size() - 1).getContent();
log.info("Dify Chat AI, uid:{} conversationId:{} prompt:{}", uid, conversationId, lastMessage);
try {
DifyChatCompletionsOptions chatCompletionsOptions = new DifyChatCompletionsOptions();
chatCompletionsOptions.setQuery(lastMessage);
chatCompletionsOptions.setResponseMode("streaming");
chatCompletionsOptions.setConversationId(conversationId);
chatCompletionsOptions.setUser(uid);
EventSource.Factory factory = EventSources.createFactory(this.okHttpClient);
ObjectMapper mapper = new ObjectMapper();
mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false);
String requestBody = mapper.writeValueAsString(chatCompletionsOptions);
if (!apiHost.endsWith("/")) {
apiHost = apiHost + "/";
}
String url = this.apiHost + "v1/chat-messages";
Request request = new Request.Builder()
.url(url)
.header("Authorization","Bearer "+apiKey)
.post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody))
.build();
//创建事件
EventSource eventSource = factory.newEventSource(request, eventSourceListener);
log.info("finish invoking Dify Chat ai");
} catch (Exception e) {
log.error("Dify Chat error", e);
eventSourceListener.onFailure(null, e, null);
throw new ParamBusinessException();
}
}
}

View File

@ -1,129 +0,0 @@
package ai.chat2db.server.web.api.controller.ai.dify.listener;
import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatChoice;
import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatCompletions;
import ai.chat2db.server.web.api.controller.ai.azure.model.AzureChatMessage;
import ai.chat2db.server.web.api.controller.ai.azure.model.AzureCompletionsUsage;
import ai.chat2db.server.web.api.controller.ai.config.LocalCache;
import ai.chat2db.server.web.api.controller.ai.dify.model.DifyChatConstant;
import ai.chat2db.server.web.api.controller.ai.dify.model.DifyChatStreamEvent;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.google.gson.Gson;
import com.unfbx.chatgpt.entity.chat.Message;
import lombok.SneakyThrows;
import lombok.extern.slf4j.Slf4j;
import okhttp3.Response;
import okhttp3.ResponseBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.springframework.web.servlet.mvc.method.annotation.SseEmitter;
import java.io.IOException;
import java.util.Objects;
@Slf4j
public class DifyChatAIEventSourceListener extends EventSourceListener {
private SseEmitter sseEmitter;
private String uid;
private ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
public DifyChatAIEventSourceListener(SseEmitter sseEmitter, String uid) {
this.sseEmitter = sseEmitter;
this.uid = uid;
}
@Override
public void onClosed(@NotNull EventSource eventSource) {
try {
sseEmitter.send(SseEmitter.event()
.id("[DONE]")
.data("[DONE]"));
} catch (IOException e) {
throw new RuntimeException(e);
}
sseEmitter.complete();
log.info("DifyChatAI close sse connection...");
}
@Override
@SneakyThrows
public void onEvent(@NotNull EventSource eventSource, @Nullable String id, @Nullable String type, @NotNull String data) {
log.info("DifyChatAI{}", data);
DifyChatStreamEvent event = mapper.readValue(data, DifyChatStreamEvent.class);
if (DifyChatConstant.EVENT_END_TAG.equals(event.getEvent())) {
log.info("DifyChatAI返回数据结束了");
sseEmitter.send(SseEmitter.event()
.id("[DONE]")
.data("[DONE]")
.reconnectTime(3000));
return;
}
String text = event.getAnswer();
LocalCache.CACHE.put(DifyChatConstant.CONVERSATION_CACHE_PREFIX + uid, event.getConversationId());
log.info("Model ID={} is created at {}.", event.getId(), event.getCreatedAt());
Message message = new Message();
message.setContent(text);
sseEmitter.send(SseEmitter.event()
.id(null)
.data(message)
.reconnectTime(3000));
}
@Override
public void onFailure(@NotNull EventSource eventSource, @Nullable Throwable t, @Nullable Response response) {
try {
if (Objects.isNull(response)) {
String message = t.getMessage();
Message sseMessage = new Message();
sseMessage.setContent(message);
sseEmitter.send(SseEmitter.event()
.id("[ERROR]")
.data(sseMessage));
sseEmitter.send(SseEmitter.event()
.id("[DONE]")
.data("[DONE]"));
sseEmitter.complete();
return;
}
ResponseBody body = response.body();
String bodyString = Objects.nonNull(t) ? t.getMessage() : "";
if (Objects.nonNull(body)) {
bodyString = body.string();
if (StringUtils.isBlank(bodyString) && Objects.nonNull(t)) {
bodyString = t.getMessage();
}
log.error("DifyChatAI sse response{}", bodyString);
} else {
log.error("DifyChatAI sse response{}error{}", response, t);
}
eventSource.cancel();
Message message = new Message();
message.setContent("DifyChatAI error" + bodyString);
sseEmitter.send(SseEmitter.event()
.id("[ERROR]")
.data(message));
sseEmitter.send(SseEmitter.event()
.id("[DONE]")
.data("[DONE]"));
sseEmitter.complete();
} catch (Exception exception) {
log.error("DifyChatAI 发送数据异常:", exception);
}
}
@Override
public void onOpen(@NotNull EventSource eventSource, @NotNull Response response) {
log.info("DifyChatAI 建立sse连接...");
}
}

View File

@ -1,8 +0,0 @@
package ai.chat2db.server.web.api.controller.ai.dify.model;
public interface DifyChatConstant {
String CONVERSATION_CACHE_PREFIX="CONVERSATION:";
String EVENT_END_TAG="message_end";
}

View File

@ -1,22 +0,0 @@
package ai.chat2db.server.web.api.controller.ai.dify.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import java.util.Map;
public class DifyChatMessage {
@JsonProperty(value = "query")
private String query;
@JsonProperty(value = "inputs")
private Map<String,String> inputs;
@JsonProperty(value = "conversation_id")
private String conversation_id;
private String user;
}

View File

@ -1,21 +0,0 @@
package ai.chat2db.server.web.api.controller.ai.dify.model;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
import lombok.ToString;
@Data
@ToString
public class DifyChatStreamEvent {
private String event;
@JsonProperty("task_id")
private String taskId;
private String id;
private String answer;
@JsonProperty("created_at")
private long createdAt;
@JsonProperty("conversation_id")
private String conversationId;
}

View File

@ -15,7 +15,6 @@ import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect;
import ai.chat2db.server.web.api.controller.ai.azure.client.AzureOpenAIClient;
import ai.chat2db.server.web.api.controller.ai.baichuan.client.BaichuanAIClient;
import ai.chat2db.server.web.api.controller.ai.chat2db.client.Chat2dbAIClient;
import ai.chat2db.server.web.api.controller.ai.dify.client.DifyChatAIClient;
import ai.chat2db.server.web.api.controller.ai.fastchat.client.FastChatAIClient;
import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient;
import ai.chat2db.server.web.api.controller.ai.tongyi.client.TongyiChatAIClient;
@ -104,23 +103,10 @@ public class ConfigController {
case ZHIPUAI:
saveZhipuChatAIConfig(request);
break;
case DIFYCHAT:
saveDifyChatAIConfig(request);
break;
}
return ActionResult.isSuccess();
}
private void saveDifyChatAIConfig(AIConfigCreateRequest request) {
SystemConfigParam param = SystemConfigParam.builder().code(DifyChatAIClient.DIFYCHAT_API_KEY).content(
request.getApiKey()).build();
configService.createOrUpdate(param);
SystemConfigParam hostParam = SystemConfigParam.builder().code(DifyChatAIClient.DIFYCHAT_HOST).content(
request.getApiHost()).build();
configService.createOrUpdate(hostParam);
DifyChatAIClient.refresh();
}
/**
* save chat2db ai config
*
@ -390,12 +376,6 @@ public class ConfigController {
config.setApiHost(Objects.nonNull(zhipuApiHost.getData()) ? zhipuApiHost.getData().getContent() : "");
config.setModel(Objects.nonNull(zhipuModel.getData()) ? zhipuModel.getData().getContent() : "");
break;
case DIFYCHAT:
DataResult<Config> difyChatApiKey = configService.find(DifyChatAIClient.DIFYCHAT_API_KEY);
DataResult<Config> difyChatApiHost = configService.find(DifyChatAIClient.DIFYCHAT_HOST);
config.setApiKey(Objects.nonNull(difyChatApiKey.getData()) ? difyChatApiKey.getData().getContent() : "");
config.setApiHost(Objects.nonNull(difyChatApiHost.getData()) ? difyChatApiHost.getData().getContent() : "");
break;
default:
break;
}

View File

@ -126,8 +126,7 @@ public class RdbDmlController {
if (DataSourceTypeEnum.MONGODB.getCode().equals(type)) {
param.setSql("db." + request.getTableName() + ".find()");
} else {
// 拼接`tableName`,避免关键字被占用问题
param.setSql("select * from " +"`"+ request.getTableName()+"`");
param.setSql("select * from " + request.getTableName());
}
return dlTemplateService.execute(param)
.map(rdbWebConverter::dto2vo);