diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml index 862f7fe8d..276e87733 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml @@ -108,6 +108,12 @@ cn.hutool hutool-all + + + com.aliyun + broadscope-bailian-sdk-java + 1.3.0 + \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenApi.java new file mode 100644 index 000000000..dbf6a7d70 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenApi.java @@ -0,0 +1,82 @@ +package cn.iocoder.yudao.framework.ai.chatqianwen; + +import com.aliyun.broadscope.bailian.sdk.AccessTokenClient; +import com.aliyun.broadscope.bailian.sdk.ApplicationClient; +import com.aliyun.broadscope.bailian.sdk.models.ChatRequestMessage; +import com.aliyun.broadscope.bailian.sdk.models.CompletionsRequest; +import com.aliyun.broadscope.bailian.sdk.models.CompletionsResponse; +import lombok.Getter; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import reactor.core.publisher.Flux; + +import java.util.List; + +/** + * 阿里 通义千问 + * + * https://www.aliyun.com/search?k=%E9%80%9A%E4%B9%89%E5%A4%A7%E6%A8%A1%E5%9E%8B&scene=all + * + * author: fansili + * time: 2024/3/13 21:09 + */ +@Getter +public class QianWenApi { + + /** + * accessKeyId、accessKeySecret、agentKey、appId 获取方式如下链接 + * https://help.aliyun.com/document_detail/2587494.html?spm=a2c4g.2587492.0.0.53f33c566sXskp + */ + private String accessKeyId; + private String accessKeySecret; + private String agentKey; + private String appId; + private String endpoint = "bailian.cn-beijing.aliyuncs.com"; + private String token; + private ApplicationClient client; + + public QianWenApi(String accessKeyId, String accessKeySecret, String agentKey, String appId, String endpoint) { + this.accessKeyId = accessKeyId; + this.accessKeySecret = accessKeySecret; + this.agentKey = agentKey; + this.appId = appId; + + if (endpoint != null) { + this.endpoint = endpoint; + } + + // 获取token + AccessTokenClient accessTokenClient = new AccessTokenClient(accessKeyId, accessKeySecret, agentKey); + token = accessTokenClient.getToken(); + // 构建client + client = ApplicationClient.builder() + .token(token) + .build(); + } + + public ResponseEntity chatCompletionEntity(ChatRequestMessage message) { + // 创建request + CompletionsRequest request = new CompletionsRequest() + .setAppId(appId) + .setMessages(List.of(message)) + .setParameters(new CompletionsRequest.Parameter().setResultFormat("message")); + // + CompletionsResponse response = client.completions(request); + int httpCode = 200; + if (!response.isSuccess()) { + System.out.printf("failed to create completion, requestId: %s, code: %s, message: %s\n", + response.getRequestId(), response.getCode(), response.getMessage()); + httpCode = 500; + } + return new ResponseEntity<>(response, HttpStatusCode.valueOf(httpCode)); + } + + public Flux chatCompletionStream(ChatRequestMessage message) { + return client.streamCompletions( + new CompletionsRequest() + .setAppId(appId) + .setMessages(List.of(message)) + .setParameters(new CompletionsRequest.Parameter().setIncrementalOutput(true)) + ); + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatClient.java new file mode 100644 index 000000000..7b4a60baf --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatClient.java @@ -0,0 +1,128 @@ +package cn.iocoder.yudao.framework.ai.chatqianwen; + +import cn.iocoder.yudao.framework.ai.chat.ChatClient; +import cn.iocoder.yudao.framework.ai.chat.ChatResponse; +import cn.iocoder.yudao.framework.ai.chat.Generation; +import cn.iocoder.yudao.framework.ai.chat.StreamingChatClient; +import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; +import cn.iocoder.yudao.framework.ai.chatqianwen.api.QianWenChatCompletionMessage; +import cn.iocoder.yudao.framework.ai.chatqianwen.api.QianWenChatCompletionRequest; +import cn.iocoder.yudao.framework.ai.chatyiyan.api.YiYanChatCompletion; +import cn.iocoder.yudao.framework.ai.chatyiyan.api.YiYanChatCompletionRequest; +import cn.iocoder.yudao.framework.ai.chatyiyan.exception.YiYanApiException; +import cn.iocoder.yudao.framework.ai.model.function.AbstractFunctionCallSupport; +import cn.iocoder.yudao.framework.ai.model.function.FunctionCallbackContext; +import com.aliyun.broadscope.bailian.sdk.models.ChatRequestMessage; +import com.aliyun.broadscope.bailian.sdk.models.ChatUserMessage; +import com.aliyun.broadscope.bailian.sdk.models.CompletionsResponse; +import lombok.extern.slf4j.Slf4j; +import org.springframework.http.ResponseEntity; +import org.springframework.retry.RetryCallback; +import org.springframework.retry.RetryContext; +import org.springframework.retry.RetryListener; +import org.springframework.retry.support.RetryTemplate; +import reactor.core.publisher.Flux; + +import java.time.Duration; +import java.util.List; +import java.util.stream.Collectors; + +/** + * 阿里 通义千问 client + * + * 文档地址:https://help.aliyun.com/document_detail/2587494.html?spm=a2c4g.2587492.0.0.53f33c566sXskp + * + * author: fansili + * time: 2024/3/13 21:06 + */ +@Slf4j +public class QianWenChatClient extends AbstractFunctionCallSupport> + implements ChatClient, StreamingChatClient { + + private QianWenApi qianWenApi; + + public QianWenChatClient(QianWenApi qianWenApi) { + super(null); + this.qianWenApi = qianWenApi; + } + + public final RetryTemplate retryTemplate = RetryTemplate.builder() + // 最大重试次数 10 + .maxAttempts(10) + .retryOn(YiYanApiException.class) + // 最大重试5次,第一次间隔3000ms,第二次3000ms * 2,第三次3000ms * 3,以此类推,最大间隔3 * 60000ms + .exponentialBackoff(Duration.ofMillis(3000), 2, Duration.ofMillis(3 * 60000)) + .withListener(new RetryListener() { + @Override + public void onError(RetryContext context, + RetryCallback callback, Throwable throwable) { + log.warn("重试异常:" + context.getRetryCount(), throwable); + }; + }) + .build(); + + public QianWenChatClient(FunctionCallbackContext functionCallbackContext) { + super(functionCallbackContext); + } + + @Override + public ChatResponse call(Prompt prompt) { + return this.retryTemplate.execute(ctx -> { + // ctx 会有重试的信息 + // 创建 request 请求,stream模式需要供应商支持 + ChatRequestMessage request = this.createRequest(prompt, false); + // 调用 callWithFunctionSupport 发送请求 + ResponseEntity responseEntity = this.callWithFunctionSupport(request); + // 获取结果封装 chatCompletion + CompletionsResponse response = responseEntity.getBody(); + if (!response.isSuccess()) { + return new ChatResponse(List.of(new Generation(String.format("failed to create completion, requestId: %s, code: %s, message: %s\n", + response.getRequestId(), response.getCode(), response.getMessage())))); + } + List generations = response.getData().getChoices().stream() + .map(item -> new Generation(item.getMessage().getContent())).collect(Collectors.toList()); + return new ChatResponse(generations); + }); + } + + private ChatRequestMessage createRequest(Prompt prompt, boolean b) { + return new ChatUserMessage(prompt.getContents()); + } + + @Override + public Flux stream(Prompt prompt) { + // ctx 会有重试的信息 + // 创建 request 请求,stream模式需要供应商支持 + ChatRequestMessage request = this.createRequest(prompt, true); + // 调用 callWithFunctionSupport 发送请求 + Flux response = this.qianWenApi.chatCompletionStream(request); + return response.map(res -> { + return new ChatResponse(List.of(new Generation(res.getData().getText()))); + }); + } + + @Override + protected QianWenChatCompletionRequest doCreateToolResponseRequest(ChatRequestMessage previousRequest, QianWenChatCompletionMessage responseMessage, List conversationHistory) { + return null; + } + + @Override + protected List doGetUserMessages(ChatRequestMessage request) { + return null; + } + + @Override + protected QianWenChatCompletionMessage doGetToolResponseMessage(ResponseEntity response) { + return null; + } + + @Override + protected ResponseEntity doChatCompletion(ChatRequestMessage request) { + return qianWenApi.chatCompletionEntity(request); + } + + @Override + protected boolean isToolFunctionCall(ResponseEntity response) { + return false; + } +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletion.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletion.java new file mode 100644 index 000000000..4e646748e --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletion.java @@ -0,0 +1,10 @@ +package cn.iocoder.yudao.framework.ai.chatqianwen.api; + +import com.aliyun.broadscope.bailian.sdk.models.CompletionsResponse; + +/** + * author: fansili + * time: 2024/3/13 21:07 + */ +public class QianWenChatCompletion extends CompletionsResponse { +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletionMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletionMessage.java new file mode 100644 index 000000000..07680cbf4 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletionMessage.java @@ -0,0 +1,8 @@ +package cn.iocoder.yudao.framework.ai.chatqianwen.api; + +/** + * author: fansili + * time: 2024/3/13 21:07 + */ +public class QianWenChatCompletionMessage { +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletionRequest.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletionRequest.java new file mode 100644 index 000000000..c3e27da62 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletionRequest.java @@ -0,0 +1,12 @@ +package cn.iocoder.yudao.framework.ai.chatqianwen.api; + +import com.aliyun.broadscope.bailian.sdk.models.ChatRequestMessage; +import com.aliyun.broadscope.bailian.sdk.models.ChatUserMessage; + +/** + * author: fansili + * time: 2024/3/13 21:07 + */ +public class QianWenChatCompletionRequest extends ChatRequestMessage { + +} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/package-info.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/package-info.java new file mode 100644 index 000000000..eb46035e0 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/package-info.java @@ -0,0 +1,9 @@ +/** + * 阿里的 通义千问 + * + * 链接:https://www.aliyun.com/search?k=%E9%80%9A%E4%B9%89%E5%A4%A7%E6%A8%A1%E5%9E%8B&scene=all + * + * author: fansili + * time: 2024/3/13 21:05 + */ +package cn.iocoder.yudao.framework.ai.chatqianwen; \ No newline at end of file diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoApi.java index 58c21349b..a053884a2 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoApi.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoApi.java @@ -132,8 +132,6 @@ public class XingHuoApi { } catch (NoSuchAlgorithmException | InvalidKeyException e) { throw new RuntimeException(e); } - System.err.println(authUrl); - System.err.println(JSONUtil.toJsonPrettyStr(request)); // wss 请求的 URI URI uri = URI.create(authUrl); // 发起 wss 请求并处理响应 diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/QianWenChatClientTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/QianWenChatClientTests.java new file mode 100644 index 000000000..745af8263 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/QianWenChatClientTests.java @@ -0,0 +1,53 @@ +package cn.iocoder.yudao.framework.ai.chat; + +import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; +import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenApi; +import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenChatClient; +import org.junit.Before; +import org.junit.Test; +import reactor.core.publisher.Flux; + +import java.util.Scanner; +import java.util.function.Consumer; + +/** + * author: fansili + * time: 2024/3/13 21:37 + */ +public class QianWenChatClientTests { + + private QianWenChatClient qianWenChatClient; + + @Before + public void setup() { + QianWenApi qianWenApi = new QianWenApi( + "", + "", + "", + "", + null + ); + qianWenChatClient = new QianWenChatClient(qianWenApi); + } + + @Test + public void callTest() { + ChatResponse call = qianWenChatClient.call(new Prompt("Java语言怎么样?")); + System.err.println(call.getResult()); + } + + @Test + public void streamTest() { + Flux flux = qianWenChatClient.stream(new Prompt("Java语言怎么样?")); + flux.subscribe(new Consumer() { + @Override + public void accept(ChatResponse chatResponse) { + System.err.print(chatResponse.getResult().getOutput().getContent()); + } + }); + + // 阻止退出 + Scanner scanner = new Scanner(System.in); + scanner.nextLine(); + } +}