Commit de32611794f0ec3282109dcc8d799b37ab8d3a7c

Authored by cherishsince
1 parent 1828d195

增加讯飞星火

Showing 14 changed files with 861 additions and 0 deletions
yudao-module-ai/pom.xml
@@ -14,6 +14,7 @@ @@ -14,6 +14,7 @@
14 <modules> 14 <modules>
15 <module>yudao-module-ai-api</module> 15 <module>yudao-module-ai-api</module>
16 <module>yudao-module-ai-biz</module> 16 <module>yudao-module-ai-biz</module>
  17 + <module>yudao-spring-boot-starter-ai</module>
17 </modules> 18 </modules>
18 19
19 <properties> 20 <properties>
yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
@@ -100,6 +100,14 @@ @@ -100,6 +100,14 @@
100 <version>4.12.0</version> 100 <version>4.12.0</version>
101 <scope>test</scope> 101 <scope>test</scope>
102 </dependency> 102 </dependency>
  103 + <dependency>
  104 + <groupId>io.projectreactor.netty</groupId>
  105 + <artifactId>reactor-netty</artifactId>
  106 + </dependency>
  107 + <dependency>
  108 + <groupId>cn.hutool</groupId>
  109 + <artifactId>hutool-all</artifactId>
  110 + </dependency>
