Commit 7a785b1ec073171e849cd775f3bf6dfc06b8a1be
1 parent
711cfcb3
阿里通义千问,继承 chat options
Showing
5 changed files
with
226 additions
and
48 deletions
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chat/ChatException.java
0 → 100644
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 | 2 | |
| 3 | 3 | import com.aliyun.broadscope.bailian.sdk.AccessTokenClient; |
| 4 | 4 | import com.aliyun.broadscope.bailian.sdk.ApplicationClient; |
| 5 | -import com.aliyun.broadscope.bailian.sdk.models.ChatRequestMessage; | |
| 6 | 5 | import com.aliyun.broadscope.bailian.sdk.models.CompletionsRequest; |
| 7 | 6 | import com.aliyun.broadscope.bailian.sdk.models.CompletionsResponse; |
| 8 | 7 | import lombok.Getter; |
| ... | ... | @@ -10,8 +9,6 @@ import org.springframework.http.HttpStatusCode; |
| 10 | 9 | import org.springframework.http.ResponseEntity; |
| 11 | 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 | 32 | private String token; |
| 36 | 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 | 36 | this.accessKeyId = accessKeyId; |
| 40 | 37 | this.accessKeySecret = accessKeySecret; |
| 41 | 38 | this.agentKey = agentKey; |
| 42 | - this.appId = appId; | |
| 43 | 39 | |
| 44 | 40 | if (endpoint != null) { |
| 45 | 41 | this.endpoint = endpoint; |
| ... | ... | @@ -54,35 +50,14 @@ public class QianWenApi { |
| 54 | 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 | 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 | 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 | 8 | import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; |
| 8 | 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 | 11 | import lombok.extern.slf4j.Slf4j; |
| 13 | 12 | import org.springframework.http.ResponseEntity; |
| 14 | 13 | import org.springframework.retry.RetryCallback; |
| ... | ... | @@ -19,6 +18,7 @@ import reactor.core.publisher.Flux; |
| 19 | 18 | |
| 20 | 19 | import java.time.Duration; |
| 21 | 20 | import java.util.List; |
| 21 | +import java.util.Optional; | |
| 22 | 22 | import java.util.stream.Collectors; |
| 23 | 23 | |
| 24 | 24 | /** |
| ... | ... | @@ -34,10 +34,17 @@ public class QianWenChatClient implements ChatClient, StreamingChatClient { |
| 34 | 34 | |
| 35 | 35 | private QianWenApi qianWenApi; |
| 36 | 36 | |
| 37 | + private ChatOptions chatOptions; | |
| 38 | + | |
| 37 | 39 | public QianWenChatClient(QianWenApi qianWenApi) { |
| 38 | 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 | 48 | public final RetryTemplate retryTemplate = RetryTemplate.builder() |
| 42 | 49 | // 最大重试次数 10 |
| 43 | 50 | .maxAttempts(10) |
| ... | ... | @@ -58,7 +65,7 @@ public class QianWenChatClient implements ChatClient, StreamingChatClient { |
| 58 | 65 | return this.retryTemplate.execute(ctx -> { |
| 59 | 66 | // ctx 会有重试的信息 |
| 60 | 67 | // 创建 request 请求,stream模式需要供应商支持 |
| 61 | - ChatRequestMessage request = this.createRequest(prompt, false); | |
| 68 | + CompletionsRequest request = this.createRequest(prompt, false); | |
| 62 | 69 | // 调用 callWithFunctionSupport 发送请求 |
| 63 | 70 | ResponseEntity<CompletionsResponse> responseEntity = qianWenApi.chatCompletionEntity(request); |
| 64 | 71 | // 获取结果封装 chatCompletion |
| ... | ... | @@ -67,21 +74,69 @@ public class QianWenChatClient implements ChatClient, StreamingChatClient { |
| 67 | 74 | return new ChatResponse(List.of(new Generation(String.format("failed to create completion, requestId: %s, code: %s, message: %s\n", |
| 68 | 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 | 135 | @Override |
| 81 | 136 | public Flux<ChatResponse> stream(Prompt prompt) { |
| 82 | 137 | // ctx 会有重试的信息 |
| 83 | 138 | // 创建 request 请求,stream模式需要供应商支持 |
| 84 | - ChatRequestMessage request = this.createRequest(prompt, true); | |
| 139 | + CompletionsRequest request = this.createRequest(prompt, true); | |
| 85 | 140 | // 调用 callWithFunctionSupport 发送请求 |
| 86 | 141 | Flux<CompletionsResponse> response = this.qianWenApi.chatCompletionStream(request); |
| 87 | 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 | 3 | import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt; |
| 4 | 4 | import cn.iocoder.yudao.framework.ai.chatqianwen.QianWenApi; |
| 5 | 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 | 8 | import org.junit.Before; |
| 7 | 9 | import org.junit.Test; |
| 8 | 10 | import reactor.core.publisher.Flux; |
| ... | ... | @@ -24,10 +26,13 @@ public class QianWenChatClientTests { |
| 24 | 26 | "", |
| 25 | 27 | "", |
| 26 | 28 | "", |
| 27 | - "", | |
| 28 | 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 | 38 | @Test | ... | ... |