Commit 7a785b1ec073171e849cd775f3bf6dfc06b8a1be

Authored by cherishsince
1 parent 711cfcb3

阿里通义千问,继承 chat options

yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/ChatException.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.chat;
  2 +
  3 +/**
  4 + * 聊天异常
  5 + *
  6 + * author: fansili
  7 + * time: 2024/3/15 20:45
  8 + */
  9 +public class ChatException extends RuntimeException {
  10 +
  11 + public ChatException(String message) {
  12 + super(message);
  13 + }
  14 +
  15 +}
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenApi.java
@@ -2,7 +2,6 @@ package cn.iocoder.yudao.framework.ai.chatqianwen; @@ -2,7 +2,6 @@ package cn.iocoder.yudao.framework.ai.chatqianwen;
2 2
3 import com.aliyun.broadscope.bailian.sdk.AccessTokenClient; 3 import com.aliyun.broadscope.bailian.sdk.AccessTokenClient;
4 import com.aliyun.broadscope.bailian.sdk.ApplicationClient; 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; 5 import com.aliyun.broadscope.bailian.sdk.models.CompletionsRequest;
7 import com.aliyun.broadscope.bailian.sdk.models.CompletionsResponse; 6 import com.aliyun.broadscope.bailian.sdk.models.CompletionsResponse;
8 import lombok.Getter; 7 import lombok.Getter;
@@ -10,8 +9,6 @@ import org.springframework.http.HttpStatusCode; @@ -10,8 +9,6 @@ import org.springframework.http.HttpStatusCode;
10 import org.springframework.http.ResponseEntity; 9 import org.springframework.http.ResponseEntity;
11 import reactor.core.publisher.Flux; 10 import reactor.core.publisher.Flux;
12 11
13 -import java.util.List;  
14 -  
15 /** 12 /**
16 * 阿里 通义千问 13 * 阿里 通义千问
17 * 14 *
@@ -35,11 +32,10 @@ public class QianWenApi { @@ -35,11 +32,10 @@ public class QianWenApi {
35 private String token; 32 private String token;
36 private ApplicationClient client; 33 private ApplicationClient client;
37 34
38 - public QianWenApi(String accessKeyId, String accessKeySecret, String agentKey, String appId, String endpoint) { 35 + public QianWenApi(String accessKeyId, String accessKeySecret, String agentKey, String endpoint) {
39 this.accessKeyId = accessKeyId; 36 this.accessKeyId = accessKeyId;
40 this.accessKeySecret = accessKeySecret; 37 this.accessKeySecret = accessKeySecret;
41 this.agentKey = agentKey; 38 this.agentKey = agentKey;
42 - this.appId = appId;  
43 39
44 if (endpoint != null) { 40 if (endpoint != null) {
45 this.endpoint = endpoint; 41 this.endpoint = endpoint;
@@ -54,35 +50,14 @@ public class QianWenApi { @@ -54,35 +50,14 @@ public class QianWenApi {
54 .build(); 50 .build();
55 } 51 }
56 52
57 - public ResponseEntity<CompletionsResponse> chatCompletionEntity(ChatRequestMessage message) {  
58 - // 创建request  
59 - CompletionsRequest request = new CompletionsRequest()  
60 - // 设置 appid  
61 - .setAppId(appId)  
62 - .setMessages(List.of(message))  
63 - // 返回choice message结果  
64 - .setParameters(new CompletionsRequest.Parameter().setResultFormat("message"));  
65 - // 53 + public ResponseEntity<CompletionsResponse> chatCompletionEntity(CompletionsRequest request) {
  54 + // 发送请求
66 CompletionsResponse response = client.completions(request); 55 CompletionsResponse response = client.completions(request);
67 - int httpCode = 200;  
68 - if (!response.isSuccess()) {  
69 - System.out.printf("failed to create completion, requestId: %s, code: %s, message: %s\n",  
70 - response.getRequestId(), response.getCode(), response.getMessage());  
71 - httpCode = 500;  
72 - }  
73 - return new ResponseEntity<>(response, HttpStatusCode.valueOf(httpCode)); 56 + // 阿里云的这个 http code 随便设置,外面判断是否成功用的 CompletionsResponse.isSuccess
  57 + return new ResponseEntity<>(response, HttpStatusCode.valueOf(200));
74 } 58 }
75 59
76 - public Flux<CompletionsResponse> chatCompletionStream(ChatRequestMessage message) {  
77 - return client.streamCompletions(  
78 - new CompletionsRequest()  
79 - // 设置 appid  
80 - .setAppId(appId)  
81 - // 开启 stream  
82 - .setStream(true)  
83 - .setMessages(List.of(message))  
84 - //开启增量输出模式,后面输出不会包含已经输出的内容  
85 - .setParameters(new CompletionsRequest.Parameter().setIncrementalOutput(true))  
86 - ); 60 + public Flux<CompletionsResponse> chatCompletionStream(CompletionsRequest request) {
  61 + return client.streamCompletions(request);
87 } 62 }
88 } 63 }
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenChatClient.java
1 package cn.iocoder.yudao.framework.ai.chatqianwen; 1 package cn.iocoder.yudao.framework.ai.chatqianwen;
2 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; 3 +import cn.hutool.core.util.IdUtil;
  4 +import cn.hutool.json.JSONUtil;
  5 +import cn.iocoder.yudao.framework.ai.chat.*;
  6 +import cn.iocoder.yudao.framework.ai.chat.messages.MessageType;
  7 +import cn.iocoder.yudao.framework.ai.chat.prompt.ChatOptions;
7 import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; 8 import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt;
8 import cn.iocoder.yudao.framework.ai.chatyiyan.exception.YiYanApiException; 9 import cn.iocoder.yudao.framework.ai.chatyiyan.exception.YiYanApiException;
9 -import com.aliyun.broadscope.bailian.sdk.models.ChatRequestMessage;  
10 -import com.aliyun.broadscope.bailian.sdk.models.ChatUserMessage;  
11 -import com.aliyun.broadscope.bailian.sdk.models.CompletionsResponse; 10 +import com.aliyun.broadscope.bailian.sdk.models.*;
12 import lombok.extern.slf4j.Slf4j; 11 import lombok.extern.slf4j.Slf4j;
13 import org.springframework.http.ResponseEntity; 12 import org.springframework.http.ResponseEntity;
14 import org.springframework.retry.RetryCallback; 13 import org.springframework.retry.RetryCallback;
@@ -19,6 +18,7 @@ import reactor.core.publisher.Flux; @@ -19,6 +18,7 @@ import reactor.core.publisher.Flux;
19 18
20 import java.time.Duration; 19 import java.time.Duration;
21 import java.util.List; 20 import java.util.List;
  21 +import java.util.Optional;
22 import java.util.stream.Collectors; 22 import java.util.stream.Collectors;
23 23
24 /** 24 /**
@@ -34,10 +34,17 @@ public class QianWenChatClient implements ChatClient, StreamingChatClient { @@ -34,10 +34,17 @@ public class QianWenChatClient implements ChatClient, StreamingChatClient {
34 34
35 private QianWenApi qianWenApi; 35 private QianWenApi qianWenApi;
36 36
  37 + private ChatOptions chatOptions;
  38 +
37 public QianWenChatClient(QianWenApi qianWenApi) { 39 public QianWenChatClient(QianWenApi qianWenApi) {
38 this.qianWenApi = qianWenApi; 40 this.qianWenApi = qianWenApi;
39 } 41 }
40 42
  43 + public QianWenChatClient(QianWenApi qianWenApi, ChatOptions chatOptions) {
  44 + this.qianWenApi = qianWenApi;
  45 + this.chatOptions = chatOptions;
  46 + }
  47 +
41 public final RetryTemplate retryTemplate = RetryTemplate.builder() 48 public final RetryTemplate retryTemplate = RetryTemplate.builder()
42 // 最大重试次数 10 49 // 最大重试次数 10
43 .maxAttempts(10) 50 .maxAttempts(10)
@@ -58,7 +65,7 @@ public class QianWenChatClient implements ChatClient, StreamingChatClient { @@ -58,7 +65,7 @@ public class QianWenChatClient implements ChatClient, StreamingChatClient {
58 return this.retryTemplate.execute(ctx -> { 65 return this.retryTemplate.execute(ctx -> {
59 // ctx 会有重试的信息 66 // ctx 会有重试的信息
60 // 创建 request 请求,stream模式需要供应商支持 67 // 创建 request 请求,stream模式需要供应商支持
61 - ChatRequestMessage request = this.createRequest(prompt, false); 68 + CompletionsRequest request = this.createRequest(prompt, false);
62 // 调用 callWithFunctionSupport 发送请求 69 // 调用 callWithFunctionSupport 发送请求
63 ResponseEntity<CompletionsResponse> responseEntity = qianWenApi.chatCompletionEntity(request); 70 ResponseEntity<CompletionsResponse> responseEntity = qianWenApi.chatCompletionEntity(request);
64 // 获取结果封装 chatCompletion 71 // 获取结果封装 chatCompletion
@@ -67,21 +74,69 @@ public class QianWenChatClient implements ChatClient, StreamingChatClient { @@ -67,21 +74,69 @@ public class QianWenChatClient implements ChatClient, StreamingChatClient {
67 return new ChatResponse(List.of(new Generation(String.format("failed to create completion, requestId: %s, code: %s, message: %s\n", 74 return new ChatResponse(List.of(new Generation(String.format("failed to create completion, requestId: %s, code: %s, message: %s\n",
68 response.getRequestId(), response.getCode(), response.getMessage())))); 75 response.getRequestId(), response.getCode(), response.getMessage()))));
69 } 76 }
70 - List<Generation> generations = response.getData().getChoices().stream()  
71 - .map(item -> new Generation(item.getMessage().getContent())).collect(Collectors.toList());  
72 - return new ChatResponse(generations); 77 + // 转换为 Generation 返回
  78 + return new ChatResponse(List.of(new Generation(response.getData().getText())));
73 }); 79 });
74 } 80 }
75 81
76 - private ChatRequestMessage createRequest(Prompt prompt, boolean b) {  
77 - return new ChatUserMessage(prompt.getContents()); 82 + private CompletionsRequest createRequest(Prompt prompt, boolean stream) {
  83 + // 两个都为null 则没有配置文件
  84 + if (chatOptions == null && prompt.getOptions() == null) {
  85 + throw new ChatException("ChatOptions 未配置参数!");
  86 + }
  87 + // 优先使用 Prompt 里面的 ChatOptions
  88 + ChatOptions options = chatOptions;
  89 + if (prompt.getOptions() != null) {
  90 + options = (ChatOptions) prompt.getOptions();
  91 + }
  92 + QianWenOptions qianWenOptions = (QianWenOptions) options;
  93 + // 需要额外处理
  94 + if (!stream) {
  95 + // 如果不需要 stream 输出,那么需要将这个设置为false,不然只会输出最后几个文字
  96 + if (qianWenOptions.getParameters() == null) {
  97 + qianWenOptions.setParameters(new CompletionsRequest.Parameter().setIncrementalOutput(false));
  98 + } else {
  99 + qianWenOptions.getParameters().setIncrementalOutput(false);
  100 + }
  101 + } else {
  102 + // 如果不需要 stream 输出,设置为true这样不会输出累加内容
  103 + if (qianWenOptions.getParameters() == null) {
  104 + qianWenOptions.setParameters(new CompletionsRequest.Parameter().setIncrementalOutput(true));
  105 + } else {
  106 + qianWenOptions.getParameters().setIncrementalOutput(true);
  107 + }
  108 + }
  109 +
  110 + // 创建request
  111 + return new CompletionsRequest()
  112 + // 请求唯一标识,请确保RequestId不重复。
  113 + .setRequestId(IdUtil.getSnowflakeNextIdStr())
  114 + // 设置 appid
  115 + .setAppId(qianWenOptions.getAppId())
  116 + .setMessages(prompt.getInstructions().stream().map(m -> {
  117 + // 转换成 千问 对于的请求message
  118 + if (MessageType.USER == m.getMessageType()) {
  119 + return new ChatUserMessage(m.getContent());
  120 + } else if (MessageType.SYSTEM == m.getMessageType()) {
  121 + return new ChatSystemMessage(m.getContent());
  122 + } else if (MessageType.ASSISTANT == m.getMessageType()) {
  123 + return new ChatAssistantMessage(m.getContent());
  124 + }
  125 + throw new ChatException(String.format("存在不能适配的消息! %s", JSONUtil.toJsonPrettyStr(m)));
  126 + }).collect(Collectors.toList()))
  127 + // 返回choice message结果
  128 + .setParameters(qianWenOptions.getParameters())
  129 + // 设置 ChatOptions 里面公共的参数
  130 + .setTopP(options.getTopP() == null ? null : options.getTopP().doubleValue())
  131 + // 设置输出方式
  132 + .setStream(stream);
78 } 133 }
79 134
80 @Override 135 @Override
81 public Flux<ChatResponse> stream(Prompt prompt) { 136 public Flux<ChatResponse> stream(Prompt prompt) {
82 // ctx 会有重试的信息 137 // ctx 会有重试的信息
83 // 创建 request 请求,stream模式需要供应商支持 138 // 创建 request 请求,stream模式需要供应商支持
84 - ChatRequestMessage request = this.createRequest(prompt, true); 139 + CompletionsRequest request = this.createRequest(prompt, true);
85 // 调用 callWithFunctionSupport 发送请求 140 // 调用 callWithFunctionSupport 发送请求
86 Flux<CompletionsResponse> response = this.qianWenApi.chatCompletionStream(request); 141 Flux<CompletionsResponse> response = this.qianWenApi.chatCompletionStream(request);
87 return response.map(res -> { 142 return response.map(res -> {
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatqianwen/QianWenOptions.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.chatqianwen;
  2 +
  3 +import cn.iocoder.yudao.framework.ai.chat.prompt.ChatOptions;
  4 +import com.aliyun.broadscope.bailian.sdk.models.CompletionsRequest;
  5 +import lombok.Data;
  6 +import lombok.experimental.Accessors;
  7 +
  8 +import java.util.List;
  9 +
  10 +/**
  11 + * 阿里云 千问 属性
  12 + *
  13 + * 地址:https://help.aliyun.com/document_detail/2684682.html?spm=a2c4g.2621347.0.0.195117e7Ytpkyo
  14 + *
  15 + * author: fansili
  16 + * time: 2024/3/15 19:57
  17 + */
  18 +@Data
  19 +@Accessors
  20 +public class QianWenOptions implements ChatOptions {
  21 +
  22 + private String appId;
  23 + /**
  24 + * 是否流式输出, 默认为否。
  25 + */
  26 + private Boolean stream;
  27 + /**
  28 + * 用户与模型的对话历史
  29 + */
  30 + private List<Message> messages;
  31 + /**
  32 + * 生成时,核采样方法的概率阈值。例如,取值为0.8时,仅保留累计概率之和大于等于0.8的概率分布中的token,
  33 + * 作为随机采样的候选集。取值范围为(0,1.0),取值越大,生成的随机性越高;取值越低,生成的随机性越低。
  34 + * 默认值为0.8。注意,取值不要大于等于1
  35 + */
  36 + private Float topP;
  37 + /**
  38 + * 模型参数设置。
  39 + */
  40 + private CompletionsRequest.Parameter parameters = new CompletionsRequest.Parameter();
  41 +
  42 + //
  43 + // 适配 ChatOptions
  44 +
  45 + @Override
  46 + public Float getTemperature() {
  47 + return Float.parseFloat(this.parameters.getTemperature().toString());
  48 + }
  49 +
  50 + @Override
  51 + public void setTemperature(Float temperature) {
  52 + this.parameters.setTemperature(Double.valueOf(temperature.toString()));
  53 + }
  54 +
  55 + @Override
  56 + public void setTopP(Float topP) {
  57 + this.topP = topP;
  58 + }
  59 +
  60 + @Override
  61 + public Integer getTopK() {
  62 + return this.parameters.getTopK();
  63 + }
  64 +
  65 + @Override
  66 + public void setTopK(Integer topK) {
  67 + this.parameters.setTopK(topK);
  68 + }
  69 +
  70 + @Data
  71 + @Accessors
  72 + public static class Message {
  73 + /**
  74 + * 角色: system、user或assistant
  75 + */
  76 + private String role;
  77 + /**
  78 + * 提示词或模型内容
  79 + */
  80 + private String content;
  81 + }
  82 +
  83 + @Data
  84 + @Accessors
  85 + public static class Parameters {
  86 + /**
  87 + * 输出格式, 默认为"text"
  88 + * "text"表示旧版本的text
  89 + * "message"表示兼容openai的message
  90 + */
  91 + private String resultFormat;
  92 + /**
  93 + * 生成时,采样候选集的大小。例如,取值为50时,仅将单次生成中得分最高的50个token组成随机采样的候选集。
  94 + * 取值越大,生成的随机性越高;取值越小,生成的确定性越高。
  95 + * 注意:如果top_k参数为空或者top_k的值大于100,表示不启用top_k策略,此时仅有top_p策略生效,默认是空。
  96 + */
  97 + private Integer topK;
  98 + /**
  99 + * 生成时使用的随机数种子,用户控制模型生成内容的随机性。
  100 + * seed支持无符号64位整数,默认值为1234。在使用seed时,模型将尽可能生成相同或相似的结果,但目前不保证每次生成的结果完全相同。
  101 + */
  102 + private Integer seed;
  103 + /**
  104 + * 用于控制随机性和多样性的程度。具体来说,temperature值控制了生成文本时对每个候选词的概率分布进行平滑的程度。
  105 + * 较高的temperature值会降低概率分布的峰值,使得更多的低概率词被选择,
  106 + * 生成结果更加多样化;而较低的temperature值则会增强概率分布的峰值,使得高概率词更容易被选择,生成结果更加确定。
  107 + * 取值范围: [0, 2),系统默认值1.0。不建议取值为0,无意义。
  108 + */
  109 + private Float temperature;
  110 + /**
  111 + * 用于限制模型生成token的数量,max_tokens设置的是生成上限,并不表示一定会生成这么多的token数量。
  112 + * 其中qwen-turbo 最大值和默认值为1500, qwen-max、qwen-max-1201 、qwen-max-longcontext 和 qwen-plus最大值和默认值均为2000。
  113 + */
  114 + private Integer maxTokens;
  115 + /**
  116 + * stop参数用于实现内容生成过程的精确控制,在生成内容即将包含指定的字符串或token_ids时自动停止,生成内容不包含指定的内容。
  117 + * 例如,如果指定stop为"你好",表示将要生成"你好"时停止;如果指定stop为[37763, 367],表示将要生成"Observation"时停止。
  118 + */
  119 + private List<String> stop;
  120 + /**
  121 + * 用于控制流式输出模式,默认False,即后面内容会包含已经输出的内容;设置为True,将开启增量输出模式,
  122 + * 后面输出不会包含已经输出的内容,您需要自行拼接整体输出,参考流式输出示例代码。
  123 + */
  124 + private Boolean incrementalOutput;
  125 + }
  126 +}
  127 +
  128 +
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/QianWenChatClientTests.java
@@ -3,6 +3,8 @@ package cn.iocoder.yudao.framework.ai.chat; @@ -3,6 +3,8 @@ package cn.iocoder.yudao.framework.ai.chat;
3 import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; 3 import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt;
4 import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenApi; 4 import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenApi;
5 import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenChatClient; 5 import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenChatClient;
  6 +import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenOptions;
  7 +import com.aliyun.broadscope.bailian.sdk.models.CompletionsRequest;
6 import org.junit.Before; 8 import org.junit.Before;
7 import org.junit.Test; 9 import org.junit.Test;
8 import reactor.core.publisher.Flux; 10 import reactor.core.publisher.Flux;
@@ -24,10 +26,13 @@ public class QianWenChatClientTests { @@ -24,10 +26,13 @@ public class QianWenChatClientTests {
24 "", 26 "",
25 "", 27 "",
26 "", 28 "",
27 - "",  
28 null 29 null
29 ); 30 );
30 - qianWenChatClient = new QianWenChatClient(qianWenApi); 31 + qianWenChatClient = new QianWenChatClient(
  32 + qianWenApi,
  33 + new QianWenOptions()
  34 + .setAppId("5f14955f201a44eb8dbe0c57250a32ce")
  35 + );
31 } 36 }
32 37
33 @Test 38 @Test