Commit 63c5f90596bcdd66e735a6559721c1f5a670388c
1 parent
2ef64a0a
【优化】兼容阿里云千问开源模型,和付费模型
Showing
8 changed files
with
121 additions
and
49 deletions
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatClient.java
| ... | ... | @@ -4,13 +4,14 @@ import cn.iocoder.yudao.framework.ai.chat.*; |
| 4 | 4 | import cn.iocoder.yudao.framework.ai.chat.prompt.ChatOptions; |
| 5 | 5 | import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; |
| 6 | 6 | import cn.iocoder.yudao.framework.ai.chatqianwen.api.QianWenApi; |
| 7 | -import cn.iocoder.yudao.framework.ai.chatqianwen.api.QianWenChatCompletionRequest; | |
| 8 | 7 | import cn.iocoder.yudao.framework.ai.chatyiyan.exception.YiYanApiException; |
| 9 | 8 | import com.alibaba.dashscope.aigc.generation.GenerationResult; |
| 10 | 9 | import com.alibaba.dashscope.aigc.generation.models.QwenParam; |
| 11 | 10 | import com.alibaba.dashscope.common.Message; |
| 11 | +import com.google.common.collect.Lists; | |
| 12 | 12 | import io.reactivex.Flowable; |
| 13 | 13 | import lombok.extern.slf4j.Slf4j; |
| 14 | +import org.jetbrains.annotations.NotNull; | |
| 14 | 15 | import org.springframework.http.ResponseEntity; |
| 15 | 16 | import org.springframework.retry.RetryCallback; |
| 16 | 17 | import org.springframework.retry.RetryContext; |
| ... | ... | @@ -71,7 +72,7 @@ public class QianWenChatClient implements ChatClient, StreamingChatClient { |
| 71 | 72 | return this.retryTemplate.execute(ctx -> { |
| 72 | 73 | // ctx 会有重试的信息 |
| 73 | 74 | // 创建 request 请求,stream模式需要供应商支持 |
| 74 | - QianWenChatCompletionRequest request = this.createRequest(prompt, false); | |
| 75 | + QwenParam request = this.createRequest(prompt, false); | |
| 75 | 76 | // 调用 callWithFunctionSupport 发送请求 |
| 76 | 77 | ResponseEntity<GenerationResult> responseEntity = qianWenApi.chatCompletionEntity(request); |
| 77 | 78 | // 获取结果封装 chatCompletion |
| ... | ... | @@ -81,11 +82,41 @@ public class QianWenChatClient implements ChatClient, StreamingChatClient { |
| 81 | 82 | // response.getRequestId(), response.getCode(), response.getMessage())))); |
| 82 | 83 | // } |
| 83 | 84 | // 转换为 Generation 返回 |
| 84 | - return new ChatResponse(List.of(new Generation(response.getOutput().getText()))); | |
| 85 | + return new ChatResponse(response.getOutput().getChoices().stream() | |
| 86 | + .map(choices -> new Generation(choices.getMessage().getContent())) | |
| 87 | + .collect(Collectors.toList())); | |
| 85 | 88 | }); |
| 86 | 89 | } |
| 87 | 90 | |
| 88 | - private QianWenChatCompletionRequest createRequest(Prompt prompt, boolean stream) { | |
| 91 | + private QwenParam createRequest(Prompt prompt, boolean stream) { | |
| 92 | + // 获取 ChatOptions | |
| 93 | + QianWenOptions chatOptions = getChatOptions(prompt); | |
| 94 | + // | |
| 95 | + List<Message> messageList = Lists.newArrayList(); | |
| 96 | + prompt.getInstructions().stream().forEach(instruction -> { | |
| 97 | + Message message = new Message(); | |
| 98 | + message.setRole(instruction.getMessageType().getValue()); | |
| 99 | + message.setContent(instruction.getContent()); | |
| 100 | + messageList.add(message); | |
| 101 | + }); | |
| 102 | + return QwenParam.builder() | |
| 103 | + .model(qianWenApi.getQianWenChatModal().getValue()) | |
| 104 | + .prompt(prompt.getContents()) | |
| 105 | + .messages(messageList) | |
| 106 | + .maxTokens(chatOptions.getMaxTokens()) | |
| 107 | + .resultFormat(QwenParam.ResultFormat.MESSAGE) | |
| 108 | + .topP(Double.valueOf(chatOptions.getTopP())) | |
| 109 | + .topK(chatOptions.getTopK()) | |
| 110 | + .temperature(chatOptions.getTemperature()) | |
| 111 | + // 控制流式输出模式,即后面的内容会包含已经输出的内容;设置为True,将开启增量输出模式,后面的输出不会包含已经输出的内容,您需要自行拼接整体输出 | |
| 112 | + .incrementalOutput(true) | |
| 113 | + /* set the random seed, optional, default to 1234 if not set */ | |
| 114 | + .seed(100) | |
| 115 | + .apiKey(qianWenApi.getApiKey()) | |
| 116 | + .build(); | |
| 117 | + } | |
| 118 | + | |
| 119 | + private @NotNull QianWenOptions getChatOptions(Prompt prompt) { | |
| 89 | 120 | // 两个都为null 则没有配置文件 |
| 90 | 121 | if (qianWenOptions == null && prompt.getOptions() == null) { |
| 91 | 122 | throw new ChatException("ChatOptions 未配置参数!"); |
| ... | ... | @@ -96,37 +127,27 @@ public class QianWenChatClient implements ChatClient, StreamingChatClient { |
| 96 | 127 | options = (ChatOptions) prompt.getOptions(); |
| 97 | 128 | } |
| 98 | 129 | // Prompt 里面是一个 ChatOptions,用户可以随意传入,这里做一下判断 |
| 99 | - if (!(options instanceof QianWenOptions qianWenOptions)) { | |
| 130 | + if (!(options instanceof QianWenOptions)) { | |
| 100 | 131 | throw new ChatException("Prompt 传入的不是 QianWenOptions!"); |
| 101 | 132 | } |
| 102 | - return (QianWenChatCompletionRequest) QianWenChatCompletionRequest.builder() | |
| 103 | - .model(qianWenApi.getQianWenChatModal().getValue()) | |
| 104 | - .apiKey(qianWenApi.getApiKey()) | |
| 105 | - .messages(prompt.getInstructions().stream().map(m -> { | |
| 106 | - Message message = new Message(); | |
| 107 | - message.setRole(m.getMessageType().getValue()); | |
| 108 | - message.setContent(m.getContent()); | |
| 109 | - return message; | |
| 110 | - }).collect(Collectors.toList())) | |
| 111 | - .resultFormat(QwenParam.ResultFormat.MESSAGE) | |
| 112 | - // 动态改变的三个参数 | |
| 113 | - .topP(Double.valueOf(qianWenOptions.getTopP())) | |
| 114 | - .topK(qianWenOptions.getTopK()) | |
| 115 | - .temperature(qianWenOptions.getTemperature()) | |
| 116 | - .incrementalOutput(true) | |
| 117 | - .build(); | |
| 133 | + return (QianWenOptions) options; | |
| 118 | 134 | } |
| 119 | 135 | |
| 120 | 136 | @Override |
| 121 | 137 | public Flux<ChatResponse> stream(Prompt prompt) { |
| 122 | 138 | // ctx 会有重试的信息 |
| 123 | 139 | // 创建 request 请求,stream模式需要供应商支持 |
| 124 | - QianWenChatCompletionRequest request = this.createRequest(prompt, true); | |
| 140 | + QwenParam request = this.createRequest(prompt, true); | |
| 125 | 141 | // 调用 callWithFunctionSupport 发送请求 |
| 126 | 142 | Flowable<GenerationResult> responseResult = this.qianWenApi.chatCompletionStream(request); |
| 143 | + | |
| 127 | 144 | return Flux.create(fluxSink -> |
| 128 | 145 | responseResult.subscribe( |
| 129 | - value -> fluxSink.next(new ChatResponse(List.of(new Generation(value.getOutput().getText())))), | |
| 146 | + value -> fluxSink.next( | |
| 147 | + new ChatResponse(value.getOutput().getChoices().stream() | |
| 148 | + .map(choices -> new Generation(choices.getMessage().getContent())) | |
| 149 | + .collect(Collectors.toList())) | |
| 150 | + ), | |
| 130 | 151 | error -> fluxSink.error(error), |
| 131 | 152 | () -> fluxSink.complete() |
| 132 | 153 | ) | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenOptions.java
| ... | ... | @@ -15,7 +15,7 @@ import java.util.List; |
| 15 | 15 | * time: 2024/3/15 19:57 |
| 16 | 16 | */ |
| 17 | 17 | @Data |
| 18 | -@Accessors | |
| 18 | +@Accessors(chain = true) | |
| 19 | 19 | public class QianWenOptions implements ChatOptions { |
| 20 | 20 | |
| 21 | 21 | /** |
| ... | ... | @@ -28,6 +28,10 @@ public class QianWenOptions implements ChatOptions { |
| 28 | 28 | * 默认值为0.8。注意,取值不要大于等于1 |
| 29 | 29 | */ |
| 30 | 30 | private Float topP; |
| 31 | + /** | |
| 32 | + * 用于限制模型生成token的数量,max_tokens设置的是生成上限,并不表示一定会生成这么多的token数量。其中qwen1.5-14b-chat、qwen1.5-7b-chat、qwen-14b-chat和qwen-7b-chat最大值和默认值均为1500,qwen-1.8b-chat、qwen-1.8b-longcontext-chat和qwen-72b-chat最大值和默认值均为2000 | |
| 33 | + */ | |
| 34 | + private Integer maxTokens = 1500; | |
| 31 | 35 | |
| 32 | 36 | // |
| 33 | 37 | // 适配 ChatOptions | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/api/QianWenApi.java
| ... | ... | @@ -4,6 +4,7 @@ import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenChatModal; |
| 4 | 4 | import cn.iocoder.yudao.framework.ai.exception.AiException; |
| 5 | 5 | import com.alibaba.dashscope.aigc.generation.Generation; |
| 6 | 6 | import com.alibaba.dashscope.aigc.generation.GenerationResult; |
| 7 | +import com.alibaba.dashscope.aigc.generation.models.QwenParam; | |
| 7 | 8 | import com.alibaba.dashscope.common.Message; |
| 8 | 9 | import com.alibaba.dashscope.common.Role; |
| 9 | 10 | import com.alibaba.dashscope.exception.InputRequiredException; |
| ... | ... | @@ -34,9 +35,7 @@ public class QianWenApi { |
| 34 | 35 | this.qianWenChatModal = qianWenChatModal; |
| 35 | 36 | } |
| 36 | 37 | |
| 37 | - public ResponseEntity<GenerationResult> chatCompletionEntity(QianWenChatCompletionRequest request) { | |
| 38 | - Message userMsg = Message.builder().role(Role.USER.getValue()).content("用萝卜、土豆、茄子做饭,给我个菜谱").build(); | |
| 39 | - | |
| 38 | + public ResponseEntity<GenerationResult> chatCompletionEntity(QwenParam request) { | |
| 40 | 39 | GenerationResult call; |
| 41 | 40 | try { |
| 42 | 41 | call = gen.call(request); |
| ... | ... | @@ -49,7 +48,7 @@ public class QianWenApi { |
| 49 | 48 | return new ResponseEntity<>(call, HttpStatusCode.valueOf(200)); |
| 50 | 49 | } |
| 51 | 50 | |
| 52 | - public Flowable<GenerationResult> chatCompletionStream(QianWenChatCompletionRequest request) { | |
| 51 | + public Flowable<GenerationResult> chatCompletionStream(QwenParam request) { | |
| 53 | 52 | Flowable<GenerationResult> resultFlowable; |
| 54 | 53 | try { |
| 55 | 54 | resultFlowable = gen.streamCall(request); | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
| ... | ... | @@ -47,6 +47,7 @@ public class YudaoAiAutoConfiguration { |
| 47 | 47 | QianWenOptions qianWenOptions = new QianWenOptions(); |
| 48 | 48 | qianWenOptions.setTopK(qianWenProperties.getTopK()); |
| 49 | 49 | qianWenOptions.setTopP(qianWenProperties.getTopP()); |
| 50 | + qianWenOptions.setMaxTokens(qianWenProperties.getMaxTokens()); | |
| 50 | 51 | qianWenOptions.setTemperature(qianWenProperties.getTemperature()); |
| 51 | 52 | return new QianWenChatClient( |
| 52 | 53 | new QianWenApi( | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/QianWenChatClientTests.java
| 1 | 1 | package cn.iocoder.yudao.framework.ai.chat; |
| 2 | 2 | |
| 3 | +import cn.iocoder.yudao.framework.ai.chat.messages.SystemMessage; | |
| 4 | +import cn.iocoder.yudao.framework.ai.chat.messages.UserMessage; | |
| 3 | 5 | import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; |
| 4 | -import cn.iocoder.yudao.framework.ai.chatqianwen.api.QianWenApi; | |
| 5 | 6 | import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenChatClient; |
| 7 | +import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenChatModal; | |
| 6 | 8 | import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenOptions; |
| 9 | +import cn.iocoder.yudao.framework.ai.chatqianwen.api.QianWenApi; | |
| 10 | +import com.alibaba.dashscope.aigc.generation.GenerationResult; | |
| 11 | +import com.alibaba.dashscope.aigc.generation.models.QwenParam; | |
| 12 | +import com.alibaba.dashscope.common.Message; | |
| 13 | +import com.alibaba.dashscope.common.MessageManager; | |
| 14 | +import com.alibaba.dashscope.common.Role; | |
| 15 | +import com.alibaba.dashscope.exception.InputRequiredException; | |
| 16 | +import com.alibaba.dashscope.exception.NoApiKeyException; | |
| 7 | 17 | import org.junit.Before; |
| 8 | 18 | import org.junit.Test; |
| 9 | 19 | import reactor.core.publisher.Flux; |
| 10 | 20 | |
| 21 | +import java.util.ArrayList; | |
| 22 | +import java.util.List; | |
| 11 | 23 | import java.util.Scanner; |
| 12 | 24 | import java.util.function.Consumer; |
| 13 | 25 | |
| ... | ... | @@ -21,28 +33,34 @@ public class QianWenChatClientTests { |
| 21 | 33 | |
| 22 | 34 | @Before |
| 23 | 35 | public void setup() { |
| 24 | - QianWenApi qianWenApi = new QianWenApi( | |
| 25 | - "LTAI5tNTVhXW4fLKUjMrr98z", | |
| 26 | - "ZJ0JQeyjzxxm5CfeTV6k1wNE9UsvZP", | |
| 27 | - "f0c1088824594f589c8f10567ccd929f_p_efm", | |
| 28 | - null | |
| 29 | - ); | |
| 36 | + QianWenApi qianWenApi = new QianWenApi("sk-Zsd81gZYg7", QianWenChatModal.QWEN_72B_CHAT); | |
| 37 | + QianWenOptions qianWenOptions = new QianWenOptions(); | |
| 38 | + qianWenOptions.setTopP(0.8F); | |
| 39 | + qianWenOptions.setTopK(3); | |
| 40 | + qianWenOptions.setTemperature(0.6F); | |
| 30 | 41 | qianWenChatClient = new QianWenChatClient( |
| 31 | 42 | qianWenApi, |
| 32 | - new QianWenOptions() | |
| 33 | - .setAppId("5f14955f201a44eb8dbe0c57250a32ce") | |
| 43 | + qianWenOptions | |
| 34 | 44 | ); |
| 35 | 45 | } |
| 36 | 46 | |
| 37 | 47 | @Test |
| 38 | 48 | public void callTest() { |
| 39 | - ChatResponse call = qianWenChatClient.call(new Prompt("Java语言怎么样?")); | |
| 49 | + List<cn.iocoder.yudao.framework.ai.chat.messages.Message> messages = new ArrayList<>(); | |
| 50 | + messages.add(new SystemMessage("你是一个优质的小红书文艺作者,抒写着各城市的美好文化和风景。")); | |
| 51 | + messages.add(new UserMessage("长沙怎么样?")); | |
| 52 | + | |
| 53 | + ChatResponse call = qianWenChatClient.call(new Prompt(messages)); | |
| 40 | 54 | System.err.println(call.getResult()); |
| 41 | 55 | } |
| 42 | 56 | |
| 43 | 57 | @Test |
| 44 | 58 | public void streamTest() { |
| 45 | - Flux<ChatResponse> flux = qianWenChatClient.stream(new Prompt("Java语言怎么样?")); | |
| 59 | + List<cn.iocoder.yudao.framework.ai.chat.messages.Message> messages = new ArrayList<>(); | |
| 60 | + messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); | |
| 61 | + messages.add(new UserMessage("长沙怎么样?")); | |
| 62 | + | |
| 63 | + Flux<ChatResponse> flux = qianWenChatClient.stream(new Prompt(messages)); | |
| 46 | 64 | flux.subscribe(new Consumer<ChatResponse>() { |
| 47 | 65 | @Override |
| 48 | 66 | public void accept(ChatResponse chatResponse) { |
| ... | ... | @@ -54,4 +72,32 @@ public class QianWenChatClientTests { |
| 54 | 72 | Scanner scanner = new Scanner(System.in); |
| 55 | 73 | scanner.nextLine(); |
| 56 | 74 | } |
| 75 | + | |
| 76 | + @Test | |
| 77 | + public void qianwenDemoTest() throws NoApiKeyException, InputRequiredException { | |
| 78 | + com.alibaba.dashscope.aigc.generation.Generation gen = new com.alibaba.dashscope.aigc.generation.Generation(); | |
| 79 | + MessageManager msgManager = new MessageManager(10); | |
| 80 | + Message systemMsg = | |
| 81 | + Message.builder().role(Role.SYSTEM.getValue()).content("You are a helpful assistant.").build(); | |
| 82 | + Message userMsg = Message.builder().role(Role.USER.getValue()).content("就当前的海洋污染的情况,写一份限塑的倡议书提纲,需要有理有据地号召大家克制地使用塑料制品").build(); | |
| 83 | + msgManager.add(systemMsg); | |
| 84 | + msgManager.add(userMsg); | |
| 85 | + QwenParam param = | |
| 86 | + QwenParam.builder().model("qwen-72b-chat").messages(msgManager.get()) | |
| 87 | + .resultFormat(QwenParam.ResultFormat.MESSAGE) | |
| 88 | + .topP(0.8) | |
| 89 | + /* set the random seed, optional, default to 1234 if not set */ | |
| 90 | + .seed(100) | |
| 91 | + .apiKey("sk-Zsd81gZYg7") | |
| 92 | + .build(); | |
| 93 | + GenerationResult result = gen.call(param); | |
| 94 | + System.out.println(result); | |
| 95 | + System.out.println("-----------------"); | |
| 96 | + System.out.println("-----------------"); | |
| 97 | + msgManager.add(result); | |
| 98 | + param.setPrompt("能否缩短一些,只讲三点"); | |
| 99 | + param.setMessages(msgManager.get()); | |
| 100 | + result = gen.call(param); | |
| 101 | + System.out.println(result); | |
| 102 | + } | |
| 57 | 103 | } | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/midjourney/MidjourneyInteractionsTests.java
| ... | ... | @@ -6,7 +6,7 @@ import cn.iocoder.yudao.framework.ai.midjourney.api.req.AttachmentsReq; |
| 6 | 6 | import cn.iocoder.yudao.framework.ai.midjourney.api.req.DescribeReq; |
| 7 | 7 | import cn.iocoder.yudao.framework.ai.midjourney.api.req.ReRollReq; |
| 8 | 8 | import cn.iocoder.yudao.framework.ai.midjourney.api.res.UploadAttachmentsRes; |
| 9 | -import com.alibaba.fastjson.JSON; | |
| 9 | +import cn.iocoder.yudao.framework.common.util.json.JsonUtils; | |
| 10 | 10 | import org.junit.Before; |
| 11 | 11 | import org.junit.Test; |
| 12 | 12 | import org.springframework.core.io.FileSystemResource; |
| ... | ... | @@ -58,7 +58,7 @@ public class MidjourneyInteractionsTests { |
| 58 | 58 | new AttachmentsReq().setFileSystemResource( |
| 59 | 59 | new FileSystemResource(new File("/Users/fansili/Downloads/DSC01402.JPG"))) |
| 60 | 60 | ); |
| 61 | - System.err.println(JSON.toJSONString(res)); | |
| 61 | + System.err.println(JsonUtils.toJsonString(res)); | |
| 62 | 62 | } |
| 63 | 63 | |
| 64 | 64 | @Test | ... | ... |
yudao-server/src/main/resources/application-local.yaml
| ... | ... | @@ -228,14 +228,11 @@ yudao: |
| 228 | 228 | qianwen: |
| 229 | 229 | enable: true |
| 230 | 230 | aiPlatform: QIAN_WEN |
| 231 | - temperature: 1 | |
| 232 | - topP: 1 | |
| 233 | - topK: 1 | |
| 234 | - endpoint: bailian.cn-beijing.aliyuncs.com | |
| 235 | - accessKeyId: LTAI5tNTVhXW4fLKUjMrr98z | |
| 236 | - accessKeySecret: ZJ0JQeyjzxxm5CfeTV6k1wNE9UsvZP | |
| 237 | - agentKey: f0c1088824594f589c8f10567ccd929f_p_efm | |
| 238 | - appId: 5f14955f201a44eb8dbe0c57250a32ce | |
| 231 | + temperature: 0.85 | |
| 232 | + topP: 0.8 | |
| 233 | + topK: 0 | |
| 234 | + api-key: sk-Zsd81gZYg7 | |
| 235 | + max-tokens: 1500 | |
| 239 | 236 | xinghuo: |
| 240 | 237 | enable: true |
| 241 | 238 | aiPlatform: XING_HUO | ... | ... |