From ec6ad9da7482ce43227828f1a33a672dabe83c67 Mon Sep 17 00:00:00 2001 From: cherishsince Date: Sat, 13 Apr 2024 16:01:42 +0800 Subject: [PATCH] api移动到api目录 --- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoApi.java | 150 ------------------------------------------------------------------------------------------------------------------------------------------------------ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoApi.java | 151 +++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanApi.java | 97 ------------------------------------------------------------------------------------------------- yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanApi.java | 98 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 249 insertions(+), 247 deletions(-) delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoApi.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoApi.java delete mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanApi.java create mode 100644 yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanApi.java diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoApi.java deleted file mode 100644 index 323b8a7..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/XingHuoApi.java +++ /dev/null @@ -1,150 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatxinghuo; - -import cn.hutool.http.HttpUtil; -import cn.hutool.json.JSONUtil; -import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletion; -import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletionRequest; -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/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoApi.java new file mode 100644 index 0000000..e2a8577 --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatxinghuo/api/XingHuoApi.java @@ -0,0 +1,151 @@ +package cn.iocoder.yudao.framework.ai.chatxinghuo.api; + +import cn.hutool.http.HttpUtil; +import cn.hutool.json.JSONUtil; +import cn.iocoder.yudao.framework.ai.chatxinghuo.XingHuoChatModel; +import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletion; +import cn.iocoder.yudao.framework.ai.chatxinghuo.api.XingHuoChatCompletionRequest; +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/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanApi.java deleted file mode 100644 index d22e8ff..0000000 --- a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/YiYanApi.java +++ /dev/null @@ -1,97 +0,0 @@ -package cn.iocoder.yudao.framework.ai.chatyiyan; - -import cn.iocoder.yudao.framework.ai.chatyiyan.api.YiYanAuthRes; -import cn.iocoder.yudao.framework.ai.chatyiyan.api.YiYanChatCompletion; -import cn.iocoder.yudao.framework.ai.chatyiyan.api.YiYanChatCompletionRequest; -import cn.iocoder.yudao.framework.ai.chatyiyan.exception.YiYanApiException; -import lombok.Data; -import org.springframework.http.HttpStatusCode; -import org.springframework.http.ResponseEntity; -import org.springframework.web.reactive.function.client.WebClient; -import reactor.core.publisher.Flux; -import reactor.core.publisher.Mono; - -/** - * 文心一言 - *

- * author: fansili - * time: 2024/3/8 21:47 - */ -@Data -public class YiYanApi { - - private static final String DEFAULT_BASE_URL = "https://aip.baidubce.com"; - - private static final String AUTH_2_TOKEN_URI = "/oauth/2.0/token"; - - public static final String DEFAULT_CHAT_MODEL = "ERNIE 4.0"; - - // 获取access_token流程 https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Ilkkrb0i5 - private String appKey; - private String secretKey; - private String token; - // token刷新时间(秒) - private int refreshTokenSecondTime; - // 发送请求 webClient - private final WebClient webClient; - // 使用的模型 - private YiYanChatModel useChatModel; - - public YiYanApi(String appKey, String secretKey, YiYanChatModel useChatModel, int refreshTokenSecondTime) { - this.appKey = appKey; - this.secretKey = secretKey; - this.useChatModel = useChatModel; - this.refreshTokenSecondTime = refreshTokenSecondTime; - - this.webClient = WebClient.builder() - .baseUrl(DEFAULT_BASE_URL) - .build(); - - token = getToken(); - } - - private String getToken() { - // 文档地址: https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Ilkkrb0i5 - ResponseEntity response = this.webClient.post() - .uri(uriBuilder -> uriBuilder.path(AUTH_2_TOKEN_URI) - .queryParam("grant_type", "client_credentials") - .queryParam("client_id", appKey) - .queryParam("client_secret", secretKey) - .build() - ) - .retrieve() - .toEntity(YiYanAuthRes.class) - .block(); - // 检查请求状态 - if (HttpStatusCode.valueOf(200) != response.getStatusCode()) { - throw new YiYanApiException("一言认证失败! api:https://aip.baidubce.com/oauth/2.0/token 请检查 client_id、client_secret 是否正确!"); - } - YiYanAuthRes body = response.getBody(); - return body.getAccess_token(); - } - - public ResponseEntity chatCompletionEntity(YiYanChatCompletionRequest request) { - // TODO: 2024/3/10 小范 这里错误信息返回的结构不一样 -// {"error_code":17,"error_msg":"Open api daily request limit reached"} - return this.webClient.post() - .uri(uriBuilder - -> uriBuilder.path(useChatModel.getUri()) - .queryParam("access_token", token) - .build()) - .body(Mono.just(request), YiYanChatCompletionRequest.class) - .retrieve() - .toEntity(YiYanChatCompletion.class) - .block(); - } - - public Flux chatCompletionStream(YiYanChatCompletionRequest request) { - return this.webClient.post() - .uri(uriBuilder - -> uriBuilder.path(useChatModel.getUri()) - .queryParam("access_token", token) - .build()) - .body(Mono.just(request), YiYanChatCompletionRequest.class) - .retrieve() - .bodyToFlux(YiYanChatCompletion.class); - } -} diff --git a/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanApi.java b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanApi.java new file mode 100644 index 0000000..ad1059b --- /dev/null +++ b/yudao-module-ai/yudao-spring-boot-starter-ai/src/main/java/cn/iocoder/yudao/framework/ai/chatyiyan/api/YiYanApi.java @@ -0,0 +1,98 @@ +package cn.iocoder.yudao.framework.ai.chatyiyan.api; + +import cn.iocoder.yudao.framework.ai.chatyiyan.YiYanChatModel; +import cn.iocoder.yudao.framework.ai.chatyiyan.api.YiYanAuthRes; +import cn.iocoder.yudao.framework.ai.chatyiyan.api.YiYanChatCompletion; +import cn.iocoder.yudao.framework.ai.chatyiyan.api.YiYanChatCompletionRequest; +import cn.iocoder.yudao.framework.ai.chatyiyan.exception.YiYanApiException; +import lombok.Data; +import org.springframework.http.HttpStatusCode; +import org.springframework.http.ResponseEntity; +import org.springframework.web.reactive.function.client.WebClient; +import reactor.core.publisher.Flux; +import reactor.core.publisher.Mono; + +/** + * 文心一言 + *

+ * author: fansili + * time: 2024/3/8 21:47 + */ +@Data +public class YiYanApi { + + private static final String DEFAULT_BASE_URL = "https://aip.baidubce.com"; + + private static final String AUTH_2_TOKEN_URI = "/oauth/2.0/token"; + + public static final String DEFAULT_CHAT_MODEL = "ERNIE 4.0"; + + // 获取access_token流程 https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Ilkkrb0i5 + private String appKey; + private String secretKey; + private String token; + // token刷新时间(秒) + private int refreshTokenSecondTime; + // 发送请求 webClient + private final WebClient webClient; + // 使用的模型 + private YiYanChatModel useChatModel; + + public YiYanApi(String appKey, String secretKey, YiYanChatModel useChatModel, int refreshTokenSecondTime) { + this.appKey = appKey; + this.secretKey = secretKey; + this.useChatModel = useChatModel; + this.refreshTokenSecondTime = refreshTokenSecondTime; + + this.webClient = WebClient.builder() + .baseUrl(DEFAULT_BASE_URL) + .build(); + + token = getToken(); + } + + private String getToken() { + // 文档地址: https://cloud.baidu.com/doc/WENXINWORKSHOP/s/Ilkkrb0i5 + ResponseEntity response = this.webClient.post() + .uri(uriBuilder -> uriBuilder.path(AUTH_2_TOKEN_URI) + .queryParam("grant_type", "client_credentials") + .queryParam("client_id", appKey) + .queryParam("client_secret", secretKey) + .build() + ) + .retrieve() + .toEntity(YiYanAuthRes.class) + .block(); + // 检查请求状态 + if (HttpStatusCode.valueOf(200) != response.getStatusCode()) { + throw new YiYanApiException("一言认证失败! api:https://aip.baidubce.com/oauth/2.0/token 请检查 client_id、client_secret 是否正确!"); + } + YiYanAuthRes body = response.getBody(); + return body.getAccess_token(); + } + + public ResponseEntity chatCompletionEntity(YiYanChatCompletionRequest request) { + // TODO: 2024/3/10 小范 这里错误信息返回的结构不一样 +// {"error_code":17,"error_msg":"Open api daily request limit reached"} + return this.webClient.post() + .uri(uriBuilder + -> uriBuilder.path(useChatModel.getUri()) + .queryParam("access_token", token) + .build()) + .body(Mono.just(request), YiYanChatCompletionRequest.class) + .retrieve() + .toEntity(YiYanChatCompletion.class) + .block(); + } + + public Flux chatCompletionStream(YiYanChatCompletionRequest request) { + return this.webClient.post() + .uri(uriBuilder + -> uriBuilder.path(useChatModel.getUri()) + .queryParam("access_token", token) + .build()) + .body(Mono.just(request), YiYanChatCompletionRequest.class) + .retrieve() + .bodyToFlux(YiYanChatCompletion.class); + } +} -- libgit2 0.21.4