Commit 0f73b61db203b9ab00597eec9265abb4368ff7ea
1 parent
de326117
增加阿里 通义千问
Showing
9 changed files
with
308 additions
and
2 deletions
yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
| @@ -108,6 +108,12 @@ | @@ -108,6 +108,12 @@ | ||
| 108 | <groupId>cn.hutool</groupId> | 108 | <groupId>cn.hutool</groupId> |
| 109 | <artifactId>hutool-all</artifactId> | 109 | <artifactId>hutool-all</artifactId> |
| 110 | </dependency> | 110 | </dependency> |
| 111 | + <!-- 阿里云 通义千问 --> | ||
| 112 | + <dependency> | ||
| 113 | + <groupId>com.aliyun</groupId> | ||
| 114 | + <artifactId>broadscope-bailian-sdk-java</artifactId> | ||
| 115 | + <version>1.3.0</version> | ||
| 116 | + </dependency> | ||
| 111 | </dependencies> | 117 | </dependencies> |
| 112 | 118 | ||
| 113 | </project> | 119 | </project> |
| 114 | \ No newline at end of file | 120 | \ No newline at end of file |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenApi.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ai.chatqianwen; | ||
| 2 | + | ||
| 3 | +import com.aliyun.broadscope.bailian.sdk.AccessTokenClient; | ||
| 4 | +import com.aliyun.broadscope.bailian.sdk.ApplicationClient; | ||
| 5 | +import com.aliyun.broadscope.bailian.sdk.models.ChatRequestMessage; | ||
| 6 | +import com.aliyun.broadscope.bailian.sdk.models.CompletionsRequest; | ||
| 7 | +import com.aliyun.broadscope.bailian.sdk.models.CompletionsResponse; | ||
| 8 | +import lombok.Getter; | ||
| 9 | +import org.springframework.http.HttpStatusCode; | ||
| 10 | +import org.springframework.http.ResponseEntity; | ||
| 11 | +import reactor.core.publisher.Flux; | ||
| 12 | + | ||
| 13 | +import java.util.List; | ||
| 14 | + | ||
| 15 | +/** | ||
| 16 | + * 阿里 通义千问 | ||
| 17 | + * | ||
| 18 | + * https://www.aliyun.com/search?k=%E9%80%9A%E4%B9%89%E5%A4%A7%E6%A8%A1%E5%9E%8B&scene=all | ||
| 19 | + * | ||
| 20 | + * author: fansili | ||
| 21 | + * time: 2024/3/13 21:09 | ||
| 22 | + */ | ||
| 23 | +@Getter | ||
| 24 | +public class QianWenApi { | ||
| 25 | + | ||
| 26 | + /** | ||
| 27 | + * accessKeyId、accessKeySecret、agentKey、appId 获取方式如下链接 | ||
| 28 | + * https://help.aliyun.com/document_detail/2587494.html?spm=a2c4g.2587492.0.0.53f33c566sXskp | ||
| 29 | + */ | ||
| 30 | + private String accessKeyId; | ||
| 31 | + private String accessKeySecret; | ||
| 32 | + private String agentKey; | ||
| 33 | + private String appId; | ||
| 34 | + private String endpoint = "bailian.cn-beijing.aliyuncs.com"; | ||
| 35 | + private String token; | ||
| 36 | + private ApplicationClient client; | ||
| 37 | + | ||
| 38 | + public QianWenApi(String accessKeyId, String accessKeySecret, String agentKey, String appId, String endpoint) { | ||
| 39 | + this.accessKeyId = accessKeyId; | ||
| 40 | + this.accessKeySecret = accessKeySecret; | ||
| 41 | + this.agentKey = agentKey; | ||
| 42 | + this.appId = appId; | ||
| 43 | + | ||
| 44 | + if (endpoint != null) { | ||
| 45 | + this.endpoint = endpoint; | ||
| 46 | + } | ||
| 47 | + | ||
| 48 | + // 获取token | ||
| 49 | + AccessTokenClient accessTokenClient = new AccessTokenClient(accessKeyId, accessKeySecret, agentKey); | ||
| 50 | + token = accessTokenClient.getToken(); | ||
| 51 | + // 构建client | ||
| 52 | + client = ApplicationClient.builder() | ||
| 53 | + .token(token) | ||
| 54 | + .build(); | ||
| 55 | + } | ||
| 56 | + | ||
| 57 | + public ResponseEntity<CompletionsResponse> chatCompletionEntity(ChatRequestMessage message) { | ||
| 58 | + // 创建request | ||
| 59 | + CompletionsRequest request = new CompletionsRequest() | ||
| 60 | + .setAppId(appId) | ||
| 61 | + .setMessages(List.of(message)) | ||
| 62 | + .setParameters(new CompletionsRequest.Parameter().setResultFormat("message")); | ||
| 63 | + // | ||
| 64 | + CompletionsResponse response = client.completions(request); | ||
| 65 | + int httpCode = 200; | ||
| 66 | + if (!response.isSuccess()) { | ||
| 67 | + System.out.printf("failed to create completion, requestId: %s, code: %s, message: %s\n", | ||
| 68 | + response.getRequestId(), response.getCode(), response.getMessage()); | ||
| 69 | + httpCode = 500; | ||
| 70 | + } | ||
| 71 | + return new ResponseEntity<>(response, HttpStatusCode.valueOf(httpCode)); | ||
| 72 | + } | ||
| 73 | + | ||
| 74 | + public Flux<CompletionsResponse> chatCompletionStream(ChatRequestMessage message) { | ||
| 75 | + return client.streamCompletions( | ||
| 76 | + new CompletionsRequest() | ||
| 77 | + .setAppId(appId) | ||
| 78 | + .setMessages(List.of(message)) | ||
| 79 | + .setParameters(new CompletionsRequest.Parameter().setIncrementalOutput(true)) | ||
| 80 | + ); | ||
| 81 | + } | ||
| 82 | +} |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatClient.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ai.chatqianwen; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.ai.chat.ChatClient; | ||
| 4 | +import cn.iocoder.yudao.framework.ai.chat.ChatResponse; | ||
| 5 | +import cn.iocoder.yudao.framework.ai.chat.Generation; | ||
| 6 | +import cn.iocoder.yudao.framework.ai.chat.StreamingChatClient; | ||
| 7 | +import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; | ||
| 8 | +import cn.iocoder.yudao.framework.ai.chatqianwen.api.QianWenChatCompletionMessage; | ||
| 9 | +import cn.iocoder.yudao.framework.ai.chatqianwen.api.QianWenChatCompletionRequest; | ||
| 10 | +import cn.iocoder.yudao.framework.ai.chatyiyan.api.YiYanChatCompletion; | ||
| 11 | +import cn.iocoder.yudao.framework.ai.chatyiyan.api.YiYanChatCompletionRequest; | ||
| 12 | +import cn.iocoder.yudao.framework.ai.chatyiyan.exception.YiYanApiException; | ||
| 13 | +import cn.iocoder.yudao.framework.ai.model.function.AbstractFunctionCallSupport; | ||
| 14 | +import cn.iocoder.yudao.framework.ai.model.function.FunctionCallbackContext; | ||
| 15 | +import com.aliyun.broadscope.bailian.sdk.models.ChatRequestMessage; | ||
| 16 | +import com.aliyun.broadscope.bailian.sdk.models.ChatUserMessage; | ||
| 17 | +import com.aliyun.broadscope.bailian.sdk.models.CompletionsResponse; | ||
| 18 | +import lombok.extern.slf4j.Slf4j; | ||
| 19 | +import org.springframework.http.ResponseEntity; | ||
| 20 | +import org.springframework.retry.RetryCallback; | ||
| 21 | +import org.springframework.retry.RetryContext; | ||
| 22 | +import org.springframework.retry.RetryListener; | ||
| 23 | +import org.springframework.retry.support.RetryTemplate; | ||
| 24 | +import reactor.core.publisher.Flux; | ||
| 25 | + | ||
| 26 | +import java.time.Duration; | ||
| 27 | +import java.util.List; | ||
| 28 | +import java.util.stream.Collectors; | ||
| 29 | + | ||
| 30 | +/** | ||
| 31 | + * 阿里 通义千问 client | ||
| 32 | + * | ||
| 33 | + * 文档地址:https://help.aliyun.com/document_detail/2587494.html?spm=a2c4g.2587492.0.0.53f33c566sXskp | ||
| 34 | + * | ||
| 35 | + * author: fansili | ||
| 36 | + * time: 2024/3/13 21:06 | ||
| 37 | + */ | ||
| 38 | +@Slf4j | ||
| 39 | +public class QianWenChatClient extends AbstractFunctionCallSupport<QianWenChatCompletionMessage, ChatRequestMessage, ResponseEntity<CompletionsResponse>> | ||
| 40 | + implements ChatClient, StreamingChatClient { | ||
| 41 | + | ||
| 42 | + private QianWenApi qianWenApi; | ||
| 43 | + | ||
| 44 | + public QianWenChatClient(QianWenApi qianWenApi) { | ||
| 45 | + super(null); | ||
| 46 | + this.qianWenApi = qianWenApi; | ||
| 47 | + } | ||
| 48 | + | ||
| 49 | + public final RetryTemplate retryTemplate = RetryTemplate.builder() | ||
| 50 | + // 最大重试次数 10 | ||
| 51 | + .maxAttempts(10) | ||
| 52 | + .retryOn(YiYanApiException.class) | ||
| 53 | + // 最大重试5次,第一次间隔3000ms,第二次3000ms * 2,第三次3000ms * 3,以此类推,最大间隔3 * 60000ms | ||
| 54 | + .exponentialBackoff(Duration.ofMillis(3000), 2, Duration.ofMillis(3 * 60000)) | ||
| 55 | + .withListener(new RetryListener() { | ||
| 56 | + @Override | ||
| 57 | + public <T extends Object, E extends Throwable> void onError(RetryContext context, | ||
| 58 | + RetryCallback<T, E> callback, Throwable throwable) { | ||
| 59 | + log.warn("重试异常:" + context.getRetryCount(), throwable); | ||
| 60 | + }; | ||
| 61 | + }) | ||
| 62 | + .build(); | ||
| 63 | + | ||
| 64 | + public QianWenChatClient(FunctionCallbackContext functionCallbackContext) { | ||
| 65 | + super(functionCallbackContext); | ||
| 66 | + } | ||
| 67 | + | ||
| 68 | + @Override | ||
| 69 | + public ChatResponse call(Prompt prompt) { | ||
| 70 | + return this.retryTemplate.execute(ctx -> { | ||
| 71 | + // ctx 会有重试的信息 | ||
| 72 | + // 创建 request 请求,stream模式需要供应商支持 | ||
| 73 | + ChatRequestMessage request = this.createRequest(prompt, false); | ||
| 74 | + // 调用 callWithFunctionSupport 发送请求 | ||
| 75 | + ResponseEntity<CompletionsResponse> responseEntity = this.callWithFunctionSupport(request); | ||
| 76 | + // 获取结果封装 chatCompletion | ||
| 77 | + CompletionsResponse response = responseEntity.getBody(); | ||
| 78 | + if (!response.isSuccess()) { | ||
| 79 | + return new ChatResponse(List.of(new Generation(String.format("failed to create completion, requestId: %s, code: %s, message: %s\n", | ||
| 80 | + response.getRequestId(), response.getCode(), response.getMessage())))); | ||
| 81 | + } | ||
| 82 | + List<Generation> generations = response.getData().getChoices().stream() | ||
| 83 | + .map(item -> new Generation(item.getMessage().getContent())).collect(Collectors.toList()); | ||
| 84 | + return new ChatResponse(generations); | ||
| 85 | + }); | ||
| 86 | + } | ||
| 87 | + | ||
| 88 | + private ChatRequestMessage createRequest(Prompt prompt, boolean b) { | ||
| 89 | + return new ChatUserMessage(prompt.getContents()); | ||
| 90 | + } | ||
| 91 | + | ||
| 92 | + @Override | ||
| 93 | + public Flux<ChatResponse> stream(Prompt prompt) { | ||
| 94 | + // ctx 会有重试的信息 | ||
| 95 | + // 创建 request 请求,stream模式需要供应商支持 | ||
| 96 | + ChatRequestMessage request = this.createRequest(prompt, true); | ||
| 97 | + // 调用 callWithFunctionSupport 发送请求 | ||
| 98 | + Flux<CompletionsResponse> response = this.qianWenApi.chatCompletionStream(request); | ||
| 99 | + return response.map(res -> { | ||
| 100 | + return new ChatResponse(List.of(new Generation(res.getData().getText()))); | ||
| 101 | + }); | ||
| 102 | + } | ||
| 103 | + | ||
| 104 | + @Override | ||
| 105 | + protected QianWenChatCompletionRequest doCreateToolResponseRequest(ChatRequestMessage previousRequest, QianWenChatCompletionMessage responseMessage, List<QianWenChatCompletionMessage> conversationHistory) { | ||
| 106 | + return null; | ||
| 107 | + } | ||
| 108 | + | ||
| 109 | + @Override | ||
| 110 | + protected List<QianWenChatCompletionMessage> doGetUserMessages(ChatRequestMessage request) { | ||
| 111 | + return null; | ||
| 112 | + } | ||
| 113 | + | ||
| 114 | + @Override | ||
| 115 | + protected QianWenChatCompletionMessage doGetToolResponseMessage(ResponseEntity<CompletionsResponse> response) { | ||
| 116 | + return null; | ||
| 117 | + } | ||
| 118 | + | ||
| 119 | + @Override | ||
| 120 | + protected ResponseEntity<CompletionsResponse> doChatCompletion(ChatRequestMessage request) { | ||
| 121 | + return qianWenApi.chatCompletionEntity(request); | ||
| 122 | + } | ||
| 123 | + | ||
| 124 | + @Override | ||
| 125 | + protected boolean isToolFunctionCall(ResponseEntity<CompletionsResponse> response) { | ||
| 126 | + return false; | ||
| 127 | + } | ||
| 128 | +} |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletion.java
0 → 100644
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletionMessage.java
0 → 100644
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenChatCompletionRequest.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ai.chatqianwen.api; | ||
| 2 | + | ||
| 3 | +import com.aliyun.broadscope.bailian.sdk.models.ChatRequestMessage; | ||
| 4 | +import com.aliyun.broadscope.bailian.sdk.models.ChatUserMessage; | ||
| 5 | + | ||
| 6 | +/** | ||
| 7 | + * author: fansili | ||
| 8 | + * time: 2024/3/13 21:07 | ||
| 9 | + */ | ||
| 10 | +public class QianWenChatCompletionRequest extends ChatRequestMessage { | ||
| 11 | + | ||
| 12 | +} |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/package-info.java
0 → 100644
| 1 | +/** | ||
| 2 | + * 阿里的 通义千问 | ||
| 3 | + * | ||
| 4 | + * 链接:https://www.aliyun.com/search?k=%E9%80%9A%E4%B9%89%E5%A4%A7%E6%A8%A1%E5%9E%8B&scene=all | ||
| 5 | + * | ||
| 6 | + * author: fansili | ||
| 7 | + * time: 2024/3/13 21:05 | ||
| 8 | + */ | ||
| 9 | +package cn.iocoder.yudao.framework.ai.chatqianwen; | ||
| 0 | \ No newline at end of file | 10 | \ No newline at end of file |
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 { | @@ -132,8 +132,6 @@ public class XingHuoApi { | ||
| 132 | } catch (NoSuchAlgorithmException | InvalidKeyException e) { | 132 | } catch (NoSuchAlgorithmException | InvalidKeyException e) { |
| 133 | throw new RuntimeException(e); | 133 | throw new RuntimeException(e); |
| 134 | } | 134 | } |
| 135 | - System.err.println(authUrl); | ||
| 136 | - System.err.println(JSONUtil.toJsonPrettyStr(request)); | ||
| 137 | // wss 请求的 URI | 135 | // wss 请求的 URI |
| 138 | URI uri = URI.create(authUrl); | 136 | URI uri = URI.create(authUrl); |
| 139 | // 发起 wss 请求并处理响应 | 137 | // 发起 wss 请求并处理响应 |
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/QianWenChatClientTests.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ai.chat; | ||
| 2 | + | ||
| 3 | +import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; | ||
| 4 | +import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenApi; | ||
| 5 | +import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenChatClient; | ||
| 6 | +import org.junit.Before; | ||
| 7 | +import org.junit.Test; | ||
| 8 | +import reactor.core.publisher.Flux; | ||
| 9 | + | ||
| 10 | +import java.util.Scanner; | ||
| 11 | +import java.util.function.Consumer; | ||
| 12 | + | ||
| 13 | +/** | ||
| 14 | + * author: fansili | ||
| 15 | + * time: 2024/3/13 21:37 | ||
| 16 | + */ | ||
| 17 | +public class QianWenChatClientTests { | ||
| 18 | + | ||
| 19 | + private QianWenChatClient qianWenChatClient; | ||
| 20 | + | ||
| 21 | + @Before | ||
| 22 | + public void setup() { | ||
| 23 | + QianWenApi qianWenApi = new QianWenApi( | ||
| 24 | + "", | ||
| 25 | + "", | ||
| 26 | + "", | ||
| 27 | + "", | ||
| 28 | + null | ||
| 29 | + ); | ||
| 30 | + qianWenChatClient = new QianWenChatClient(qianWenApi); | ||
| 31 | + } | ||
| 32 | + | ||
| 33 | + @Test | ||
| 34 | + public void callTest() { | ||
| 35 | + ChatResponse call = qianWenChatClient.call(new Prompt("Java语言怎么样?")); | ||
| 36 | + System.err.println(call.getResult()); | ||
| 37 | + } | ||
| 38 | + | ||
| 39 | + @Test | ||
| 40 | + public void streamTest() { | ||
| 41 | + Flux<ChatResponse> flux = qianWenChatClient.stream(new Prompt("Java语言怎么样?")); | ||
| 42 | + flux.subscribe(new Consumer<ChatResponse>() { | ||
| 43 | + @Override | ||
| 44 | + public void accept(ChatResponse chatResponse) { | ||
| 45 | + System.err.print(chatResponse.getResult().getOutput().getContent()); | ||
| 46 | + } | ||
| 47 | + }); | ||
| 48 | + | ||
| 49 | + // 阻止退出 | ||
| 50 | + Scanner scanner = new Scanner(System.in); | ||
| 51 | + scanner.nextLine(); | ||
| 52 | + } | ||
| 53 | +} |