From 645dfae003c723dd268ce0cacc0f0bfaea1a8314 Mon Sep 17 00:00:00 2001 From: YunaiV Date: Sat, 18 May 2024 10:15:40 +0800 Subject: [PATCH] 【代码评审】AI:讯飞星火的接入调整 --- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/config/AiChatClientFactory.java | 6 +++--- yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/vo/AiChatModalConfigVO.java | 2 +- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java | 6 +++--- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiImageProperties.java | 4 ++-- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java | 3 +-- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java | 13 ++++++++++--- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatClient.java | 137 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatModel.java | 53 +++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoOptions.java | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoApi.java | 150 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletion.java | 48 ++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletionMessage.java | 8 ++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletionRequest.java | 107 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoChatClient.java | 136 ---------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoChatModel.java | 53 ----------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoOptions.java | 77 ----------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoApi.java | 149 ----------------------------------------------------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletion.java | 48 ------------------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionMessage.java | 8 -------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionRequest.java | 107 ----------------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientMainTests.java | 4 ++-- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientTests.java | 8 ++++---- yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoOkHttpTests.java | 6 +++--- yudao-server/src/main/resources/application-local.yaml | 31 ------------------------------- yudao-server/src/main/resources/application.yaml | 31 ++++++++++++++++++++++++++++++- 25 files changed, 639 insertions(+), 633 deletions(-) create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatClient.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatModel.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoOptions.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoApi.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletion.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletionMessage.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletionRequest.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoChatClient.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoChatModel.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoOptions.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoApi.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletion.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionMessage.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionRequest.java diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/config/AiChatClientFactory.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/config/AiChatClientFactory.java index d5863db..14dc2b8 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/config/AiChatClientFactory.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/config/AiChatClientFactory.java @@ -4,7 +4,7 @@ import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; import org.springframework.ai.chat.ChatClient; import org.springframework.ai.chat.StreamingChatClient; import org.springframework.ai.models.tongyi.QianWenChatClient; -import org.springframework.ai.models.xinghuo.XingHuoChatClient; +import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatClient; import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatClient; import org.springframework.ai.ollama.OllamaChatClient; import org.springframework.beans.factory.annotation.Autowired; @@ -27,7 +27,7 @@ public class AiChatClientFactory { public ChatClient getChatClient(AiPlatformEnum platformEnum) { if (AiPlatformEnum.QIAN_WEN == platformEnum) { return applicationContext.getBean(QianWenChatClient.class); - } else if (AiPlatformEnum.YIYAN == platformEnum) { + } else if (AiPlatformEnum.YI_YAN == platformEnum) { return applicationContext.getBean(YiYanChatClient.class); } else if (AiPlatformEnum.XING_HUO == platformEnum) { return applicationContext.getBean(XingHuoChatClient.class); @@ -42,7 +42,7 @@ public class AiChatClientFactory { // } if (AiPlatformEnum.QIAN_WEN == platformEnum) { return applicationContext.getBean(QianWenChatClient.class); - } else if (AiPlatformEnum.YIYAN == platformEnum) { + } else if (AiPlatformEnum.YI_YAN == platformEnum) { return applicationContext.getBean(YiYanChatClient.class); } else if (AiPlatformEnum.XING_HUO == platformEnum) { return applicationContext.getBean(XingHuoChatClient.class); diff --git a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/vo/AiChatModalConfigVO.java b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/vo/AiChatModalConfigVO.java index 723e533..4ed060e 100644 --- a/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/vo/AiChatModalConfigVO.java +++ b/yudao-module-ai/yudao-module-ai-biz/src/main/java/cn/iocoder/yudao/module/ai/dal/vo/AiChatModalConfigVO.java @@ -3,7 +3,7 @@ package cn.iocoder.yudao.module.ai.dal.vo; import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; import lombok.Data; import lombok.experimental.Accessors; -import org.springframework.ai.models.xinghuo.XingHuoChatModel; +import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanChatModel; /** diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java index 67226ec..13f8f71 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiAutoConfiguration.java @@ -5,9 +5,9 @@ import org.springframework.ai.models.tongyi.QianWenChatClient; import org.springframework.ai.models.tongyi.QianWenChatModal; import org.springframework.ai.models.tongyi.QianWenOptions; import org.springframework.ai.models.tongyi.api.QianWenApi; -import org.springframework.ai.models.xinghuo.XingHuoChatClient; -import org.springframework.ai.models.xinghuo.XingHuoOptions; -import org.springframework.ai.models.xinghuo.api.XingHuoApi; +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.yiyan.YiYanChatClient; import cn.iocoder.yudao.framework.ai.core.model.yiyan.YiYanChatOptions; import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanApi; diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiImageProperties.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiImageProperties.java index 1e6d9d9..ebae45f 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiImageProperties.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiImageProperties.java @@ -1,8 +1,8 @@ package cn.iocoder.yudao.framework.ai.config; import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import org.springframework.ai.models.xinghuo.XingHuoChatModel; -import org.springframework.ai.models.xinghuo.XingHuoOptions; +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.yiyan.api.YiYanChatModel; import lombok.Data; import lombok.experimental.Accessors; diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java index 62bd131..16a6bc4 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/config/YudaoAiProperties.java @@ -1,7 +1,7 @@ package cn.iocoder.yudao.framework.ai.config; import cn.iocoder.yudao.framework.ai.core.enums.AiPlatformEnum; -import org.springframework.ai.models.xinghuo.XingHuoChatModel; +import cn.iocoder.yudao.framework.ai.core.model.xinghuo.XingHuoChatModel; import cn.iocoder.yudao.framework.ai.core.model.yiyan.api.YiYanChatModel; import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageModelEnum; import cn.iocoder.yudao.framework.ai.core.enums.OpenAiImageStyleEnum; @@ -21,7 +21,6 @@ import org.springframework.boot.context.properties.ConfigurationProperties; @ConfigurationProperties(prefix = "yudao.ai") public class YudaoAiProperties { - private String initSource; private QianWenProperties qianwen; private XingHuoProperties xinghuo; private YiYanProperties yiyan; diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java index cd60cc9..a85fffe 100644 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/enums/AiPlatformEnum.java @@ -16,16 +16,23 @@ public enum AiPlatformEnum { OPENAI("OpenAI", "OpenAI"), OLLAMA("Ollama", "Ollama"), - YIYAN("YiYan", "文心一言"), + YI_YAN("YiYan", "文心一言"), // 百度 + XING_HUO("XingHuo", "星火"), // 讯飞 + + QIAN_WEN("qianwen", "千问"), // 阿里 - QIAN_WEN("qianwen", "千问"), - XING_HUO("xinghuo", "星火"), OPEN_AI_DALL("dall", "dall"), MIDJOURNEY("midjourney", "midjourney"), ; + /** + * 平台 + */ private final String platform; + /** + * 平台名 + */ private final String name; // public static List CHAT_PLATFORM_LIST = Lists.newArrayList( 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 new file mode 100644 index 0000000..5537fc1 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatClient.java @@ -0,0 +1,137 @@ +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 org.springframework.ai.chat.*; +import org.springframework.ai.chat.prompt.ChatOptions; +import org.springframework.ai.chat.prompt.Prompt; +import lombok.extern.slf4j.Slf4j; +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.List; +import java.util.stream.Collectors; + +// TODO @fan:参考 yiyan 的修改建议,调整下 xinghuo 的实现;可以等 yiyan 修改完建议,然后我 review 完,再改这个哈; +/** + * 讯飞星火 client + *

+ * author: fansili + * time: 2024/3/11 10:19 + */ +@Slf4j +public class XingHuoChatClient implements ChatClient, StreamingChatClient { + + private XingHuoApi xingHuoApi; + + private XingHuoOptions xingHuoOptions; + + 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); + } + + ; + }) + .build(); + + public XingHuoChatClient(XingHuoApi xingHuoApi) { + this.xingHuoApi = xingHuoApi; + } + + public XingHuoChatClient(XingHuoApi xingHuoApi, XingHuoOptions xingHuoOptions) { + this.xingHuoApi = xingHuoApi; + this.xingHuoOptions = xingHuoOptions; + } + + @Override + public ChatResponse call(Prompt prompt) { + + 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()))); + }); + } + + @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))); + }); + } + + private XingHuoOptions getChatOptions(Prompt prompt) { + // 两个都为null 则没有配置文件 + if (xingHuoOptions == null && prompt.getOptions() == null) { + throw new ChatException("ChatOptions 未配置参数!"); + } + // 优先使用 Prompt 里面的 ChatOptions + ChatOptions options = xingHuoOptions; + if (prompt.getOptions() != null) { + options = (ChatOptions) prompt.getOptions(); + } + // Prompt 里面是一个 ChatOptions,用户可以随意传入,这里做一下判断 + if (!(options instanceof XingHuoOptions)) { + throw new ChatException("Prompt 传入的不是 XingHuoOptions!"); + } + return (XingHuoOptions) options; + } + + 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); + } +} 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 new file mode 100644 index 0000000..e9ef5ef --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoChatModel.java @@ -0,0 +1,53 @@ +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/XingHuoOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoOptions.java new file mode 100644 index 0000000..cb47538 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/XingHuoOptions.java @@ -0,0 +1,77 @@ +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 { + + /** + * 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 new file mode 100644 index 0000000..b267de8 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoApi.java @@ -0,0 +1,150 @@ +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 来处理接收到的消息 + Flux messageFlux = 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(); // 订阅以开始会话 + }); + return messageFlux; + } +} 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 new file mode 100644 index 0000000..46aff87 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletion.java @@ -0,0 +1,48 @@ +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 new file mode 100644 index 0000000..f527b10 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletionMessage.java @@ -0,0 +1,8 @@ +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 new file mode 100644 index 0000000..f3ca1eb --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/core/model/xinghuo/api/XingHuoChatCompletionRequest.java @@ -0,0 +1,107 @@ +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/org/springframework/ai/models/xinghuo/XingHuoChatClient.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoChatClient.java deleted file mode 100644 index 0010a75..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoChatClient.java +++ /dev/null @@ -1,136 +0,0 @@ -package org.springframework.ai.models.xinghuo; - -import cn.hutool.core.bean.BeanUtil; -import cn.hutool.core.exceptions.ExceptionUtil; -import cn.iocoder.yudao.framework.ai.core.exception.ChatException; -import org.springframework.ai.chat.*; -import org.springframework.ai.chat.prompt.ChatOptions; -import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.models.xinghuo.api.XingHuoApi; -import org.springframework.ai.models.xinghuo.api.XingHuoChatCompletion; -import org.springframework.ai.models.xinghuo.api.XingHuoChatCompletionRequest; -import lombok.extern.slf4j.Slf4j; -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.List; -import java.util.stream.Collectors; - -/** - * 讯飞星火 client - *

- * author: fansili - * time: 2024/3/11 10:19 - */ -@Slf4j -public class XingHuoChatClient implements ChatClient, StreamingChatClient { - - private XingHuoApi xingHuoApi; - - private XingHuoOptions xingHuoOptions; - - 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); - } - - ; - }) - .build(); - - public XingHuoChatClient(XingHuoApi xingHuoApi) { - this.xingHuoApi = xingHuoApi; - } - - public XingHuoChatClient(XingHuoApi xingHuoApi, XingHuoOptions xingHuoOptions) { - this.xingHuoApi = xingHuoApi; - this.xingHuoOptions = xingHuoOptions; - } - - @Override - public ChatResponse call(Prompt prompt) { - - 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()))); - }); - } - - @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))); - }); - } - - private XingHuoOptions getChatOptions(Prompt prompt) { - // 两个都为null 则没有配置文件 - if (xingHuoOptions == null && prompt.getOptions() == null) { - throw new ChatException("ChatOptions 未配置参数!"); - } - // 优先使用 Prompt 里面的 ChatOptions - ChatOptions options = xingHuoOptions; - if (prompt.getOptions() != null) { - options = (ChatOptions) prompt.getOptions(); - } - // Prompt 里面是一个 ChatOptions,用户可以随意传入,这里做一下判断 - if (!(options instanceof XingHuoOptions)) { - throw new ChatException("Prompt 传入的不是 XingHuoOptions!"); - } - return (XingHuoOptions) options; - } - - 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); - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoChatModel.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoChatModel.java deleted file mode 100644 index 859e41c..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoChatModel.java +++ /dev/null @@ -1,53 +0,0 @@ -package org.springframework.ai.models.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/org/springframework/ai/models/xinghuo/XingHuoOptions.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoOptions.java deleted file mode 100644 index bdc6710..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/XingHuoOptions.java +++ /dev/null @@ -1,77 +0,0 @@ -package org.springframework.ai.models.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 { - - /** - * 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/org/springframework/ai/models/xinghuo/api/XingHuoApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoApi.java deleted file mode 100644 index 027e8a2..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoApi.java +++ /dev/null @@ -1,149 +0,0 @@ -package org.springframework.ai.models.xinghuo.api; - -import cn.hutool.http.HttpUtil; -import cn.hutool.json.JSONUtil; -import org.springframework.ai.models.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; - -/** - * 讯飞星火 属性、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 来处理接收到的消息 - Flux messageFlux = 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(); // 订阅以开始会话 - }); - return messageFlux; - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletion.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletion.java deleted file mode 100644 index e419d52..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletion.java +++ /dev/null @@ -1,48 +0,0 @@ -package org.springframework.ai.models.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/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionMessage.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionMessage.java deleted file mode 100644 index f0e0f8d..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionMessage.java +++ /dev/null @@ -1,8 +0,0 @@ -package org.springframework.ai.models.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/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionRequest.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionRequest.java deleted file mode 100644 index 3f2b211..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/org/springframework/ai/models/xinghuo/api/XingHuoChatCompletionRequest.java +++ /dev/null @@ -1,107 +0,0 @@ -package org.springframework.ai.models.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/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 index 77cf9ef..524bd3e 100644 --- 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 @@ -2,8 +2,8 @@ package cn.iocoder.yudao.framework.ai.chat; import cn.hutool.http.HttpUtil; import cn.hutool.json.JSONUtil; -import org.springframework.ai.models.xinghuo.api.XingHuoChatCompletion; -import org.springframework.ai.models.xinghuo.api.XingHuoChatCompletionRequest; +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; 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 4479390..8d9cb38 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 @@ -5,10 +5,10 @@ import org.springframework.ai.chat.messages.Message; import org.springframework.ai.chat.messages.SystemMessage; import org.springframework.ai.chat.messages.UserMessage; import org.springframework.ai.chat.prompt.Prompt; -import org.springframework.ai.models.xinghuo.XingHuoChatClient; -import org.springframework.ai.models.xinghuo.XingHuoChatModel; -import org.springframework.ai.models.xinghuo.XingHuoOptions; -import org.springframework.ai.models.xinghuo.api.XingHuoApi; +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 reactor.core.publisher.Flux; 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 index f45f964..5a08741 100644 --- 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 @@ -2,9 +2,9 @@ package cn.iocoder.yudao.framework.ai.chat; import cn.hutool.http.HttpUtil; import cn.hutool.json.JSONUtil; -import org.springframework.ai.models.xinghuo.XingHuoChatClient; -import org.springframework.ai.models.xinghuo.api.XingHuoChatCompletion; -import org.springframework.ai.models.xinghuo.api.XingHuoChatCompletionRequest; +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; diff --git a/yudao-server/src/main/resources/application-local.yaml b/yudao-server/src/main/resources/application-local.yaml index 93302bd..99c888f 100644 --- a/yudao-server/src/main/resources/application-local.yaml +++ b/yudao-server/src/main/resources/application-local.yaml @@ -209,37 +209,6 @@ wx: # 芋道配置项,设置当前项目所有自定义的配置 yudao: - ai: - initSource: yaml - qianwen: - enable: true - aiPlatform: QIAN_WEN - max-tokens: 1500 - temperature: 0.85 - topP: 0.8 - topK: 0 - api-key: sk-Zsd81gZYg7 - xinghuo: - enable: true - aiPlatform: XING_HUO - max-tokens: 1500 - temperature: 0.85 - topP: 0.8 - topK: 0 - appId: 13c8cca6 - appKey: cb6415c19d6162cda07b47316fcb0416 - secretKey: Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh - model: XING_HUO_3_5 - openAiImage: - enable: true - api-key: ${OPEN_AI_KEY} - model: dall_e_2 - style: vivid - midjourney: - enable: true - token: MTE4MjE3MjY2MjkxNTY3ODIzOA.GEV1SG.c49F8lZoGCUHwsj8O0UdodmM6nyQHvuD2fXflw - guild-id: 1237948819677904956 - channel-id: 1237948819677904960 captcha: enable: false # 本地环境,暂时关闭图片验证码,方便登录等接口的测试; security: diff --git a/yudao-server/src/main/resources/application.yaml b/yudao-server/src/main/resources/application.yaml index 52dea3f..549fd9e 100644 --- a/yudao-server/src/main/resources/application.yaml +++ b/yudao-server/src/main/resources/application.yaml @@ -153,7 +153,7 @@ spring.ai: yudao.ai: yiyan: enable: true - aiPlatform: YIYAN # TODO @fan:建议每个都独立配置属性类 + aiPlatform: YI_YAN # TODO @fan:建议每个都独立配置属性类 max-tokens: 1500 temperature: 0.85 topP: 0.8 @@ -162,6 +162,35 @@ yudao.ai: secretKey: R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK refreshTokenSecondTime: 86400 model: ERNIE4_3_5_8K + 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 + openAiImage: + enable: true + api-key: ${OPEN_AI_KEY} + model: dall_e_2 + style: vivid + midjourney: + enable: true + token: MTE4MjE3MjY2MjkxNTY3ODIzOA.GEV1SG.c49F8lZoGCUHwsj8O0UdodmM6nyQHvuD2fXflw + guild-id: 1237948819677904956 + channel-id: 1237948819677904960 --- #################### 芋道相关配置 #################### -- libgit2 0.21.4