Ver Fonte

增加阿里 通义千问

cherishsince há 1 ano atrás
pai
commit
0f73b61db2

+ 6 - 0
yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml

@@ -108,6 +108,12 @@
             <groupId>cn.hutool</groupId>
             <artifactId>hutool-all</artifactId>
         </dependency>
+        <!--    阿里云 通义千问    -->
+        <dependency>
+            <groupId>com.aliyun</groupId>
+            <artifactId>broadscope-bailian-sdk-java</artifactId>
+            <version>1.3.0</version>
+        </dependency>
     </dependencies>
 
 </project>

+ 82 - 0
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<CompletionsResponse> 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<CompletionsResponse> chatCompletionStream(ChatRequestMessage message) {
+        return client.streamCompletions(
+                new CompletionsRequest()
+                        .setAppId(appId)
+                        .setMessages(List.of(message))
+                        .setParameters(new CompletionsRequest.Parameter().setIncrementalOutput(true))
+        );
+    }
+}

+ 128 - 0
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<QianWenChatCompletionMessage, ChatRequestMessage, ResponseEntity<CompletionsResponse>>
+        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 <T extends Object, E extends Throwable> void onError(RetryContext context,
+                                                                            RetryCallback<T, E> 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<CompletionsResponse> 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<Generation> 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<ChatResponse> stream(Prompt prompt) {
+        // ctx 会有重试的信息
+        // 创建 request 请求,stream模式需要供应商支持
+        ChatRequestMessage request = this.createRequest(prompt, true);
+        // 调用 callWithFunctionSupport 发送请求
+        Flux<CompletionsResponse> 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<QianWenChatCompletionMessage> conversationHistory) {
+        return null;
+    }
+
+    @Override
+    protected List<QianWenChatCompletionMessage> doGetUserMessages(ChatRequestMessage request) {
+        return null;
+    }
+
+    @Override
+    protected QianWenChatCompletionMessage doGetToolResponseMessage(ResponseEntity<CompletionsResponse> response) {
+        return null;
+    }
+
+    @Override
+    protected ResponseEntity<CompletionsResponse> doChatCompletion(ChatRequestMessage request) {
+        return qianWenApi.chatCompletionEntity(request);
+    }
+
+    @Override
+    protected boolean isToolFunctionCall(ResponseEntity<CompletionsResponse> response) {
+        return false;
+    }
+}

+ 10 - 0
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 {
+}

+ 8 - 0
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 {
+}

+ 12 - 0
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 {
+
+}

+ 9 - 0
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;

+ 0 - 2
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 请求并处理响应

+ 53 - 0
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<ChatResponse> flux = qianWenChatClient.stream(new Prompt("Java语言怎么样?"));
+        flux.subscribe(new Consumer<ChatResponse>() {
+            @Override
+            public void accept(ChatResponse chatResponse) {
+                System.err.print(chatResponse.getResult().getOutput().getContent());
+            }
+        });
+
+        // 阻止退出
+        Scanner scanner = new Scanner(System.in);
+        scanner.nextLine();
+    }
+}