mirror of
https://gitee.com/huangge1199_admin/vue-pro.git
synced 2025-01-31 09:30:05 +08:00
【代码优化】AI:优化 xinghuo 的接入,复用 OpenAI,同时将 ws 替换成 htt 调用
This commit is contained in:
parent
2e73959a2c
commit
2d36ec4858
@ -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>
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -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;
|
||||
|
||||
public XingHuoChatClient(String apiKey, String secretKey) {
|
||||
this(apiKey, secretKey,
|
||||
XingHuoChatOptions.builder().model(MODEL_DEFAULT).temperature(0.7F).build());
|
||||
}
|
||||
|
||||
;
|
||||
})
|
||||
.build();
|
||||
|
||||
public XingHuoChatClient(XingHuoApi xingHuoApi) {
|
||||
this.xingHuoApi = xingHuoApi;
|
||||
public XingHuoChatClient(String apiKey, String secretKey, XingHuoChatOptions options) {
|
||||
this(apiKey, secretKey, options, RetryUtils.DEFAULT_RETRY_TEMPLATE);
|
||||
}
|
||||
|
||||
public XingHuoChatClient(XingHuoApi xingHuoApi, XingHuoOptions xingHuoOptions) {
|
||||
this.xingHuoApi = xingHuoApi;
|
||||
this.xingHuoOptions = xingHuoOptions;
|
||||
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!");
|
||||
}
|
||||
return (XingHuoOptions) options;
|
||||
// 2.2 补充默认 options
|
||||
if (this.defaultOptions != null) {
|
||||
request = ModelOptionsUtils.merge(request, this.defaultOptions, OpenAiApi.ChatCompletionRequest.class);
|
||||
}
|
||||
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);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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();
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
/**
|
||||
* 取值范围 (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;
|
||||
// }
|
||||
|
||||
}
|
@ -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(); // 订阅以开始会话
|
||||
});
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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 {
|
||||
}
|
@ -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> 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;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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:
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
// 初始化 xingHuoChatClient
|
||||
xingHuoChatClient = new XingHuoChatClient(
|
||||
new XingHuoApi(
|
||||
"13c8cca6",
|
||||
private final XingHuoChatClient client = new XingHuoChatClient(
|
||||
"cb6415c19d6162cda07b47316fcb0416",
|
||||
"Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh"
|
||||
),
|
||||
new XingHuoOptions().setChatModel(XingHuoChatModel.XING_HUO_3_5)
|
||||
);
|
||||
"Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh");
|
||||
|
||||
@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();
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
}
|
@ -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(
|
||||
|
@ -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() {
|
||||
|
@ -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,36 +171,23 @@ spring:
|
||||
tongyi:
|
||||
api-key: sk-Zsd81gZYg7
|
||||
|
||||
yudao.ai:
|
||||
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
|
||||
model: generalv3.5
|
||||
midjourney:
|
||||
enable: true
|
||||
# base-url: https://api.holdai.top/mj-relax/mj
|
||||
# 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: https://suno-55ishh05u-status2xxs-projects.vercel.app
|
||||
base-url: http://127.0.0.1:3001
|
||||
|
||||
--- #################### 芋道相关配置 ####################
|
||||
|
Loading…
Reference in New Issue
Block a user