103 </dependencies> 111 </dependencies>
104 112
105 </project> 113 </project>
106 \ No newline at end of file 114 \ No newline at end of file
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoApi.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.chatxinghuo;
  2 +
  3 +import cn.hutool.http.HttpUtil;
  4 +import cn.hutool.json.JSONUtil;
  5 +import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletion;
  6 +import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletionRequest;
  7 +import lombok.Data;
  8 +import org.springframework.http.HttpStatusCode;
  9 +import org.springframework.http.ResponseEntity;
  10 +import org.springframework.web.reactive.function.client.WebClient;
  11 +import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient;
  12 +import reactor.core.publisher.Flux;
  13 +import reactor.core.publisher.Mono;
  14 +
  15 +import javax.crypto.Mac;
  16 +import javax.crypto.spec.SecretKeySpec;
  17 +import java.net.URI;
  18 +import java.nio.charset.Charset;
  19 +import java.security.InvalidKeyException;
  20 +import java.security.NoSuchAlgorithmException;
  21 +import java.text.SimpleDateFormat;
  22 +import java.util.*;
  23 +import java.util.stream.Collectors;
  24 +
  25 +/**
  26 + * 讯飞星火 属性、api
  27 + * <p>
  28 + * 文档地址:https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
  29 + * <p>
  30 + * author: fansili
  31 + * time: 2024/3/11 10:12
  32 + */
  33 +@Data
  34 +public class XingHuoApi {
  35 +
  36 + private static final String DEFAULT_BASE_URL = "wss://spark-api.xf-yun.com";
  37 +
  38 + private String appId;
  39 + private String appKey;
  40 + private String secretKey;
  41 + private WebClient webClient;
  42 + private XingHuoChatModel useChatModel;
  43 + // 创建 WebSocketClient 实例
  44 + private ReactorNettyWebSocketClient socketClient = new ReactorNettyWebSocketClient();
  45 +
  46 + public XingHuoApi(String appId, String appKey, String secretKey, XingHuoChatModel useChatModel) {
  47 + this.appId = appId;
  48 + this.appKey = appKey;
  49 + this.secretKey = secretKey;
  50 + this.useChatModel = useChatModel;
  51 +
  52 + }
  53 +
  54 + public ResponseEntity<XingHuoChatCompletion> chatCompletionEntity(XingHuoChatCompletionRequest request) {
  55 + String authUrl;
  56 + try {
  57 + authUrl = getAuthorizationUrl("spark-api.xf-yun.com", useChatModel.getUri());
  58 + } catch (NoSuchAlgorithmException | InvalidKeyException e) {
  59 + throw new RuntimeException(e);
  60 + }
  61 + // wss 请求的 URI
  62 + URI uri = URI.create(authUrl);
  63 + // 发起 wss 请求并处理响应
  64 + Flux<XingHuoChatCompletion> messageFlux = Flux.create(sink -> {
  65 + socketClient.execute(uri, session ->
  66 + session.send(Mono.just(session.textMessage(JSONUtil.toJsonStr(request))))
  67 + .thenMany(session.receive()
  68 + .map(WebSocketMessage -> {
  69 + return JSONUtil.toBean(WebSocketMessage.getPayloadAsText(), XingHuoChatCompletion.class);
  70 + })
  71 + .doOnNext(sink::next) // 将接收到的消息推送到 Flux 中
  72 + .doOnError(sink::error) // 处理错误
  73 + .doOnTerminate(sink::complete)) // 完成时关闭 sink
  74 + .then())
  75 + .subscribe(); // 订阅以开始会话
  76 + });
  77 + // 阻塞获取所有结果
  78 + List<XingHuoChatCompletion> responseList = messageFlux.collectList().block();
  79 + // 拼接 content
  80 + String responseContent = responseList.stream().map(item -> {
  81 + // 获取 content
  82 + return item.getPayload().getChoices().getText().stream().map(XingHuoChatCompletion.Text::getContent).collect(Collectors.joining());
  83 + }).collect(Collectors.joining());
  84 + // 将多个合并成一个
  85 + XingHuoChatCompletion xingHuoChatCompletion = new XingHuoChatCompletion();
  86 + xingHuoChatCompletion.setPayload(new XingHuoChatCompletion.Payload().setChoices(new XingHuoChatCompletion.Choices().setText(List.of(new XingHuoChatCompletion.Text().setContent(responseContent)))));
  87 + return new ResponseEntity<>(xingHuoChatCompletion, HttpStatusCode.valueOf(200));
  88 + }
  89 +
  90 +
  91 + /**
  92 + * 获取验证请求url
  93 + *
  94 + * @return
  95 + */
  96 + public String getAuthorizationUrl(String host, String path) throws NoSuchAlgorithmException, InvalidKeyException {
  97 + // 获取鉴权时间 date
  98 + SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
  99 + format.setTimeZone(TimeZone.getTimeZone("GMT"));
  100 + String date = format.format(new Date());
  101 +
  102 + // 获取signature_origin字段
  103 + StringBuilder builder = new StringBuilder("host: ").append(host).append("\n").
  104 + append("date: ").append(date).append("\n").
  105 + append("GET ").append(path).append(" HTTP/1.1");
  106 +
  107 + // 获得signatue
  108 + Charset charset = Charset.forName("UTF-8");
  109 + Mac mac = Mac.getInstance("hmacsha256");
  110 + SecretKeySpec sp = new SecretKeySpec(secretKey.getBytes(charset), "hmacsha256");
  111 + mac.init(sp);
  112 + byte[] basebefore = mac.doFinal(builder.toString().getBytes(charset));
  113 + String signature = Base64.getEncoder().encodeToString(basebefore);
  114 + //获得 authorization_origin
  115 + String authorization_origin = String.format("api_key=\"%s\",algorithm=\"%s\",headers=\"%s\",signature=\"%s\"", appKey, "hmac-sha256", "host date request-line", signature);
  116 + //获得authorization
  117 + String authorization = Base64.getEncoder().encodeToString(authorization_origin.getBytes(charset));
  118 + // 获取httpUrl
  119 + Map<String, Object> param = new HashMap<>();
  120 + param.put("authorization", authorization);
  121 + param.put("date", date);
  122 + param.put("host", host);
  123 +
  124 + String toParams = HttpUtil.toParams(param);
  125 + return "wss://" + host + path + "?" + toParams;
  126 + }
  127 +
  128 + public Flux<XingHuoChatCompletion> chatCompletionStream(XingHuoChatCompletionRequest request) {
  129 + String authUrl;
  130 + try {
  131 + authUrl = getAuthorizationUrl("spark-api.xf-yun.com", useChatModel.getUri());
  132 + } catch (NoSuchAlgorithmException | InvalidKeyException e) {
  133 + throw new RuntimeException(e);
  134 + }
  135 + System.err.println(authUrl);
  136 + System.err.println(JSONUtil.toJsonPrettyStr(request));
  137 + // wss 请求的 URI
  138 + URI uri = URI.create(authUrl);
  139 + // 发起 wss 请求并处理响应
  140 + // 创建一个 Flux 来处理接收到的消息
  141 + Flux<XingHuoChatCompletion> messageFlux = Flux.create(sink -> {
  142 + socketClient.execute(uri, session ->
  143 + session.send(Mono.just(session.textMessage(JSONUtil.toJsonStr(request))))
  144 + .thenMany(session.receive()
  145 + .map(WebSocketMessage -> JSONUtil.toBean(WebSocketMessage.getPayloadAsText(), XingHuoChatCompletion.class))
  146 + .doOnNext(sink::next) // 将接收到的消息推送到 Flux 中
  147 + .doOnError(sink::error) // 处理错误
  148 + .doOnTerminate(sink::complete)) // 完成时关闭 sink
  149 + .then())
  150 + .subscribe(); // 订阅以开始会话
  151 + });
  152 + return messageFlux;
  153 + }
  154 +}
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoChatClient.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.chatxinghuo;
  2 +
  3 +import cn.iocoder.yudao.framework.ai.chat.ChatClient;
  4 +import cn.iocoder.yudao.framework.ai.chat.ChatResponse;
  5 +import cn.iocoder.yudao.framework.ai.chat.Generation;
  6 +import cn.iocoder.yudao.framework.ai.chat.StreamingChatClient;
  7 +import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt;
  8 +import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletion;
  9 +import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletionMessage;
  10 +import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletionRequest;
  11 +import cn.iocoder.yudao.framework.ai.chatxinghuo.exception.XingHuoApiException;
  12 +import cn.iocoder.yudao.framework.ai.model.function.AbstractFunctionCallSupport;
  13 +import lombok.extern.slf4j.Slf4j;
  14 +import org.springframework.http.ResponseEntity;
  15 +import org.springframework.retry.RetryCallback;
  16 +import org.springframework.retry.RetryContext;
  17 +import org.springframework.retry.RetryListener;
  18 +import org.springframework.retry.support.RetryTemplate;
  19 +import reactor.core.publisher.Flux;
  20 +
  21 +import java.time.Duration;
  22 +import java.util.List;
  23 +import java.util.stream.Collectors;
  24 +
  25 +/**
  26 + * 讯飞星火 client
  27 + * <p>
  28 + * author: fansili
  29 + * time: 2024/3/11 10:19
  30 + */
  31 +@Slf4j
  32 +public class XingHuoChatClient extends AbstractFunctionCallSupport<XingHuoChatCompletionMessage, XingHuoChatCompletionRequest, ResponseEntity<XingHuoChatCompletion>>
  33 + implements ChatClient, StreamingChatClient {
  34 +
  35 + private XingHuoApi xingHuoApi;
  36 +
  37 + public final RetryTemplate retryTemplate = RetryTemplate.builder()
  38 + // 最大重试次数 10
  39 + .maxAttempts(10)
  40 + .retryOn(XingHuoApiException.class)
  41 + // 最大重试5次,第一次间隔3000ms,第二次3000ms * 2,第三次3000ms * 3,以此类推,最大间隔3 * 60000ms
  42 + .exponentialBackoff(Duration.ofMillis(3000), 2, Duration.ofMillis(3 * 60000))
  43 + .withListener(new RetryListener() {
  44 + @Override
  45 + public <T extends Object, E extends Throwable> void onError(RetryContext context,
  46 + RetryCallback<T, E> callback, Throwable throwable) {
  47 + log.warn("重试异常:" + context.getRetryCount(), throwable);
  48 + }
  49 +
  50 + ;
  51 + })
  52 + .build();
  53 +
  54 + public XingHuoChatClient(XingHuoApi xingHuoApi) {
  55 + super(null);
  56 + this.xingHuoApi = xingHuoApi;
  57 + }
  58 +
  59 + @Override
  60 + public ChatResponse call(Prompt prompt) {
  61 +
  62 + return this.retryTemplate.execute(ctx -> {
  63 + // ctx 会有重试的信息
  64 + // 创建 request 请求,stream模式需要供应商支持
  65 + XingHuoChatCompletionRequest request = this.createRequest(prompt, false);
  66 + // 调用 callWithFunctionSupport 发送请求
  67 + ResponseEntity<XingHuoChatCompletion> response = this.callWithFunctionSupport(request);
  68 + // 获取结果封装 ChatResponse
  69 + return new ChatResponse(List.of(new Generation(response.getBody().getPayload().getChoices().getText().get(0).getContent())));
  70 + });
  71 + }
  72 +
  73 + private XingHuoChatCompletionRequest createRequest(Prompt prompt, boolean b) {
  74 + // 创建 header
  75 + XingHuoChatCompletionRequest.Header header = new XingHuoChatCompletionRequest.Header().setApp_id(xingHuoApi.getAppId());
  76 + // 创建 params
  77 + XingHuoChatCompletionRequest.Parameter parameter = new XingHuoChatCompletionRequest.Parameter()
  78 + .setChat(new XingHuoChatCompletionRequest.Parameter.Chat().setDomain(xingHuoApi.getUseChatModel().getValue()));
  79 + // 创建 payload text 信息
  80 + XingHuoChatCompletionRequest.Payload.Message.Text text = new XingHuoChatCompletionRequest.Payload.Message.Text();
  81 + text.setRole(XingHuoChatCompletionRequest.Payload.Message.Text.Role.USER.getName());
  82 + text.setContent(prompt.getContents());
  83 + // 创建 payload
  84 + XingHuoChatCompletionRequest.Payload payload = new XingHuoChatCompletionRequest.Payload()
  85 + .setMessage(new XingHuoChatCompletionRequest.Payload.Message().setText(List.of(text)));
  86 + // 创建 request
  87 + return new XingHuoChatCompletionRequest()
  88 + .setHeader(header)
  89 + .setParameter(parameter)
  90 + .setPayload(payload);
  91 + }
  92 +
  93 + @Override
  94 + public Flux<ChatResponse> stream(Prompt prompt) {
  95 + // 创建 request 请求,stream模式需要供应商支持
  96 + XingHuoChatCompletionRequest request = this.createRequest(prompt, false);
  97 + // 发送请求
  98 + Flux<XingHuoChatCompletion> response = this.xingHuoApi.chatCompletionStream(request);
  99 + return response.map(res -> {
  100 + String content = res.getPayload().getChoices().getText().stream()
  101 + .map(item -> item.getContent()).collect(Collectors.joining());
  102 + return new ChatResponse(List.of(new Generation(content)));
  103 + });
  104 + }
  105 +
  106 + @Override
  107 + protected XingHuoChatCompletionRequest doCreateToolResponseRequest(XingHuoChatCompletionRequest previousRequest, XingHuoChatCompletionMessage responseMessage, List<XingHuoChatCompletionMessage> conversationHistory) {
  108 + return null;
  109 + }
  110 +
  111 + @Override
  112 + protected List<XingHuoChatCompletionMessage> doGetUserMessages(XingHuoChatCompletionRequest request) {
  113 + return null;
  114 + }
  115 +
  116 + @Override
  117 + protected XingHuoChatCompletionMessage doGetToolResponseMessage(ResponseEntity<XingHuoChatCompletion> response) {
  118 + return null;
  119 + }
  120 +
  121 + @Override
  122 + protected ResponseEntity<XingHuoChatCompletion> doChatCompletion(XingHuoChatCompletionRequest request) {
  123 + return xingHuoApi.chatCompletionEntity(request);
  124 + }
  125 +
  126 + @Override
  127 + protected boolean isToolFunctionCall(ResponseEntity<XingHuoChatCompletion> response) {
  128 + return false;
  129 + }
  130 +}
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoChatModel.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.chatxinghuo;
  2 +
  3 +import lombok.Getter;
  4 +
  5 +/**
  6 + * 讯飞星火 模型
  7 + *
  8 + * 文档地址:https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
  9 + *
  10 + * 1tokens 约等于1.5个中文汉字 或者 0.8个英文单词
  11 + * 星火V1.5支持[搜索]内置插件;星火V2.0、V3.0和V3.5支持[搜索]、[天气]、[日期]、[诗词]、[字词]、[股票]六个内置插件
  12 + * 星火V3.5 现已支持system、Function Call 功能。
  13 + *
  14 + * author: fansili
  15 + * time: 2024/3/11 10:12
  16 + */
  17 +@Getter
  18 +public enum XingHuoChatModel {
  19 +
  20 +// 文档地址:https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
  21 +// general指向V1.5版本;
  22 +// generalv2指向V2版本;
  23 +// generalv3指向V3版本;
  24 +// generalv3.5指向V3.5版本;
  25 +
  26 + XING_HUO_1_5("星火大模型1.5", "general", "/v1.1/chat"),
  27 + XING_HUO_2_0("星火大模型2.0", "generalv2", "/v2.1/chat"),
  28 + XING_HUO_3_0("星火大模型3.0", "generalv3", "/v3.1/chat"),
  29 + XING_HUO_3_5("星火大模型3.5", "generalv3.5", "/v3.5/chat"),
  30 +
  31 + ;
  32 +
  33 + XingHuoChatModel(String name, String value, String uri) {
  34 + this.name = name;
  35 + this.value = value;
  36 + this.uri = uri;
  37 + }
  38 +
  39 + private String name;
  40 +
  41 + private String value;
  42 +
  43 + private String uri;
  44 +}
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoChatCompletion.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.chatxinghuo.api;
  2 +
  3 +import lombok.Data;
  4 +import lombok.experimental.Accessors;
  5 +
  6 +import java.util.List;
  7 +
  8 +/**
  9 + * author: fansili
  10 + * time: 2024/3/11 10:20
  11 + */
  12 +@Data
  13 +@Accessors(chain = true)
  14 +public class XingHuoChatCompletion {
  15 + private Header header;
  16 + private Payload payload;
  17 +
  18 + @Data
  19 + @Accessors(chain = true)
  20 + public static class Header {
  21 + private int code;
  22 + private String message;
  23 + private String sid;
  24 + private int status;
  25 + }
  26 +
  27 + @Data
  28 + @Accessors(chain = true)
  29 + public static class Payload {
  30 + private Choices choices;
  31 + }
  32 +
  33 + @Data
  34 + @Accessors(chain = true)
  35 + public static class Choices {
  36 + private int status;
  37 + private int seq;
  38 + private List<Text> text;
  39 + }
  40 +
  41 + @Data
  42 + @Accessors(chain = true)
  43 + public static class Text {
  44 + private String content;
  45 + private String role;
  46 + private int index;
  47 + }
  48 +}
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoChatCompletionMessage.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.chatxinghuo.api;
  2 +
  3 +/**
  4 + * author: fansili
  5 + * time: 2024/3/11 10:20
  6 + */
  7 +public class XingHuoChatCompletionMessage {
  8 +}
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoChatCompletionRequest.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.chatxinghuo.api;
  2 +
  3 +import lombok.Data;
  4 +import lombok.Getter;
  5 +import lombok.experimental.Accessors;
  6 +
  7 +import java.util.List;
  8 +
  9 +/**
  10 + * 讯飞星火 request
  11 + *
  12 + * author: fansili
  13 + * time: 2024/3/11 10:20
  14 + */
  15 +@Data
  16 +@Accessors(chain = true)
  17 +public class XingHuoChatCompletionRequest {
  18 +
  19 + private Header header;
  20 + private Parameter parameter;
  21 + private Payload payload;
  22 +
  23 + @Data
  24 + @Accessors(chain = true)
  25 + public static class Header {
  26 + private String app_id;
  27 + private String uid;
  28 + }
  29 +
  30 + @Data
  31 + @Accessors(chain = true)
  32 + public static class Parameter {
  33 + private Chat chat;
  34 +
  35 + @Data
  36 + @Accessors(chain = true)
  37 + public static class Chat {
  38 + /**
  39 + * https://www.xfyun.cn/doc/spark/Web.html#_1-%E6%8E%A5%E5%8F%A3%E8%AF%B4%E6%98%8E
  40 + *
  41 + * 指定访问的领域:
  42 + * general指向V1.5版本;
  43 + * generalv2指向V2版本;
  44 + * generalv3指向V3版本;
  45 + * generalv3.5指向V3.5版本;
  46 + * 注意:不同的取值对应的url也不一样!
  47 + */
  48 + private String domain = "general";
  49 + private Double temperature = 0.5;
  50 + private Integer max_tokens = 2048;
  51 + }
  52 + }
  53 +
  54 + @Data
  55 + @Accessors(chain = true)
  56 + public static class Payload {
  57 + private Message message;
  58 +
  59 + @Data
  60 + @Accessors(chain = true)
  61 + public static class Message {
  62 + private List<Text> text;
  63 +
  64 +
  65 + @Data
  66 + @Accessors(chain = true)
  67 + public static class Text {
  68 + /**
  69 + * 角色
  70 + */
  71 + private String role;
  72 + /**
  73 + * 消息内容
  74 + */
  75 + private String content;
  76 + private Integer index;
  77 +
  78 + @Getter
  79 + public static enum Role {
  80 + SYSTEM("system"),
  81 + USER("user"),
  82 + ASSISTANT("assistant");
  83 + private String name;
  84 +
  85 + private Role(String name) {
  86 + this.name = name;
  87 + }
  88 + }
  89 + }
  90 + }
  91 + }
  92 +}
yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/exception/XingHuoApiException.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.chatxinghuo.exception;
  2 +
  3 +/**
  4 + * 讯飞星火 exception
  5 + *
  6 + * author: fansili
  7 + * time: 2024/3/11 10:22
  8 + */
  9 +public class XingHuoApiException extends RuntimeException {
  10 +
  11 + public XingHuoApiException(String message) {
  12 + super(message);
  13 + }
  14 +}
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientMainTests.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.chat;
  2 +
  3 +import cn.hutool.http.HttpUtil;
  4 +import cn.hutool.json.JSONUtil;
  5 +import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletion;
  6 +import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletionRequest;
  7 +import org.springframework.web.reactive.socket.client.ReactorNettyWebSocketClient;
  8 +import org.springframework.web.reactive.socket.client.WebSocketClient;
  9 +import reactor.core.publisher.Flux;
  10 +
  11 +import javax.crypto.Mac;
  12 +import javax.crypto.spec.SecretKeySpec;
  13 +import java.net.MalformedURLException;
  14 +import java.net.URI;
  15 +import java.nio.charset.Charset;
  16 +import java.security.InvalidKeyException;
  17 +import java.security.NoSuchAlgorithmException;
  18 +import java.text.SimpleDateFormat;
  19 +import java.util.*;
  20 +
  21 +/**
  22 + * author: fansili
  23 + * time: 2024/3/13 20:47
  24 + */
  25 +public class XingHuoChatClientMainTests {
  26 +
  27 +
  28 + private static final String HOST_URL = "http://spark-api.xf-yun.com/v3.5/chat";
  29 + private static final String API_KEY = "cb6415c19d6162cda07b47316fcb0416";
  30 + private static final String API_SECRET = "Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh";
  31 +
  32 + public static void main(String[] args) throws MalformedURLException, NoSuchAlgorithmException, InvalidKeyException {
  33 + String authUrl = getAuthorizationUrl("spark-api.xf-yun.com", "/v3.5/chat");
  34 + System.err.println(authUrl);
  35 +
  36 + XingHuoChatCompletionRequest.Header header = new XingHuoChatCompletionRequest.Header().setApp_id("13c8cca6");
  37 + XingHuoChatCompletionRequest.Parameter parameter
  38 + = new XingHuoChatCompletionRequest.Parameter()
  39 + .setChat(new XingHuoChatCompletionRequest.Parameter.Chat().setDomain("generalv3.5"));
  40 +
  41 +
  42 + XingHuoChatCompletionRequest.Payload.Message.Text text = new XingHuoChatCompletionRequest.Payload.Message.Text();
  43 + text.setRole(XingHuoChatCompletionRequest.Payload.Message.Text.Role.USER.getName());
  44 + text.setContent("世界上最好的开发语言是什么?");
  45 + XingHuoChatCompletionRequest.Payload payload = new XingHuoChatCompletionRequest.Payload()
  46 + .setMessage(new XingHuoChatCompletionRequest.Payload.Message().setText(List.of(text)));
  47 + XingHuoChatCompletionRequest request = new XingHuoChatCompletionRequest()
  48 + .setHeader(header)
  49 + .setParameter(parameter)
  50 + .setPayload(payload);
  51 +
  52 + System.err.println(JSONUtil.toJsonPrettyStr(request));
  53 +
  54 +
  55 + // 创建 WebSocketClient 实例
  56 + WebSocketClient client = new ReactorNettyWebSocketClient();
  57 +
  58 + // wss 请求的 URI
  59 + URI uri = URI.create(authUrl);
  60 +
  61 + // 发起 wss 请求并处理响应
  62 + client.execute(uri, session ->
  63 + // 使用会话发送消息,并接收回应
  64 + session.send(Flux.just(session.textMessage(JSONUtil.toJsonStr(request))))
  65 + .thenMany(session.receive()
  66 + .map(WebSocketMessage -> {
  67 + System.err.println(WebSocketMessage.getPayloadAsText());
  68 + return JSONUtil.toBean(WebSocketMessage.getPayloadAsText(), XingHuoChatCompletion.class);
  69 + })
  70 + .log()) // 打印接收到的消息
  71 + .then())
  72 + .block(); // 等待操作完成或超时
  73 +
  74 + // 阻止退出
  75 + Scanner scanner = new Scanner(System.in);
  76 + scanner.nextLine();
  77 + }
  78 +
  79 +
  80 + /**
  81 + * 获取验证请求url
  82 + *
  83 + * @return
  84 + */
  85 + public static String getAuthorizationUrl(String host, String path) throws NoSuchAlgorithmException, InvalidKeyException {
  86 + // 获取鉴权时间 date
  87 + SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
  88 + format.setTimeZone(TimeZone.getTimeZone("GMT"));
  89 + String date = format.format(new Date());
  90 +
  91 + // 获取signature_origin字段
  92 + StringBuilder builder = new StringBuilder("host: ").append(host).append("\n").
  93 + append("date: ").append(date).append("\n").
  94 + append("GET ").append(path).append(" HTTP/1.1");
  95 +
  96 + // 获得signatue
  97 + Charset charset = Charset.forName("UTF-8");
  98 + Mac mac = Mac.getInstance("hmacsha256");
  99 + SecretKeySpec sp = new SecretKeySpec(API_SECRET.getBytes(charset), "hmacsha256");
  100 + mac.init(sp);
  101 + byte[] basebefore = mac.doFinal(builder.toString().getBytes(charset));
  102 + String signature = Base64.getEncoder().encodeToString(basebefore);
  103 + //获得 authorization_origin
  104 + String authorization_origin = String.format("api_key=\"%s\",algorithm=\"%s\",headers=\"%s\",signature=\"%s\"", API_KEY, "hmac-sha256", "host date request-line", signature);
  105 + //获得authorization
  106 + String authorization = Base64.getEncoder().encodeToString(authorization_origin.getBytes(charset));
  107 + // 获取httpUrl
  108 + Map<String, Object> param = new HashMap<>();
  109 + param.put("authorization", authorization);
  110 + param.put("date", date);
  111 + param.put("host", host);
  112 +
  113 + String toParams = HttpUtil.toParams(param);
  114 + return "wss://" + host + path + "?" + toParams;
  115 + }
  116 +
  117 +}
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoChatClientTests.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.chat;
  2 +
  3 +import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt;
  4 +import cn.iocoder.yudao.framework.ai.chatxinghuo.XingHuoApi;
  5 +import cn.iocoder.yudao.framework.ai.chatxinghuo.XingHuoChatClient;
  6 +import cn.iocoder.yudao.framework.ai.chatxinghuo.XingHuoChatModel;
  7 +import org.junit.Before;
  8 +import org.junit.Test;
  9 +import reactor.core.publisher.Flux;
  10 +
  11 +import java.util.Scanner;
  12 +import java.util.function.Consumer;
  13 +
  14 +/**
  15 + * 讯飞星火 tests
  16 + * <p>
  17 + * author: fansili
  18 + * time: 2024/3/11 11:00
  19 + */
  20 +public class XingHuoChatClientTests {
  21 +
  22 + private XingHuoChatClient xingHuoChatClient;
  23 +
  24 + @Before
  25 + public void setup() {
  26 + // 初始化 xingHuoChatClient
  27 + xingHuoChatClient = new XingHuoChatClient(
  28 + new XingHuoApi(
  29 + "13c8cca6",
  30 + "cb6415c19d6162cda07b47316fcb0416",
  31 + "Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh",
  32 + XingHuoChatModel.XING_HUO_3_5
  33 + )
  34 + );
  35 + }
  36 +
  37 + @Test
  38 + public void callTest() {
  39 + ChatResponse call = xingHuoChatClient.call(new Prompt("java和go那个性能更好!"));
  40 + System.err.println(call.getResult());
  41 + }
  42 +
  43 + @Test
  44 + public void streamTest() {
  45 + Flux<ChatResponse> stream = xingHuoChatClient.stream(new Prompt("java和go那个性能更好!"));
  46 + stream.subscribe(new Consumer<ChatResponse>() {
  47 + @Override
  48 + public void accept(ChatResponse chatResponse) {
  49 + System.err.print(chatResponse.getResult().getOutput().getContent());
  50 + }
  51 + });
  52 + // 阻止退出
  53 + Scanner scanner = new Scanner(System.in);
  54 + scanner.nextLine();
  55 + }
  56 +}
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/XingHuoOkHttpTests.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.chat;
  2 +
  3 +import cn.hutool.http.HttpUtil;
  4 +import cn.hutool.json.JSONUtil;
  5 +import cn.iocoder.yudao.framework.ai.chatxinghuo.XingHuoChatClient;
  6 +import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletion;
  7 +import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletionRequest;
  8 +import okhttp3.*;
  9 +import org.jetbrains.annotations.NotNull;
  10 +
  11 +import javax.crypto.Mac;
  12 +import javax.crypto.spec.SecretKeySpec;
  13 +import java.net.MalformedURLException;
  14 +import java.nio.charset.Charset;
  15 +import java.security.InvalidKeyException;
  16 +import java.security.NoSuchAlgorithmException;
  17 +import java.text.SimpleDateFormat;
  18 +import java.util.*;
  19 +
  20 +/**
  21 + * 讯飞星火 tests
  22 + * <p>
  23 + * author: fansili
  24 + * time: 2024/3/11 11:00
  25 + */
  26 +public class XingHuoOkHttpTests {
  27 +
  28 + private static final String HOST_URL = "http://spark-api.xf-yun.com/v3.5/chat";
  29 + private static final String API_KEY = "cb6415c19d6162cda07b47316fcb0416";
  30 + private static final String API_SECRET = "Y2JiYTIxZjA3MDMxMjNjZjQzYzVmNzdh";
  31 +
  32 + private XingHuoChatClient xingHuoChatClient;
  33 +
  34 + public static void main(String[] args) throws MalformedURLException, NoSuchAlgorithmException, InvalidKeyException {
  35 + String authUrl = getAuthorizationUrl("spark-api.xf-yun.com", "/v3.5/chat");
  36 + System.err.println(authUrl);
  37 +
  38 + XingHuoChatCompletionRequest.Header header = new XingHuoChatCompletionRequest.Header().setApp_id("13c8cca6");
  39 + XingHuoChatCompletionRequest.Parameter parameter
  40 + = new XingHuoChatCompletionRequest.Parameter()
  41 + .setChat(new XingHuoChatCompletionRequest.Parameter.Chat().setDomain("generalv3.5"));
  42 +
  43 +
  44 + XingHuoChatCompletionRequest.Payload.Message.Text text = new XingHuoChatCompletionRequest.Payload.Message.Text();
  45 + text.setRole(XingHuoChatCompletionRequest.Payload.Message.Text.Role.USER.getName());
  46 + text.setContent("世界上最好的开发语言是什么?");
  47 + XingHuoChatCompletionRequest.Payload payload = new XingHuoChatCompletionRequest.Payload()
  48 + .setMessage(new XingHuoChatCompletionRequest.Payload.Message().setText(List.of(text)));
  49 + XingHuoChatCompletionRequest request = new XingHuoChatCompletionRequest()
  50 + .setHeader(header)
  51 + .setParameter(parameter)
  52 + .setPayload(payload);
  53 +
  54 + System.err.println(JSONUtil.toJsonPrettyStr(request));
  55 +
  56 + OkHttpClient client = new OkHttpClient();
  57 + Request request2 = new Request.Builder()
  58 + .url(authUrl) // 替换为你的 wss URL
  59 + .build();
  60 +
  61 + WebSocketListener webSocketListener = new WebSocketListener() {
  62 +
  63 + @Override
  64 + public void onOpen(@NotNull WebSocket webSocket, @NotNull Response response) {
  65 + boolean send = webSocket.send(JSONUtil.toJsonStr(request));
  66 + System.err.println("发送 -> " + send);
  67 + System.err.println("链接成功!");
  68 + }
  69 +
  70 + @Override
  71 + public void onMessage(@NotNull WebSocket webSocket, @NotNull String text) {
  72 + super.onMessage(webSocket, text);
  73 +// System.err.println(text);
  74 + XingHuoChatCompletion response = JSONUtil.toBean(text, XingHuoChatCompletion.class);
  75 + for (XingHuoChatCompletion.Text text1 : response.getPayload().getChoices().getText()) {
  76 + System.err.print(text1.getContent());
  77 + }
  78 + }
  79 + };
  80 +
  81 + WebSocket webSocket = client.newWebSocket(request2, webSocketListener);
  82 +// webSocket.send(JSONUtil.toJsonStr(request));
  83 +
  84 +
  85 + // Trigger shutdown of the dispatcher's executor so this process can exit cleanly.
  86 + client.dispatcher().executorService().shutdown();
  87 + // 阻止退出
  88 + Scanner scanner = new Scanner(System.in);
  89 + scanner.nextLine();
  90 + }
  91 +
  92 +
  93 + /**
  94 + * 获取验证请求url
  95 + *
  96 + * @return
  97 + */
  98 + public static String getAuthorizationUrl(String host, String path) throws NoSuchAlgorithmException, InvalidKeyException {
  99 + // 获取鉴权时间 date
  100 + SimpleDateFormat format = new SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US);
  101 + format.setTimeZone(TimeZone.getTimeZone("GMT"));
  102 + String date = format.format(new Date());
  103 +
  104 + // 获取signature_origin字段
  105 + StringBuilder builder = new StringBuilder("host: ").append(host).append("\n").
  106 + append("date: ").append(date).append("\n").
  107 + append("GET ").append(path).append(" HTTP/1.1");
  108 +
  109 + // 获得signatue
  110 + Charset charset = Charset.forName("UTF-8");
  111 + Mac mac = Mac.getInstance("hmacsha256");
  112 + SecretKeySpec sp = new SecretKeySpec(API_SECRET.getBytes(charset), "hmacsha256");
  113 + mac.init(sp);
  114 + byte[] basebefore = mac.doFinal(builder.toString().getBytes(charset));
  115 + String signature = Base64.getEncoder().encodeToString(basebefore);
  116 + //获得 authorization_origin
  117 + String authorization_origin = String.format("api_key=\"%s\",algorithm=\"%s\",headers=\"%s\",signature=\"%s\"", API_KEY, "hmac-sha256", "host date request-line", signature);
  118 + //获得authorization
  119 + String authorization = Base64.getEncoder().encodeToString(authorization_origin.getBytes(charset));
  120 + // 获取httpUrl
  121 + Map<String, Object> param = new HashMap<>();
  122 + param.put("authorization", authorization);
  123 + param.put("date", date);
  124 + param.put("host", host);
  125 +
  126 + String toParams = HttpUtil.toParams(param);
  127 + return "wss://" + host + path + "?" + toParams;
  128 + }
  129 +
  130 +}
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/chat/YiYanChatTests.java 0 → 100644
  1 +package cn.iocoder.yudao.framework.ai.chat;
  2 +
  3 +import cn.iocoder.yudao.framework.ai.chat.prompt.Prompt;
  4 +import cn.iocoder.yudao.framework.ai.chatyiyan.YiYanApi;
  5 +import cn.iocoder.yudao.framework.ai.chatyiyan.YiYanChatClient;
  6 +import cn.iocoder.yudao.framework.ai.chatyiyan.YiYanChatModel;
  7 +import org.junit.Before;
  8 +import org.junit.Test;
  9 +import reactor.core.publisher.Flux;
  10 +
  11 +import java.util.Scanner;
  12 +import java.util.function.Consumer;
  13 +
  14 +/**
  15 + * chat 文心一言
  16 + *
  17 + * author: fansili
  18 + * time: 2024/3/12 20:59
  19 + */
  20 +public class YiYanChatTests {
  21 +
  22 + private YiYanChatClient yiYanChatClient;
  23 +
  24 + @Before
  25 + public void setup() {
  26 + YiYanApi yiYanApi = new YiYanApi(
  27 + "x0cuLZ7XsaTCU08vuJWO87Lg",
  28 + "R9mYF9dl9KASgi5RUq0FQt3wRisSnOcK",
  29 + YiYanChatModel.ERNIE4_3_5_8K,
  30 + 86400
  31 + );
  32 + yiYanChatClient = new YiYanChatClient(yiYanApi);
  33 + }
  34 +
  35 + @Test
  36 + public void callTest() {
  37 + ChatResponse call = yiYanChatClient.call(new Prompt("什么编程语言最好?"));
  38 + System.err.println(call.getResult());
  39 + }
  40 +
  41 + @Test
  42 + public void streamTest() {
  43 + Flux<ChatResponse> fluxResponse = yiYanChatClient.stream(new Prompt("用java帮我写一个快排算法?"));
  44 + fluxResponse.subscribe(new Consumer<ChatResponse>() {
  45 + @Override
  46 + public void accept(ChatResponse chatResponse) {
  47 + System.err.print(chatResponse.getResult().getOutput().getContent());
  48 + }
  49 + });
  50 + // 阻止退出
  51 + Scanner scanner = new Scanner(System.in);
  52 + scanner.nextLine();
  53 + }
  54 +}
yudao-module-ai/yudao-spring-boot-starter-ai/src/test/java/cn/iocoder/yudao/framework/ai/package-info.java 0 → 100644
  1 +/**
  2 + * author: fansili
  3 + * time: 2024/3/12 21:03
  4 + */
  5 +package cn.iocoder.yudao.framework.ai;
0 \ No newline at end of file 6 \ No newline at end of file