Commit 63c5f90596bcdd66e735a6559721c1f5a670388c

Authored by cherishsince
1 parent 2ef64a0a

【优化】兼容阿里云千问开源模型,和付费模型

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
... ... @@ -48,6 +48,10 @@ public class YudaoAiProperties {
48 48 */
49 49 private String apiKey;
50 50 /**
  51 + * 用于限制模型生成token的数量,max_tokens设置的是生成上限,并不表示一定会生成这么多的token数量
  52 + */
  53 + private Integer maxTokens;
  54 + /**
51 55 * model
52 56 */
53 57 private YiYanChatModel model;
... ...
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
... ...