Commit 2d36ec485832d02d14d0d92fd45cb561210738a4
1 parent
2e73959a
【代码优化】AI:优化 xinghuo 的接入,复用 OpenAI,同时将 ws 替换成 htt 调用
Showing
19 changed files
with
259 additions
and
951 deletions
yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
| ... | ... | @@ -33,11 +33,6 @@ |
| 33 | 33 | <artifactId>spring-ai-stability-ai-spring-boot-starter</artifactId> |
| 34 | 34 | <version>${spring-ai.version}</version> |
| 35 | 35 | </dependency> |
| 36 | -<!-- <dependency>--> | |
| 37 | -<!-- <groupId>org.springframework.ai</groupId>--> | |
| 38 | -<!-- <artifactId>spring-ai-vertex-ai-gemini</artifactId>--> | |
| 39 | -<!-- <version>1.0.3</version>--> | |
| 40 | -<!-- </dependency>--> | |
| 41 | 36 | |
| 42 | 37 | <dependency> |
| 43 | 38 | <groupId>cn.iocoder.boot</groupId> |
| ... | ... | @@ -61,8 +56,8 @@ |
| 61 | 56 | |
| 62 | 57 | <!-- Test 测试相关 --> |
| 63 | 58 | <dependency> |
| 64 | - <groupId>junit</groupId> | |
| 65 | - <artifactId>junit</artifactId> | |
| 59 | + <groupId>org.springframework.boot</groupId> | |
| 60 | + <artifactId>spring-boot-starter-test</artifactId> | |
| 66 | 61 | <scope>test</scope> |
| 67 | 62 | </dependency> |
| 68 | 63 | </dependencies> | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java
| ... | ... | @@ -5,8 +5,7 @@ import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactoryImpl; |
| 5 | 5 | import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; |
| 6 | 6 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; |
| 7 | 7 | import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient; |
| 8 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoOptions; | |
| 9 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoApi; | |
| 8 | +import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions; | |
| 10 | 9 | import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration; |
| 11 | 10 | import lombok.extern.slf4j.Slf4j; |
| 12 | 11 | import org.springframework.boot.autoconfigure.AutoConfiguration; |
| ... | ... | @@ -36,21 +35,14 @@ public class YudaoAiAutoConfiguration { |
| 36 | 35 | @Bean |
| 37 | 36 | @ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true") |
| 38 | 37 | public XingHuoChatClient xingHuoChatClient(YudaoAiProperties yudaoAiProperties) { |
| 39 | - YudaoAiProperties.XingHuoProperties xingHuoProperties = yudaoAiProperties.getXinghuo(); | |
| 40 | - // 转换配置 | |
| 41 | - XingHuoOptions xingHuoOptions = new XingHuoOptions(); | |
| 42 | - xingHuoOptions.setChatModel(xingHuoProperties.getModel()); | |
| 43 | - xingHuoOptions.setTopK(xingHuoProperties.getTopK()); | |
| 44 | - xingHuoOptions.setTemperature(xingHuoProperties.getTemperature()); | |
| 45 | - xingHuoOptions.setMaxTokens(xingHuoProperties.getMaxTokens()); | |
| 46 | - return new XingHuoChatClient( | |
| 47 | - new XingHuoApi( | |
| 48 | - xingHuoProperties.getAppId(), | |
| 49 | - xingHuoProperties.getAppKey(), | |
| 50 | - xingHuoProperties.getSecretKey() | |
| 51 | - ), | |
| 52 | - xingHuoOptions | |
| 53 | - ); | |
| 38 | + YudaoAiProperties.XingHuoProperties properties = yudaoAiProperties.getXinghuo(); | |
| 39 | + XingHuoChatOptions options = XingHuoChatOptions.builder() | |
| 40 | + .model(properties.getModel()) | |
| 41 | + .temperature(properties.getTemperature()) | |
| 42 | + .maxTokens(properties.getMaxTokens()) | |
| 43 | + .topK(properties.getTopK()) | |
| 44 | + .build(); | |
| 45 | + return new XingHuoChatClient(properties.getAppKey(), properties.getSecretKey(), options); | |
| 54 | 46 | } |
| 55 | 47 | |
| 56 | 48 | @Bean | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java
| 1 | 1 | package cn.iocoder.yudao.framework.ai.config; |
| 2 | 2 | |
| 3 | -import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; | |
| 4 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; | |
| 5 | 3 | import lombok.Data; |
| 6 | -import lombok.experimental.Accessors; | |
| 7 | -import org.springframework.ai.autoconfigure.openai.OpenAiImageProperties; | |
| 8 | 4 | import org.springframework.boot.context.properties.ConfigurationProperties; |
| 9 | 5 | |
| 10 | 6 | /** |
| 11 | - * ai 自动配置 | |
| 7 | + * 芋道 AI 配置类 | |
| 12 | 8 | * |
| 13 | 9 | * @author fansili |
| 14 | - * @time 2024/4/12 16:29 | |
| 15 | 10 | * @since 1.0 |
| 16 | 11 | */ |
| 17 | -@Data | |
| 18 | -@Accessors(chain = true) | |
| 19 | 12 | @ConfigurationProperties(prefix = "yudao.ai") |
| 13 | +@Data | |
| 20 | 14 | public class YudaoAiProperties { |
| 21 | 15 | |
| 16 | + /** | |
| 17 | + * 讯飞星火 | |
| 18 | + */ | |
| 22 | 19 | private XingHuoProperties xinghuo; |
| 23 | - private OpenAiImageProperties openAiImage; | |
| 24 | - private MidjourneyProperties midjourney; | |
| 25 | - private SunoProperties suno; | |
| 26 | - | |
| 27 | - @Data | |
| 28 | - @Accessors(chain = true) | |
| 29 | - public static class ChatProperties { | |
| 30 | 20 | |
| 31 | - private boolean enable = false; | |
| 32 | - | |
| 33 | - private AiPlatformEnum aiPlatform; | |
| 34 | - | |
| 35 | - private Float temperature; | |
| 36 | - | |
| 37 | - private Float topP; | |
| 21 | + /** | |
| 22 | + * Midjourney 绘图 | |
| 23 | + */ | |
| 24 | + private MidjourneyProperties midjourney; | |
| 38 | 25 | |
| 39 | - private Integer topK; | |
| 40 | - /** | |
| 41 | - * 用于限制模型生成token的数量,max_tokens设置的是生成上限,并不表示一定会生成这么多的token数量 | |
| 42 | - */ | |
| 43 | - private Integer maxTokens; | |
| 44 | - } | |
| 26 | + /** | |
| 27 | + * Suno 音乐 | |
| 28 | + */ | |
| 29 | + private SunoProperties suno; | |
| 45 | 30 | |
| 46 | 31 | @Data |
| 47 | - public static class XingHuoProperties extends ChatProperties { | |
| 32 | + public static class XingHuoProperties { | |
| 48 | 33 | |
| 34 | + private String enable; | |
| 49 | 35 | private String appId; |
| 50 | 36 | private String appKey; |
| 51 | 37 | private String secretKey; |
| 52 | - private XingHuoChatModel model; | |
| 38 | + | |
| 39 | + private String model; | |
| 40 | + private Float temperature; | |
| 41 | + private Integer maxTokens; | |
| 42 | + private Integer topK; | |
| 53 | 43 | |
| 54 | 44 | } |
| 55 | 45 | |
| ... | ... | @@ -57,8 +47,9 @@ public class YudaoAiProperties { |
| 57 | 47 | public static class MidjourneyProperties { |
| 58 | 48 | |
| 59 | 49 | private String enable; |
| 60 | - private String apiKey; | |
| 61 | 50 | private String baseUrl; |
| 51 | + | |
| 52 | + private String apiKey; | |
| 62 | 53 | private String notifyUrl; |
| 63 | 54 | |
| 64 | 55 | } |
| ... | ... | @@ -68,9 +59,6 @@ public class YudaoAiProperties { |
| 68 | 59 | |
| 69 | 60 | private boolean enable = false; |
| 70 | 61 | |
| 71 | - /** | |
| 72 | - * API 服务的基本地址 | |
| 73 | - */ | |
| 74 | 62 | private String baseUrl; |
| 75 | 63 | |
| 76 | 64 | } | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/factory/AiClientFactoryImpl.java
| ... | ... | @@ -12,7 +12,6 @@ import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; |
| 12 | 12 | import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; |
| 13 | 13 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; |
| 14 | 14 | import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient; |
| 15 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoApi; | |
| 16 | 15 | import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration; |
| 17 | 16 | import com.alibaba.cloud.ai.tongyi.TongYiConnectionProperties; |
| 18 | 17 | import com.alibaba.cloud.ai.tongyi.chat.TongYiChatModel; |
| ... | ... | @@ -178,11 +177,9 @@ public class AiClientFactoryImpl implements AiClientFactory { |
| 178 | 177 | private static XingHuoChatClient buildXingHuoChatClient(String key) { |
| 179 | 178 | List<String> keys = StrUtil.split(key, '|'); |
| 180 | 179 | Assert.equals(keys.size(), 3, "XingHuoChatClient 的密钥需要 (appid|appKey|secretKey) 格式"); |
| 181 | - String appId = keys.get(0); | |
| 182 | 180 | String appKey = keys.get(1); |
| 183 | 181 | String secretKey = keys.get(2); |
| 184 | - XingHuoApi xingHuoApi = new XingHuoApi(appId, appKey, secretKey); | |
| 185 | - return new XingHuoChatClient(xingHuoApi); | |
| 182 | + return new XingHuoChatClient(appKey, secretKey); | |
| 186 | 183 | } |
| 187 | 184 | |
| 188 | 185 | /** | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatClient.java
| 1 | 1 | package cn.iocoder.yudao.framework.ai.core.model.xinghuo; |
| 2 | 2 | |
| 3 | -import cn.hutool.core.bean.BeanUtil; | |
| 4 | -import cn.hutool.core.exceptions.ExceptionUtil; | |
| 5 | -import cn.iocoder.yudao.framework.ai.core.exception.ChatException; | |
| 6 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoApi; | |
| 7 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoChatCompletion; | |
| 8 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoChatCompletionRequest; | |
| 3 | +import cn.hutool.core.lang.Assert; | |
| 9 | 4 | import lombok.extern.slf4j.Slf4j; |
| 5 | +import org.springframework.ai.chat.metadata.ChatGenerationMetadata; | |
| 10 | 6 | import org.springframework.ai.chat.model.ChatModel; |
| 11 | 7 | import org.springframework.ai.chat.model.ChatResponse; |
| 12 | 8 | import org.springframework.ai.chat.model.Generation; |
| 13 | -import org.springframework.ai.chat.model.StreamingChatModel; | |
| 14 | 9 | import org.springframework.ai.chat.prompt.ChatOptions; |
| 15 | 10 | import org.springframework.ai.chat.prompt.Prompt; |
| 11 | +import org.springframework.ai.model.ModelOptionsUtils; | |
| 12 | +import org.springframework.ai.openai.OpenAiChatOptions; | |
| 13 | +import org.springframework.ai.openai.api.OpenAiApi; | |
| 14 | +import org.springframework.ai.openai.metadata.OpenAiChatResponseMetadata; | |
| 15 | +import org.springframework.ai.retry.RetryUtils; | |
| 16 | 16 | import org.springframework.http.ResponseEntity; |
| 17 | -import org.springframework.retry.RetryCallback; | |
| 18 | -import org.springframework.retry.RetryContext; | |
| 19 | -import org.springframework.retry.RetryListener; | |
| 20 | 17 | import org.springframework.retry.support.RetryTemplate; |
| 21 | 18 | import reactor.core.publisher.Flux; |
| 22 | 19 | |
| 23 | -import java.time.Duration; | |
| 20 | +import java.util.HashMap; | |
| 24 | 21 | import java.util.List; |
| 25 | -import java.util.stream.Collectors; | |
| 22 | +import java.util.Map; | |
| 23 | + | |
| 24 | +import static cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions.MODEL_DEFAULT; | |
| 26 | 25 | |
| 27 | -// TODO @fan:参考 yiyan 的修改建议,调整下 xinghuo 的实现;可以等 yiyan 修改完建议,然后我 review 完,再改这个哈; | |
| 28 | 26 | /** |
| 29 | - * 讯飞星火 client | |
| 30 | - * <p> | |
| 31 | - * author: fansili | |
| 32 | - * time: 2024/3/11 10:19 | |
| 27 | + * 讯飞星火 {@link ChatModel} 实现类 | |
| 28 | + * | |
| 29 | + * @author fansili | |
| 33 | 30 | */ |
| 34 | 31 | @Slf4j |
| 35 | -public class XingHuoChatClient implements ChatModel, StreamingChatModel { | |
| 36 | - | |
| 37 | - private XingHuoApi xingHuoApi; | |
| 38 | - | |
| 39 | - private XingHuoOptions xingHuoOptions; | |
| 40 | - | |
| 41 | - public final RetryTemplate retryTemplate = RetryTemplate.builder() | |
| 42 | - // 最大重试次数 10 | |
| 43 | - .maxAttempts(3) | |
| 44 | - .retryOn(ChatException.class) | |
| 45 | - // 最大重试5次,第一次间隔3000ms,第二次3000ms * 2,第三次3000ms * 3,以此类推,最大间隔3 * 60000ms | |
| 46 | - .exponentialBackoff(Duration.ofMillis(3000), 2, Duration.ofMillis(3 * 60000)) | |
| 47 | - .withListener(new RetryListener() { | |
| 48 | - @Override | |
| 49 | - public <T extends Object, E extends Throwable> void onError(RetryContext context, | |
| 50 | - RetryCallback<T, E> callback, Throwable throwable) { | |
| 51 | - System.err.println("正在重试... " + ExceptionUtil.getMessage(throwable)); | |
| 52 | - log.warn("重试异常:" + context.getRetryCount(), throwable); | |
| 53 | - } | |
| 32 | +public class XingHuoChatClient implements ChatModel { | |
| 33 | + | |
| 34 | + private static final String BASE_URL = "https://spark-api-open.xf-yun.com"; | |
| 54 | 35 | |
| 55 | - ; | |
| 56 | - }) | |
| 57 | - .build(); | |
| 36 | + private final XingHuoChatOptions defaultOptions; | |
| 37 | + private final RetryTemplate retryTemplate; | |
| 58 | 38 | |
| 59 | - public XingHuoChatClient(XingHuoApi xingHuoApi) { | |
| 60 | - this.xingHuoApi = xingHuoApi; | |
| 39 | + /** | |
| 40 | + * 星火兼容 OpenAI 的 HTTP 接口,所以复用它的实现,简化接入成本 | |
| 41 | + * | |
| 42 | + * 不过要注意,星火没有完全兼容,所以不能使用 {@link org.springframework.ai.openai.OpenAiChatModel} 调用,但是实现会参考它 | |
| 43 | + */ | |
| 44 | + private final OpenAiApi openAiApi; | |
| 45 | + | |
| 46 | + public XingHuoChatClient(String apiKey, String secretKey) { | |
| 47 | + this(apiKey, secretKey, | |
| 48 | + XingHuoChatOptions.builder().model(MODEL_DEFAULT).temperature(0.7F).build()); | |
| 61 | 49 | } |
| 62 | 50 | |
| 63 | - public XingHuoChatClient(XingHuoApi xingHuoApi, XingHuoOptions xingHuoOptions) { | |
| 64 | - this.xingHuoApi = xingHuoApi; | |
| 65 | - this.xingHuoOptions = xingHuoOptions; | |
| 51 | + public XingHuoChatClient(String apiKey, String secretKey, XingHuoChatOptions options) { | |
| 52 | + this(apiKey, secretKey, options, RetryUtils.DEFAULT_RETRY_TEMPLATE); | |
| 53 | + } | |
| 54 | + | |
| 55 | + public XingHuoChatClient(String apiKey, String secretKey, XingHuoChatOptions options, RetryTemplate retryTemplate) { | |
| 56 | + Assert.notEmpty(apiKey, "apiKey 不能为空"); | |
| 57 | + Assert.notEmpty(secretKey, "secretKey 不能为空"); | |
| 58 | + Assert.notNull(options, "options 不能为空"); | |
| 59 | + Assert.notNull(retryTemplate, "retryTemplate 不能为空"); | |
| 60 | + this.openAiApi = new OpenAiApi(BASE_URL, apiKey + ":" + secretKey); | |
| 61 | + this.defaultOptions = options; | |
| 62 | + this.retryTemplate = retryTemplate; | |
| 66 | 63 | } |
| 67 | 64 | |
| 68 | 65 | @Override |
| 69 | 66 | public ChatResponse call(Prompt prompt) { |
| 67 | + OpenAiApi.ChatCompletionRequest request = createRequest(prompt, false); | |
| 70 | 68 | return this.retryTemplate.execute(ctx -> { |
| 71 | - // ctx 会有重试的信息 | |
| 72 | - // 获取 chatOptions 属性 | |
| 73 | - XingHuoOptions chatOptions = this.getChatOptions(prompt); | |
| 74 | - // 创建 request 请求,stream模式需要供应商支持 | |
| 75 | - XingHuoChatCompletionRequest request = this.createRequest(prompt, chatOptions); | |
| 76 | - // 调用 callWithFunctionSupport 发送请求 | |
| 77 | - ResponseEntity<XingHuoChatCompletion> response = xingHuoApi.chatCompletionEntity(request, chatOptions.getChatModel()); | |
| 78 | - // 获取结果封装 ChatResponse | |
| 79 | - return new ChatResponse(List.of(new Generation(response.getBody().getPayload().getChoices().getText().get(0).getContent()))); | |
| 69 | + // 1.1 发起调用 | |
| 70 | + ResponseEntity<OpenAiApi.ChatCompletion> completionEntity = openAiApi.chatCompletionEntity(request); | |
| 71 | + // 1.2 校验结果 | |
| 72 | + OpenAiApi.ChatCompletion chatCompletion = completionEntity.getBody(); | |
| 73 | + if (chatCompletion == null) { | |
| 74 | + log.warn("No chat completion returned for prompt: {}", prompt); | |
| 75 | + return new ChatResponse(List.of()); | |
| 76 | + } | |
| 77 | + List<OpenAiApi.ChatCompletion.Choice> choices = chatCompletion.choices(); | |
| 78 | + if (choices == null) { | |
| 79 | + log.warn("No choices returned for prompt: {}", prompt); | |
| 80 | + return new ChatResponse(List.of()); | |
| 81 | + } | |
| 82 | + | |
| 83 | + // 2. 转换 ChatResponse 返回 | |
| 84 | + List<Generation> generations = choices.stream().map(choice -> { | |
| 85 | + Generation generation = new Generation(choice.message().content(), toMap(chatCompletion.id(), choice)); | |
| 86 | + if (choice.finishReason() != null) { | |
| 87 | + generation.withGenerationMetadata(ChatGenerationMetadata.from(choice.finishReason().name(), null)); | |
| 88 | + } | |
| 89 | + return generation; | |
| 90 | + }).toList(); | |
| 91 | + return new ChatResponse(generations, | |
| 92 | + OpenAiChatResponseMetadata.from(completionEntity.getBody())); | |
| 80 | 93 | }); |
| 81 | 94 | } |
| 82 | 95 | |
| 83 | - @Override | |
| 84 | - public ChatOptions getDefaultOptions() { | |
| 85 | - // TODO 芋艿:需要跟进下 | |
| 86 | - throw new UnsupportedOperationException(); | |
| 96 | + private Map<String, Object> toMap(String id, OpenAiApi.ChatCompletion.Choice choice) { | |
| 97 | + Map<String, Object> map = new HashMap<>(); | |
| 98 | + OpenAiApi.ChatCompletionMessage message = choice.message(); | |
| 99 | + if (message.role() != null) { | |
| 100 | + map.put("role", message.role().name()); | |
| 101 | + } | |
| 102 | + if (choice.finishReason() != null) { | |
| 103 | + map.put("finishReason", choice.finishReason().name()); | |
| 104 | + } | |
| 105 | + map.put("id", id); | |
| 106 | + return map; | |
| 87 | 107 | } |
| 88 | 108 | |
| 89 | 109 | @Override |
| 90 | 110 | public Flux<ChatResponse> stream(Prompt prompt) { |
| 91 | - // 获取 chatOptions 属性 | |
| 92 | - XingHuoOptions chatOptions = this.getChatOptions(prompt); | |
| 93 | - // 创建 request 请求,stream模式需要供应商支持 | |
| 94 | - XingHuoChatCompletionRequest request = this.createRequest(prompt, chatOptions); | |
| 95 | - // 发送请求 | |
| 96 | - Flux<XingHuoChatCompletion> response = this.xingHuoApi.chatCompletionStream(request, chatOptions.getChatModel()); | |
| 97 | - return response.map(res -> { | |
| 98 | - String content = res.getPayload().getChoices().getText().stream() | |
| 99 | - .map(item -> item.getContent()).collect(Collectors.joining()); | |
| 100 | - return new ChatResponse(List.of(new Generation(content))); | |
| 111 | + OpenAiApi.ChatCompletionRequest request = createRequest(prompt, true); | |
| 112 | + return this.retryTemplate.execute(ctx -> { | |
| 113 | + // 1. 发起调用 | |
| 114 | + Flux<OpenAiApi.ChatCompletionChunk> response = this.openAiApi.chatCompletionStream(request); | |
| 115 | + return response.map(chatCompletion -> { | |
| 116 | + String id = chatCompletion.id(); | |
| 117 | + // 2. 转换 ChatResponse 返回 | |
| 118 | + List<Generation> generations = chatCompletion.choices().stream().map(choice -> { | |
| 119 | + String finish = (choice.finishReason() != null ? choice.finishReason().name() : ""); | |
| 120 | + Generation generation = new Generation(choice.delta().content(), | |
| 121 | + Map.of("id", id, "role", choice.delta().role().name(), "finishReason", finish)); | |
| 122 | + if (choice.finishReason() != null) { | |
| 123 | + generation = generation.withGenerationMetadata( | |
| 124 | + ChatGenerationMetadata.from(choice.finishReason().name(), null)); | |
| 125 | + } | |
| 126 | + return generation; | |
| 127 | + }).toList(); | |
| 128 | + return new ChatResponse(generations); | |
| 129 | + }); | |
| 101 | 130 | }); |
| 102 | 131 | } |
| 103 | 132 | |
| 104 | - private XingHuoOptions getChatOptions(Prompt prompt) { | |
| 105 | - // 两个都为null 则没有配置文件 | |
| 106 | - if (xingHuoOptions == null && prompt.getOptions() == null) { | |
| 107 | - throw new ChatException("ChatOptions 未配置参数!"); | |
| 108 | - } | |
| 109 | - // 优先使用 Prompt 里面的 ChatOptions | |
| 110 | - ChatOptions options = xingHuoOptions; | |
| 133 | + OpenAiApi.ChatCompletionRequest createRequest(Prompt prompt, boolean stream) { | |
| 134 | + // 1. 构建 ChatCompletionMessage 对象 | |
| 135 | + List<OpenAiApi.ChatCompletionMessage> chatCompletionMessages = prompt.getInstructions().stream().map(m -> | |
| 136 | + new OpenAiApi.ChatCompletionMessage(m.getContent(), OpenAiApi.ChatCompletionMessage.Role.valueOf(m.getMessageType().name()))).toList(); | |
| 137 | + OpenAiApi.ChatCompletionRequest request = new OpenAiApi.ChatCompletionRequest(chatCompletionMessages, stream); | |
| 138 | + | |
| 139 | + // 2.1 补充 prompt 内置的 options | |
| 111 | 140 | if (prompt.getOptions() != null) { |
| 112 | - options = (ChatOptions) prompt.getOptions(); | |
| 141 | + if (prompt.getOptions() instanceof ChatOptions runtimeOptions) { | |
| 142 | + OpenAiChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions, | |
| 143 | + ChatOptions.class, OpenAiChatOptions.class); | |
| 144 | + request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, OpenAiApi.ChatCompletionRequest.class); | |
| 145 | + } else { | |
| 146 | + throw new IllegalArgumentException("Prompt options are not of type ChatOptions: " | |
| 147 | + + prompt.getOptions().getClass().getSimpleName()); | |
| 148 | + } | |
| 113 | 149 | } |
| 114 | - // Prompt 里面是一个 ChatOptions,用户可以随意传入,这里做一下判断 | |
| 115 | - if (!(options instanceof XingHuoOptions)) { | |
| 116 | - throw new ChatException("Prompt 传入的不是 XingHuoOptions!"); | |
| 150 | + // 2.2 补充默认 options | |
| 151 | + if (this.defaultOptions != null) { | |
| 152 | + request = ModelOptionsUtils.merge(request, this.defaultOptions, OpenAiApi.ChatCompletionRequest.class); | |
| 117 | 153 | } |
| 118 | - return (XingHuoOptions) options; | |
| 154 | + return request; | |
| 119 | 155 | } |
| 120 | 156 | |
| 121 | - private XingHuoChatCompletionRequest createRequest(Prompt prompt, XingHuoOptions xingHuoOptions) { | |
| 122 | - // 创建 header | |
| 123 | - XingHuoChatCompletionRequest.Header header = new XingHuoChatCompletionRequest.Header().setApp_id(xingHuoApi.getAppId()); | |
| 124 | - // 创建 params | |
| 125 | - XingHuoChatCompletionRequest.Parameter.Chat chatParameter = new XingHuoChatCompletionRequest.Parameter.Chat(); | |
| 126 | - BeanUtil.copyProperties(xingHuoOptions, chatParameter); | |
| 127 | - chatParameter.setDomain(xingHuoOptions.getChatModel().getModel()); | |
| 128 | - XingHuoChatCompletionRequest.Parameter parameter = new XingHuoChatCompletionRequest.Parameter().setChat(chatParameter); | |
| 129 | - // 创建 payload text 信息 | |
| 130 | - List<XingHuoChatCompletionRequest.Payload.Message.Text> texts = prompt.getInstructions().stream().map(message -> { | |
| 131 | - XingHuoChatCompletionRequest.Payload.Message.Text text = new XingHuoChatCompletionRequest.Payload.Message.Text(); | |
| 132 | - text.setContent(message.getContent()); | |
| 133 | - text.setRole(message.getMessageType().getValue()); | |
| 134 | - return text; | |
| 135 | - }).collect(Collectors.toList()); | |
| 136 | - // 创建 payload | |
| 137 | - XingHuoChatCompletionRequest.Payload payload = new XingHuoChatCompletionRequest.Payload() | |
| 138 | - .setMessage(new XingHuoChatCompletionRequest.Payload.Message().setText(texts)); | |
| 139 | - // 创建 request | |
| 140 | - return new XingHuoChatCompletionRequest() | |
| 141 | - .setHeader(header) | |
| 142 | - .setParameter(parameter) | |
| 143 | - .setPayload(payload); | |
| 157 | + @Override | |
| 158 | + public ChatOptions getDefaultOptions() { | |
| 159 | + return XingHuoChatOptions.fromOptions(defaultOptions); | |
| 144 | 160 | } |
| 161 | + | |
| 145 | 162 | } | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatModel.java deleted
| 1 | -package cn.iocoder.yudao.framework.ai.core.model.xinghuo; | |
| 2 | - | |
| 3 | -import lombok.Getter; | |
| 4 | - | |
| 5 | -/** | |
| 6 | - * 讯飞星火 模型 | |
| 7 | - * | |
| 8 | - * 文档地址:https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E | |
| 9 | - * | |
| 10 | - * 1tokens 约等于1.5个中文汉字 或者 0.8个英文单词 | |
| 11 | - * 星火V1.5支持[搜索]内置插件;星火V2.0、V3.0和V3.5支持[搜索]、[天气]、[日期]、[诗词]、[字词]、[股票]六个内置插件 | |
| 12 | - * 星火V3.5 现已支持system、Function Call 功能。 | |
| 13 | - * | |
| 14 | - * author: fansili | |
| 15 | - * time: 2024/3/11 10:12 | |
| 16 | - */ | |
| 17 | -@Getter | |
| 18 | -public enum XingHuoChatModel { | |
| 19 | - | |
| 20 | -// 文档地址:https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E | |
| 21 | -// general指向V1.5版本; | |
| 22 | -// generalv2指向V2版本; | |
| 23 | -// generalv3指向V3版本; | |
| 24 | -// generalv3.5指向V3.5版本; | |
| 25 | - | |
| 26 | - XING_HUO_1_5("星火大模型1.5", "general", "/v1.1/chat"), | |
| 27 | - XING_HUO_2_0("星火大模型2.0", "generalv2", "/v2.1/chat"), | |
| 28 | - XING_HUO_3_0("星火大模型3.0", "generalv3", "/v3.1/chat"), | |
| 29 | - XING_HUO_3_5("星火大模型3.5", "generalv3.5", "/v3.5/chat"), | |
| 30 | - | |
| 31 | - ; | |
| 32 | - | |
| 33 | - XingHuoChatModel(String name, String model, String uri) { | |
| 34 | - this.name = name; | |
| 35 | - this.model = model; | |
| 36 | - this.uri = uri; | |
| 37 | - } | |
| 38 | - | |
| 39 | - private String name; | |
| 40 | - | |
| 41 | - private String model; | |
| 42 | - | |
| 43 | - private String uri; | |
| 44 | - | |
| 45 | - public static XingHuoChatModel valueOfModel(String model) { | |
| 46 | - for (XingHuoChatModel itemEnum : XingHuoChatModel.values()) { | |
| 47 | - if (itemEnum.getModel().equals(model)) { | |
| 48 | - return itemEnum; | |
| 49 | - } | |
| 50 | - } | |
| 51 | - throw new IllegalArgumentException("Invalid MessageType value: " + model); | |
| 52 | - } | |
| 53 | -} |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatOptions.java
0 → 100644
| 1 | +package cn.iocoder.yudao.framework.ai.core.model.xinghuo; | |
| 2 | + | |
| 3 | +import lombok.AllArgsConstructor; | |
| 4 | +import lombok.Builder; | |
| 5 | +import lombok.Data; | |
| 6 | +import lombok.NoArgsConstructor; | |
| 7 | +import org.springframework.ai.chat.prompt.ChatOptions; | |
| 8 | + | |
| 9 | +/** | |
| 10 | + * 讯飞星火 {@link ChatOptions} 实现类 | |
| 11 | + * | |
| 12 | + * 参考文档:<a href="https://www.xfyun.cn/doc/spark/HTTP%E8%B0%83%E7%94%A8%E6%96%87%E6%A1%A3.html">HTTP 调用</a> | |
| 13 | + * | |
| 14 | + * @author fansili | |
| 15 | + */ | |
| 16 | +@Data | |
| 17 | +@NoArgsConstructor | |
| 18 | +@AllArgsConstructor | |
| 19 | +@Builder | |
| 20 | +public class XingHuoChatOptions implements ChatOptions { | |
| 21 | + | |
| 22 | + public static final String MODEL_DEFAULT = "generalv3.5"; | |
| 23 | + | |
| 24 | + /** | |
| 25 | + * 模型 | |
| 26 | + */ | |
| 27 | + private String model; | |
| 28 | + /** | |
| 29 | + * 温度 | |
| 30 | + */ | |
| 31 | + private Float temperature; | |
| 32 | + /** | |
| 33 | + * 最大 Token | |
| 34 | + */ | |
| 35 | + private Integer maxTokens; | |
| 36 | + /** | |
| 37 | + * K 个候选 | |
| 38 | + */ | |
| 39 | + private Integer topK; | |
| 40 | + | |
| 41 | + @Override | |
| 42 | + public Float getTopP() { | |
| 43 | + return null; | |
| 44 | + } | |
| 45 | + | |
| 46 | + public static XingHuoChatOptions fromOptions(XingHuoChatOptions fromOptions) { | |
| 47 | + return XingHuoChatOptions.builder() | |
| 48 | + .model(fromOptions.getModel()) | |
| 49 | + .temperature(fromOptions.getTemperature()) | |
| 50 | + .maxTokens(fromOptions.getMaxTokens()) | |
| 51 | + .topK(fromOptions.getTopK()) | |
| 52 | + .build(); | |
| 53 | + } | |
| 54 | + | |
| 55 | +} | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoOptions.java deleted
| 1 | -package cn.iocoder.yudao.framework.ai.core.model.xinghuo; | |
| 2 | - | |
| 3 | -import org.springframework.ai.chat.prompt.ChatOptions; | |
| 4 | -import lombok.Data; | |
| 5 | -import lombok.experimental.Accessors; | |
| 6 | - | |
| 7 | -/** | |
| 8 | - * 讯飞星火 | |
| 9 | - * <p> | |
| 10 | - * author: fansili | |
| 11 | - * time: 2024/3/16 20:29 | |
| 12 | - */ | |
| 13 | -@Data | |
| 14 | -@Accessors(chain = true) | |
| 15 | -public class XingHuoOptions implements ChatOptions { | |
| 16 | - | |
| 17 | - // TODO @fan:这里 model 参数,然后使用 string | |
| 18 | - /** | |
| 19 | - * https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E | |
| 20 | - * <p> | |
| 21 | - * 指定访问的领域: | |
| 22 | - * general指向V1.5版本; | |
| 23 | - * generalv2指向V2版本; | |
| 24 | - * generalv3指向V3版本; | |
| 25 | - * generalv3.5指向V3.5版本; | |
| 26 | - * 注意:不同的取值对应的url也不一样! | |
| 27 | - */ | |
| 28 | - private XingHuoChatModel chatModel = XingHuoChatModel.XING_HUO_3_5; | |
| 29 | - /** | |
| 30 | - * 取值范围 (0,1] ,默认值0.5 | |
| 31 | - */ | |
| 32 | - private Float temperature; | |
| 33 | - /** | |
| 34 | - * V1.5取值为[1,4096] | |
| 35 | - * V2.0、V3.0和V3.5取值为[1,8192],默认为2048。 | |
| 36 | - */ | |
| 37 | - private Integer maxTokens; | |
| 38 | - /** | |
| 39 | - * 取值为[1,6],默认为4 | |
| 40 | - */ | |
| 41 | - private Integer topK; | |
| 42 | - /** | |
| 43 | - * 需要保障用户下的唯一性,用于关联用户会话 | |
| 44 | - */ | |
| 45 | - private String chatId; | |
| 46 | - | |
| 47 | - @Override | |
| 48 | - public Float getTemperature() { | |
| 49 | - return this.temperature; | |
| 50 | - } | |
| 51 | - | |
| 52 | -// @Override | |
| 53 | -// public void setTemperature(Float temperature) { | |
| 54 | -// this.temperature = temperature; | |
| 55 | -// } | |
| 56 | - | |
| 57 | - @Override | |
| 58 | - public Float getTopP() { | |
| 59 | - return null; | |
| 60 | - } | |
| 61 | - | |
| 62 | -// @Override | |
| 63 | -// public void setTopP(Float topP) { | |
| 64 | -// | |
| 65 | -// } | |
| 66 | - | |
| 67 | - @Override | |
| 68 | - public Integer getTopK() { | |
| 69 | - return this.topK; | |
| 70 | - } | |
| 71 | - | |
| 72 | -// @Override | |
| 73 | -// public void setTopK(Integer topK) { | |
| 74 | -// this.topK = topK; | |
| 75 | -// } | |
| 76 | - | |
| 77 | -} |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoApi.java deleted
| 1 | -package cn.iocoder.yudao.framework.ai.core.model.xinghuo.api; | |
| 2 | - | |
| 3 | -import cn.hutool.http.HttpUtil; | |
| 4 | -import cn.hutool.json.JSONUtil; | |
| 5 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; | |
| 6 | -import lombok.Data; | |
| 7 | -import org.springframework.http.HttpStatusCode; | |
| 8 | -import org.springframework.http.ResponseEntity; | |
| 9 | -import org.springframework.web.reactive.function.client.WebClient; | |
| 10 | -import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; | |
| 11 | -import reactor.core.publisher.Flux; | |
| 12 | -import reactor.core.publisher.Mono; | |
| 13 | - | |
| 14 | -import javax.crypto.Mac; | |
| 15 | -import javax.crypto.spec.SecretKeySpec; | |
| 16 | -import java.net.URI; | |
| 17 | -import java.nio.charset.Charset; | |
| 18 | -import java.security.InvalidKeyException; | |
| 19 | -import java.security.NoSuchAlgorithmException; | |
| 20 | -import java.text.SimpleDateFormat; | |
| 21 | -import java.util.*; | |
| 22 | -import java.util.stream.Collectors; | |
| 23 | - | |
| 24 | -// TODO @fan:讯飞使用 spring websocket 接入,还是 okhttp?确认了,未使用的最好删除下,反正 git 也能找回 history | |
| 25 | -/** | |
| 26 | - * 讯飞星火 属性、api | |
| 27 | - * <p> | |
| 28 | - * 文档地址:https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E | |
| 29 | - * <p> | |
| 30 | - * author: fansili | |
| 31 | - * time: 2024/3/11 10:12 | |
| 32 | - */ | |
| 33 | -@Data | |
| 34 | -public class XingHuoApi { | |
| 35 | - | |
| 36 | - private static final String DEFAULT_BASE_URL = "wss://spark-api.xf-yun.com"; | |
| 37 | - | |
| 38 | - private String appId; | |
| 39 | - private String appKey; | |
| 40 | - private String secretKey; | |
| 41 | - private WebClient webClient; | |
| 42 | - // 创建 WebSocketClient 实例 | |
| 43 | - private ReactorNettyWebSocketClient socketClient = new ReactorNettyWebSocketClient(); | |
| 44 | - | |
| 45 | - public XingHuoApi(String appId, String appKey, String secretKey) { | |
| 46 | - this.appId = appId; | |
| 47 | - this.appKey = appKey; | |
| 48 | - this.secretKey = secretKey; | |
| 49 | - } | |
| 50 | - | |
| 51 | - public ResponseEntity<XingHuoChatCompletion> chatCompletionEntity(XingHuoChatCompletionRequest request, XingHuoChatModel xingHuoChatModel) { | |
| 52 | - String authUrl; | |
| 53 | - try { | |
| 54 | -// XingHuoChatModel useChatModel; | |
| 55 | - authUrl = getAuthorizationUrl("spark-api.xf-yun.com", xingHuoChatModel.getUri()); | |
| 56 | - } catch (NoSuchAlgorithmException | InvalidKeyException e) { | |
| 57 | - throw new RuntimeException(e); | |
| 58 | - } | |
| 59 | - // wss 请求的 URI | |
| 60 | - URI uri = URI.create(authUrl); | |
| 61 | - // 发起 wss 请求并处理响应 | |
| 62 | - Flux<XingHuoChatCompletion> messageFlux = Flux.create(sink -> { | |
| 63 | - socketClient.execute(uri, session -> | |
| 64 | - session.send(Mono.just(session.textMessage(JSONUtil.toJsonStr(request)))) | |
| 65 | - .thenMany(session.receive() | |
| 66 | - .map(WebSocketMessage -> { | |
| 67 | - return JSONUtil.toBean(WebSocketMessage.getPayloadAsText(), XingHuoChatCompletion.class); | |
| 68 | - }) | |
| 69 | - .doOnNext(sink::next) // 将接收到的消息推送到 Flux 中 | |
| 70 | - .doOnError(sink::error) // 处理错误 | |
| 71 | - .doOnTerminate(sink::complete)) // 完成时关闭 sink | |
| 72 | - .then()) | |
| 73 | - .subscribe(); // 订阅以开始会话 | |
| 74 | - }); | |
| 75 | - // 阻塞获取所有结果 | |
| 76 | - List<XingHuoChatCompletion> responseList = messageFlux.collectList().block(); | |
| 77 | - // 拼接 content | |
| 78 | - String responseContent = responseList.stream().map(item -> { | |
| 79 | - // 获取 content | |
| 80 | - return item.getPayload().getChoices().getText().stream().map(XingHuoChatCompletion.Text::getContent).collect(Collectors.joining()); | |
| 81 | - }).collect(Collectors.joining()); | |
| 82 | - // 将多个合并成一个 | |
| 83 | - XingHuoChatCompletion xingHuoChatCompletion = new XingHuoChatCompletion(); | |
| 84 | - xingHuoChatCompletion.setPayload(new XingHuoChatCompletion.Payload().setChoices(new XingHuoChatCompletion.Choices().setText(List.of(new XingHuoChatCompletion.Text().setContent(responseContent))))); | |
| 85 | - return new ResponseEntity<>(xingHuoChatCompletion, HttpStatusCode.valueOf(200)); | |
| 86 | - } | |
| 87 | - | |
| 88 | - | |
| 89 | - /** | |
| 90 | - * 获取验证请求url | |
| 91 | - * | |
| 92 | - * @return | |
| 93 | - */ | |
| 94 | - public String getAuthorizationUrl(String host, String path) throws NoSuchAlgorithmException, InvalidKeyException { | |
| 95 | - // 获取鉴权时间 date | |
| 96 | - SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); | |
| 97 | - format.setTimeZone(TimeZone.getTimeZone("GMT")); | |
| 98 | - String date = format.format(new Date()); | |
| 99 | - | |
| 100 | - // 获取signature_origin字段 | |
| 101 | - StringBuilder builder = new StringBuilder("host: ").append(host).append("\n"). | |
| 102 | - append("date: ").append(date).append("\n"). | |
| 103 | - append("GET ").append(path).append(" HTTP/1.1"); | |
| 104 | - | |
| 105 | - // 获得signatue | |
| 106 | - Charset charset = Charset.forName("UTF-8"); | |
| 107 | - Mac mac = Mac.getInstance("hmacsha256"); | |
| 108 | - SecretKeySpec sp = new SecretKeySpec(secretKey.getBytes(charset), "hmacsha256"); | |
| 109 | - mac.init(sp); | |
| 110 | - byte[] basebefore = mac.doFinal(builder.toString().getBytes(charset)); | |
| 111 | - String signature = Base64.getEncoder().encodeToString(basebefore); | |
| 112 | - //获得 authorization_origin | |
| 113 | - String authorization_origin = String.format("api_key=\"%s\",algorithm=\"%s\",headers=\"%s\",signature=\"%s\"", appKey, "hmac-sha256", "host date request-line", signature); | |
| 114 | - //获得authorization | |
| 115 | - String authorization = Base64.getEncoder().encodeToString(authorization_origin.getBytes(charset)); | |
| 116 | - // 获取httpUrl | |
| 117 | - Map<String, Object> param = new HashMap<>(); | |
| 118 | - param.put("authorization", authorization); | |
| 119 | - param.put("date", date); | |
| 120 | - param.put("host", host); | |
| 121 | - | |
| 122 | - String toParams = HttpUtil.toParams(param); | |
| 123 | - return "wss://" + host + path + "?" + toParams; | |
| 124 | - } | |
| 125 | - | |
| 126 | - public Flux<XingHuoChatCompletion> chatCompletionStream(XingHuoChatCompletionRequest request, XingHuoChatModel xingHuoChatModel) { | |
| 127 | - String authUrl; | |
| 128 | - try { | |
| 129 | - authUrl = getAuthorizationUrl("spark-api.xf-yun.com", xingHuoChatModel.getUri()); | |
| 130 | - } catch (NoSuchAlgorithmException | InvalidKeyException e) { | |
| 131 | - throw new RuntimeException(e); | |
| 132 | - } | |
| 133 | - // wss 请求的 URI | |
| 134 | - URI uri = URI.create(authUrl); | |
| 135 | - // 发起 wss 请求并处理响应 | |
| 136 | - // 创建一个 Flux 来处理接收到的消息 | |
| 137 | - return Flux.create(sink -> { | |
| 138 | - socketClient.execute(uri, session -> | |
| 139 | - session.send(Mono.just(session.textMessage(JSONUtil.toJsonStr(request)))) | |
| 140 | - .thenMany(session.receive() | |
| 141 | - .map(WebSocketMessage -> JSONUtil.toBean(WebSocketMessage.getPayloadAsText(), XingHuoChatCompletion.class)) | |
| 142 | - .doOnNext(sink::next) // 将接收到的消息推送到 Flux 中 | |
| 143 | - .doOnError(sink::error) // 处理错误 | |
| 144 | - .doOnTerminate(sink::complete)) // 完成时关闭 sink | |
| 145 | - .then()) | |
| 146 | - .subscribe(); // 订阅以开始会话 | |
| 147 | - }); | |
| 148 | - } | |
| 149 | -} |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletion.java deleted
| 1 | -package cn.iocoder.yudao.framework.ai.core.model.xinghuo.api; | |
| 2 | - | |
| 3 | -import lombok.Data; | |
| 4 | -import lombok.experimental.Accessors; | |
| 5 | - | |
| 6 | -import java.util.List; | |
| 7 | - | |
| 8 | -/** | |
| 9 | - * author: fansili | |
| 10 | - * time: 2024/3/11 10:20 | |
| 11 | - */ | |
| 12 | -@Data | |
| 13 | -@Accessors(chain = true) | |
| 14 | -public class XingHuoChatCompletion { | |
| 15 | - private Header header; | |
| 16 | - private Payload payload; | |
| 17 | - | |
| 18 | - @Data | |
| 19 | - @Accessors(chain = true) | |
| 20 | - public static class Header { | |
| 21 | - private int code; | |
| 22 | - private String message; | |
| 23 | - private String sid; | |
| 24 | - private int status; | |
| 25 | - } | |
| 26 | - | |
| 27 | - @Data | |
| 28 | - @Accessors(chain = true) | |
| 29 | - public static class Payload { | |
| 30 | - private Choices choices; | |
| 31 | - } | |
| 32 | - | |
| 33 | - @Data | |
| 34 | - @Accessors(chain = true) | |
| 35 | - public static class Choices { | |
| 36 | - private int status; | |
| 37 | - private int seq; | |
| 38 | - private List<Text> text; | |
| 39 | - } | |
| 40 | - | |
| 41 | - @Data | |
| 42 | - @Accessors(chain = true) | |
| 43 | - public static class Text { | |
| 44 | - private String content; | |
| 45 | - private String role; | |
| 46 | - private int index; | |
| 47 | - } | |
| 48 | -} |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletionMessage.java deleted
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletionRequest.java deleted
| 1 | -package cn.iocoder.yudao.framework.ai.core.model.xinghuo.api; | |
| 2 | - | |
| 3 | -import lombok.Data; | |
| 4 | -import lombok.Getter; | |
| 5 | -import lombok.experimental.Accessors; | |
| 6 | - | |
| 7 | -import java.util.List; | |
| 8 | - | |
| 9 | -/** | |
| 10 | - * 讯飞星火 request | |
| 11 | - * | |
| 12 | - * author: fansili | |
| 13 | - * time: 2024/3/11 10:20 | |
| 14 | - */ | |
| 15 | -@Data | |
| 16 | -@Accessors(chain = true) | |
| 17 | -public class XingHuoChatCompletionRequest { | |
| 18 | - | |
| 19 | - private Header header; | |
| 20 | - private Parameter parameter; | |
| 21 | - private Payload payload; | |
| 22 | - | |
| 23 | - @Data | |
| 24 | - @Accessors(chain = true) | |
| 25 | - public static class Header { | |
| 26 | - private String app_id; | |
| 27 | - private String uid; | |
| 28 | - } | |
| 29 | - | |
| 30 | - @Data | |
| 31 | - @Accessors(chain = true) | |
| 32 | - public static class Parameter { | |
| 33 | - private Chat chat; | |
| 34 | - | |
| 35 | - @Data | |
| 36 | - @Accessors(chain = true) | |
| 37 | - public static class Chat { | |
| 38 | - /** | |
| 39 | - * https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E | |
| 40 | - * | |
| 41 | - * 指定访问的领域: | |
| 42 | - * general指向V1.5版本; | |
| 43 | - * generalv2指向V2版本; | |
| 44 | - * generalv3指向V3版本; | |
| 45 | - * generalv3.5指向V3.5版本; | |
| 46 | - * 注意:不同的取值对应的url也不一样! | |
| 47 | - */ | |
| 48 | - private String domain = "generalv3.5"; | |
| 49 | - /** | |
| 50 | - * 取值范围 (0,1] ,默认值0.5 | |
| 51 | - */ | |
| 52 | - private Float temperature; | |
| 53 | - /** | |
| 54 | - * V1.5取值为[1,4096] | |
| 55 | - * V2.0、V3.0和V3.5取值为[1,8192],默认为2048。 | |
| 56 | - */ | |
| 57 | - private Integer max_tokens; | |
| 58 | - /** | |
| 59 | - * 取值为[1,6],默认为4 | |
| 60 | - */ | |
| 61 | - private Integer top_k; | |
| 62 | - /** | |
| 63 | - * 需要保障用户下的唯一性,用于关联用户会话 | |
| 64 | - */ | |
| 65 | - private String chat_id; | |
| 66 | - } | |
| 67 | - } | |
| 68 | - | |
| 69 | - @Data | |
| 70 | - @Accessors(chain = true) | |
| 71 | - public static class Payload { | |
| 72 | - private Message message; | |
| 73 | - | |
| 74 | - @Data | |
| 75 | - @Accessors(chain = true) | |
| 76 | - public static class Message { | |
| 77 | - private List<Text> text; | |
| 78 | - | |
| 79 | - | |
| 80 | - @Data | |
| 81 | - @Accessors(chain = true) | |
| 82 | - public static class Text { | |
| 83 | - /** | |
| 84 | - * 角色 | |
| 85 | - */ | |
| 86 | - private String role; | |
| 87 | - /** | |
| 88 | - * 消息内容 | |
| 89 | - */ | |
| 90 | - private String content; | |
| 91 | - private Integer index; | |
| 92 | - | |
| 93 | - @Getter | |
| 94 | - public static enum Role { | |
| 95 | - SYSTEM("system"), | |
| 96 | - USER("user"), | |
| 97 | - ASSISTANT("assistant"); | |
| 98 | - private String name; | |
| 99 | - | |
| 100 | - private Role(String name) { | |
| 101 | - this.name = name; | |
| 102 | - } | |
| 103 | - } | |
| 104 | - } | |
| 105 | - } | |
| 106 | - } | |
| 107 | -} |
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java
| ... | ... | @@ -2,8 +2,7 @@ package cn.iocoder.yudao.framework.ai.core.util; |
| 2 | 2 | |
| 3 | 3 | import cn.hutool.core.util.StrUtil; |
| 4 | 4 | import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; |
| 5 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; | |
| 6 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoOptions; | |
| 5 | +import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions; | |
| 7 | 6 | import com.alibaba.cloud.ai.tongyi.chat.TongYiChatOptions; |
| 8 | 7 | import org.springframework.ai.chat.messages.*; |
| 9 | 8 | import org.springframework.ai.chat.prompt.ChatOptions; |
| ... | ... | @@ -31,8 +30,7 @@ public class AiUtils { |
| 31 | 30 | // return QianFanChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build(); |
| 32 | 31 | return QianFanChatOptions.builder().withTemperature(temperatureF).withMaxTokens(maxTokens).build(); |
| 33 | 32 | case XING_HUO: |
| 34 | - return new XingHuoOptions().setChatModel(XingHuoChatModel.valueOfModel(model)).setTemperature(temperatureF) | |
| 35 | - .setMaxTokens(maxTokens); | |
| 33 | + return XingHuoChatOptions.builder().model(model).temperature(temperatureF).maxTokens(maxTokens).build(); | |
| 36 | 34 | case QIAN_WEN: |
| 37 | 35 | return TongYiChatOptions.builder().withModel(model).withTemperature(temperature).withMaxTokens(maxTokens).build(); |
| 38 | 36 | default: | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientMainTests.java deleted
| 1 | -package cn.iocoder.yudao.framework.ai.chat; | |
| 2 | - | |
| 3 | -import cn.hutool.http.HttpUtil; | |
| 4 | -import cn.hutool.json.JSONUtil; | |
| 5 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoChatCompletion; | |
| 6 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoChatCompletionRequest; | |
| 7 | -import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient; | |
| 8 | -import org.springframework.web.reactive.socket.client.WebSocketClient; | |
| 9 | -import reactor.core.publisher.Flux; | |
| 10 | - | |
| 11 | -import javax.crypto.Mac; | |
| 12 | -import javax.crypto.spec.SecretKeySpec; | |
| 13 | -import java.net.MalformedURLException; | |
| 14 | -import java.net.URI; | |
| 15 | -import java.nio.charset.Charset; | |
| 16 | -import java.security.InvalidKeyException; | |
| 17 | -import java.security.NoSuchAlgorithmException; | |
| 18 | -import java.text.SimpleDateFormat; | |
| 19 | -import java.util.*; | |
| 20 | - | |
| 21 | -// TODO 芋艿:整理单测 | |
| 22 | -/** | |
| 23 | - * author: fansili | |
| 24 | - * time: 2024/3/13 20:47 | |
| 25 | - */ | |
| 26 | -public class XingHuoChatClientMainTests { | |
| 27 | - | |
| 28 | - | |
| 29 | - private static final String HOST_URL = "http://spark-api.xf-yun.com/v3.5/chat"; | |
| 30 | - private static final String API_KEY = "cb6415c19d6162cda07b47316fcb0416"; | |
| 31 | - private static final String API_SECRET = "Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh"; | |
| 32 | - | |
| 33 | - public static void main(String[] args) throws MalformedURLException, NoSuchAlgorithmException, InvalidKeyException { | |
| 34 | - String authUrl = getAuthorizationUrl("spark-api.xf-yun.com", "/v3.5/chat"); | |
| 35 | - System.err.println(authUrl); | |
| 36 | - | |
| 37 | - XingHuoChatCompletionRequest.Header header = new XingHuoChatCompletionRequest.Header().setApp_id("13c8cca6"); | |
| 38 | - XingHuoChatCompletionRequest.Parameter parameter | |
| 39 | - = new XingHuoChatCompletionRequest.Parameter() | |
| 40 | - .setChat(new XingHuoChatCompletionRequest.Parameter.Chat().setDomain("generalv3.5")); | |
| 41 | - | |
| 42 | - | |
| 43 | - XingHuoChatCompletionRequest.Payload.Message.Text text = new XingHuoChatCompletionRequest.Payload.Message.Text(); | |
| 44 | - text.setRole(XingHuoChatCompletionRequest.Payload.Message.Text.Role.USER.getName()); | |
| 45 | - text.setContent("世界上最好的开发语言是什么?"); | |
| 46 | - XingHuoChatCompletionRequest.Payload payload = new XingHuoChatCompletionRequest.Payload() | |
| 47 | - .setMessage(new XingHuoChatCompletionRequest.Payload.Message().setText(List.of(text))); | |
| 48 | - XingHuoChatCompletionRequest request = new XingHuoChatCompletionRequest() | |
| 49 | - .setHeader(header) | |
| 50 | - .setParameter(parameter) | |
| 51 | - .setPayload(payload); | |
| 52 | - | |
| 53 | - System.err.println(JSONUtil.toJsonPrettyStr(request)); | |
| 54 | - | |
| 55 | - | |
| 56 | - // 创建 WebSocketClient 实例 | |
| 57 | - WebSocketClient client = new ReactorNettyWebSocketClient(); | |
| 58 | - | |
| 59 | - // wss 请求的 URI | |
| 60 | - URI uri = URI.create(authUrl); | |
| 61 | - | |
| 62 | - // 发起 wss 请求并处理响应 | |
| 63 | - client.execute(uri, session -> | |
| 64 | - // 使用会话发送消息,并接收回应 | |
| 65 | - session.send(Flux.just(session.textMessage(JSONUtil.toJsonStr(request)))) | |
| 66 | - .thenMany(session.receive() | |
| 67 | - .map(WebSocketMessage -> { | |
| 68 | - System.err.println(WebSocketMessage.getPayloadAsText()); | |
| 69 | - return JSONUtil.toBean(WebSocketMessage.getPayloadAsText(), XingHuoChatCompletion.class); | |
| 70 | - }) | |
| 71 | - .log()) // 打印接收到的消息 | |
| 72 | - .then()) | |
| 73 | - .block(); // 等待操作完成或超时 | |
| 74 | - | |
| 75 | - // 阻止退出 | |
| 76 | - Scanner scanner = new Scanner(System.in); | |
| 77 | - scanner.nextLine(); | |
| 78 | - } | |
| 79 | - | |
| 80 | - | |
| 81 | - /** | |
| 82 | - * 获取验证请求url | |
| 83 | - * | |
| 84 | - * @return | |
| 85 | - */ | |
| 86 | - public static String getAuthorizationUrl(String host, String path) throws NoSuchAlgorithmException, InvalidKeyException { | |
| 87 | - // 获取鉴权时间 date | |
| 88 | - SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); | |
| 89 | - format.setTimeZone(TimeZone.getTimeZone("GMT")); | |
| 90 | - String date = format.format(new Date()); | |
| 91 | - | |
| 92 | - // 获取signature_origin字段 | |
| 93 | - StringBuilder builder = new StringBuilder("host: ").append(host).append("\n"). | |
| 94 | - append("date: ").append(date).append("\n"). | |
| 95 | - append("GET ").append(path).append(" HTTP/1.1"); | |
| 96 | - | |
| 97 | - // 获得signatue | |
| 98 | - Charset charset = Charset.forName("UTF-8"); | |
| 99 | - Mac mac = Mac.getInstance("hmacsha256"); | |
| 100 | - SecretKeySpec sp = new SecretKeySpec(API_SECRET.getBytes(charset), "hmacsha256"); | |
| 101 | - mac.init(sp); | |
| 102 | - byte[] basebefore = mac.doFinal(builder.toString().getBytes(charset)); | |
| 103 | - String signature = Base64.getEncoder().encodeToString(basebefore); | |
| 104 | - //获得 authorization_origin | |
| 105 | - String authorization_origin = String.format("api_key=\"%s\",algorithm=\"%s\",headers=\"%s\",signature=\"%s\"", API_KEY, "hmac-sha256", "host date request-line", signature); | |
| 106 | - //获得authorization | |
| 107 | - String authorization = Base64.getEncoder().encodeToString(authorization_origin.getBytes(charset)); | |
| 108 | - // 获取httpUrl | |
| 109 | - Map<String, Object> param = new HashMap<>(); | |
| 110 | - param.put("authorization", authorization); | |
| 111 | - param.put("date", date); | |
| 112 | - param.put("host", host); | |
| 113 | - | |
| 114 | - String toParams = HttpUtil.toParams(param); | |
| 115 | - return "wss://" + host + path + "?" + toParams; | |
| 116 | - } | |
| 117 | - | |
| 118 | -} |
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientTests.java
| 1 | 1 | package cn.iocoder.yudao.framework.ai.chat; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient; |
| 4 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; | |
| 5 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoOptions; | |
| 6 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoApi; | |
| 7 | -import org.junit.Before; | |
| 8 | -import org.junit.Test; | |
| 4 | +import org.junit.jupiter.api.Test; | |
| 9 | 5 | import org.springframework.ai.chat.messages.Message; |
| 10 | 6 | import org.springframework.ai.chat.messages.SystemMessage; |
| 11 | 7 | import org.springframework.ai.chat.messages.UserMessage; |
| ... | ... | @@ -15,58 +11,44 @@ import reactor.core.publisher.Flux; |
| 15 | 11 | |
| 16 | 12 | import java.util.ArrayList; |
| 17 | 13 | import java.util.List; |
| 18 | -import java.util.Scanner; | |
| 19 | -import java.util.function.Consumer; | |
| 20 | 14 | |
| 21 | -// TODO 芋艿:整理单测 | |
| 22 | 15 | /** |
| 23 | - * 讯飞星火 tests | |
| 24 | - * <p> | |
| 25 | - * author: fansili | |
| 26 | - * time: 2024/3/11 11:00 | |
| 16 | + * {@link XingHuoChatClient} 集成测试 | |
| 17 | + * | |
| 18 | + * @author fansili | |
| 27 | 19 | */ |
| 28 | 20 | public class XingHuoChatClientTests { |
| 29 | 21 | |
| 30 | - private XingHuoChatClient xingHuoChatClient; | |
| 31 | - | |
| 32 | - @Before | |
| 33 | - public void setup() { | |
| 34 | - // 初始化 xingHuoChatClient | |
| 35 | - xingHuoChatClient = new XingHuoChatClient( | |
| 36 | - new XingHuoApi( | |
| 37 | - "13c8cca6", | |
| 38 | - "cb6415c19d6162cda07b47316fcb0416", | |
| 39 | - "Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh" | |
| 40 | - ), | |
| 41 | - new XingHuoOptions().setChatModel(XingHuoChatModel.XING_HUO_3_5) | |
| 42 | - ); | |
| 43 | - } | |
| 22 | + private final XingHuoChatClient client = new XingHuoChatClient( | |
| 23 | + "cb6415c19d6162cda07b47316fcb0416", | |
| 24 | + "Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh"); | |
| 44 | 25 | |
| 45 | 26 | @Test |
| 46 | - public void callTest() { | |
| 27 | + public void testCall() { | |
| 28 | + // 准备参数 | |
| 47 | 29 | List<Message> messages = new ArrayList<>(); |
| 48 | 30 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| 49 | - messages.add(new UserMessage("长沙怎么样?")); | |
| 31 | + messages.add(new UserMessage("1 + 1 = ?")); | |
| 50 | 32 | |
| 51 | - ChatResponse call = xingHuoChatClient.call(new Prompt(messages)); | |
| 52 | - System.err.println(call.getResult()); | |
| 33 | + // 调用 | |
| 34 | + ChatResponse response = client.call(new Prompt(messages)); | |
| 35 | + // 打印结果 | |
| 36 | + System.err.println(response); | |
| 53 | 37 | } |
| 54 | 38 | |
| 55 | 39 | @Test |
| 56 | - public void streamTest() { | |
| 40 | + public void testStream() { | |
| 41 | + // 准备参数 | |
| 57 | 42 | List<Message> messages = new ArrayList<>(); |
| 58 | 43 | messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); |
| 59 | - messages.add(new UserMessage("长沙怎么样?")); | |
| 60 | - | |
| 61 | - Flux<ChatResponse> stream = xingHuoChatClient.stream(new Prompt(messages)); | |
| 62 | - stream.subscribe(new Consumer<ChatResponse>() { | |
| 63 | - @Override | |
| 64 | - public void accept(ChatResponse chatResponse) { | |
| 65 | - System.err.print(chatResponse.getResult().getOutput().getContent()); | |
| 66 | - } | |
| 67 | - }); | |
| 68 | - // 阻止退出 | |
| 69 | - Scanner scanner = new Scanner(System.in); | |
| 70 | - scanner.nextLine(); | |
| 44 | + messages.add(new UserMessage("1 + 1 = ?")); | |
| 45 | + | |
| 46 | + // 调用 | |
| 47 | + Flux<ChatResponse> flux = client.stream(new Prompt(messages)); | |
| 48 | + // 打印结果 | |
| 49 | + List<ChatResponse> responses = flux.collectList().block(); | |
| 50 | + assert responses != null; | |
| 51 | + responses.forEach(System.err::println); | |
| 71 | 52 | } |
| 53 | + | |
| 72 | 54 | } | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoOkHttpTests.java deleted
| 1 | -package cn.iocoder.yudao.framework.ai.chat; | |
| 2 | - | |
| 3 | -import cn.hutool.http.HttpUtil; | |
| 4 | -import cn.hutool.json.JSONUtil; | |
| 5 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient; | |
| 6 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoChatCompletion; | |
| 7 | -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoChatCompletionRequest; | |
| 8 | -import okhttp3.*; | |
| 9 | -import org.jetbrains.annotations.NotNull; | |
| 10 | - | |
| 11 | -import javax.crypto.Mac; | |
| 12 | -import javax.crypto.spec.SecretKeySpec; | |
| 13 | -import java.net.MalformedURLException; | |
| 14 | -import java.nio.charset.Charset; | |
| 15 | -import java.security.InvalidKeyException; | |
| 16 | -import java.security.NoSuchAlgorithmException; | |
| 17 | -import java.text.SimpleDateFormat; | |
| 18 | -import java.util.*; | |
| 19 | - | |
| 20 | -// TODO 芋艿:整理单测 | |
| 21 | -/** | |
| 22 | - * 讯飞星火 tests | |
| 23 | - * <p> | |
| 24 | - * author: fansili | |
| 25 | - * time: 2024/3/11 11:00 | |
| 26 | - */ | |
| 27 | -public class XingHuoOkHttpTests { | |
| 28 | - | |
| 29 | - private static final String HOST_URL = "http://spark-api.xf-yun.com/v3.5/chat"; | |
| 30 | - private static final String API_KEY = "cb6415c19d6162cda07b47316fcb0416"; | |
| 31 | - private static final String API_SECRET = "Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh"; | |
| 32 | - | |
| 33 | - private XingHuoChatClient xingHuoChatClient; | |
| 34 | - | |
| 35 | - public static void main(String[] args) throws MalformedURLException, NoSuchAlgorithmException, InvalidKeyException { | |
| 36 | - String authUrl = getAuthorizationUrl("spark-api.xf-yun.com", "/v3.5/chat"); | |
| 37 | - System.err.println(authUrl); | |
| 38 | - | |
| 39 | - XingHuoChatCompletionRequest.Header header = new XingHuoChatCompletionRequest.Header().setApp_id("13c8cca6"); | |
| 40 | - XingHuoChatCompletionRequest.Parameter parameter | |
| 41 | - = new XingHuoChatCompletionRequest.Parameter() | |
| 42 | - .setChat(new XingHuoChatCompletionRequest.Parameter.Chat().setDomain("generalv3.5")); | |
| 43 | - | |
| 44 | - | |
| 45 | - XingHuoChatCompletionRequest.Payload.Message.Text text = new XingHuoChatCompletionRequest.Payload.Message.Text(); | |
| 46 | - text.setRole(XingHuoChatCompletionRequest.Payload.Message.Text.Role.USER.getName()); | |
| 47 | - text.setContent("世界上最好的开发语言是什么?"); | |
| 48 | - XingHuoChatCompletionRequest.Payload payload = new XingHuoChatCompletionRequest.Payload() | |
| 49 | - .setMessage(new XingHuoChatCompletionRequest.Payload.Message().setText(List.of(text))); | |
| 50 | - XingHuoChatCompletionRequest request = new XingHuoChatCompletionRequest() | |
| 51 | - .setHeader(header) | |
| 52 | - .setParameter(parameter) | |
| 53 | - .setPayload(payload); | |
| 54 | - | |
| 55 | - System.err.println(JSONUtil.toJsonPrettyStr(request)); | |
| 56 | - | |
| 57 | - OkHttpClient client = new OkHttpClient(); | |
| 58 | - Request request2 = new Request.Builder() | |
| 59 | - .url(authUrl) // 替换为你的 wss URL | |
| 60 | - .build(); | |
| 61 | - | |
| 62 | - WebSocketListener webSocketListener = new WebSocketListener() { | |
| 63 | - | |
| 64 | - @Override | |
| 65 | - public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) { | |
| 66 | - boolean send = webSocket.send(JSONUtil.toJsonStr(request)); | |
| 67 | - System.err.println("发送 -> " + send); | |
| 68 | - System.err.println("链接成功!"); | |
| 69 | - } | |
| 70 | - | |
| 71 | - @Override | |
| 72 | - public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) { | |
| 73 | - super.onMessage(webSocket, text); | |
| 74 | -// System.err.println(text); | |
| 75 | - XingHuoChatCompletion response = JSONUtil.toBean(text, XingHuoChatCompletion.class); | |
| 76 | - for (XingHuoChatCompletion.Text text1 : response.getPayload().getChoices().getText()) { | |
| 77 | - System.err.print(text1.getContent()); | |
| 78 | - } | |
| 79 | - } | |
| 80 | - }; | |
| 81 | - | |
| 82 | - WebSocket webSocket = client.newWebSocket(request2, webSocketListener); | |
| 83 | -// webSocket.send(JSONUtil.toJsonStr(request)); | |
| 84 | - | |
| 85 | - | |
| 86 | - // Trigger shutdown of the dispatcher's executor so this process can exit cleanly. | |
| 87 | - client.dispatcher().executorService().shutdown(); | |
| 88 | - // 阻止退出 | |
| 89 | - Scanner scanner = new Scanner(System.in); | |
| 90 | - scanner.nextLine(); | |
| 91 | - } | |
| 92 | - | |
| 93 | - | |
| 94 | - /** | |
| 95 | - * 获取验证请求url | |
| 96 | - * | |
| 97 | - * @return | |
| 98 | - */ | |
| 99 | - public static String getAuthorizationUrl(String host, String path) throws NoSuchAlgorithmException, InvalidKeyException { | |
| 100 | - // 获取鉴权时间 date | |
| 101 | - SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US); | |
| 102 | - format.setTimeZone(TimeZone.getTimeZone("GMT")); | |
| 103 | - String date = format.format(new Date()); | |
| 104 | - | |
| 105 | - // 获取signature_origin字段 | |
| 106 | - StringBuilder builder = new StringBuilder("host: ").append(host).append("\n"). | |
| 107 | - append("date: ").append(date).append("\n"). | |
| 108 | - append("GET ").append(path).append(" HTTP/1.1"); | |
| 109 | - | |
| 110 | - // 获得signatue | |
| 111 | - Charset charset = Charset.forName("UTF-8"); | |
| 112 | - Mac mac = Mac.getInstance("hmacsha256"); | |
| 113 | - SecretKeySpec sp = new SecretKeySpec(API_SECRET.getBytes(charset), "hmacsha256"); | |
| 114 | - mac.init(sp); | |
| 115 | - byte[] basebefore = mac.doFinal(builder.toString().getBytes(charset)); | |
| 116 | - String signature = Base64.getEncoder().encodeToString(basebefore); | |
| 117 | - //获得 authorization_origin | |
| 118 | - String authorization_origin = String.format("api_key=\"%s\",algorithm=\"%s\",headers=\"%s\",signature=\"%s\"", API_KEY, "hmac-sha256", "host date request-line", signature); | |
| 119 | - //获得authorization | |
| 120 | - String authorization = Base64.getEncoder().encodeToString(authorization_origin.getBytes(charset)); | |
| 121 | - // 获取httpUrl | |
| 122 | - Map<String, Object> param = new HashMap<>(); | |
| 123 | - param.put("authorization", authorization); | |
| 124 | - param.put("date", date); | |
| 125 | - param.put("host", host); | |
| 126 | - | |
| 127 | - String toParams = HttpUtil.toParams(param); | |
| 128 | - return "wss://" + host + path + "?" + toParams; | |
| 129 | - } | |
| 130 | - | |
| 131 | -} |
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/OpenAiImageClientTests.java
| 1 | 1 | package cn.iocoder.yudao.framework.ai.image; |
| 2 | 2 | |
| 3 | +import org.junit.jupiter.api.BeforeEach; | |
| 4 | +import org.junit.jupiter.api.Test; | |
| 3 | 5 | import org.springframework.ai.image.ImagePrompt; |
| 4 | 6 | import org.springframework.ai.image.ImageResponse; |
| 5 | -import org.junit.Before; | |
| 6 | -import org.junit.Test; | |
| 7 | 7 | import org.springframework.ai.openai.OpenAiImageModel; |
| 8 | 8 | import org.springframework.ai.openai.api.OpenAiImageApi; |
| 9 | 9 | |
| ... | ... | @@ -25,7 +25,7 @@ public class OpenAiImageClientTests { |
| 25 | 25 | |
| 26 | 26 | private OpenAiImageModel openAiImageClient; |
| 27 | 27 | |
| 28 | - @Before | |
| 28 | + @BeforeEach | |
| 29 | 29 | public void setup() { |
| 30 | 30 | // 初始化 openAiImageClient |
| 31 | 31 | this.openAiImageClient = new OpenAiImageModel( | ... | ... |
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/music/SunoTests.java
| 1 | 1 | package cn.iocoder.yudao.framework.ai.music; |
| 2 | 2 | |
| 3 | 3 | import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; |
| 4 | -import org.junit.Before; | |
| 5 | -import org.junit.Test; | |
| 4 | +import org.junit.jupiter.api.Test; | |
| 6 | 5 | |
| 7 | 6 | import java.util.List; |
| 8 | 7 | |
| ... | ... | @@ -13,14 +12,8 @@ import java.util.List; |
| 13 | 12 | */ |
| 14 | 13 | public class SunoTests { |
| 15 | 14 | |
| 16 | - private SunoApi sunoApi; | |
| 17 | - | |
| 18 | - @Before | |
| 19 | - public void setup() { | |
| 20 | - String url = "https://suno-55ishh05u-status2xxs-projects.vercel.app"; | |
| 21 | -// String url = "http://127.0.0.1:3001"; | |
| 22 | - this.sunoApi = new SunoApi(url); | |
| 23 | - } | |
| 15 | + private final SunoApi sunoApi = new SunoApi("https://suno-55ishh05u-status2xxs-projects.vercel.app"); | |
| 16 | +// private final SunoApi sunoApi = new SunoApi("http://127.0.0.1:3001"); | |
| 24 | 17 | |
| 25 | 18 | @Test |
| 26 | 19 | public void selectById() { | ... | ... |
yudao-server/src/main/resources/application.yaml
| ... | ... | @@ -162,11 +162,6 @@ spring: |
| 162 | 162 | base-url: https://api.gptsapi.net |
| 163 | 163 | stabilityai: |
| 164 | 164 | api-key: sk-e53UqbboF8QJCscYvzJscJxJXoFcFg4iJjl1oqgE7baJETmx |
| 165 | - vertex: | |
| 166 | - ai: | |
| 167 | - gemini: | |
| 168 | - project-id: 1 # TODO 芋艿:缺配置 | |
| 169 | - location: 2 | |
| 170 | 165 | qianfan: # 文心一言 |
| 171 | 166 | api-key: x0cuLZ7XsaTCU08vuJWO87Lg |
| 172 | 167 | secret-key: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK |
| ... | ... | @@ -176,37 +171,24 @@ spring: |
| 176 | 171 | tongyi: |
| 177 | 172 | api-key: sk-Zsd81gZYg7 |
| 178 | 173 | |
| 179 | -yudao.ai: | |
| 180 | - xinghuo: | |
| 181 | - enable: true | |
| 182 | - aiPlatform: XING_HUO # TODO @fan:建议每个都独立配置属性类 | |
| 183 | - max-tokens: 1500 | |
| 184 | - temperature: 0.85 | |
| 185 | - topP: 0.8 | |
| 186 | - topK: 0 | |
| 187 | - appId: 13c8cca6 | |
| 188 | - appKey: cb6415c19d6162cda07b47316fcb0416 | |
| 189 | - secretKey: Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh | |
| 190 | - model: XING_HUO_3_5 | |
| 191 | - qianwen: | |
| 192 | - enable: true | |
| 193 | - aiPlatform: QIAN_WEN | |
| 194 | - max-tokens: 1500 | |
| 195 | - temperature: 0.85 | |
| 196 | - topP: 0.8 | |
| 197 | - topK: 0 | |
| 198 | - api-key: sk-Zsd81gZYg7 | |
| 199 | - model: QWEN_TURBO | |
| 200 | - midjourney: | |
| 201 | - enable: true | |
| 202 | -# base-url: https://api.holdai.top/mj-relax/mj | |
| 203 | - base-url: https://api.holdai.top/mj | |
| 204 | - api-key: sk-dZEPiVaNcT3FHhef51996bAa0bC74806BeAb620dA5Da10Bf | |
| 205 | - notify-url: http://java.nat300.top/admin-api/ai/image/midjourney/notify | |
| 206 | - suno: | |
| 207 | - enable: true | |
| 208 | -# base-url: https://suno-55ishh05u-status2xxs-projects.vercel.app | |
| 209 | - base-url: http://127.0.0.1:3001 | |
| 174 | +yudao: | |
| 175 | + ai: | |
| 176 | + xinghuo: | |
| 177 | + enable: true | |
| 178 | + appId: 13c8cca6 | |
| 179 | + appKey: cb6415c19d6162cda07b47316fcb0416 | |
| 180 | + secretKey: Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh | |
| 181 | + model: generalv3.5 | |
| 182 | + midjourney: | |
| 183 | + enable: true | |
| 184 | + # base-url: https://api.holdai.top/mj-relax/mj | |
| 185 | + base-url: https://api.holdai.top/mj | |
| 186 | + api-key: sk-dZEPiVaNcT3FHhef51996bAa0bC74806BeAb620dA5Da10Bf | |
| 187 | + notify-url: http://java.nat300.top/admin-api/ai/image/midjourney/notify | |
| 188 | + suno: | |
| 189 | + enable: true | |
| 190 | + # base-url: https://suno-55ishh05u-status2xxs-projects.vercel.app | |
| 191 | + base-url: http://127.0.0.1:3001 | |
| 210 | 192 | |
| 211 | 193 | --- #################### 芋道相关配置 #################### |
| 212 | 194 | ... | ... |