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);
}
/**
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatClient.java
index 9c4e52c3a..52b58c6e8 100644
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatClient.java
+++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatClient.java
@@ -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
- *
- * 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 void onError(RetryContext context,
- RetryCallback 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 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 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 choices = chatCompletion.choices();
+ if (choices == null) {
+ log.warn("No choices returned for prompt: {}", prompt);
+ return new ChatResponse(List.of());
+ }
+
+ // 2. 转换 ChatResponse 返回
+ List 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 toMap(String id, OpenAiApi.ChatCompletion.Choice choice) {
+ Map 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 stream(Prompt prompt) {
- // 获取 chatOptions 属性
- XingHuoOptions chatOptions = this.getChatOptions(prompt);
- // 创建 request 请求,stream模式需要供应商支持
- XingHuoChatCompletionRequest request = this.createRequest(prompt, chatOptions);
- // 发送请求
- Flux 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 response = this.openAiApi.chatCompletionStream(request);
+ return response.map(chatCompletion -> {
+ String id = chatCompletion.id();
+ // 2. 转换 ChatResponse 返回
+ List 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 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 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);
}
+
}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatModel.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatModel.java
deleted file mode 100644
index e9ef5ef35..000000000
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatModel.java
+++ /dev/null
@@ -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.0、V3.0和V3.5支持[搜索]、[天气]、[日期]、[诗词]、[字词]、[股票]六个内置插件
- * 星火V3.5 现已支持system、Function 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);
- }
-}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatOptions.java
new file mode 100644
index 000000000..e3287b613
--- /dev/null
+++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatOptions.java
@@ -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} 实现类
+ *
+ * 参考文档:HTTP 调用
+ *
+ * @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();
+ }
+
+}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoOptions.java
deleted file mode 100644
index ccec8598d..000000000
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoOptions.java
+++ /dev/null
@@ -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;
-
-/**
- * 讯飞星火
- *
- * 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
- *
- * 指定访问的领域:
- * general指向V1.5版本;
- * generalv2指向V2版本;
- * generalv3指向V3版本;
- * generalv3.5指向V3.5版本;
- * 注意:不同的取值对应的url也不一样!
- */
- private XingHuoChatModel chatModel = XingHuoChatModel.XING_HUO_3_5;
- /**
- * 取值范围 (0,1] ,默认值0.5
- */
- private Float temperature;
- /**
- * V1.5取值为[1,4096]
- * V2.0、V3.0和V3.5取值为[1,8192],默认为2048。
- */
- private Integer maxTokens;
- /**
- * 取值为[1,6],默认为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;
-// }
-
-}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoApi.java
deleted file mode 100644
index eeceb525a..000000000
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoApi.java
+++ /dev/null
@@ -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
- *
- * 文档地址:https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
- *
- * 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 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 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 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 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 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(); // 订阅以开始会话
- });
- }
-}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletion.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletion.java
deleted file mode 100644
index 46aff8717..000000000
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletion.java
+++ /dev/null
@@ -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;
- }
-
- @Data
- @Accessors(chain = true)
- public static class Text {
- private String content;
- private String role;
- private int index;
- }
-}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletionMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletionMessage.java
deleted file mode 100644
index f527b10d4..000000000
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletionMessage.java
+++ /dev/null
@@ -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 {
-}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletionRequest.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletionRequest.java
deleted file mode 100644
index f3ca1eb30..000000000
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletionRequest.java
+++ /dev/null
@@ -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";
- /**
- * 取值范围 (0,1] ,默认值0.5
- */
- private Float temperature;
- /**
- * V1.5取值为[1,4096]
- * V2.0、V3.0和V3.5取值为[1,8192],默认为2048。
- */
- private Integer max_tokens;
- /**
- * 取值为[1,6],默认为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;
-
-
- @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;
- }
- }
- }
- }
- }
-}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java
index 306b216a3..f3f6f7d9b 100644
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/util/AiUtils.java
+++ b/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;
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:
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientMainTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientMainTests.java
deleted file mode 100644
index 22bb31ae0..000000000
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientMainTests.java
+++ /dev/null
@@ -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 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;
- }
-
-}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientTests.java
index 00f4c1d37..b55aecfdb 100644
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientTests.java
+++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientTests.java
@@ -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
- *
- * 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 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 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 flux = client.stream(new Prompt(messages));
+ // 打印结果
+ List responses = flux.collectList().block();
+ assert responses != null;
+ responses.forEach(System.err::println);
}
- @Test
- public void streamTest() {
- List messages = new ArrayList<>();
- messages.add(new SystemMessage("你是一个优质的文言文作者,用文言文描述着各城市的人文风景。"));
- messages.add(new UserMessage("长沙怎么样?"));
-
- Flux stream = xingHuoChatClient.stream(new Prompt(messages));
- stream.subscribe(new Consumer() {
- @Override
- public void accept(ChatResponse chatResponse) {
- System.err.print(chatResponse.getResult().getOutput().getContent());
- }
- });
- // 阻止退出
- Scanner scanner = new Scanner(System.in);
- scanner.nextLine();
- }
}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoOkHttpTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoOkHttpTests.java
deleted file mode 100644
index c0d4211e3..000000000
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoOkHttpTests.java
+++ /dev/null
@@ -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
- *
- * 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 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;
- }
-
-}
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/OpenAiImageClientTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/OpenAiImageClientTests.java
index 7c73b2de2..f942f25bf 100644
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/OpenAiImageClientTests.java
+++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/image/OpenAiImageClientTests.java
@@ -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(
diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/music/SunoTests.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/music/SunoTests.java
index e5bf6be38..f6df134ba 100644
--- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/music/SunoTests.java
+++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/music/SunoTests.java
@@ -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() {
diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml
index b2e2ddcd7..8c325b88d 100644
--- a/yudao-server/src/main/resources/application.yaml
+++ b/yudao-server/src/main/resources/application.yaml
@@ -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
--- #################### 芋道相关配置 ####################