Skip to content

Commit a3de4f8

Browse files
authored
Merge pull request OtterMind#1036 from leven-space/main
FIX OtterMind#1035 And FIX OtterMind#980 Dify Chat API Support
2 parents da5671b + 3d3e585 commit a3de4f8

13 files changed

Lines changed: 590 additions & 55 deletions

File tree

chat2db-client/src/blocks/Setting/AiSetting/aiTypeConfig.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ const AITypeName = {
1414
[AIType.TONGYIQIANWENAI]: i18n('setting.tab.aiType.tongyiqianwen'),
1515
[AIType.OPENAI]: 'Open AI',
1616
[AIType.AZUREAI]: 'Azure AI',
17+
[AIType.DIFYCHAT]: 'Dify AI',
1718
[AIType.RESTAI]: i18n('setting.tab.custom'),
1819
};
1920

@@ -53,6 +54,10 @@ const AIFormConfig: Record<AIType, IAiConfigBooleans> = {
5354
apiHost: true,
5455
model: true,
5556
},
57+
[AIType.DIFYCHAT]: {
58+
apiKey: true,
59+
apiHost: true,
60+
},
5661
[AIType.RESTAI]: {
5762
apiKey: true,
5863
apiHost: true,

chat2db-client/src/typings/ai.ts

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
export enum AIType {
22
CHAT2DBAI = 'CHAT2DBAI',
33
ZHIPUAI = 'ZHIPUAI',
4-
BAICHUANAI='BAICHUANAI',
5-
WENXINAI='WENXINAI',
4+
BAICHUANAI = 'BAICHUANAI',
5+
WENXINAI = 'WENXINAI',
66
// TONGYIQIANWENAI='TONGYIQIANWENAI',
77
OPENAI = 'OPENAI',
88
AZUREAI = 'AZUREAI',
99
RESTAI = 'RESTAI',
10+
DIFYCHAT = 'DIFYCHAT',
11+
1012
}
1113

1214
export interface IRemainingUse {

chat2db-server/chat2db-server-domain/chat2db-server-domain-api/src/main/java/ai/chat2db/server/domain/api/enums/AiSqlSourceEnum.java

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,11 @@ public enum AiSqlSourceEnum implements BaseEnum<String> {
6262
*/
6363
FASTCHATAI("FAST CHAT AI"),
6464

65+
/**
66+
* FAST CHAT AI
67+
*/
68+
DIFYCHAT("DIFY CHAT AI"),
69+
6570
;
6671

6772
final String description;

chat2db-server/chat2db-server-web/chat2db-server-web-api/src/main/java/ai/chat2db/server/web/api/controller/ai/ChatController.java

Lines changed: 61 additions & 32 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,9 @@
2727
import ai.chat2db.server.web.api.controller.ai.claude.model.ClaudeChatMessage;
2828
import ai.chat2db.server.web.api.controller.ai.config.LocalCache;
2929
import ai.chat2db.server.web.api.controller.ai.converter.ChatConverter;
30+
import ai.chat2db.server.web.api.controller.ai.dify.client.DifyChatAIClient;
31+
import ai.chat2db.server.web.api.controller.ai.dify.listener.DifyChatAIEventSourceListener;
32+
import ai.chat2db.server.web.api.controller.ai.dify.model.DifyChatConstant;
3033
import ai.chat2db.server.web.api.controller.ai.enums.PromptType;
3134
import ai.chat2db.server.web.api.controller.ai.fastchat.client.FastChatAIClient;
3235
import ai.chat2db.server.web.api.controller.ai.fastchat.embeddings.FastChatEmbeddingResponse;
@@ -132,7 +135,7 @@ public class ChatController {
132135
/**
133136
* 自定义模型流式输出接口DEMO
134137
* <p>
135-
* Note:使用自己本地的流式输出的自定义AI,接口输入和输出需与该样例保持一致
138+
* Note:使用自己本地的流式输出的自定义AI,接口输入和输出需与该样例保持一致
136139
* </p>
137140
*
138141
* @param queryRequest
@@ -171,7 +174,7 @@ public SseEmitter customChat(@RequestBody ChatRequest queryRequest) throws IOExc
171174
/**
172175
* 自定义模型非流式输出接口DEMO
173176
* <p>
174-
* Note:使用自己本地的飞流式输出自定义AI,接口输入和输出需与该样例保持一致
177+
* Note:使用自己本地的飞流式输出自定义AI,接口输入和输出需与该样例保持一致
175178
* </p>
176179
*
177180
* @param queryRequest
@@ -196,7 +199,7 @@ public String customNonStreamChat(@RequestBody ChatRequest queryRequest) throws
196199
@GetMapping("/chat")
197200
@CrossOrigin
198201
public SseEmitter completions(ChatQueryRequest queryRequest, @RequestHeader Map<String, String> headers)
199-
throws IOException {
202+
throws IOException {
200203
//默认30秒超时,设置为0L则永不超时
201204
SseEmitter sseEmitter = new SseEmitter(CHAT_TIMEOUT);
202205
String uid = headers.get("uid");
@@ -230,14 +233,14 @@ public SseEmitter distributeAISql(ChatQueryRequest queryRequest, SseEmitter sseE
230233
}
231234
uid = aiSqlSourceEnum.getCode() + uid;
232235
switch (Objects.requireNonNull(aiSqlSourceEnum)) {
233-
case OPENAI :
236+
case OPENAI:
234237
return chatWithOpenAi(queryRequest, sseEmitter, uid);
235238
case CHAT2DBAI:
236239
return chatWithChat2dbAi(queryRequest, sseEmitter, uid);
237-
case RESTAI :
240+
case RESTAI:
238241
case FASTCHATAI:
239242
return chatWithFastChatAi(queryRequest, sseEmitter, uid);
240-
case AZUREAI :
243+
case AZUREAI:
241244
return chatWithAzureAi(queryRequest, sseEmitter, uid);
242245
case CLAUDEAI:
243246
return chatWithClaudeAi(queryRequest, sseEmitter, uid);
@@ -249,10 +252,35 @@ public SseEmitter distributeAISql(ChatQueryRequest queryRequest, SseEmitter sseE
249252
return chatWithTongyiChatAi(queryRequest, sseEmitter, uid);
250253
case ZHIPUAI:
251254
return chatWithZhipuChatAi(queryRequest, sseEmitter, uid);
255+
case DIFYCHAT:
256+
return chatWithDifyChatAi(queryRequest, sseEmitter, uid);
252257
}
253258
return chatWithOpenAi(queryRequest, sseEmitter, uid);
254259
}
255260

261+
private SseEmitter chatWithDifyChatAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid) throws IOException {
262+
String prompt = buildPrompt(queryRequest);
263+
if (prompt.length() / TOKEN_CONVERT_CHAR_LENGTH > MAX_PROMPT_LENGTH) {
264+
log.error("exceed max token length:{},input length:{}", MAX_PROMPT_LENGTH,
265+
prompt.length() / TOKEN_CONVERT_CHAR_LENGTH);
266+
throw new ParamBusinessException();
267+
}
268+
269+
prompt = prompt.replaceAll("#", "");
270+
log.info(prompt);
271+
272+
Message currentMessage = Message.builder().content(prompt).role(Message.Role.USER).build();
273+
List<Message> messages = new ArrayList<>();
274+
messages.add(currentMessage);
275+
buildSseEmitter(sseEmitter, uid);
276+
277+
DifyChatAIEventSourceListener eventSourceListener = new DifyChatAIEventSourceListener(sseEmitter, uid);
278+
String conversationId = (String) LocalCache.CACHE.get(DifyChatConstant.CONVERSATION_CACHE_PREFIX + uid);
279+
DifyChatAIClient.getInstance().streamCompletions(messages, eventSourceListener, uid, conversationId);
280+
LocalCache.CACHE.put(uid, JSONUtil.toJsonStr(messages), LocalCache.TIMEOUT);
281+
return sseEmitter;
282+
}
283+
256284
/**
257285
* 使用自定义AI接口进行聊天
258286
*
@@ -276,11 +304,11 @@ private SseEmitter chatWithRestAi(ChatQueryRequest prompt, SseEmitter sseEmitter
276304
* @throws IOException
277305
*/
278306
private SseEmitter chatWithOpenAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid)
279-
throws IOException {
307+
throws IOException {
280308
String prompt = buildPrompt(queryRequest);
281309
if (prompt.length() / TOKEN_CONVERT_CHAR_LENGTH > MAX_PROMPT_LENGTH) {
282310
log.error("提示语超出最大长度:{},输入长度:{}, 请重新输入", MAX_PROMPT_LENGTH,
283-
prompt.length() / TOKEN_CONVERT_CHAR_LENGTH);
311+
prompt.length() / TOKEN_CONVERT_CHAR_LENGTH);
284312
throw new ParamBusinessException();
285313
}
286314

@@ -307,11 +335,11 @@ private SseEmitter chatWithOpenAi(ChatQueryRequest queryRequest, SseEmitter sseE
307335
* @throws IOException
308336
*/
309337
private SseEmitter chatWithChat2dbAi(ChatQueryRequest queryRequest, SseEmitter sseEmitter, String uid)
310-
throws IOException {
338+
throws IOException {
311339
String prompt = buildPrompt(queryRequest);
312340
if (prompt.length() / TOKEN_CONVERT_CHAR_LENGTH > MAX_PROMPT_LENGTH) {
313341
log.error("exceed max token length:{},input length:{}", MAX_PROMPT_LENGTH,
314-
prompt.length() / TOKEN_CONVERT_CHAR_LENGTH);
342+
prompt.length() / TOKEN_CONVERT_CHAR_LENGTH);
315343
throw new ParamBusinessException();
316344
}
317345

@@ -344,7 +372,7 @@ private SseEmitter chatWithAzureAi(ChatQueryRequest queryRequest, SseEmitter sse
344372
prompt.length() / TOKEN_CONVERT_CHAR_LENGTH);
345373
throw new ParamBusinessException();
346374
}
347-
List<AzureChatMessage> messages = (List<AzureChatMessage>)LocalCache.CACHE.get(uid);
375+
List<AzureChatMessage> messages = (List<AzureChatMessage>) LocalCache.CACHE.get(uid);
348376
if (CollectionUtils.isNotEmpty(messages)) {
349377
if (messages.size() >= contextLength) {
350378
messages = messages.subList(1, contextLength);
@@ -363,6 +391,7 @@ private SseEmitter chatWithAzureAi(ChatQueryRequest queryRequest, SseEmitter sse
363391
return sseEmitter;
364392
}
365393

394+
366395
/**
367396
* chat with fast chat openai
368397
*
@@ -455,7 +484,7 @@ private SseEmitter chatWithBaichuanAi(ChatQueryRequest queryRequest, SseEmitter
455484
* @return
456485
*/
457486
private List<FastChatMessage> getFastChatMessage(String uid, String prompt) {
458-
List<FastChatMessage> messages = (List<FastChatMessage>)LocalCache.CACHE.get(uid);
487+
List<FastChatMessage> messages = (List<FastChatMessage>) LocalCache.CACHE.get(uid);
459488
if (CollectionUtils.isNotEmpty(messages)) {
460489
if (messages.size() >= contextLength) {
461490
messages = messages.subList(1, contextLength);
@@ -531,17 +560,17 @@ private SseEmitter buildSseEmitter(SseEmitter sseEmitter, String uid) throws IOE
531560
log.info(LocalDateTime.now() + ", uid#" + uid + ", on completion");
532561
});
533562
sseEmitter.onTimeout(
534-
() -> log.info(LocalDateTime.now() + ", uid#" + uid + ", on timeout#" + sseEmitter.getTimeout()));
563+
() -> log.info(LocalDateTime.now() + ", uid#" + uid + ", on timeout#" + sseEmitter.getTimeout()));
535564
sseEmitter.onError(
536-
throwable -> {
537-
try {
538-
log.info(LocalDateTime.now() + ", uid#" + "765431" + ", on error#" + throwable.toString());
539-
sseEmitter.send(SseEmitter.event().id("765431").name("发生异常!").data(throwable.getMessage())
540-
.reconnectTime(3000));
541-
} catch (IOException e) {
542-
e.printStackTrace();
565+
throwable -> {
566+
try {
567+
log.info(LocalDateTime.now() + ", uid#" + "765431" + ", on error#" + throwable.toString());
568+
sseEmitter.send(SseEmitter.event().id("765431").name("发生异常!").data(throwable.getMessage())
569+
.reconnectTime(3000));
570+
} catch (IOException e) {
571+
e.printStackTrace();
572+
}
543573
}
544-
}
545574
);
546575
return sseEmitter;
547576
}
@@ -554,13 +583,13 @@ private SseEmitter buildSseEmitter(SseEmitter sseEmitter, String uid) throws IOE
554583
* @return
555584
*/
556585
private String buildTableColumn(TableQueryParam tableQueryParam,
557-
List<String> tableNames) {
586+
List<String> tableNames) {
558587
if (CollectionUtils.isEmpty(tableNames)) {
559588
return "";
560589
}
561590
List<String> schemaContent = Lists.newArrayList();
562591
try {
563-
schemaContent = tableNames.stream().map(tableName -> {
592+
schemaContent = tableNames.stream().map(tableName -> {
564593
tableQueryParam.setTableName(tableName);
565594
return queryTableDdl(tableName, tableQueryParam);
566595
}).collect(Collectors.toList());
@@ -610,19 +639,19 @@ private String buildPrompt(ChatQueryRequest queryRequest) {
610639
}
611640
String prompt = queryRequest.getMessage();
612641
String promptType = StringUtils.isBlank(queryRequest.getPromptType()) ? PromptType.NL_2_SQL.getCode()
613-
: queryRequest.getPromptType();
642+
: queryRequest.getPromptType();
614643
PromptType pType = EasyEnumUtils.getEnum(PromptType.class, promptType);
615644
String ext = StringUtils.isNotBlank(queryRequest.getExt()) ? queryRequest.getExt() : "";
616645
String schemaProperty = StringUtils.isNotEmpty(properties) ? String.format(
617-
"### 请根据以下table properties和SQL input%s. %s\n#\n### %s SQL tables, with their properties:\n#\n# "
618-
+ "%s\n#\n#\n### SQL input: %s", pType.getDescription(), ext, dataSourceType,
619-
properties, prompt) : String.format("### 请根据以下SQL input%s. %s\n#\n### SQL input: %s",
620-
pType.getDescription(), ext, prompt);
646+
"### 请根据以下table properties和SQL input%s. %s\n#\n### %s SQL tables, with their properties:\n#\n# "
647+
+ "%s\n#\n#\n### SQL input: %s", pType.getDescription(), ext, dataSourceType,
648+
properties, prompt) : String.format("### 请根据以下SQL input%s. %s\n#\n### SQL input: %s",
649+
pType.getDescription(), ext, prompt);
621650
switch (pType) {
622651
case SQL_2_SQL:
623652
schemaProperty = StringUtils.isNotBlank(queryRequest.getDestSqlType()) ? String.format(
624-
"%s\n#\n### 目标SQL类型: %s", schemaProperty, queryRequest.getDestSqlType()) : String.format(
625-
"%s\n#\n### 目标SQL类型: %s", schemaProperty, dataSourceType);
653+
"%s\n#\n### 目标SQL类型: %s", schemaProperty, queryRequest.getDestSqlType()) : String.format(
654+
"%s\n#\n### 目标SQL类型: %s", schemaProperty, dataSourceType);
626655
default:
627656
break;
628657
}
@@ -711,7 +740,7 @@ public String queryDatabaseSchema(ChatQueryRequest queryRequest) {
711740
DataResult<TableSchemaResponse> result = gatewayClientService.schemaVectorSearch(tableSchemaRequest);
712741
List<String> schemas = Lists.newArrayList();
713742
if (Objects.nonNull(result.getData()) && CollectionUtils.isNotEmpty(result.getData().getTableSchemas())) {
714-
for(TableSchema data: result.getData().getTableSchemas()){
743+
for (TableSchema data : result.getData().getTableSchemas()) {
715744
schemas.add(data.getTableSchema());
716745
}
717746
}
@@ -751,7 +780,7 @@ public String querySchemaByEs(ChatQueryRequest queryRequest) {
751780
DataResult<EsTableSchemaResponse> result = gatewayClientService.schemaEsSearch(tableSchemaRequest);
752781
List<String> schemas = Lists.newArrayList();
753782
if (Objects.nonNull(result.getData()) && CollectionUtils.isNotEmpty(result.getData().getTableSchemas())) {
754-
for(EsTableSchema data: result.getData().getTableSchemas()){
783+
for (EsTableSchema data : result.getData().getTableSchemas()) {
755784
schemas.add(data.getTableSchemaContent());
756785
}
757786
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
package ai.chat2db.server.web.api.controller.ai.chat2db.model;
2+
3+
import com.fasterxml.jackson.annotation.JsonProperty;
4+
import lombok.Data;
5+
6+
import java.util.Map;
7+
8+
@Data
9+
public class DifyChatCompletionsOptions {
10+
/**
11+
* (选填)以键值对方式提供用户输入字段,与提示词编排中的变量对应。Key 为变量名称,Value 是参数值。
12+
* 如果字段类型为 Select,传入的 Value 需为预设选项之一。
13+
*/
14+
private Map<String, String> inputs;
15+
16+
/**
17+
* 用户输入/提问内容
18+
*/
19+
private String query;
20+
21+
/**
22+
* blocking 阻塞型,等待执行完毕后返回结果。(请求若流程较长可能会被中断)
23+
* streaming 流式返回。基于 SSE(Server-Sent Events)实现流式返回。
24+
*/
25+
@JsonProperty("response_mode")
26+
private String responseMode = "blocking";
27+
28+
/**
29+
* (必填)‼️ 会话标识符,首次对话为 conversation_id: "" ‼️,如果要继续对话请传入上下文返回的 conversation_id
30+
*/
31+
@JsonProperty("conversation_id")
32+
private String conversationId;
33+
34+
/**
35+
* 用户标识,由开发者定义规则,需保证用户标识在应用内唯一。
36+
*/
37+
private String user;
38+
39+
40+
}

0 commit comments

Comments
 (0)