Commit 2d36ec485832d02d14d0d92fd45cb561210738a4

Authored by YunaiV
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,11 +33,6 @@
33 <artifactId>spring-ai-stability-ai-spring-boot-starter</artifactId> 33 <artifactId>spring-ai-stability-ai-spring-boot-starter</artifactId>
34 <version>${spring-ai.version}</version> 34 <version>${spring-ai.version}</version>
35 </dependency> 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 <dependency> 37 <dependency>
43 <groupId>cn.iocoder.boot</groupId> 38 <groupId>cn.iocoder.boot</groupId>
@@ -61,8 +56,8 @@ @@ -61,8 +56,8 @@
61 56
62 <!-- Test 测试相关 --> 57 <!-- Test 测试相关 -->
63 <dependency> 58 <dependency>
64 - <groupId>junit</groupId>  
65 - <artifactId>junit</artifactId> 59 + <groupId>org.springframework.boot</groupId>
  60 + <artifactId>spring-boot-starter-test</artifactId>
66 <scope>test</scope> 61 <scope>test</scope>
67 </dependency> 62 </dependency>
68 </dependencies> 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,8 +5,7 @@ import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactoryImpl;
5 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; 5 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
6 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; 6 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
7 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient; 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 import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration; 9 import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration;
11 import lombok.extern.slf4j.Slf4j; 10 import lombok.extern.slf4j.Slf4j;
12 import org.springframework.boot.autoconfigure.AutoConfiguration; 11 import org.springframework.boot.autoconfigure.AutoConfiguration;
@@ -36,21 +35,14 @@ public class YudaoAiAutoConfiguration { @@ -36,21 +35,14 @@ public class YudaoAiAutoConfiguration {
36 @Bean 35 @Bean
37 @ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true") 36 @ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true")
38 public XingHuoChatClient xingHuoChatClient(YudaoAiProperties yudaoAiProperties) { 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 @Bean 48 @Bean
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java
1 package cn.iocoder.yudao.framework.ai.config; 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 import lombok.Data; 3 import lombok.Data;
6 -import lombok.experimental.Accessors;  
7 -import org.springframework.ai.autoconfigure.openai.OpenAiImageProperties;  
8 import org.springframework.boot.context.properties.ConfigurationProperties; 4 import org.springframework.boot.context.properties.ConfigurationProperties;
9 5
10 /** 6 /**
11 - * ai 自动配置 7 + * 芋道 AI 配置类
12 * 8 *
13 * @author fansili 9 * @author fansili
14 - * @time 2024/4/12 16:29  
15 * @since 1.0 10 * @since 1.0
16 */ 11 */
17 -@Data  
18 -@Accessors(chain = true)  
19 @ConfigurationProperties(prefix = "yudao.ai") 12 @ConfigurationProperties(prefix = "yudao.ai")
  13 +@Data
20 public class YudaoAiProperties { 14 public class YudaoAiProperties {
21 15
  16 + /**
  17 + * 讯飞星火
  18 + */
22 private XingHuoProperties xinghuo; 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 @Data 31 @Data
47 - public static class XingHuoProperties extends ChatProperties { 32 + public static class XingHuoProperties {
48 33
  34 + private String enable;
49 private String appId; 35 private String appId;
50 private String appKey; 36 private String appKey;
51 private String secretKey; 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,8 +47,9 @@ public class YudaoAiProperties {
57 public static class MidjourneyProperties { 47 public static class MidjourneyProperties {
58 48
59 private String enable; 49 private String enable;
60 - private String apiKey;  
61 private String baseUrl; 50 private String baseUrl;
  51 +
  52 + private String apiKey;
62 private String notifyUrl; 53 private String notifyUrl;
63 54
64 } 55 }
@@ -68,9 +59,6 @@ public class YudaoAiProperties { @@ -68,9 +59,6 @@ public class YudaoAiProperties {
68 59
69 private boolean enable = false; 60 private boolean enable = false;
70 61
71 - /**  
72 - * API 服务的基本地址  
73 - */  
74 private String baseUrl; 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,7 +12,6 @@ import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
12 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi; 12 import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
13 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; 13 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
14 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient; 14 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient;
15 -import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoApi;  
16 import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration; 15 import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration;
17 import com.alibaba.cloud.ai.tongyi.TongYiConnectionProperties; 16 import com.alibaba.cloud.ai.tongyi.TongYiConnectionProperties;
18 import com.alibaba.cloud.ai.tongyi.chat.TongYiChatModel; 17 import com.alibaba.cloud.ai.tongyi.chat.TongYiChatModel;
@@ -178,11 +177,9 @@ public class AiClientFactoryImpl implements AiClientFactory { @@ -178,11 +177,9 @@ public class AiClientFactoryImpl implements AiClientFactory {
178 private static XingHuoChatClient buildXingHuoChatClient(String key) { 177 private static XingHuoChatClient buildXingHuoChatClient(String key) {
179 List<String> keys = StrUtil.split(key, '|'); 178 List<String> keys = StrUtil.split(key, '|');
180 Assert.equals(keys.size(), 3, "XingHuoChatClient 的密钥需要 (appid|appKey|secretKey) 格式"); 179 Assert.equals(keys.size(), 3, "XingHuoChatClient 的密钥需要 (appid|appKey|secretKey) 格式");
181 - String appId = keys.get(0);  
182 String appKey = keys.get(1); 180 String appKey = keys.get(1);
183 String secretKey = keys.get(2); 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 package cn.iocoder.yudao.framework.ai.core.model.xinghuo; 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 import lombok.extern.slf4j.Slf4j; 4 import lombok.extern.slf4j.Slf4j;
  5 +import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
10 import org.springframework.ai.chat.model.ChatModel; 6 import org.springframework.ai.chat.model.ChatModel;
11 import org.springframework.ai.chat.model.ChatResponse; 7 import org.springframework.ai.chat.model.ChatResponse;
12 import org.springframework.ai.chat.model.Generation; 8 import org.springframework.ai.chat.model.Generation;
13 -import org.springframework.ai.chat.model.StreamingChatModel;  
14 import org.springframework.ai.chat.prompt.ChatOptions; 9 import org.springframework.ai.chat.prompt.ChatOptions;
15 import org.springframework.ai.chat.prompt.Prompt; 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 import org.springframework.http.ResponseEntity; 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 import org.springframework.retry.support.RetryTemplate; 17 import org.springframework.retry.support.RetryTemplate;
21 import reactor.core.publisher.Flux; 18 import reactor.core.publisher.Flux;
22 19
23 -import java.time.Duration; 20 +import java.util.HashMap;
24 import java.util.List; 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 @Slf4j 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 @Override 65 @Override
69 public ChatResponse call(Prompt prompt) { 66 public ChatResponse call(Prompt prompt) {
  67 + OpenAiApi.ChatCompletionRequest request = createRequest(prompt, false);
70 return this.retryTemplate.execute(ctx -> { 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 @Override 109 @Override
90 public Flux<ChatResponse> stream(Prompt prompt) { 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 if (prompt.getOptions() != null) { 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
1 -package cn.iocoder.yudao.framework.ai.core.model.xinghuo.api;  
2 -  
3 -/**  
4 - * author: fansili  
5 - * time: 2024/3/11 10:20  
6 - */  
7 -public class XingHuoChatCompletionMessage {  
8 -}  
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,8 +2,7 @@ package cn.iocoder.yudao.framework.ai.core.util;
2 2
3 import cn.hutool.core.util.StrUtil; 3 import cn.hutool.core.util.StrUtil;
4 import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; 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 import com.alibaba.cloud.ai.tongyi.chat.TongYiChatOptions; 6 import com.alibaba.cloud.ai.tongyi.chat.TongYiChatOptions;
8 import org.springframework.ai.chat.messages.*; 7 import org.springframework.ai.chat.messages.*;
9 import org.springframework.ai.chat.prompt.ChatOptions; 8 import org.springframework.ai.chat.prompt.ChatOptions;
@@ -31,8 +30,7 @@ public class AiUtils { @@ -31,8 +30,7 @@ public class AiUtils {
31 // return QianFanChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build(); 30 // return QianFanChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();
32 return QianFanChatOptions.builder().withTemperature(temperatureF).withMaxTokens(maxTokens).build(); 31 return QianFanChatOptions.builder().withTemperature(temperatureF).withMaxTokens(maxTokens).build();
33 case XING_HUO: 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 case QIAN_WEN: 34 case QIAN_WEN:
37 return TongYiChatOptions.builder().withModel(model).withTemperature(temperature).withMaxTokens(maxTokens).build(); 35 return TongYiChatOptions.builder().withModel(model).withTemperature(temperature).withMaxTokens(maxTokens).build();
38 default: 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 package cn.iocoder.yudao.framework.ai.chat; 1 package cn.iocoder.yudao.framework.ai.chat;
2 2
3 import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient; 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 import org.springframework.ai.chat.messages.Message; 5 import org.springframework.ai.chat.messages.Message;
10 import org.springframework.ai.chat.messages.SystemMessage; 6 import org.springframework.ai.chat.messages.SystemMessage;
11 import org.springframework.ai.chat.messages.UserMessage; 7 import org.springframework.ai.chat.messages.UserMessage;
@@ -15,58 +11,44 @@ import reactor.core.publisher.Flux; @@ -15,58 +11,44 @@ import reactor.core.publisher.Flux;
15 11
16 import java.util.ArrayList; 12 import java.util.ArrayList;
17 import java.util.List; 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 public class XingHuoChatClientTests { 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 @Test 26 @Test
46 - public void callTest() { 27 + public void testCall() {
  28 + // 准备参数
47 List<Message> messages = new ArrayList<>(); 29 List<Message> messages = new ArrayList<>();
48 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); 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 @Test 39 @Test
56 - public void streamTest() { 40 + public void testStream() {
  41 + // 准备参数
57 List<Message> messages = new ArrayList<>(); 42 List<Message> messages = new ArrayList<>();
58 messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。")); 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 package cn.iocoder.yudao.framework.ai.image; 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 import org.springframework.ai.image.ImagePrompt; 5 import org.springframework.ai.image.ImagePrompt;
4 import org.springframework.ai.image.ImageResponse; 6 import org.springframework.ai.image.ImageResponse;
5 -import org.junit.Before;  
6 -import org.junit.Test;  
7 import org.springframework.ai.openai.OpenAiImageModel; 7 import org.springframework.ai.openai.OpenAiImageModel;
8 import org.springframework.ai.openai.api.OpenAiImageApi; 8 import org.springframework.ai.openai.api.OpenAiImageApi;
9 9
@@ -25,7 +25,7 @@ public class OpenAiImageClientTests { @@ -25,7 +25,7 @@ public class OpenAiImageClientTests {
25 25
26 private OpenAiImageModel openAiImageClient; 26 private OpenAiImageModel openAiImageClient;
27 27
28 - @Before 28 + @BeforeEach
29 public void setup() { 29 public void setup() {
30 // 初始化 openAiImageClient 30 // 初始化 openAiImageClient
31 this.openAiImageClient = new OpenAiImageModel( 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 package cn.iocoder.yudao.framework.ai.music; 1 package cn.iocoder.yudao.framework.ai.music;
2 2
3 import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi; 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 import java.util.List; 6 import java.util.List;
8 7
@@ -13,14 +12,8 @@ import java.util.List; @@ -13,14 +12,8 @@ import java.util.List;
13 */ 12 */
14 public class SunoTests { 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 @Test 18 @Test
26 public void selectById() { 19 public void selectById() {
yudao-server/src/main/resources/application.yaml
@@ -162,11 +162,6 @@ spring: @@ -162,11 +162,6 @@ spring:
162 base-url: https://api.gptsapi.net 162 base-url: https://api.gptsapi.net
163 stabilityai: 163 stabilityai:
164 api-key: sk-e53UqbboF8QJCscYvzJscJxJXoFcFg4iJjl1oqgE7baJETmx 164 api-key: sk-e53UqbboF8QJCscYvzJscJxJXoFcFg4iJjl1oqgE7baJETmx
165 - vertex:  
166 - ai:  
167 - gemini:  
168 - project-id: 1 # TODO 芋艿:缺配置  
169 - location: 2  
170 qianfan: # 文心一言 165 qianfan: # 文心一言
171 api-key: x0cuLZ7XsaTCU08vuJWO87Lg 166 api-key: x0cuLZ7XsaTCU08vuJWO87Lg
172 secret-key: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK 167 secret-key: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK
@@ -176,37 +171,24 @@ spring: @@ -176,37 +171,24 @@ spring:
176 tongyi: 171 tongyi:
177 api-key: sk-Zsd81gZYg7 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