|
|
|
|
@ -2,12 +2,14 @@ package org.dromara.ai.process.provider.processor.impl;
|
|
|
|
|
|
|
|
|
|
//import com.example.deepseek.dto.EmbeddingResponse;
|
|
|
|
|
|
|
|
|
|
import com.alibaba.dashscope.aigc.generation.*;
|
|
|
|
|
import com.fasterxml.jackson.core.JsonParser;
|
|
|
|
|
import com.fasterxml.jackson.core.JsonProcessingException;
|
|
|
|
|
import com.fasterxml.jackson.databind.JsonNode;
|
|
|
|
|
import com.fasterxml.jackson.databind.ObjectMapper;
|
|
|
|
|
import com.fasterxml.jackson.databind.node.ObjectNode;
|
|
|
|
|
import com.tencentcloudapi.lkeap.v20240522.models.GetEmbeddingResponse;
|
|
|
|
|
import org.checkerframework.checker.units.qual.A;
|
|
|
|
|
import org.dromara.ai.domain.AiChatMessage;
|
|
|
|
|
import org.dromara.ai.domain.AiChatMessageDetail;
|
|
|
|
|
import org.dromara.ai.mapper.AiChatMessageDetailMapper;
|
|
|
|
|
@ -22,14 +24,28 @@ import org.dromara.system.api.model.LoginUser;
|
|
|
|
|
import org.springframework.beans.factory.annotation.Autowired;
|
|
|
|
|
import org.springframework.stereotype.Component;
|
|
|
|
|
import reactor.core.publisher.Flux;
|
|
|
|
|
import reactor.core.publisher.FluxSink;
|
|
|
|
|
import reactor.core.publisher.Mono;
|
|
|
|
|
|
|
|
|
|
import java.io.IOException;
|
|
|
|
|
import java.util.ArrayList;
|
|
|
|
|
import java.util.Collections;
|
|
|
|
|
import java.util.List;
|
|
|
|
|
import java.util.concurrent.atomic.AtomicBoolean;
|
|
|
|
|
import java.util.regex.Matcher;
|
|
|
|
|
import java.util.regex.Pattern;
|
|
|
|
|
|
|
|
|
|
import java.util.Arrays;
|
|
|
|
|
import java.lang.System;
|
|
|
|
|
|
|
|
|
|
import com.alibaba.dashscope.common.Message;
|
|
|
|
|
import com.alibaba.dashscope.common.Role;
|
|
|
|
|
import com.alibaba.dashscope.exception.ApiException;
|
|
|
|
|
import com.alibaba.dashscope.exception.InputRequiredException;
|
|
|
|
|
import com.alibaba.dashscope.exception.NoApiKeyException;
|
|
|
|
|
import io.reactivex.Flowable;
|
|
|
|
|
import io.reactivex.schedulers.Schedulers;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* @Author xins
|
|
|
|
|
* @Date 2025/8/15 11:00
|
|
|
|
|
@ -42,11 +58,7 @@ public class TongYiQianWenProcessor extends BaseAIProviderProcessor {
|
|
|
|
|
// 用于解析流式JSON块的模式
|
|
|
|
|
private static final Pattern JSON_PATTERN = Pattern.compile("\\{(?:[^{}]|\\{(?:[^{}]|\\{[^{}]*\\})*\\})*\\}");
|
|
|
|
|
|
|
|
|
|
private final String apiKey = "sk-e1df7a607644479e8ebad3be233ddafa";
|
|
|
|
|
private final String apiUrl = "https://api.t.com/v1/";
|
|
|
|
|
|
|
|
|
|
private static final String API_URL = "https://api.deepseek.com/v1/chat/completions";
|
|
|
|
|
private final String deepSeekChatModel = "deepseek-chat";
|
|
|
|
|
private static final String ALIYUN_MODEL_NAME = "qwen-plus";
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Autowired
|
|
|
|
|
@ -54,14 +66,57 @@ public class TongYiQianWenProcessor extends BaseAIProviderProcessor {
|
|
|
|
|
@Autowired
|
|
|
|
|
private AiChatMessageDetailMapper aiChatMessageDetailMapper;
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 普通http请求(非流式请求),直接返回完整回复
|
|
|
|
|
* @param url
|
|
|
|
|
* @param requestBody
|
|
|
|
|
* @param apiKey
|
|
|
|
|
* @return AIResponse
|
|
|
|
|
*/
|
|
|
|
|
@Override
|
|
|
|
|
public Mono<AIResponse> standardRequest(String url,String requestBody, String apiKey){
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 普通http请求(非流式请求),直接返回完整回复
|
|
|
|
|
* @param aiRequest
|
|
|
|
|
* @return AIResponse
|
|
|
|
|
*/
|
|
|
|
|
private Mono<AIResponse> aliStandardRequest(AIRequest aiRequest){
|
|
|
|
|
Generation gen = new Generation();
|
|
|
|
|
Message systemMsg = Message.builder()
|
|
|
|
|
.role(Role.SYSTEM.getValue())
|
|
|
|
|
.content("You are a helpful assistant.")
|
|
|
|
|
.build();
|
|
|
|
|
Message userMsg = Message.builder()
|
|
|
|
|
.role(Role.USER.getValue())
|
|
|
|
|
.content(aiRequest.getText())
|
|
|
|
|
.build();
|
|
|
|
|
GenerationParam param = GenerationParam.builder()
|
|
|
|
|
// 若没有配置环境变量,请用阿里云百炼API Key将下行替换为:.apiKey("sk-xxx")
|
|
|
|
|
.apiKey(aiRequest.getApiKey())
|
|
|
|
|
// 模型列表:https://help.aliyun.com/zh/model-studio/getting-started/models
|
|
|
|
|
.model(ALIYUN_MODEL_NAME)
|
|
|
|
|
.messages(Arrays.asList(systemMsg, userMsg))
|
|
|
|
|
.resultFormat(GenerationParam.ResultFormat.MESSAGE)
|
|
|
|
|
.build();
|
|
|
|
|
try {
|
|
|
|
|
GenerationResult generationResult = gen.call(param);
|
|
|
|
|
return Mono.just(extractAliAIResponse(generationResult));
|
|
|
|
|
} catch (NoApiKeyException e) {
|
|
|
|
|
throw new RuntimeException(e);
|
|
|
|
|
} catch (InputRequiredException e) {
|
|
|
|
|
throw new RuntimeException(e);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
throw new RuntimeException(e);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
public Mono<AIResponse> chatTest(AIRequest request) {
|
|
|
|
|
AIMessage aiMessage = new AIMessage();
|
|
|
|
|
aiMessage.setRole("user");
|
|
|
|
|
aiMessage.setContent("这是一个测试请求,请回复'测试成功'");
|
|
|
|
|
request.setMessages(Collections.singletonList(
|
|
|
|
|
aiMessage
|
|
|
|
|
));
|
|
|
|
|
request.setText("这是一个测试请求,请回复'测试成功'");
|
|
|
|
|
String apiEncryptFlag = request.getApiKeyEncryptFlag();
|
|
|
|
|
if(apiEncryptFlag.equals("1")){
|
|
|
|
|
request.setApiKey(EncryptUtils.decryptByBase64(request.getApiKey()));
|
|
|
|
|
@ -74,56 +129,89 @@ public class TongYiQianWenProcessor extends BaseAIProviderProcessor {
|
|
|
|
|
@Override
|
|
|
|
|
public Mono<AIResponse> chat(AIRequest request) {
|
|
|
|
|
try {
|
|
|
|
|
ObjectNode rootNode = objectMapper.createObjectNode();
|
|
|
|
|
rootNode.put("model", deepSeekChatModel);
|
|
|
|
|
rootNode.set("messages", objectMapper.valueToTree(request.getMessages()));
|
|
|
|
|
|
|
|
|
|
if (request.getTemperature() != null) {
|
|
|
|
|
rootNode.put("temperature", request.getTemperature());
|
|
|
|
|
}
|
|
|
|
|
if (request.getMaxTokens() != null) {
|
|
|
|
|
rootNode.put("max_tokens", request.getMaxTokens());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
String requestBody = objectMapper.writeValueAsString(rootNode);
|
|
|
|
|
return standardRequest(API_URL, requestBody, request.getApiKey());
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
configureApiKey(request);
|
|
|
|
|
return aliStandardRequest(request);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
return buildErrorResponse("构建请求失败: " + e.getMessage());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 创建通义千问流式调用
|
|
|
|
|
*/
|
|
|
|
|
private Flowable<GenerationResult> createStreamFlowable(AIRequest aiRequest)
|
|
|
|
|
throws ApiException, NoApiKeyException, InputRequiredException {
|
|
|
|
|
Generation gen = new Generation();
|
|
|
|
|
|
|
|
|
|
List<AIMessage> aiMessages = aiRequest.getMessages();
|
|
|
|
|
|
|
|
|
|
List<Message> messages = new ArrayList<>();
|
|
|
|
|
Message systemMsg = Message.builder()
|
|
|
|
|
.role(Role.SYSTEM.getValue())
|
|
|
|
|
.content("You are a helpful assistant.")
|
|
|
|
|
.build();
|
|
|
|
|
|
|
|
|
|
messages.add(systemMsg);
|
|
|
|
|
for(AIMessage aiMessage:aiMessages){
|
|
|
|
|
Message inputMessage = Message.builder()
|
|
|
|
|
.role(aiMessage.getRole())
|
|
|
|
|
.content(aiMessage.getContent())
|
|
|
|
|
.build();
|
|
|
|
|
messages.add(inputMessage);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GenerationParam param = GenerationParam.builder()
|
|
|
|
|
.apiKey(aiRequest.getApiKey())
|
|
|
|
|
.model(ALIYUN_MODEL_NAME)
|
|
|
|
|
.messages(messages)
|
|
|
|
|
.resultFormat(GenerationParam.ResultFormat.MESSAGE)
|
|
|
|
|
.build();
|
|
|
|
|
|
|
|
|
|
return gen.streamCall(param);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Flux<String> chatStreamContent(AIRequest request, LoginUser loginUser) {
|
|
|
|
|
try {
|
|
|
|
|
ObjectNode rootNode = objectMapper.createObjectNode();
|
|
|
|
|
rootNode.put("model", deepSeekChatModel);
|
|
|
|
|
rootNode.set("messages", objectMapper.valueToTree(request.getMessages()));
|
|
|
|
|
rootNode.put("stream", true);
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (request.getTemperature() != null) {
|
|
|
|
|
rootNode.put("temperature", request.getTemperature());
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// request.setApiKey();
|
|
|
|
|
// 执行流式请求并收集完整响应
|
|
|
|
|
StringBuilder fullResponseBuilder = new StringBuilder();
|
|
|
|
|
String requestBody = objectMapper.writeValueAsString(rootNode);
|
|
|
|
|
/**
|
|
|
|
|
* 从 GenerationResult 中提取内容
|
|
|
|
|
*/
|
|
|
|
|
private static String extractContent(GenerationResult result) {
|
|
|
|
|
if (result == null || result.getOutput() == null) {
|
|
|
|
|
return null;
|
|
|
|
|
// return executeStreamRequest(API_URL, requestBody, apiKey).doOnNext(chunk -> {
|
|
|
|
|
// // 收集每个chunk
|
|
|
|
|
// fullResponseBuilder.append(chunk);
|
|
|
|
|
// })
|
|
|
|
|
// .doOnComplete(() -> {
|
|
|
|
|
// // 流完成后保存到数据库
|
|
|
|
|
// saveChatMessage(request, fullResponseBuilder.toString());
|
|
|
|
|
// })
|
|
|
|
|
// .doOnError(error -> {
|
|
|
|
|
// // 错误处理
|
|
|
|
|
//// log.error("流式请求出错", error);
|
|
|
|
|
// });
|
|
|
|
|
} catch (IOException e) {
|
|
|
|
|
return Flux.error(new RuntimeException("构建请求失败: " + e.getMessage()));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
return result.getOutput()
|
|
|
|
|
.getChoices()
|
|
|
|
|
.get(0)
|
|
|
|
|
.getMessage()
|
|
|
|
|
.getContent();
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
return null;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
/**
|
|
|
|
|
* 检查是否生成完成
|
|
|
|
|
*/
|
|
|
|
|
private static boolean isFinished(GenerationResult result) {
|
|
|
|
|
if (result == null || result.getOutput() == null) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
String finishReason = result.getOutput()
|
|
|
|
|
.getChoices()
|
|
|
|
|
.get(0)
|
|
|
|
|
.getFinishReason();
|
|
|
|
|
return "stop".equals(finishReason);
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@ -162,6 +250,18 @@ public class TongYiQianWenProcessor extends BaseAIProviderProcessor {
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
protected AIResponse extractAliAIResponse(GenerationResult generationResult) throws Exception {
|
|
|
|
|
List<GenerationOutput.Choice> choices = generationResult.getOutput().getChoices();
|
|
|
|
|
String content = choices.get(0).getMessage().getContent();
|
|
|
|
|
GenerationUsage generationUsage = generationResult.getUsage();
|
|
|
|
|
TokenUsage tokenUsage = new TokenUsage(
|
|
|
|
|
generationUsage.getInputTokens().longValue(),generationUsage.getOutputTokens().longValue(),
|
|
|
|
|
generationUsage.getTotalTokens().longValue()
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
return new AIResponse(content, tokenUsage);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
protected AIResponse extractAIResponse(String json) throws Exception {
|
|
|
|
|
JsonNode node = objectMapper.readTree(json);
|
|
|
|
|
@ -264,7 +364,67 @@ public class TongYiQianWenProcessor extends BaseAIProviderProcessor {
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
public Flux<String> chatStream(AIRequest request, LoginUser loginUser) {
|
|
|
|
|
return null;
|
|
|
|
|
configureApiKey(request);
|
|
|
|
|
return Flux.create(sink -> {
|
|
|
|
|
try {
|
|
|
|
|
Flowable<GenerationResult> flowable = createStreamFlowable(request);
|
|
|
|
|
AtomicBoolean isCompleted = new AtomicBoolean(false);
|
|
|
|
|
// 用于收集完整响应和token信息
|
|
|
|
|
TokenUsage finalTokenUsage = new TokenUsage(0L, 0L, 0L);
|
|
|
|
|
flowable.subscribe(
|
|
|
|
|
// onNext
|
|
|
|
|
generationResult -> {
|
|
|
|
|
if (sink.isCancelled()) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
try {
|
|
|
|
|
String content = extractContent(generationResult);
|
|
|
|
|
if (content != null && !content.isEmpty()) {
|
|
|
|
|
sink.next(content);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// 检查是否结束
|
|
|
|
|
if (isFinished(generationResult)) {
|
|
|
|
|
if (!isCompleted.getAndSet(true)) {
|
|
|
|
|
sink.complete();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
GenerationUsage usage = generationResult.getUsage();
|
|
|
|
|
finalTokenUsage.setPromptToken(usage.getInputTokens().longValue());
|
|
|
|
|
finalTokenUsage.setCompletionToken(usage.getOutputTokens().longValue());
|
|
|
|
|
finalTokenUsage.setTotalToken(usage.getTotalTokens().longValue());
|
|
|
|
|
|
|
|
|
|
saveChatMessage(request, content, finalTokenUsage, loginUser);
|
|
|
|
|
}
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
sink.error(e);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// onError
|
|
|
|
|
error -> {
|
|
|
|
|
if (!isCompleted.getAndSet(true)) {
|
|
|
|
|
sink.error(error);
|
|
|
|
|
}
|
|
|
|
|
},
|
|
|
|
|
// onComplete
|
|
|
|
|
() -> {
|
|
|
|
|
if (!isCompleted.getAndSet(true)) {
|
|
|
|
|
sink.complete();
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
// 设置取消回调
|
|
|
|
|
sink.onCancel(() -> {
|
|
|
|
|
isCompleted.set(true);
|
|
|
|
|
// 可以在这里添加资源清理逻辑
|
|
|
|
|
});
|
|
|
|
|
|
|
|
|
|
} catch (Exception e) {
|
|
|
|
|
sink.error(e);
|
|
|
|
|
}
|
|
|
|
|
}, FluxSink.OverflowStrategy.BUFFER);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
@Override
|
|
|
|
|
|