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();
+ }
+}