【代码优化】AI:优化 xinghuo 的接入,复用 OpenAI,同时将 ws 替换成 htt 调用

This commit is contained in:
YunaiV 2024-07-06 08:56:36 +08:00
parent 2e73959a2c
commit 2d36ec4858
19 changed files with 259 additions and 951 deletions

View File

@ -33,11 +33,6 @@
<artifactId>spring-ai-stability-ai-spring-boot-starter</artifactId>
<version>${spring-ai.version}</version>
</dependency>
<!-- <dependency>-->
<!-- <groupId>org.springframework.ai</groupId>-->
<!-- <artifactId>spring-ai-vertex-ai-gemini</artifactId>-->
<!-- <version>1.0.3</version>-->
<!-- </dependency>-->
<dependency>
<groupId>cn.iocoder.boot</groupId>
@ -61,8 +56,8 @@
<!-- Test 测试相关 -->
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-test</artifactId>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -5,8 +5,7 @@ import cn.iocoder.yudao.framework.ai.core.factory.AiClientFactoryImpl;
import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoOptions;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoApi;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions;
import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration;
import lombok.extern.slf4j.Slf4j;
import org.springframework.boot.autoconfigure.AutoConfiguration;
@ -36,21 +35,14 @@ public class YudaoAiAutoConfiguration {
@Bean
@ConditionalOnProperty(value = "yudao.ai.xinghuo.enable", havingValue = "true")
public XingHuoChatClient xingHuoChatClient(YudaoAiProperties yudaoAiProperties) {
YudaoAiProperties.XingHuoProperties xingHuoProperties = yudaoAiProperties.getXinghuo();
// 转换配置
XingHuoOptions xingHuoOptions = new XingHuoOptions();
xingHuoOptions.setChatModel(xingHuoProperties.getModel());
xingHuoOptions.setTopK(xingHuoProperties.getTopK());
xingHuoOptions.setTemperature(xingHuoProperties.getTemperature());
xingHuoOptions.setMaxTokens(xingHuoProperties.getMaxTokens());
return new XingHuoChatClient(
new XingHuoApi(
xingHuoProperties.getAppId(),
xingHuoProperties.getAppKey(),
xingHuoProperties.getSecretKey()
),
xingHuoOptions
);
YudaoAiProperties.XingHuoProperties properties = yudaoAiProperties.getXinghuo();
XingHuoChatOptions options = XingHuoChatOptions.builder()
.model(properties.getModel())
.temperature(properties.getTemperature())
.maxTokens(properties.getMaxTokens())
.topK(properties.getTopK())
.build();
return new XingHuoChatClient(properties.getAppKey(), properties.getSecretKey(), options);
}
@Bean

View File

@ -1,55 +1,45 @@
package cn.iocoder.yudao.framework.ai.config;
import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
import lombok.Data;
import lombok.experimental.Accessors;
import org.springframework.ai.autoconfigure.openai.OpenAiImageProperties;
import org.springframework.boot.context.properties.ConfigurationProperties;
/**
* ai 自动配置
* 芋道 AI 配置类
*
* @author fansili
* @time 2024/4/12 16:29
* @since 1.0
*/
@Data
@Accessors(chain = true)
@ConfigurationProperties(prefix = "yudao.ai")
@Data
public class YudaoAiProperties {
/**
* 讯飞星火
*/
private XingHuoProperties xinghuo;
private OpenAiImageProperties openAiImage;
/**
* Midjourney 绘图
*/
private MidjourneyProperties midjourney;
/**
* Suno 音乐
*/
private SunoProperties suno;
@Data
@Accessors(chain = true)
public static class ChatProperties {
private boolean enable = false;
private AiPlatformEnum aiPlatform;
private Float temperature;
private Float topP;
private Integer topK;
/**
* 用于限制模型生成token的数量max_tokens设置的是生成上限并不表示一定会生成这么多的token数量
*/
private Integer maxTokens;
}
@Data
public static class XingHuoProperties extends ChatProperties {
public static class XingHuoProperties {
private String enable;
private String appId;
private String appKey;
private String secretKey;
private XingHuoChatModel model;
private String model;
private Float temperature;
private Integer maxTokens;
private Integer topK;
}
@ -57,8 +47,9 @@ public class YudaoAiProperties {
public static class MidjourneyProperties {
private String enable;
private String apiKey;
private String baseUrl;
private String apiKey;
private String notifyUrl;
}
@ -68,9 +59,6 @@ public class YudaoAiProperties {
private boolean enable = false;
/**
* API 服务的基本地址
*/
private String baseUrl;
}

View File

@ -12,7 +12,6 @@ import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
import cn.iocoder.yudao.framework.ai.core.model.midjourney.api.MidjourneyApi;
import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoApi;
import com.alibaba.cloud.ai.tongyi.TongYiAutoConfiguration;
import com.alibaba.cloud.ai.tongyi.TongYiConnectionProperties;
import com.alibaba.cloud.ai.tongyi.chat.TongYiChatModel;
@ -178,11 +177,9 @@ public class AiClientFactoryImpl implements AiClientFactory {
private static XingHuoChatClient buildXingHuoChatClient(String key) {
List<String> keys = StrUtil.split(key, '|');
Assert.equals(keys.size(), 3, "XingHuoChatClient 的密钥需要 (appid|appKey|secretKey) 格式");
String appId = keys.get(0);
String appKey = keys.get(1);
String secretKey = keys.get(2);
XingHuoApi xingHuoApi = new XingHuoApi(appId, appKey, secretKey);
return new XingHuoChatClient(xingHuoApi);
return new XingHuoChatClient(appKey, secretKey);
}
/**

View File

@ -1,145 +1,162 @@
package cn.iocoder.yudao.framework.ai.core.model.xinghuo;
import cn.hutool.core.bean.BeanUtil;
import cn.hutool.core.exceptions.ExceptionUtil;
import cn.iocoder.yudao.framework.ai.core.exception.ChatException;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoApi;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoChatCompletion;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoChatCompletionRequest;
import cn.hutool.core.lang.Assert;
import lombok.extern.slf4j.Slf4j;
import org.springframework.ai.chat.metadata.ChatGenerationMetadata;
import org.springframework.ai.chat.model.ChatModel;
import org.springframework.ai.chat.model.ChatResponse;
import org.springframework.ai.chat.model.Generation;
import org.springframework.ai.chat.model.StreamingChatModel;
import org.springframework.ai.chat.prompt.ChatOptions;
import org.springframework.ai.chat.prompt.Prompt;
import org.springframework.ai.model.ModelOptionsUtils;
import org.springframework.ai.openai.OpenAiChatOptions;
import org.springframework.ai.openai.api.OpenAiApi;
import org.springframework.ai.openai.metadata.OpenAiChatResponseMetadata;
import org.springframework.ai.retry.RetryUtils;
import org.springframework.http.ResponseEntity;
import org.springframework.retry.RetryCallback;
import org.springframework.retry.RetryContext;
import org.springframework.retry.RetryListener;
import org.springframework.retry.support.RetryTemplate;
import reactor.core.publisher.Flux;
import java.time.Duration;
import java.util.HashMap;
import java.util.List;
import java.util.stream.Collectors;
import java.util.Map;
import static cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions.MODEL_DEFAULT;
// TODO @fan参考 yiyan 的修改建议调整下 xinghuo 的实现可以等 yiyan 修改完建议然后我 review 再改这个哈
/**
* 讯飞星火 client
* <p>
* author: fansili
* time: 2024/3/11 10:19
* 讯飞星火 {@link ChatModel} 实现类
*
* @author fansili
*/
@Slf4j
public class XingHuoChatClient implements ChatModel, StreamingChatModel {
public class XingHuoChatClient implements ChatModel {
private XingHuoApi xingHuoApi;
private static final String BASE_URL = "https://spark-api-open.xf-yun.com";
private XingHuoOptions xingHuoOptions;
private final XingHuoChatOptions defaultOptions;
private final RetryTemplate retryTemplate;
public final RetryTemplate retryTemplate = RetryTemplate.builder()
// 最大重试次数 10
.maxAttempts(3)
.retryOn(ChatException.class)
// 最大重试5次第一次间隔3000ms第二次3000ms * 2第三次3000ms * 3以此类推最大间隔3 * 60000ms
.exponentialBackoff(Duration.ofMillis(3000), 2, Duration.ofMillis(3 * 60000))
.withListener(new RetryListener() {
@Override
public <T extends Object, E extends Throwable> void onError(RetryContext context,
RetryCallback<T, E> callback, Throwable throwable) {
System.err.println("正在重试... " + ExceptionUtil.getMessage(throwable));
log.warn("重试异常:" + context.getRetryCount(), throwable);
}
/**
* 星火兼容 OpenAI HTTP 接口所以复用它的实现简化接入成本
*
* 不过要注意星火没有完全兼容所以不能使用 {@link org.springframework.ai.openai.OpenAiChatModel} 调用但是实现会参考它
*/
private final OpenAiApi openAiApi;
;
})
.build();
public XingHuoChatClient(XingHuoApi xingHuoApi) {
this.xingHuoApi = xingHuoApi;
public XingHuoChatClient(String apiKey, String secretKey) {
this(apiKey, secretKey,
XingHuoChatOptions.builder().model(MODEL_DEFAULT).temperature(0.7F).build());
}
public XingHuoChatClient(XingHuoApi xingHuoApi, XingHuoOptions xingHuoOptions) {
this.xingHuoApi = xingHuoApi;
this.xingHuoOptions = xingHuoOptions;
public XingHuoChatClient(String apiKey, String secretKey, XingHuoChatOptions options) {
this(apiKey, secretKey, options, RetryUtils.DEFAULT_RETRY_TEMPLATE);
}
public XingHuoChatClient(String apiKey, String secretKey, XingHuoChatOptions options, RetryTemplate retryTemplate) {
Assert.notEmpty(apiKey, "apiKey 不能为空");
Assert.notEmpty(secretKey, "secretKey 不能为空");
Assert.notNull(options, "options 不能为空");
Assert.notNull(retryTemplate, "retryTemplate 不能为空");
this.openAiApi = new OpenAiApi(BASE_URL, apiKey + ":" + secretKey);
this.defaultOptions = options;
this.retryTemplate = retryTemplate;
}
@Override
public ChatResponse call(Prompt prompt) {
OpenAiApi.ChatCompletionRequest request = createRequest(prompt, false);
return this.retryTemplate.execute(ctx -> {
// ctx 会有重试的信息
// 获取 chatOptions 属性
XingHuoOptions chatOptions = this.getChatOptions(prompt);
// 创建 request 请求stream模式需要供应商支持
XingHuoChatCompletionRequest request = this.createRequest(prompt, chatOptions);
// 调用 callWithFunctionSupport 发送请求
ResponseEntity<XingHuoChatCompletion> response = xingHuoApi.chatCompletionEntity(request, chatOptions.getChatModel());
// 获取结果封装 ChatResponse
return new ChatResponse(List.of(new Generation(response.getBody().getPayload().getChoices().getText().get(0).getContent())));
// 1.1 发起调用
ResponseEntity<OpenAiApi.ChatCompletion> completionEntity = openAiApi.chatCompletionEntity(request);
// 1.2 校验结果
OpenAiApi.ChatCompletion chatCompletion = completionEntity.getBody();
if (chatCompletion == null) {
log.warn("No chat completion returned for prompt: {}", prompt);
return new ChatResponse(List.of());
}
List<OpenAiApi.ChatCompletion.Choice> choices = chatCompletion.choices();
if (choices == null) {
log.warn("No choices returned for prompt: {}", prompt);
return new ChatResponse(List.of());
}
// 2. 转换 ChatResponse 返回
List<Generation> generations = choices.stream().map(choice -> {
Generation generation = new Generation(choice.message().content(), toMap(chatCompletion.id(), choice));
if (choice.finishReason() != null) {
generation.withGenerationMetadata(ChatGenerationMetadata.from(choice.finishReason().name(), null));
}
return generation;
}).toList();
return new ChatResponse(generations,
OpenAiChatResponseMetadata.from(completionEntity.getBody()));
});
}
@Override
public ChatOptions getDefaultOptions() {
// TODO 芋艿需要跟进下
throw new UnsupportedOperationException();
private Map<String, Object> toMap(String id, OpenAiApi.ChatCompletion.Choice choice) {
Map<String, Object> map = new HashMap<>();
OpenAiApi.ChatCompletionMessage message = choice.message();
if (message.role() != null) {
map.put("role", message.role().name());
}
if (choice.finishReason() != null) {
map.put("finishReason", choice.finishReason().name());
}
map.put("id", id);
return map;
}
@Override
public Flux<ChatResponse> stream(Prompt prompt) {
// 获取 chatOptions 属性
XingHuoOptions chatOptions = this.getChatOptions(prompt);
// 创建 request 请求stream模式需要供应商支持
XingHuoChatCompletionRequest request = this.createRequest(prompt, chatOptions);
// 发送请求
Flux<XingHuoChatCompletion> response = this.xingHuoApi.chatCompletionStream(request, chatOptions.getChatModel());
return response.map(res -> {
String content = res.getPayload().getChoices().getText().stream()
.map(item -> item.getContent()).collect(Collectors.joining());
return new ChatResponse(List.of(new Generation(content)));
OpenAiApi.ChatCompletionRequest request = createRequest(prompt, true);
return this.retryTemplate.execute(ctx -> {
// 1. 发起调用
Flux<OpenAiApi.ChatCompletionChunk> response = this.openAiApi.chatCompletionStream(request);
return response.map(chatCompletion -> {
String id = chatCompletion.id();
// 2. 转换 ChatResponse 返回
List<Generation> generations = chatCompletion.choices().stream().map(choice -> {
String finish = (choice.finishReason() != null ? choice.finishReason().name() : "");
Generation generation = new Generation(choice.delta().content(),
Map.of("id", id, "role", choice.delta().role().name(), "finishReason", finish));
if (choice.finishReason() != null) {
generation = generation.withGenerationMetadata(
ChatGenerationMetadata.from(choice.finishReason().name(), null));
}
return generation;
}).toList();
return new ChatResponse(generations);
});
});
}
private XingHuoOptions getChatOptions(Prompt prompt) {
// 两个都为null 则没有配置文件
if (xingHuoOptions == null && prompt.getOptions() == null) {
throw new ChatException("ChatOptions 未配置参数!");
}
// 优先使用 Prompt 里面的 ChatOptions
ChatOptions options = xingHuoOptions;
OpenAiApi.ChatCompletionRequest createRequest(Prompt prompt, boolean stream) {
// 1. 构建 ChatCompletionMessage 对象
List<OpenAiApi.ChatCompletionMessage> chatCompletionMessages = prompt.getInstructions().stream().map(m ->
new OpenAiApi.ChatCompletionMessage(m.getContent(), OpenAiApi.ChatCompletionMessage.Role.valueOf(m.getMessageType().name()))).toList();
OpenAiApi.ChatCompletionRequest request = new OpenAiApi.ChatCompletionRequest(chatCompletionMessages, stream);
// 2.1 补充 prompt 内置的 options
if (prompt.getOptions() != null) {
options = (ChatOptions) prompt.getOptions();
if (prompt.getOptions() instanceof ChatOptions runtimeOptions) {
OpenAiChatOptions updatedRuntimeOptions = ModelOptionsUtils.copyToTarget(runtimeOptions,
ChatOptions.class, OpenAiChatOptions.class);
request = ModelOptionsUtils.merge(updatedRuntimeOptions, request, OpenAiApi.ChatCompletionRequest.class);
} else {
throw new IllegalArgumentException("Prompt options are not of type ChatOptions: "
+ prompt.getOptions().getClass().getSimpleName());
}
}
// Prompt 里面是一个 ChatOptions用户可以随意传入这里做一下判断
if (!(options instanceof XingHuoOptions)) {
throw new ChatException("Prompt 传入的不是 XingHuoOptions!");
// 2.2 补充默认 options
if (this.defaultOptions != null) {
request = ModelOptionsUtils.merge(request, this.defaultOptions, OpenAiApi.ChatCompletionRequest.class);
}
return (XingHuoOptions) options;
return request;
}
private XingHuoChatCompletionRequest createRequest(Prompt prompt, XingHuoOptions xingHuoOptions) {
// 创建 header
XingHuoChatCompletionRequest.Header header = new XingHuoChatCompletionRequest.Header().setApp_id(xingHuoApi.getAppId());
// 创建 params
XingHuoChatCompletionRequest.Parameter.Chat chatParameter = new XingHuoChatCompletionRequest.Parameter.Chat();
BeanUtil.copyProperties(xingHuoOptions, chatParameter);
chatParameter.setDomain(xingHuoOptions.getChatModel().getModel());
XingHuoChatCompletionRequest.Parameter parameter = new XingHuoChatCompletionRequest.Parameter().setChat(chatParameter);
// 创建 payload text 信息
List<XingHuoChatCompletionRequest.Payload.Message.Text> texts = prompt.getInstructions().stream().map(message -> {
XingHuoChatCompletionRequest.Payload.Message.Text text = new XingHuoChatCompletionRequest.Payload.Message.Text();
text.setContent(message.getContent());
text.setRole(message.getMessageType().getValue());
return text;
}).collect(Collectors.toList());
// 创建 payload
XingHuoChatCompletionRequest.Payload payload = new XingHuoChatCompletionRequest.Payload()
.setMessage(new XingHuoChatCompletionRequest.Payload.Message().setText(texts));
// 创建 request
return new XingHuoChatCompletionRequest()
.setHeader(header)
.setParameter(parameter)
.setPayload(payload);
@Override
public ChatOptions getDefaultOptions() {
return XingHuoChatOptions.fromOptions(defaultOptions);
}
}

View File

@ -1,53 +0,0 @@
package cn.iocoder.yudao.framework.ai.core.model.xinghuo;
import lombok.Getter;
/**
* 讯飞星火 模型
*
* 文档地址https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
*
* 1tokens 约等于1.5个中文汉字 或者 0.8个英文单词
* 星火V1.5支持[搜索]内置插件星火V2.0V3.0和V3.5支持[搜索][天气][日期][诗词][字词][股票]六个内置插件
* 星火V3.5 现已支持systemFunction Call 功能
*
* author: fansili
* time: 2024/3/11 10:12
*/
@Getter
public enum XingHuoChatModel {
// 文档地址https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
// general指向V1.5版本;
// generalv2指向V2版本;
// generalv3指向V3版本;
// generalv3.5指向V3.5版本;
XING_HUO_1_5("星火大模型1.5", "general", "/v1.1/chat"),
XING_HUO_2_0("星火大模型2.0", "generalv2", "/v2.1/chat"),
XING_HUO_3_0("星火大模型3.0", "generalv3", "/v3.1/chat"),
XING_HUO_3_5("星火大模型3.5", "generalv3.5", "/v3.5/chat"),
;
XingHuoChatModel(String name, String model, String uri) {
this.name = name;
this.model = model;
this.uri = uri;
}
private String name;
private String model;
private String uri;
public static XingHuoChatModel valueOfModel(String model) {
for (XingHuoChatModel itemEnum : XingHuoChatModel.values()) {
if (itemEnum.getModel().equals(model)) {
return itemEnum;
}
}
throw new IllegalArgumentException("Invalid MessageType value: " + model);
}
}

View File

@ -0,0 +1,55 @@
package cn.iocoder.yudao.framework.ai.core.model.xinghuo;
import lombok.AllArgsConstructor;
import lombok.Builder;
import lombok.Data;
import lombok.NoArgsConstructor;
import org.springframework.ai.chat.prompt.ChatOptions;
/**
* 讯飞星火 {@link ChatOptions} 实现类
*
* 参考文档<a href="https://www.xfyun.cn/doc/spark/HTTP%E8%B0%83%E7%94%A8%E6%96%87%E6%A1%A3.html">HTTP 调用</a>
*
* @author fansili
*/
@Data
@NoArgsConstructor
@AllArgsConstructor
@Builder
public class XingHuoChatOptions implements ChatOptions {
public static final String MODEL_DEFAULT = "generalv3.5";
/**
* 模型
*/
private String model;
/**
* 温度
*/
private Float temperature;
/**
* 最大 Token
*/
private Integer maxTokens;
/**
* K 个候选
*/
private Integer topK;
@Override
public Float getTopP() {
return null;
}
public static XingHuoChatOptions fromOptions(XingHuoChatOptions fromOptions) {
return XingHuoChatOptions.builder()
.model(fromOptions.getModel())
.temperature(fromOptions.getTemperature())
.maxTokens(fromOptions.getMaxTokens())
.topK(fromOptions.getTopK())
.build();
}
}

View File

@ -1,77 +0,0 @@
package cn.iocoder.yudao.framework.ai.core.model.xinghuo;
import org.springframework.ai.chat.prompt.ChatOptions;
import lombok.Data;
import lombok.experimental.Accessors;
/**
* 讯飞星火
* <p>
* author: fansili
* time: 2024/3/16 20:29
*/
@Data
@Accessors(chain = true)
public class XingHuoOptions implements ChatOptions {
// TODO @fan这里 model 参数然后使用 string
/**
* https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
* <p>
* 指定访问的领域:
* general指向V1.5版本;
* generalv2指向V2版本;
* generalv3指向V3版本;
* generalv3.5指向V3.5版本;
* 注意不同的取值对应的url也不一样
*/
private XingHuoChatModel chatModel = XingHuoChatModel.XING_HUO_3_5;
/**
* 取值范围 (01] 默认值0.5
*/
private Float temperature;
/**
* V1.5取值为[1,4096]
* V2.0V3.0和V3.5取值为[1,8192]默认为2048
*/
private Integer maxTokens;
/**
* 取值为[16],默认为4
*/
private Integer topK;
/**
* 需要保障用户下的唯一性用于关联用户会话
*/
private String chatId;
@Override
public Float getTemperature() {
return this.temperature;
}
// @Override
// public void setTemperature(Float temperature) {
// this.temperature = temperature;
// }
@Override
public Float getTopP() {
return null;
}
// @Override
// public void setTopP(Float topP) {
//
// }
@Override
public Integer getTopK() {
return this.topK;
}
// @Override
// public void setTopK(Integer topK) {
// this.topK = topK;
// }
}

View File

@ -1,149 +0,0 @@
package cn.iocoder.yudao.framework.ai.core.model.xinghuo.api;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
import lombok.Data;
import org.springframework.http.HttpStatusCode;
import org.springframework.http.ResponseEntity;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.URI;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;
import java.util.stream.Collectors;
// TODO @fan讯飞使用 spring websocket 接入还是 okhttp确认了未使用的最好删除下反正 git 也能找回 history
/**
* 讯飞星火 属性api
* <p>
* 文档地址https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
* <p>
* author: fansili
* time: 2024/3/11 10:12
*/
@Data
public class XingHuoApi {
private static final String DEFAULT_BASE_URL = "wss://spark-api.xf-yun.com";
private String appId;
private String appKey;
private String secretKey;
private WebClient webClient;
// 创建 WebSocketClient 实例
private ReactorNettyWebSocketClient socketClient = new ReactorNettyWebSocketClient();
public XingHuoApi(String appId, String appKey, String secretKey) {
this.appId = appId;
this.appKey = appKey;
this.secretKey = secretKey;
}
public ResponseEntity<XingHuoChatCompletion> chatCompletionEntity(XingHuoChatCompletionRequest request, XingHuoChatModel xingHuoChatModel) {
String authUrl;
try {
// XingHuoChatModel useChatModel;
authUrl = getAuthorizationUrl("spark-api.xf-yun.com", xingHuoChatModel.getUri());
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException(e);
}
// wss 请求的 URI
URI uri = URI.create(authUrl);
// 发起 wss 请求并处理响应
Flux<XingHuoChatCompletion> messageFlux = Flux.create(sink -> {
socketClient.execute(uri, session ->
session.send(Mono.just(session.textMessage(JSONUtil.toJsonStr(request))))
.thenMany(session.receive()
.map(WebSocketMessage -> {
return JSONUtil.toBean(WebSocketMessage.getPayloadAsText(), XingHuoChatCompletion.class);
})
.doOnNext(sink::next) // 将接收到的消息推送到 Flux
.doOnError(sink::error) // 处理错误
.doOnTerminate(sink::complete)) // 完成时关闭 sink
.then())
.subscribe(); // 订阅以开始会话
});
// 阻塞获取所有结果
List<XingHuoChatCompletion> responseList = messageFlux.collectList().block();
// 拼接 content
String responseContent = responseList.stream().map(item -> {
// 获取 content
return item.getPayload().getChoices().getText().stream().map(XingHuoChatCompletion.Text::getContent).collect(Collectors.joining());
}).collect(Collectors.joining());
// 将多个合并成一个
XingHuoChatCompletion xingHuoChatCompletion = new XingHuoChatCompletion();
xingHuoChatCompletion.setPayload(new XingHuoChatCompletion.Payload().setChoices(new XingHuoChatCompletion.Choices().setText(List.of(new XingHuoChatCompletion.Text().setContent(responseContent)))));
return new ResponseEntity<>(xingHuoChatCompletion, HttpStatusCode.valueOf(200));
}
/**
* 获取验证请求url
*
* @return
*/
public String getAuthorizationUrl(String host, String path) throws NoSuchAlgorithmException, InvalidKeyException {
// 获取鉴权时间 date
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
String date = format.format(new Date());
// 获取signature_origin字段
StringBuilder builder = new StringBuilder("host: ").append(host).append("\n").
append("date: ").append(date).append("\n").
append("GET ").append(path).append(" HTTP/1.1");
// 获得signatue
Charset charset = Charset.forName("UTF-8");
Mac mac = Mac.getInstance("hmacsha256");
SecretKeySpec sp = new SecretKeySpec(secretKey.getBytes(charset), "hmacsha256");
mac.init(sp);
byte[] basebefore = mac.doFinal(builder.toString().getBytes(charset));
String signature = Base64.getEncoder().encodeToString(basebefore);
//获得 authorization_origin
String authorization_origin = String.format("api_key=\"%s\",algorithm=\"%s\",headers=\"%s\",signature=\"%s\"", appKey, "hmac-sha256", "host date request-line", signature);
//获得authorization
String authorization = Base64.getEncoder().encodeToString(authorization_origin.getBytes(charset));
// 获取httpUrl
Map<String, Object> param = new HashMap<>();
param.put("authorization", authorization);
param.put("date", date);
param.put("host", host);
String toParams = HttpUtil.toParams(param);
return "wss://" + host + path + "?" + toParams;
}
public Flux<XingHuoChatCompletion> chatCompletionStream(XingHuoChatCompletionRequest request, XingHuoChatModel xingHuoChatModel) {
String authUrl;
try {
authUrl = getAuthorizationUrl("spark-api.xf-yun.com", xingHuoChatModel.getUri());
} catch (NoSuchAlgorithmException | InvalidKeyException e) {
throw new RuntimeException(e);
}
// wss 请求的 URI
URI uri = URI.create(authUrl);
// 发起 wss 请求并处理响应
// 创建一个 Flux 来处理接收到的消息
return Flux.create(sink -> {
socketClient.execute(uri, session ->
session.send(Mono.just(session.textMessage(JSONUtil.toJsonStr(request))))
.thenMany(session.receive()
.map(WebSocketMessage -> JSONUtil.toBean(WebSocketMessage.getPayloadAsText(), XingHuoChatCompletion.class))
.doOnNext(sink::next) // 将接收到的消息推送到 Flux
.doOnError(sink::error) // 处理错误
.doOnTerminate(sink::complete)) // 完成时关闭 sink
.then())
.subscribe(); // 订阅以开始会话
});
}
}

View File

@ -1,48 +0,0 @@
package cn.iocoder.yudao.framework.ai.core.model.xinghuo.api;
import lombok.Data;
import lombok.experimental.Accessors;
import java.util.List;
/**
* author: fansili
* time: 2024/3/11 10:20
*/
@Data
@Accessors(chain = true)
public class XingHuoChatCompletion {
private Header header;
private Payload payload;
@Data
@Accessors(chain = true)
public static class Header {
private int code;
private String message;
private String sid;
private int status;
}
@Data
@Accessors(chain = true)
public static class Payload {
private Choices choices;
}
@Data
@Accessors(chain = true)
public static class Choices {
private int status;
private int seq;
private List<Text> text;
}
@Data
@Accessors(chain = true)
public static class Text {
private String content;
private String role;
private int index;
}
}

View File

@ -1,8 +0,0 @@
package cn.iocoder.yudao.framework.ai.core.model.xinghuo.api;
/**
* author: fansili
* time: 2024/3/11 10:20
*/
public class XingHuoChatCompletionMessage {
}

View File

@ -1,107 +0,0 @@
package cn.iocoder.yudao.framework.ai.core.model.xinghuo.api;
import lombok.Data;
import lombok.Getter;
import lombok.experimental.Accessors;
import java.util.List;
/**
* 讯飞星火 request
*
* author: fansili
* time: 2024/3/11 10:20
*/
@Data
@Accessors(chain = true)
public class XingHuoChatCompletionRequest {
private Header header;
private Parameter parameter;
private Payload payload;
@Data
@Accessors(chain = true)
public static class Header {
private String app_id;
private String uid;
}
@Data
@Accessors(chain = true)
public static class Parameter {
private Chat chat;
@Data
@Accessors(chain = true)
public static class Chat {
/**
* https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
*
* 指定访问的领域:
* general指向V1.5版本;
* generalv2指向V2版本;
* generalv3指向V3版本;
* generalv3.5指向V3.5版本;
* 注意不同的取值对应的url也不一样
*/
private String domain = "generalv3.5";
/**
* 取值范围 (01] 默认值0.5
*/
private Float temperature;
/**
* V1.5取值为[1,4096]
* V2.0V3.0和V3.5取值为[1,8192]默认为2048
*/
private Integer max_tokens;
/**
* 取值为[16],默认为4
*/
private Integer top_k;
/**
* 需要保障用户下的唯一性用于关联用户会话
*/
private String chat_id;
}
}
@Data
@Accessors(chain = true)
public static class Payload {
private Message message;
@Data
@Accessors(chain = true)
public static class Message {
private List<Text> text;
@Data
@Accessors(chain = true)
public static class Text {
/**
* 角色
*/
private String role;
/**
* 消息内容
*/
private String content;
private Integer index;
@Getter
public static enum Role {
SYSTEM("system"),
USER("user"),
ASSISTANT("assistant");
private String name;
private Role(String name) {
this.name = name;
}
}
}
}
}
}

View File

@ -2,8 +2,7 @@ package cn.iocoder.yudao.framework.ai.core.util;
import cn.hutool.core.util.StrUtil;
import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoOptions;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatOptions;
import com.alibaba.cloud.ai.tongyi.chat.TongYiChatOptions;
import org.springframework.ai.chat.messages.*;
import org.springframework.ai.chat.prompt.ChatOptions;
@ -31,8 +30,7 @@ public class AiUtils {
// return QianFanChatOptions.builder().withModel(model).withTemperature(temperatureF).withMaxTokens(maxTokens).build();
return QianFanChatOptions.builder().withTemperature(temperatureF).withMaxTokens(maxTokens).build();
case XING_HUO:
return new XingHuoOptions().setChatModel(XingHuoChatModel.valueOfModel(model)).setTemperature(temperatureF)
.setMaxTokens(maxTokens);
return XingHuoChatOptions.builder().model(model).temperature(temperatureF).maxTokens(maxTokens).build();
case QIAN_WEN:
return TongYiChatOptions.builder().withModel(model).withTemperature(temperature).withMaxTokens(maxTokens).build();
default:

View File

@ -1,118 +0,0 @@
package cn.iocoder.yudao.framework.ai.chat;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoChatCompletion;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoChatCompletionRequest;
import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient;
import org.springframework.web.reactive.socket.client.WebSocketClient;
import reactor.core.publisher.Flux;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.MalformedURLException;
import java.net.URI;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;
// TODO 芋艿整理单测
/**
* author: fansili
* time: 2024/3/13 20:47
*/
public class XingHuoChatClientMainTests {
private static final String HOST_URL = "http://spark-api.xf-yun.com/v3.5/chat";
private static final String API_KEY = "cb6415c19d6162cda07b47316fcb0416";
private static final String API_SECRET = "Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh";
public static void main(String[] args) throws MalformedURLException, NoSuchAlgorithmException, InvalidKeyException {
String authUrl = getAuthorizationUrl("spark-api.xf-yun.com", "/v3.5/chat");
System.err.println(authUrl);
XingHuoChatCompletionRequest.Header header = new XingHuoChatCompletionRequest.Header().setApp_id("13c8cca6");
XingHuoChatCompletionRequest.Parameter parameter
= new XingHuoChatCompletionRequest.Parameter()
.setChat(new XingHuoChatCompletionRequest.Parameter.Chat().setDomain("generalv3.5"));
XingHuoChatCompletionRequest.Payload.Message.Text text = new XingHuoChatCompletionRequest.Payload.Message.Text();
text.setRole(XingHuoChatCompletionRequest.Payload.Message.Text.Role.USER.getName());
text.setContent("世界上最好的开发语言是什么?");
XingHuoChatCompletionRequest.Payload payload = new XingHuoChatCompletionRequest.Payload()
.setMessage(new XingHuoChatCompletionRequest.Payload.Message().setText(List.of(text)));
XingHuoChatCompletionRequest request = new XingHuoChatCompletionRequest()
.setHeader(header)
.setParameter(parameter)
.setPayload(payload);
System.err.println(JSONUtil.toJsonPrettyStr(request));
// 创建 WebSocketClient 实例
WebSocketClient client = new ReactorNettyWebSocketClient();
// wss 请求的 URI
URI uri = URI.create(authUrl);
// 发起 wss 请求并处理响应
client.execute(uri, session ->
// 使用会话发送消息并接收回应
session.send(Flux.just(session.textMessage(JSONUtil.toJsonStr(request))))
.thenMany(session.receive()
.map(WebSocketMessage -> {
System.err.println(WebSocketMessage.getPayloadAsText());
return JSONUtil.toBean(WebSocketMessage.getPayloadAsText(), XingHuoChatCompletion.class);
})
.log()) // 打印接收到的消息
.then())
.block(); // 等待操作完成或超时
// 阻止退出
Scanner scanner = new Scanner(System.in);
scanner.nextLine();
}
/**
* 获取验证请求url
*
* @return
*/
public static String getAuthorizationUrl(String host, String path) throws NoSuchAlgorithmException, InvalidKeyException {
// 获取鉴权时间 date
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
String date = format.format(new Date());
// 获取signature_origin字段
StringBuilder builder = new StringBuilder("host: ").append(host).append("\n").
append("date: ").append(date).append("\n").
append("GET ").append(path).append(" HTTP/1.1");
// 获得signatue
Charset charset = Charset.forName("UTF-8");
Mac mac = Mac.getInstance("hmacsha256");
SecretKeySpec sp = new SecretKeySpec(API_SECRET.getBytes(charset), "hmacsha256");
mac.init(sp);
byte[] basebefore = mac.doFinal(builder.toString().getBytes(charset));
String signature = Base64.getEncoder().encodeToString(basebefore);
//获得 authorization_origin
String authorization_origin = String.format("api_key=\"%s\",algorithm=\"%s\",headers=\"%s\",signature=\"%s\"", API_KEY, "hmac-sha256", "host date request-line", signature);
//获得authorization
String authorization = Base64.getEncoder().encodeToString(authorization_origin.getBytes(charset));
// 获取httpUrl
Map<String, Object> param = new HashMap<>();
param.put("authorization", authorization);
param.put("date", date);
param.put("host", host);
String toParams = HttpUtil.toParams(param);
return "wss://" + host + path + "?" + toParams;
}
}

View File

@ -1,11 +1,7 @@
package cn.iocoder.yudao.framework.ai.chat;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoOptions;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoApi;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import org.springframework.ai.chat.messages.Message;
import org.springframework.ai.chat.messages.SystemMessage;
import org.springframework.ai.chat.messages.UserMessage;
@ -15,58 +11,44 @@ import reactor.core.publisher.Flux;
import java.util.ArrayList;
import java.util.List;
import java.util.Scanner;
import java.util.function.Consumer;
// TODO 芋艿整理单测
/**
* 讯飞星火 tests
* <p>
* author: fansili
* time: 2024/3/11 11:00
* {@link XingHuoChatClient} 集成测试
*
* @author fansili
*/
public class XingHuoChatClientTests {
private XingHuoChatClient xingHuoChatClient;
private final XingHuoChatClient client = new XingHuoChatClient(
"cb6415c19d6162cda07b47316fcb0416",
"Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh");
@Before
public void setup() {
// 初始化 xingHuoChatClient
xingHuoChatClient = new XingHuoChatClient(
new XingHuoApi(
"13c8cca6",
"cb6415c19d6162cda07b47316fcb0416",
"Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh"
),
new XingHuoOptions().setChatModel(XingHuoChatModel.XING_HUO_3_5)
);
@Test
public void testCall() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("1 + 1 = "));
// 调用
ChatResponse response = client.call(new Prompt(messages));
// 打印结果
System.err.println(response);
}
@Test
public void callTest() {
public void testStream() {
// 准备参数
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("长沙怎么样"));
messages.add(new UserMessage("1 + 1 = "));
ChatResponse call = xingHuoChatClient.call(new Prompt(messages));
System.err.println(call.getResult());
// 调用
Flux<ChatResponse> flux = client.stream(new Prompt(messages));
// 打印结果
List<ChatResponse> responses = flux.collectList().block();
assert responses != null;
responses.forEach(System.err::println);
}
@Test
public void streamTest() {
List<Message> messages = new ArrayList<>();
messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
messages.add(new UserMessage("长沙怎么样?"));
Flux<ChatResponse> stream = xingHuoChatClient.stream(new Prompt(messages));
stream.subscribe(new Consumer<ChatResponse>() {
@Override
public void accept(ChatResponse chatResponse) {
System.err.print(chatResponse.getResult().getOutput().getContent());
}
});
// 阻止退出
Scanner scanner = new Scanner(System.in);
scanner.nextLine();
}
}

View File

@ -1,131 +0,0 @@
package cn.iocoder.yudao.framework.ai.chat;
import cn.hutool.http.HttpUtil;
import cn.hutool.json.JSONUtil;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoChatCompletion;
import cn.iocoder.yudao.framework.ai.core.model.xinghuo.api.XingHuoChatCompletionRequest;
import okhttp3.*;
import org.jetbrains.annotations.NotNull;
import javax.crypto.Mac;
import javax.crypto.spec.SecretKeySpec;
import java.net.MalformedURLException;
import java.nio.charset.Charset;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.text.SimpleDateFormat;
import java.util.*;
// TODO 芋艿整理单测
/**
* 讯飞星火 tests
* <p>
* author: fansili
* time: 2024/3/11 11:00
*/
public class XingHuoOkHttpTests {
private static final String HOST_URL = "http://spark-api.xf-yun.com/v3.5/chat";
private static final String API_KEY = "cb6415c19d6162cda07b47316fcb0416";
private static final String API_SECRET = "Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh";
private XingHuoChatClient xingHuoChatClient;
public static void main(String[] args) throws MalformedURLException, NoSuchAlgorithmException, InvalidKeyException {
String authUrl = getAuthorizationUrl("spark-api.xf-yun.com", "/v3.5/chat");
System.err.println(authUrl);
XingHuoChatCompletionRequest.Header header = new XingHuoChatCompletionRequest.Header().setApp_id("13c8cca6");
XingHuoChatCompletionRequest.Parameter parameter
= new XingHuoChatCompletionRequest.Parameter()
.setChat(new XingHuoChatCompletionRequest.Parameter.Chat().setDomain("generalv3.5"));
XingHuoChatCompletionRequest.Payload.Message.Text text = new XingHuoChatCompletionRequest.Payload.Message.Text();
text.setRole(XingHuoChatCompletionRequest.Payload.Message.Text.Role.USER.getName());
text.setContent("世界上最好的开发语言是什么?");
XingHuoChatCompletionRequest.Payload payload = new XingHuoChatCompletionRequest.Payload()
.setMessage(new XingHuoChatCompletionRequest.Payload.Message().setText(List.of(text)));
XingHuoChatCompletionRequest request = new XingHuoChatCompletionRequest()
.setHeader(header)
.setParameter(parameter)
.setPayload(payload);
System.err.println(JSONUtil.toJsonPrettyStr(request));
OkHttpClient client = new OkHttpClient();
Request request2 = new Request.Builder()
.url(authUrl) // 替换为你的 wss URL
.build();
WebSocketListener webSocketListener = new WebSocketListener() {
@Override
public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) {
boolean send = webSocket.send(JSONUtil.toJsonStr(request));
System.err.println("发送 -> " + send);
System.err.println("链接成功!");
}
@Override
public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) {
super.onMessage(webSocket, text);
// System.err.println(text);
XingHuoChatCompletion response = JSONUtil.toBean(text, XingHuoChatCompletion.class);
for (XingHuoChatCompletion.Text text1 : response.getPayload().getChoices().getText()) {
System.err.print(text1.getContent());
}
}
};
WebSocket webSocket = client.newWebSocket(request2, webSocketListener);
// webSocket.send(JSONUtil.toJsonStr(request));
// Trigger shutdown of the dispatcher's executor so this process can exit cleanly.
client.dispatcher().executorService().shutdown();
// 阻止退出
Scanner scanner = new Scanner(System.in);
scanner.nextLine();
}
/**
* 获取验证请求url
*
* @return
*/
public static String getAuthorizationUrl(String host, String path) throws NoSuchAlgorithmException, InvalidKeyException {
// 获取鉴权时间 date
SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
format.setTimeZone(TimeZone.getTimeZone("GMT"));
String date = format.format(new Date());
// 获取signature_origin字段
StringBuilder builder = new StringBuilder("host: ").append(host).append("\n").
append("date: ").append(date).append("\n").
append("GET ").append(path).append(" HTTP/1.1");
// 获得signatue
Charset charset = Charset.forName("UTF-8");
Mac mac = Mac.getInstance("hmacsha256");
SecretKeySpec sp = new SecretKeySpec(API_SECRET.getBytes(charset), "hmacsha256");
mac.init(sp);
byte[] basebefore = mac.doFinal(builder.toString().getBytes(charset));
String signature = Base64.getEncoder().encodeToString(basebefore);
//获得 authorization_origin
String authorization_origin = String.format("api_key=\"%s\",algorithm=\"%s\",headers=\"%s\",signature=\"%s\"", API_KEY, "hmac-sha256", "host date request-line", signature);
//获得authorization
String authorization = Base64.getEncoder().encodeToString(authorization_origin.getBytes(charset));
// 获取httpUrl
Map<String, Object> param = new HashMap<>();
param.put("authorization", authorization);
param.put("date", date);
param.put("host", host);
String toParams = HttpUtil.toParams(param);
return "wss://" + host + path + "?" + toParams;
}
}

View File

@ -1,9 +1,9 @@
package cn.iocoder.yudao.framework.ai.image;
import org.junit.jupiter.api.BeforeEach;
import org.junit.jupiter.api.Test;
import org.springframework.ai.image.ImagePrompt;
import org.springframework.ai.image.ImageResponse;
import org.junit.Before;
import org.junit.Test;
import org.springframework.ai.openai.OpenAiImageModel;
import org.springframework.ai.openai.api.OpenAiImageApi;
@ -25,7 +25,7 @@ public class OpenAiImageClientTests {
private OpenAiImageModel openAiImageClient;
@Before
@BeforeEach
public void setup() {
// 初始化 openAiImageClient
this.openAiImageClient = new OpenAiImageModel(

View File

@ -1,8 +1,7 @@
package cn.iocoder.yudao.framework.ai.music;
import cn.iocoder.yudao.framework.ai.core.model.suno.api.SunoApi;
import org.junit.Before;
import org.junit.Test;
import org.junit.jupiter.api.Test;
import java.util.List;
@ -13,14 +12,8 @@ import java.util.List;
*/
public class SunoTests {
private SunoApi sunoApi;
@Before
public void setup() {
String url = "https://suno-55ishh05u-status2xxs-projects.vercel.app";
// String url = "http://127.0.0.1:3001";
this.sunoApi = new SunoApi(url);
}
private final SunoApi sunoApi = new SunoApi("https://suno-55ishh05u-status2xxs-projects.vercel.app");
// private final SunoApi sunoApi = new SunoApi("http://127.0.0.1:3001");
@Test
public void selectById() {

View File

@ -162,11 +162,6 @@ spring:
base-url: https://api.gptsapi.net
stabilityai:
api-key: sk-e53UqbboF8QJCscYvzJscJxJXoFcFg4iJjl1oqgE7baJETmx
vertex:
ai:
gemini:
project-id: 1 # TODO 芋艿:缺配置
location: 2
qianfan: # 文心一言
api-key: x0cuLZ7XsaTCU08vuJWO87Lg
secret-key: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK
@ -176,37 +171,24 @@ spring:
tongyi:
api-key: sk-Zsd81gZYg7
yudao.ai:
xinghuo:
enable: true
aiPlatform: XING_HUO # TODO @fan建议每个都独立配置属性类
max-tokens: 1500
temperature: 0.85
topP: 0.8
topK: 0
appId: 13c8cca6
appKey: cb6415c19d6162cda07b47316fcb0416
secretKey: Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh
model: XING_HUO_3_5
qianwen:
enable: true
aiPlatform: QIAN_WEN
max-tokens: 1500
temperature: 0.85
topP: 0.8
topK: 0
api-key: sk-Zsd81gZYg7
model: QWEN_TURBO
midjourney:
enable: true
# base-url: https://api.holdai.top/mj-relax/mj
base-url: https://api.holdai.top/mj
api-key: sk-dZEPiVaNcT3FHhef51996bAa0bC74806BeAb620dA5Da10Bf
notify-url: http://java.nat300.top/admin-api/ai/image/midjourney/notify
suno:
enable: true
# base-url: https://suno-55ishh05u-status2xxs-projects.vercel.app
base-url: http://127.0.0.1:3001
yudao:
ai:
xinghuo:
enable: true
appId: 13c8cca6
appKey: cb6415c19d6162cda07b47316fcb0416
secretKey: Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh
model: generalv3.5
midjourney:
enable: true
# base-url: https://api.holdai.top/mj-relax/mj
base-url: https://api.holdai.top/mj
api-key: sk-dZEPiVaNcT3FHhef51996bAa0bC74806BeAb620dA5Da10Bf
notify-url: http://java.nat300.top/admin-api/ai/image/midjourney/notify
suno:
enable: true
# base-url: https://suno-55ishh05u-status2xxs-projects.vercel.app
base-url: http://127.0.0.1:3001
--- #################### 芋道相关配置 ####################