Commit de32611794f0ec3282109dcc8d799b37ab8d3a7c
1 parent
1828d195
增加讯飞星火
Showing
14 changed files
with
861 additions
and
0 deletions
yudao-module-ai/pom.xml
yudao-module-ai/yudao-spring-boot-starter-ai/pom.xml
| ... | ... | @@ -100,6 +100,14 @@ |
| 100 | 100 | <version>4.12.0</version> |
| 101 | 101 | <scope>test</scope> |
| 102 | 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 | 111 | </dependencies> |
| 104 | 112 | |
| 105 | 113 | </project> |
| 106 | 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
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 | +} | ... | ... |