Merge remote-tracking branch 'origin/developing' into developing

This commit is contained in:
JiaJu Zhuang
2023-07-29 16:54:54 +08:00
38 changed files with 2091 additions and 435 deletions

View File

@ -38,10 +38,6 @@
<groupId>com.unfbx</groupId>
<artifactId>chatgpt-java</artifactId>
</dependency>
<dependency>
<groupId>com.azure</groupId>
<artifactId>azure-ai-openai</artifactId>
</dependency>
<dependency>
<groupId>com.theokanning.openai-gpt3-java</groupId>
<artifactId>service</artifactId>

View File

@ -16,18 +16,18 @@ import ai.chat2db.server.domain.api.param.TableQueryParam;
import ai.chat2db.server.domain.api.service.ConfigService;
import ai.chat2db.server.domain.api.service.DataSourceService;
import ai.chat2db.server.domain.api.service.TableService;
import ai.chat2db.server.tools.common.util.I18nUtils;
import ai.chat2db.server.web.api.controller.ai.azure.client.AzureOpenAIClient;
import ai.chat2db.server.web.api.controller.ai.listener.AzureOpenAIEventSourceListener;
import ai.chat2db.spi.model.TableColumn;
import ai.chat2db.server.tools.base.wrapper.result.DataResult;
import ai.chat2db.server.tools.common.exception.ParamBusinessException;
import ai.chat2db.server.tools.common.util.EasyEnumUtils;
import ai.chat2db.server.web.api.aspect.ConnectionInfoAspect;
import ai.chat2db.server.web.api.controller.ai.azure.client.AzureOpenAIClient;
import ai.chat2db.server.web.api.controller.ai.azure.models.AzureChatMessage;
import ai.chat2db.server.web.api.controller.ai.azure.models.AzureChatRole;
import ai.chat2db.server.web.api.controller.ai.config.LocalCache;
import ai.chat2db.server.web.api.controller.ai.converter.ChatConverter;
import ai.chat2db.server.web.api.controller.ai.enums.GptVersionType;
import ai.chat2db.server.web.api.controller.ai.enums.PromptType;
import ai.chat2db.server.web.api.controller.ai.listener.AzureOpenAIEventSourceListener;
import ai.chat2db.server.web.api.controller.ai.listener.OpenAIEventSourceListener;
import ai.chat2db.server.web.api.controller.ai.listener.RestAIEventSourceListener;
import ai.chat2db.server.web.api.controller.ai.request.ChatQueryRequest;
@ -35,10 +35,9 @@ import ai.chat2db.server.web.api.controller.ai.request.ChatRequest;
import ai.chat2db.server.web.api.controller.ai.rest.client.RestAIClient;
import ai.chat2db.server.web.api.util.ApplicationContextUtil;
import ai.chat2db.server.web.api.util.OpenAIClient;
import ai.chat2db.spi.model.TableColumn;
import cn.hutool.core.util.StrUtil;
import cn.hutool.json.JSONUtil;
import com.azure.ai.openai.models.ChatMessage;
import com.azure.ai.openai.models.ChatRole;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;
import com.unfbx.chatgpt.entity.chat.Message;
@ -357,7 +356,7 @@ public class ChatController {
prompt.length() / TOKEN_CONVERT_CHAR_LENGTH);
throw new ParamBusinessException();
}
List<ChatMessage> messages = (List<ChatMessage>)LocalCache.CACHE.get(uid);
List<AzureChatMessage> messages = (List<AzureChatMessage>)LocalCache.CACHE.get(uid);
if (CollectionUtils.isNotEmpty(messages)) {
if (messages.size() >= contextLength) {
messages = messages.subList(1, contextLength);
@ -365,12 +364,13 @@ public class ChatController {
} else {
messages = Lists.newArrayList();
}
ChatMessage currentMessage = new ChatMessage(ChatRole.USER).setContent(prompt);
AzureChatMessage currentMessage = new AzureChatMessage(AzureChatRole.USER).setContent(prompt);
messages.add(currentMessage);
sseEmitter.send(SseEmitter.event().id(uid).name("sseEmitter connected").data(LocalDateTime.now()).reconnectTime(3000));
sseEmitter.onCompletion(() -> {
log.info(LocalDateTime.now() + ", uid#" + uid + ", sseEmitter on completion");
SseEmitter.event().id("[DONE]").data("[DONE]");
});
sseEmitter.onTimeout(
() -> log.info(LocalDateTime.now() + ", uid#" + uid + ", sseEmitter on timeout#" + sseEmitter.getTimeout()));

View File

@ -69,7 +69,8 @@ public class AzureOpenAIClient {
deployId = deployConfig.getContent();
}
log.info("refresh azure openai apikey:{}", maskApiKey(key));
OPEN_AI_CLIENT = new AzureOpenAiStreamClient(key, apiEndpoint, deployId);
OPEN_AI_CLIENT = AzureOpenAiStreamClient.builder().apiKey(key).endpoint(apiEndpoint).deployId(deployId)
.build();
apiKey = key;
}

View File

@ -2,21 +2,25 @@ package ai.chat2db.server.web.api.controller.ai.azure.client;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import ai.chat2db.server.tools.common.exception.ParamBusinessException;
import com.azure.ai.openai.OpenAIClient;
import com.azure.ai.openai.OpenAIClientBuilder;
import com.azure.ai.openai.models.ChatChoice;
import com.azure.ai.openai.models.ChatCompletions;
import com.azure.ai.openai.models.ChatCompletionsOptions;
import com.azure.ai.openai.models.ChatMessage;
import com.azure.ai.openai.models.CompletionsUsage;
import com.azure.core.credential.AzureKeyCredential;
import com.azure.core.util.IterableStream;
import ai.chat2db.server.web.api.controller.ai.azure.interceptor.AzureHeaderAuthorizationInterceptor;
import ai.chat2db.server.web.api.controller.ai.azure.models.AzureChatCompletionsOptions;
import ai.chat2db.server.web.api.controller.ai.azure.models.AzureChatMessage;
import cn.hutool.http.ContentType;
import com.fasterxml.jackson.databind.ObjectMapper;
import lombok.Getter;
import lombok.extern.slf4j.Slf4j;
import okhttp3.MediaType;
import okhttp3.OkHttpClient;
import okhttp3.Request;
import okhttp3.RequestBody;
import okhttp3.sse.EventSource;
import okhttp3.sse.EventSourceListener;
import okhttp3.sse.EventSources;
import org.apache.commons.collections4.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
/**
* 自定义AI接口client
@ -26,31 +30,116 @@ import org.apache.commons.lang3.StringUtils;
@Slf4j
public class AzureOpenAiStreamClient {
/**
* apikey
*/
@Getter
@NotNull
private String apiKey;
/**
* endpoint
*/
@Getter
@NotNull
private String endpoint;
/**
* deployId
*/
@Getter
private String deployId;
/**
* client
* okHttpClient
*/
private OpenAIClient client;
@Getter
private OkHttpClient okHttpClient;
/**
* 构造实例对象
*
* @param apiKey
* @param endpoint
* @param builder
*/
public AzureOpenAiStreamClient(String apiKey, String endpoint, String deployId) {
this.deployId = deployId;
if (StringUtils.isBlank(apiKey)) {
return;
private AzureOpenAiStreamClient(Builder builder) {
this.apiKey = builder.apiKey;
this.endpoint = builder.endpoint;
this.deployId = builder.deployId;
if (Objects.isNull(builder.okHttpClient)) {
builder.okHttpClient = this.okHttpClient();
}
this.client = new OpenAIClientBuilder()
.credential(new AzureKeyCredential(apiKey))
.endpoint(endpoint)
.buildClient();
okHttpClient = builder.okHttpClient;
}
/**
* okhttpclient
*/
private OkHttpClient okHttpClient() {
OkHttpClient okHttpClient = new OkHttpClient
.Builder()
.addInterceptor(new AzureHeaderAuthorizationInterceptor(this.apiKey))
.connectTimeout(10, TimeUnit.SECONDS)
.writeTimeout(50, TimeUnit.SECONDS)
.readTimeout(50, TimeUnit.SECONDS)
.build();
return okHttpClient;
}
/**
* 构造
*
* @return
*/
public static AzureOpenAiStreamClient.Builder builder() {
return new AzureOpenAiStreamClient.Builder();
}
public static final class Builder {
private String apiKey;
private String endpoint;
private String deployId;
/**
* 自定义OkhttpClient
*/
private OkHttpClient okHttpClient;
public Builder() {
}
public AzureOpenAiStreamClient.Builder apiKey(String apiKeyValue) {
this.apiKey = apiKeyValue;
return this;
}
/**
* @param endpointValue
* @return
*/
public AzureOpenAiStreamClient.Builder endpoint(String endpointValue) {
this.endpoint = endpointValue;
return this;
}
/**
* @param deployIdValue
* @return
*/
public AzureOpenAiStreamClient.Builder deployId(String deployIdValue) {
this.deployId = deployIdValue;
return this;
}
public AzureOpenAiStreamClient.Builder okHttpClient(OkHttpClient val) {
this.okHttpClient = val;
return this;
}
public AzureOpenAiStreamClient build() {
return new AzureOpenAiStreamClient(this);
}
}
/**
@ -59,49 +148,40 @@ public class AzureOpenAiStreamClient {
* @param chatMessages
* @param eventSourceListener
*/
public void streamCompletions(List<ChatMessage> chatMessages, EventSourceListener eventSourceListener) {
public void streamCompletions(List<AzureChatMessage> chatMessages, EventSourceListener eventSourceListener) {
if (CollectionUtils.isEmpty(chatMessages)) {
log.error("参数异常Azure Prompt不能为空");
log.error("param errorAzure Prompt cannot be empty");
throw new ParamBusinessException("prompt");
}
if (Objects.isNull(eventSourceListener)) {
log.error("参数异常AzureEventSourceListener不能为空");
log.error("param errorAzureEventSourceListener cannot be empty");
throw new ParamBusinessException();
}
log.info("开始调用Azure Open AI, prompt:{}", chatMessages.get(chatMessages.size() - 1).getContent());
log.info("Azure Open AI, prompt:{}", chatMessages.get(chatMessages.size() - 1).getContent());
try {
IterableStream<ChatCompletions> chatCompletionsStream = client.getChatCompletionsStream(deployId,
new ChatCompletionsOptions(chatMessages));
chatCompletionsStream.forEach(chatCompletions -> {
String text = "";
log.info("Model ID={} is created at {}.", chatCompletions.getId(),
chatCompletions.getCreated());
for (ChatChoice choice : chatCompletions.getChoices()) {
ChatMessage message = choice.getDelta();
if (message != null) {
log.info("Index: {}, Chat Role: {}", choice.getIndex(), message.getRole());
text = message.getContent();
}
}
if (Objects.nonNull(text)) {
eventSourceListener.onEvent(null, "[DATA]", null, text);
}
CompletionsUsage usage = chatCompletions.getUsage();
if (usage != null) {
log.info(
"Usage: number of prompt token is {}, number of completion token is {}, and number of total "
+ "tokens in request and response is {}.%n", usage.getPromptTokens(),
usage.getCompletionTokens(), usage.getTotalTokens());
}
});
log.info("结束调用非流式输出自定义AI");
AzureChatCompletionsOptions chatCompletionsOptions = new AzureChatCompletionsOptions(chatMessages);
chatCompletionsOptions.setStream(true);
chatCompletionsOptions.setModel(this.deployId);
EventSource.Factory factory = EventSources.createFactory(this.okHttpClient);
ObjectMapper mapper = new ObjectMapper();
String requestBody = mapper.writeValueAsString(chatCompletionsOptions);
if (!endpoint.endsWith("/")) {
endpoint = endpoint + "/";
}
String url = this.endpoint + "openai/deployments/"+ deployId + "/chat/completions?api-version=2023-05-15";
Request request = new Request.Builder()
.url(url)
.post(RequestBody.create(MediaType.parse(ContentType.JSON.getValue()), requestBody))
.build();
//创建事件
EventSource eventSource = factory.newEventSource(request, eventSourceListener);
log.info("finish invoking azure ai");
} catch (Exception e) {
log.error("请求参数解析异常", e);
log.error("azure ai error", e);
eventSourceListener.onFailure(null, e, null);
throw new ParamBusinessException();
} finally {
eventSourceListener.onEvent(null, "[DONE]", null, "[DONE]");
}
}

View File

@ -0,0 +1,42 @@
package ai.chat2db.server.web.api.controller.ai.azure.interceptor;
import java.io.IOException;
import java.util.List;
import java.util.Map;
import java.util.function.Function;
import java.util.stream.Collectors;
import cn.hutool.core.util.RandomUtil;
import cn.hutool.http.ContentType;
import cn.hutool.http.Header;
import lombok.Getter;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
/**
* 描述请求增加header apikey
*
* @author grt
* @since 2023-03-23
*/
@Getter
public class AzureHeaderAuthorizationInterceptor implements Interceptor {
private String apiKey;
public AzureHeaderAuthorizationInterceptor(String apiKey) {
this.apiKey = apiKey;
}
@Override
public Response intercept(Chain chain) throws IOException {
Request original = chain.request();
Request request = original.newBuilder()
.header("api-key", apiKey)
.header(Header.CONTENT_TYPE.getValue(), ContentType.JSON.getValue())
.method(original.method(), original.body())
.build();
return chain.proceed(request);
}
}

View File

@ -0,0 +1,91 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// Code generated by Microsoft (R) AutoRest Code Generator.
package ai.chat2db.server.web.api.controller.ai.azure.models;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* The representation of a single prompt completion as part of an overall chat completions request. Generally, `n`
* choices are generated per provided prompt with a default value of 1. Token limits and other settings may limit the
* number of choices generated.
*/
@Data
public final class AzureChatChoice {
/*
* The chat message for a given chat completions prompt.
*/
@JsonProperty(value = "message")
private AzureChatMessage message;
/*
* The ordered index associated with this chat completions choice.
*/
@JsonProperty(value = "index")
private int index;
/*
* The reason that this chat completions choice completed its generated.
*/
@JsonProperty(value = "finish_reason")
private AzureCompletionsFinishReason finishReason;
/*
* The delta message content for a streaming response.
*/
@JsonProperty(value = "delta")
private AzureChatMessage delta;
/**
* Creates an instance of ChatChoice class.
*
* @param index the index value to set.
* @param finishReason the finishReason value to set.
*/
@JsonCreator
private AzureChatChoice(
@JsonProperty(value = "index") int index,
@JsonProperty(value = "finish_reason") AzureCompletionsFinishReason finishReason) {
this.index = index;
this.finishReason = finishReason;
}
/**
* Get the message property: The chat message for a given chat completions prompt.
*
* @return the message value.
*/
public AzureChatMessage getMessage() {
return this.message;
}
/**
* Get the index property: The ordered index associated with this chat completions choice.
*
* @return the index value.
*/
public int getIndex() {
return this.index;
}
/**
* Get the finishReason property: The reason that this chat completions choice completed its generated.
*
* @return the finishReason value.
*/
public AzureCompletionsFinishReason getFinishReason() {
return this.finishReason;
}
/**
* Get the delta property: The delta message content for a streaming response.
*
* @return the delta value.
*/
public AzureChatMessage getDelta() {
return this.delta;
}
}

View File

@ -0,0 +1,96 @@
package ai.chat2db.server.web.api.controller.ai.azure.models;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class AzureChatCompletions {
/*
* A unique identifier associated with this chat completions response.
*/
private String id;
/*
* The first timestamp associated with generation activity for this completions response,
* represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970.
*/
private int created;
/*
* The collection of completions choices associated with this completions response.
* Generally, `n` choices are generated per provided prompt with a default value of 1.
* Token limits and other settings may limit the number of choices generated.
*/
@JsonProperty(value = "choices")
private List<AzureChatChoice> choices;
/*
* Usage information for tokens processed and generated as part of this completions operation.
*/
private AzureCompletionsUsage usage;
/**
* Creates an instance of ChatCompletions class.
*
* @param id the id value to set.
* @param created the created value to set.
* @param choices the choices value to set.
* @param usage the usage value to set.
*/
@JsonCreator
private AzureChatCompletions(
@JsonProperty(value = "id") String id,
@JsonProperty(value = "created") int created,
@JsonProperty(value = "choices") List<AzureChatChoice> choices,
@JsonProperty(value = "usage") AzureCompletionsUsage usage) {
this.id = id;
this.created = created;
this.choices = choices;
this.usage = usage;
}
/**
* Get the id property: A unique identifier associated with this chat completions response.
*
* @return the id value.
*/
public String getId() {
return this.id;
}
/**
* Get the created property: The first timestamp associated with generation activity for this completions response,
* represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970.
*
* @return the created value.
*/
public int getCreated() {
return this.created;
}
/**
* Get the choices property: The collection of completions choices associated with this completions response.
* Generally, `n` choices are generated per provided prompt with a default value of 1. Token limits and other
* settings may limit the number of choices generated.
*
* @return the choices value.
*/
public List<AzureChatChoice> getChoices() {
return this.choices;
}
/**
* Get the usage property: Usage information for tokens processed and generated as part of this completions
* operation.
*
* @return the usage value.
*/
public AzureCompletionsUsage getUsage() {
return this.usage;
}
}

View File

@ -0,0 +1,395 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// Code generated by Microsoft (R) AutoRest Code Generator.
package ai.chat2db.server.web.api.controller.ai.azure.models;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* The configuration information for a chat completions request. Completions support a wide variety of tasks and
* generate text that continues from or "completes" provided prompt data.
*/
@Data
public final class AzureChatCompletionsOptions {
/*
* The collection of context messages associated with this chat completions request.
* Typical usage begins with a chat message for the System role that provides instructions for
* the behavior of the assistant, followed by alternating messages between the User and
* Assistant roles.
*/
private List<AzureChatMessage> messages;
///*
// * The maximum number of tokens to generate.
// */
////@JsonProperty(value = "max_tokens")
//private Integer maxTokens;
//
///*
// * The sampling temperature to use that controls the apparent creativity of generated completions.
// * Higher values will make output more random while lower values will make results more focused
// * and deterministic.
// * It is not recommended to modify temperature and top_p for the same completions request as the
// * interaction of these two settings is difficult to predict.
// */
////@JsonProperty(value = "temperature")
//private Double temperature;
//
///*
// * An alternative to sampling with temperature called nucleus sampling. This value causes the
// * model to consider the results of tokens with the provided probability mass. As an example, a
// * value of 0.15 will cause only the tokens comprising the top 15% of probability mass to be
// * considered.
// * It is not recommended to modify temperature and top_p for the same completions request as the
// * interaction of these two settings is difficult to predict.
// */
////@JsonProperty(value = "top_p")
//private Double topP;
//
///*
// * A map between GPT token IDs and bias scores that influences the probability of specific tokens
// * appearing in a completions response. Token IDs are computed via external tokenizer tools, while
// * bias scores reside in the range of -100 to 100 with minimum and maximum values corresponding to
// * a full ban or exclusive selection of a token, respectively. The exact behavior of a given bias
// * score varies by model.
// */
////@JsonProperty(value = "logit_bias")
//private Map<String, Integer> logitBias;
//
///*
// * An identifier for the caller or end user of the operation. This may be used for tracking
// * or rate-limiting purposes.
// */
////@JsonProperty(value = "user")
//private String user;
//
///*
// * The number of chat completions choices that should be generated for a chat completions
// * response.
// * Because this setting can generate many completions, it may quickly consume your token quota.
// * Use carefully and ensure reasonable settings for max_tokens and stop.
// */
////@JsonProperty(value = "n")
//private Integer n;
//
///*
// * A collection of textual sequences that will end completions generation.
// */
////@JsonProperty(value = "stop")
//private List<String> stop;
//
///*
// * A value that influences the probability of generated tokens appearing based on their existing
// * presence in generated text.
// * Positive values will make tokens less likely to appear when they already exist and increase the
// * model's likelihood to output new topics.
// */
////@JsonProperty(value = "presence_penalty")
//private Double presencePenalty;
//
///*
// * A value that influences the probability of generated tokens appearing based on their cumulative
// * frequency in generated text.
// * Positive values will make tokens less likely to appear as their frequency increases and
// * decrease the likelihood of the model repeating the same statements verbatim.
// */
////@JsonProperty(value = "frequency_penalty")
//private Double frequencyPenalty;
/*
* A value indicating whether chat completions should be streamed for this request.
*/
//@JsonProperty(value = "stream")
private Boolean stream;
//
/*
* The model name to provide as part of this completions request.
* Not applicable to Azure OpenAI, where deployment information should be included in the Azure
* resource URI that's connected to.
*/
//@JsonProperty(value = "model")
private String model;
/**
* Creates an instance of ChatCompletionsOptions class.
*
* @param messages the messages value to set.
*/
@JsonCreator
public AzureChatCompletionsOptions(@JsonProperty(value = "messages") List<AzureChatMessage> messages) {
this.messages = messages;
}
//
///**
// * Get the messages property: The collection of context messages associated with this chat completions request.
// * Typical usage begins with a chat message for the System role that provides instructions for the behavior of the
// * assistant, followed by alternating messages between the User and Assistant roles.
// *
// * @return the messages value.
// */
//public List<AzureChatMessage> getMessages() {
// return this.messages;
//}
//
///**
// * Get the maxTokens property: The maximum number of tokens to generate.
// *
// * @return the maxTokens value.
// */
//public Integer getMaxTokens() {
// return this.maxTokens;
//}
//
///**
// * Set the maxTokens property: The maximum number of tokens to generate.
// *
// * @param maxTokens the maxTokens value to set.
// * @return the ChatCompletionsOptions object itself.
// */
//public AzureChatCompletionsOptions setMaxTokens(Integer maxTokens) {
// this.maxTokens = maxTokens;
// return this;
//}
//
///**
// * Get the temperature property: The sampling temperature to use that controls the apparent creativity of generated
// * completions. Higher values will make output more random while lower values will make results more focused and
// * deterministic. It is not recommended to modify temperature and top_p for the same completions request as the
// * interaction of these two settings is difficult to predict.
// *
// * @return the temperature value.
// */
//public Double getTemperature() {
// return this.temperature;
//}
//
///**
// * Set the temperature property: The sampling temperature to use that controls the apparent creativity of generated
// * completions. Higher values will make output more random while lower values will make results more focused and
// * deterministic. It is not recommended to modify temperature and top_p for the same completions request as the
// * interaction of these two settings is difficult to predict.
// *
// * @param temperature the temperature value to set.
// * @return the ChatCompletionsOptions object itself.
// */
//public AzureChatCompletionsOptions setTemperature(Double temperature) {
// this.temperature = temperature;
// return this;
//}
//
///**
// * Get the topP property: An alternative to sampling with temperature called nucleus sampling. This value causes the
// * model to consider the results of tokens with the provided probability mass. As an example, a value of 0.15 will
// * cause only the tokens comprising the top 15% of probability mass to be considered. It is not recommended to
// * modify temperature and top_p for the same completions request as the interaction of these two settings is
// * difficult to predict.
// *
// * @return the topP value.
// */
//public Double getTopP() {
// return this.topP;
//}
//
///**
// * Set the topP property: An alternative to sampling with temperature called nucleus sampling. This value causes the
// * model to consider the results of tokens with the provided probability mass. As an example, a value of 0.15 will
// * cause only the tokens comprising the top 15% of probability mass to be considered. It is not recommended to
// * modify temperature and top_p for the same completions request as the interaction of these two settings is
// * difficult to predict.
// *
// * @param topP the topP value to set.
// * @return the ChatCompletionsOptions object itself.
// */
//public AzureChatCompletionsOptions setTopP(Double topP) {
// this.topP = topP;
// return this;
//}
//
///**
// * Get the logitBias property: A map between GPT token IDs and bias scores that influences the probability of
// * specific tokens appearing in a completions response. Token IDs are computed via external tokenizer tools, while
// * bias scores reside in the range of -100 to 100 with minimum and maximum values corresponding to a full ban or
// * exclusive selection of a token, respectively. The exact behavior of a given bias score varies by model.
// *
// * @return the logitBias value.
// */
//public Map<String, Integer> getLogitBias() {
// return this.logitBias;
//}
//
///**
// * Set the logitBias property: A map between GPT token IDs and bias scores that influences the probability of
// * specific tokens appearing in a completions response. Token IDs are computed via external tokenizer tools, while
// * bias scores reside in the range of -100 to 100 with minimum and maximum values corresponding to a full ban or
// * exclusive selection of a token, respectively. The exact behavior of a given bias score varies by model.
// *
// * @param logitBias the logitBias value to set.
// * @return the ChatCompletionsOptions object itself.
// */
//public AzureChatCompletionsOptions setLogitBias(Map<String, Integer> logitBias) {
// this.logitBias = logitBias;
// return this;
//}
//
///**
// * Get the user property: An identifier for the caller or end user of the operation. This may be used for tracking
// * or rate-limiting purposes.
// *
// * @return the user value.
// */
//public String getUser() {
// return this.user;
//}
//
///**
// * Set the user property: An identifier for the caller or end user of the operation. This may be used for tracking
// * or rate-limiting purposes.
// *
// * @param user the user value to set.
// * @return the ChatCompletionsOptions object itself.
// */
//public AzureChatCompletionsOptions setUser(String user) {
// this.user = user;
// return this;
//}
//
///**
// * Get the n property: The number of chat completions choices that should be generated for a chat completions
// * response. Because this setting can generate many completions, it may quickly consume your token quota. Use
// * carefully and ensure reasonable settings for max_tokens and stop.
// *
// * @return the n value.
// */
//public Integer getN() {
// return this.n;
//}
//
///**
// * Set the n property: The number of chat completions choices that should be generated for a chat completions
// * response. Because this setting can generate many completions, it may quickly consume your token quota. Use
// * carefully and ensure reasonable settings for max_tokens and stop.
// *
// * @param n the n value to set.
// * @return the ChatCompletionsOptions object itself.
// */
//public AzureChatCompletionsOptions setN(Integer n) {
// this.n = n;
// return this;
//}
//
///**
// * Get the stop property: A collection of textual sequences that will end completions generation.
// *
// * @return the stop value.
// */
//public List<String> getStop() {
// return this.stop;
//}
//
///**
// * Set the stop property: A collection of textual sequences that will end completions generation.
// *
// * @param stop the stop value to set.
// * @return the ChatCompletionsOptions object itself.
// */
//public AzureChatCompletionsOptions setStop(List<String> stop) {
// this.stop = stop;
// return this;
//}
//
///**
// * Get the presencePenalty property: A value that influences the probability of generated tokens appearing based on
// * their existing presence in generated text. Positive values will make tokens less likely to appear when they
// * already exist and increase the model's likelihood to output new topics.
// *
// * @return the presencePenalty value.
// */
//public Double getPresencePenalty() {
// return this.presencePenalty;
//}
//
///**
// * Set the presencePenalty property: A value that influences the probability of generated tokens appearing based on
// * their existing presence in generated text. Positive values will make tokens less likely to appear when they
// * already exist and increase the model's likelihood to output new topics.
// *
// * @param presencePenalty the presencePenalty value to set.
// * @return the ChatCompletionsOptions object itself.
// */
//public AzureChatCompletionsOptions setPresencePenalty(Double presencePenalty) {
// this.presencePenalty = presencePenalty;
// return this;
//}
//
///**
// * Get the frequencyPenalty property: A value that influences the probability of generated tokens appearing based on
// * their cumulative frequency in generated text. Positive values will make tokens less likely to appear as their
// * frequency increases and decrease the likelihood of the model repeating the same statements verbatim.
// *
// * @return the frequencyPenalty value.
// */
//public Double getFrequencyPenalty() {
// return this.frequencyPenalty;
//}
//
///**
// * Set the frequencyPenalty property: A value that influences the probability of generated tokens appearing based on
// * their cumulative frequency in generated text. Positive values will make tokens less likely to appear as their
// * frequency increases and decrease the likelihood of the model repeating the same statements verbatim.
// *
// * @param frequencyPenalty the frequencyPenalty value to set.
// * @return the ChatCompletionsOptions object itself.
// */
//public AzureChatCompletionsOptions setFrequencyPenalty(Double frequencyPenalty) {
// this.frequencyPenalty = frequencyPenalty;
// return this;
//}
/**
* Get the stream property: A value indicating whether chat completions should be streamed for this request.
*
* @return the stream value.
*/
public Boolean isStream() {
return this.stream;
}
/**
* Set the stream property: A value indicating whether chat completions should be streamed for this request.
*
* @param stream the stream value to set.
* @return the ChatCompletionsOptions object itself.
*/
public AzureChatCompletionsOptions setStream(Boolean stream) {
this.stream = stream;
return this;
}
/**
* Get the model property: The model name to provide as part of this completions request. Not applicable to Azure
* OpenAI, where deployment information should be included in the Azure resource URI that's connected to.
*
* @return the model value.
*/
public String getModel() {
return this.model;
}
/**
* Set the model property: The model name to provide as part of this completions request. Not applicable to Azure
* OpenAI, where deployment information should be included in the Azure resource URI that's connected to.
*
* @param model the model value to set.
* @return the ChatCompletionsOptions object itself.
*/
public AzureChatCompletionsOptions setModel(String model) {
this.model = model;
return this;
}
}

View File

@ -0,0 +1,61 @@
package ai.chat2db.server.web.api.controller.ai.azure.models;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class AzureChatMessage {
/*
* The role associated with this message payload.
*/
@JsonProperty(value = "role")
private AzureChatRole role;
/*
* The text associated with this message payload.
*/
@JsonProperty(value = "content")
private String content;
/**
* Creates an instance of ChatMessage class.
*
* @param role the role value to set.
*/
@JsonCreator
public AzureChatMessage(@JsonProperty(value = "role") AzureChatRole role) {
this.role = role;
}
/**
* Get the role property: The role associated with this message payload.
*
* @return the role value.
*/
public AzureChatRole getRole() {
return this.role;
}
/**
* Get the content property: The text associated with this message payload.
*
* @return the content value.
*/
public String getContent() {
return this.content;
}
/**
* Set the content property: The text associated with this message payload.
*
* @param content the content value to set.
* @return the ChatMessage object itself.
*/
public AzureChatMessage setContent(String content) {
this.content = content;
return this;
}
}

View File

@ -0,0 +1,46 @@
package ai.chat2db.server.web.api.controller.ai.azure.models;
import java.util.Collection;
import com.fasterxml.jackson.annotation.JsonCreator;
public class AzureChatRole extends AzureExpandableStringEnum<AzureChatRole> {
/** The role that instructs or sets the behavior of the assistant. */
public static final AzureChatRole SYSTEM = fromString("system");
/** The role that provides responses to system-instructed, user-prompted input. */
public static final AzureChatRole ASSISTANT = fromString("assistant");
/** The role that provides input for chat completions. */
public static final AzureChatRole USER = fromString("user");
/**
* Creates a new instance of ChatRole value.
*
* @deprecated Use the {@link #fromString(String)} factory method.
*/
@Deprecated
public AzureChatRole() {}
/**
* Creates or finds a ChatRole from its string representation.
*
* @param name a name to look for.
* @return the corresponding ChatRole.
*/
@JsonCreator
public static AzureChatRole fromString(String name) {
return fromString(name, AzureChatRole.class);
}
/**
* Gets known ChatRole values.
*
* @return known ChatRole values.
*/
public static Collection<AzureChatRole> values() {
return values(AzureChatRole.class);
}
}

View File

@ -0,0 +1,97 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// Code generated by Microsoft (R) AutoRest Code Generator.
package ai.chat2db.server.web.api.controller.ai.azure.models;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* The representation of a single prompt completion as part of an overall completions request. Generally, `n` choices
* are generated per provided prompt with a default value of 1. Token limits and other settings may limit the number of
* choices generated.
*/
@Data
public final class AzureChoice {
/*
* The generated text for a given completions prompt.
*/
@JsonProperty(value = "text")
private String text;
/*
* The ordered index associated with this completions choice.
*/
@JsonProperty(value = "index")
private int index;
/*
* The log probabilities model for tokens associated with this completions choice.
*/
@JsonProperty(value = "logprobs")
private AzureCompletionsLogProbabilityModel logprobs;
/*
* Reason for finishing
*/
@JsonProperty(value = "finish_reason")
private AzureCompletionsFinishReason finishReason;
/**
* Creates an instance of Choice class.
*
* @param text the text value to set.
* @param index the index value to set.
* @param logprobs the logprobs value to set.
* @param finishReason the finishReason value to set.
*/
@JsonCreator
private AzureChoice(
@JsonProperty(value = "text") String text,
@JsonProperty(value = "index") int index,
@JsonProperty(value = "logprobs") AzureCompletionsLogProbabilityModel logprobs,
@JsonProperty(value = "finish_reason") AzureCompletionsFinishReason finishReason) {
this.text = text;
this.index = index;
this.logprobs = logprobs;
this.finishReason = finishReason;
}
/**
* Get the text property: The generated text for a given completions prompt.
*
* @return the text value.
*/
public String getText() {
return this.text;
}
/**
* Get the index property: The ordered index associated with this completions choice.
*
* @return the index value.
*/
public int getIndex() {
return this.index;
}
/**
* Get the logprobs property: The log probabilities model for tokens associated with this completions choice.
*
* @return the logprobs value.
*/
public AzureCompletionsLogProbabilityModel getLogprobs() {
return this.logprobs;
}
/**
* Get the finishReason property: Reason for finishing.
*
* @return the finishReason value.
*/
public AzureCompletionsFinishReason getFinishReason() {
return this.finishReason;
}
}

View File

@ -0,0 +1,98 @@
package ai.chat2db.server.web.api.controller.ai.azure.models;
import java.util.List;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
@Data
public class AzureCompletions {
/*
* A unique identifier associated with this completions response.
*/
@JsonProperty(value = "id")
private String id;
/*
* The first timestamp associated with generation activity for this completions response,
* represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970.
*/
@JsonProperty(value = "created")
private int created;
/*
* The collection of completions choices associated with this completions response.
* Generally, `n` choices are generated per provided prompt with a default value of 1.
* Token limits and other settings may limit the number of choices generated.
*/
@JsonProperty(value = "choices")
private List<AzureChoice> choices;
/*
* Usage information for tokens processed and generated as part of this completions operation.
*/
@JsonProperty(value = "usage")
private AzureCompletionsUsage usage;
/**
* Creates an instance of Completions class.
*
* @param id the id value to set.
* @param created the created value to set.
* @param choices the choices value to set.
* @param usage the usage value to set.
*/
@JsonCreator
private AzureCompletions(
@JsonProperty(value = "id") String id,
@JsonProperty(value = "created") int created,
@JsonProperty(value = "choices") List<AzureChoice> choices,
@JsonProperty(value = "usage") AzureCompletionsUsage usage) {
this.id = id;
this.created = created;
this.choices = choices;
this.usage = usage;
}
/**
* Get the id property: A unique identifier associated with this completions response.
*
* @return the id value.
*/
public String getId() {
return this.id;
}
/**
* Get the created property: The first timestamp associated with generation activity for this completions response,
* represented as seconds since the beginning of the Unix epoch of 00:00 on 1 Jan 1970.
*
* @return the created value.
*/
public int getCreated() {
return this.created;
}
/**
* Get the choices property: The collection of completions choices associated with this completions response.
* Generally, `n` choices are generated per provided prompt with a default value of 1. Token limits and other
* settings may limit the number of choices generated.
*
* @return the choices value.
*/
public List<AzureChoice> getChoices() {
return this.choices;
}
/**
* Get the usage property: Usage information for tokens processed and generated as part of this completions
* operation.
*
* @return the usage value.
*/
public AzureCompletionsUsage getUsage() {
return this.usage;
}
}

View File

@ -0,0 +1,51 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// Code generated by Microsoft (R) AutoRest Code Generator.
package ai.chat2db.server.web.api.controller.ai.azure.models;
import java.util.Collection;
import com.fasterxml.jackson.annotation.JsonCreator;
/** Representation of the manner in which a completions response concluded. */
public final class AzureCompletionsFinishReason extends AzureExpandableStringEnum<AzureCompletionsFinishReason> {
/** Completions ended normally and reached its end of token generation. */
public static final AzureCompletionsFinishReason STOPPED = fromString("stopped");
/** Completions exhausted available token limits before generation could complete. */
public static final AzureCompletionsFinishReason TOKEN_LIMIT_REACHED = fromString("tokenLimitReached");
/**
* Completions generated a response that was identified as potentially sensitive per content moderation policies.
*/
public static final AzureCompletionsFinishReason CONTENT_FILTERED = fromString("contentFiltered");
/**
* Creates a new instance of CompletionsFinishReason value.
*
* @deprecated Use the {@link #fromString(String)} factory method.
*/
@Deprecated
public AzureCompletionsFinishReason() {}
/**
* Creates or finds a CompletionsFinishReason from its string representation.
*
* @param name a name to look for.
* @return the corresponding CompletionsFinishReason.
*/
@JsonCreator
public static AzureCompletionsFinishReason fromString(String name) {
return fromString(name, AzureCompletionsFinishReason.class);
}
/**
* Gets known CompletionsFinishReason values.
*
* @return known CompletionsFinishReason values.
*/
public static Collection<AzureCompletionsFinishReason> values() {
return values(AzureCompletionsFinishReason.class);
}
}

View File

@ -0,0 +1,96 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// Code generated by Microsoft (R) AutoRest Code Generator.
package ai.chat2db.server.web.api.controller.ai.azure.models;
import java.util.List;
import java.util.Map;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/** Representation of a log probabilities model for a completions generation. */
@Data
public final class AzureCompletionsLogProbabilityModel {
/*
* The textual forms of tokens evaluated in this probability model.
*/
@JsonProperty(value = "tokens")
private List<String> tokens;
/*
* A collection of log probability values for the tokens in this completions data.
*/
@JsonProperty(value = "token_logprobs")
private List<Double> tokenLogprobs;
/*
* A mapping of tokens to maximum log probability values in this completions data.
*/
@JsonProperty(value = "top_logprobs")
private List<Map<String, Double>> topLogprobs;
/*
* The text offsets associated with tokens in this completions data.
*/
@JsonProperty(value = "text_offset")
private List<Integer> textOffset;
/**
* Creates an instance of CompletionsLogProbabilityModel class.
*
* @param tokens the tokens value to set.
* @param tokenLogprobs the tokenLogprobs value to set.
* @param topLogprobs the topLogprobs value to set.
* @param textOffset the textOffset value to set.
*/
@JsonCreator
private AzureCompletionsLogProbabilityModel(
@JsonProperty(value = "tokens") List<String> tokens,
@JsonProperty(value = "token_logprobs") List<Double> tokenLogprobs,
@JsonProperty(value = "top_logprobs") List<Map<String, Double>> topLogprobs,
@JsonProperty(value = "text_offset") List<Integer> textOffset) {
this.tokens = tokens;
this.tokenLogprobs = tokenLogprobs;
this.topLogprobs = topLogprobs;
this.textOffset = textOffset;
}
/**
* Get the tokens property: The textual forms of tokens evaluated in this probability model.
*
* @return the tokens value.
*/
public List<String> getTokens() {
return this.tokens;
}
/**
* Get the tokenLogprobs property: A collection of log probability values for the tokens in this completions data.
*
* @return the tokenLogprobs value.
*/
public List<Double> getTokenLogprobs() {
return this.tokenLogprobs;
}
/**
* Get the topLogprobs property: A mapping of tokens to maximum log probability values in this completions data.
*
* @return the topLogprobs value.
*/
public List<Map<String, Double>> getTopLogprobs() {
return this.topLogprobs;
}
/**
* Get the textOffset property: The text offsets associated with tokens in this completions data.
*
* @return the textOffset value.
*/
public List<Integer> getTextOffset() {
return this.textOffset;
}
}

View File

@ -0,0 +1,78 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
// Code generated by Microsoft (R) AutoRest Code Generator.
package ai.chat2db.server.web.api.controller.ai.azure.models;
import com.fasterxml.jackson.annotation.JsonCreator;
import com.fasterxml.jackson.annotation.JsonProperty;
import lombok.Data;
/**
* Representation of the token counts processed for a completions request. Counts consider all tokens across prompts,
* choices, choice alternates, best_of generations, and other consumers.
*/
@Data
public final class AzureCompletionsUsage {
/*
* The number of tokens generated across all completions emissions.
*/
@JsonProperty(value = "completion_tokens")
private int completionTokens;
/*
* The number of tokens in the provided prompts for the completions request.
*/
@JsonProperty(value = "prompt_tokens")
private int promptTokens;
/*
* The total number of tokens processed for the completions request and response.
*/
@JsonProperty(value = "total_tokens")
private int totalTokens;
/**
* Creates an instance of CompletionsUsage class.
*
* @param completionTokens the completionTokens value to set.
* @param promptTokens the promptTokens value to set.
* @param totalTokens the totalTokens value to set.
*/
@JsonCreator
private AzureCompletionsUsage(
@JsonProperty(value = "completion_tokens") int completionTokens,
@JsonProperty(value = "prompt_tokens") int promptTokens,
@JsonProperty(value = "total_tokens") int totalTokens) {
this.completionTokens = completionTokens;
this.promptTokens = promptTokens;
this.totalTokens = totalTokens;
}
/**
* Get the completionTokens property: The number of tokens generated across all completions emissions.
*
* @return the completionTokens value.
*/
public int getCompletionTokens() {
return this.completionTokens;
}
/**
* Get the promptTokens property: The number of tokens in the provided prompts for the completions request.
*
* @return the promptTokens value.
*/
public int getPromptTokens() {
return this.promptTokens;
}
/**
* Get the totalTokens property: The total number of tokens processed for the completions request and response.
*
* @return the totalTokens value.
*/
public int getTotalTokens() {
return this.totalTokens;
}
}

View File

@ -0,0 +1,147 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package ai.chat2db.server.web.api.controller.ai.azure.models;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.ConcurrentHashMap;
import ai.chat2db.server.web.api.controller.ai.azure.util.AzureReflectionUtils;
import com.fasterxml.jackson.annotation.JsonValue;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import static java.lang.invoke.MethodType.methodType;
/**
* Base implementation for expandable, single string enums.
*
* @param <T> a specific expandable enum type
*/
public abstract class AzureExpandableStringEnum<T extends AzureExpandableStringEnum<T>> {
private static final Map<Class<?>, MethodHandle> CONSTRUCTORS = new ConcurrentHashMap<>();
private static final Map<Class<?>, ConcurrentHashMap<String, ? extends AzureExpandableStringEnum<?>>> VALUES
= new ConcurrentHashMap<>();
private static final Logger LOGGER = LoggerFactory.getLogger(AzureExpandableStringEnum.class);
private String name;
private Class<T> clazz;
/**
* Creates a new instance of {@link AzureExpandableStringEnum} without a {@link #toString()} value.
* <p>
* This constructor shouldn't be called as it will produce a {@link AzureExpandableStringEnum} which doesn't
* have a String enum value.
*
* @deprecated Use the {@link #fromString(String, Class)} factory method.
*/
@Deprecated
public AzureExpandableStringEnum() {
}
/**
* Creates an instance of the specific expandable string enum from a String.
*
* @param name The value to create the instance from.
* @param clazz The class of the expandable string enum.
* @param <T> the class of the expandable string enum.
* @return The expandable string enum instance.
*
* @throws RuntimeException wrapping implementation class constructor exception (if any is thrown).
*/
@SuppressWarnings({"unchecked", "deprecation"})
protected static <T extends AzureExpandableStringEnum<T>> T fromString(String name, Class<T> clazz) {
if (name == null) {
return null;
}
ConcurrentHashMap<String, ?> clazzValues = VALUES.computeIfAbsent(clazz, key -> new ConcurrentHashMap<>());
T value = (T) clazzValues.get(name);
if (value != null) {
return value;
} else {
MethodHandle ctor = CONSTRUCTORS.computeIfAbsent(clazz, AzureExpandableStringEnum::getDefaultConstructor);
if (ctor == null) {
// logged in ExpandableStringEnum::getDefaultConstructor
return null;
}
try {
value = (T) ctor.invoke();
} catch (Throwable e) {
LOGGER.warn("Failed to create {}, default constructor threw exception", clazz.getName(), e);
return null;
}
return value.nameAndAddValue(name, value, clazz);
}
}
private static <T> MethodHandle getDefaultConstructor(Class<T> clazz) {
try {
MethodHandles.Lookup lookup = AzureReflectionUtils.getLookupToUse(clazz);
return lookup.findConstructor(clazz, methodType(void.class));
} catch (NoSuchMethodException | IllegalAccessException e) {
LOGGER.info("Can't find or access default constructor for {}, make sure corresponding package is open to azure-core", clazz.getName(), e);
} catch (Exception e) {
LOGGER.info("Failed to get lookup for {}", clazz.getName(), e);
}
return null;
}
@SuppressWarnings("unchecked")
T nameAndAddValue(String name, T value, Class<T> clazz) {
this.name = name;
this.clazz = clazz;
((ConcurrentHashMap<String, T>) VALUES.get(clazz)).put(name, value);
return (T) this;
}
/**
* Gets a collection of all known values to an expandable string enum type.
*
* @param clazz the class of the expandable string enum.
* @param <T> the class of the expandable string enum.
* @return A collection of all known values for the given {@code clazz}.
*/
@SuppressWarnings("unchecked")
protected static <T extends AzureExpandableStringEnum<T>> Collection<T> values(Class<T> clazz) {
return new ArrayList<T>((Collection<T>) VALUES.getOrDefault(clazz, new ConcurrentHashMap<>()).values());
}
@Override
@JsonValue
public String toString() {
return this.name;
}
@Override
public int hashCode() {
return Objects.hash(this.clazz, this.name);
}
@SuppressWarnings("unchecked")
@Override
public boolean equals(Object obj) {
if (obj == null) {
return false;
} else if (clazz == null || !clazz.isAssignableFrom(obj.getClass())) {
return false;
} else if (obj == this) {
return true;
} else if (this.name == null) {
return ((AzureExpandableStringEnum<T>) obj).name == null;
} else {
return this.name.equals(((AzureExpandableStringEnum<T>) obj).name);
}
}
}

View File

@ -0,0 +1,190 @@
// Copyright (c) Microsoft Corporation. All rights reserved.
// Licensed under the MIT License.
package ai.chat2db.server.web.api.controller.ai.azure.util;
import java.lang.invoke.MethodHandle;
import java.lang.invoke.MethodHandles;
import java.lang.invoke.MethodType;
import java.lang.reflect.Constructor;
import java.security.PrivilegedExceptionAction;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Utility methods that aid in performing reflective operations.
*/
@SuppressWarnings("deprecation")
public final class AzureReflectionUtils {
private static final Logger LOGGER = LoggerFactory.getLogger(AzureReflectionUtils.class);
private static final boolean MODULE_BASED;
private static final MethodHandle CLASS_GET_MODULE_METHOD_HANDLE;
private static final MethodHandle MODULE_IS_NAMED_METHOD_HANDLE;
private static final MethodHandle MODULE_ADD_READS_METHOD_HANDLE;
private static final MethodHandle METHOD_HANDLES_PRIVATE_LOOKUP_IN_METHOD_HANDLE;
private static final MethodHandle MODULE_IS_OPEN_UNCONDITIONALLY_METHOD_HANDLE;
private static final MethodHandle MODULE_IS_OPEN_TO_OTHER_MODULE_METHOD_HANDLE;
private static final MethodHandles.Lookup LOOKUP;
private static final Object CORE_MODULE;
private static final MethodHandle JDK_INTERNAL_PRIVATE_LOOKUP_IN_CONSTRUCTOR;
static {
boolean moduleBased = false;
MethodHandle classGetModule = null;
MethodHandle moduleIsNamed = null;
MethodHandle moduleAddReads = null;
MethodHandle methodHandlesPrivateLookupIn = null;
MethodHandle moduleIsOpenUnconditionally = null;
MethodHandle moduleIsOpenToOtherModule = null;
MethodHandles.Lookup lookup = MethodHandles.lookup();
Object coreModule = null;
MethodHandle jdkInternalPrivateLookupInConstructor = null;
try {
Class<?> moduleClass = Class.forName("java.lang.Module");
classGetModule = lookup.unreflect(Class.class.getDeclaredMethod("getModule"));
moduleIsNamed = lookup.unreflect(moduleClass.getDeclaredMethod("isNamed"));
moduleAddReads = lookup.unreflect(moduleClass.getDeclaredMethod("addReads", moduleClass));
methodHandlesPrivateLookupIn = lookup.findStatic(MethodHandles.class, "privateLookupIn",
MethodType.methodType(MethodHandles.Lookup.class, Class.class, MethodHandles.Lookup.class));
moduleIsOpenUnconditionally = lookup.unreflect(moduleClass.getDeclaredMethod("isOpen", String.class));
moduleIsOpenToOtherModule = lookup.unreflect(
moduleClass.getDeclaredMethod("isOpen", String.class, moduleClass));
coreModule = classGetModule.invokeWithArguments(AzureReflectionUtils.class);
moduleBased = true;
} catch (Throwable throwable) {
if (throwable instanceof Error) {
throw (Error) throwable;
} else {
LOGGER.error("Unable to create MethodHandles to use Java 9+ MethodHandles.privateLookupIn. "
+ "Will attempt to fallback to using the package-private constructor.", throwable);
}
}
if (!moduleBased) {
try {
Constructor<MethodHandles.Lookup> privateLookupInConstructor =
MethodHandles.Lookup.class.getDeclaredConstructor(Class.class);
if (!privateLookupInConstructor.isAccessible()) {
privateLookupInConstructor.setAccessible(true);
}
jdkInternalPrivateLookupInConstructor = lookup.unreflectConstructor(privateLookupInConstructor);
} catch (ReflectiveOperationException ex) {
LOGGER.error("Unable to use package-private MethodHandles.Lookup constructor.", ex);
}
}
MODULE_BASED = moduleBased;
CLASS_GET_MODULE_METHOD_HANDLE = classGetModule;
MODULE_IS_NAMED_METHOD_HANDLE = moduleIsNamed;
MODULE_ADD_READS_METHOD_HANDLE = moduleAddReads;
METHOD_HANDLES_PRIVATE_LOOKUP_IN_METHOD_HANDLE = methodHandlesPrivateLookupIn;
MODULE_IS_OPEN_UNCONDITIONALLY_METHOD_HANDLE = moduleIsOpenUnconditionally;
MODULE_IS_OPEN_TO_OTHER_MODULE_METHOD_HANDLE = moduleIsOpenToOtherModule;
LOOKUP = lookup;
CORE_MODULE = coreModule;
JDK_INTERNAL_PRIVATE_LOOKUP_IN_CONSTRUCTOR = jdkInternalPrivateLookupInConstructor;
}
/**
* Gets the {@link MethodHandles.Lookup} to use when performing reflective operations.
* <p>
* If Java 8 is being used this will always return {@link MethodHandles.Lookup#publicLookup()} as Java 8 doesn't
* have module boundaries that will prevent reflective access to the {@code targetClass}.
* <p>
* If Java 9 or above is being used this will return a {@link MethodHandles.Lookup} based on whether the module
* containing the {@code targetClass} exports the package containing the class. Otherwise, the
* {@link MethodHandles.Lookup} associated to {@code com.azure.core} will attempt to read the module containing
* {@code targetClass}.
*
* @param targetClass The {@link Class} that will need to be reflectively accessed.
* @return The {@link MethodHandles.Lookup} that will allow {@code com.azure.core} to access the {@code targetClass}
* reflectively.
* @throws Exception If the underlying reflective calls throw an exception.
*/
public static MethodHandles.Lookup getLookupToUse(Class<?> targetClass) throws Exception {
try {
if (MODULE_BASED) {
Object responseModule = CLASS_GET_MODULE_METHOD_HANDLE.invoke(targetClass);
// The unnamed module is opened unconditionally, have Core read it and use a private proxy lookup to
// enable all lookup scenarios.
if (!(boolean) MODULE_IS_NAMED_METHOD_HANDLE.invoke(responseModule)) {
MODULE_ADD_READS_METHOD_HANDLE.invokeWithArguments(CORE_MODULE, responseModule);
return performSafePrivateLookupIn(targetClass);
}
// If the response module is the Core module return the Core private lookup.
if (responseModule == CORE_MODULE) {
return LOOKUP;
}
// Next check if the target class module is opened either unconditionally or to Core's module. If so,
// also use a private proxy lookup to enable all lookup scenarios.
String packageName = targetClass.getPackage().getName();
if ((boolean) MODULE_IS_OPEN_UNCONDITIONALLY_METHOD_HANDLE
.invokeWithArguments(responseModule, packageName)
|| (boolean) MODULE_IS_OPEN_TO_OTHER_MODULE_METHOD_HANDLE
.invokeWithArguments(responseModule, packageName, CORE_MODULE)) {
MODULE_ADD_READS_METHOD_HANDLE.invokeWithArguments(CORE_MODULE, responseModule);
return performSafePrivateLookupIn(targetClass);
}
// Otherwise, return the public lookup as there are no specialty ways to access the other module.
return MethodHandles.publicLookup();
} else {
return (MethodHandles.Lookup) JDK_INTERNAL_PRIVATE_LOOKUP_IN_CONSTRUCTOR.invoke(targetClass);
}
} catch (Throwable throwable) {
// invoke(Class<?) throws a Throwable as the underlying method being called through reflection can throw
// anything, but the constructor being called is owned by the Java SDKs which won't throw Throwable. So,
// only Error needs to be inspected and handled specially, otherwise it can be assumed the Throwable is
// a type of Exception which can be thrown based on this method having Exception checked.
if (throwable instanceof Error) {
throw (Error) throwable;
} else {
throw (Exception) throwable;
}
}
}
@SuppressWarnings("removal")
private static MethodHandles.Lookup performSafePrivateLookupIn(Class<?> targetClass) throws Throwable {
// MethodHandles::privateLookupIn() throws SecurityException if denied by the security manager
if (System.getSecurityManager() == null) {
return (MethodHandles.Lookup) METHOD_HANDLES_PRIVATE_LOOKUP_IN_METHOD_HANDLE
.invokeExact(targetClass, LOOKUP);
} else {
return java.security.AccessController.doPrivileged((PrivilegedExceptionAction<MethodHandles.Lookup>) () -> {
try {
return (MethodHandles.Lookup) METHOD_HANDLES_PRIVATE_LOOKUP_IN_METHOD_HANDLE
.invokeExact(targetClass, LOOKUP);
} catch (Throwable throwable) {
if (throwable instanceof Error) {
throw (Error) throwable;
} else {
throw (Exception) throwable;
}
}
});
}
}
public static boolean isModuleBased() {
return MODULE_BASED;
}
AzureReflectionUtils() {
}
}

View File

@ -2,7 +2,12 @@ package ai.chat2db.server.web.api.controller.ai.listener;
import java.util.Objects;
import com.azure.ai.openai.models.Completions;
import ai.chat2db.server.web.api.controller.ai.azure.models.AzureChatChoice;
import ai.chat2db.server.web.api.controller.ai.azure.models.AzureChatCompletions;
import ai.chat2db.server.web.api.controller.ai.azure.models.AzureChatMessage;
import ai.chat2db.server.web.api.controller.ai.azure.models.AzureCompletionsUsage;
import ai.chat2db.server.web.api.controller.ai.response.ChatCompletionResponse;
import com.fasterxml.jackson.databind.DeserializationFeature;
import com.fasterxml.jackson.databind.ObjectMapper;
import com.unfbx.chatgpt.entity.chat.Message;
import lombok.SneakyThrows;
@ -25,6 +30,8 @@ public class AzureOpenAIEventSourceListener extends EventSourceListener {
private SseEmitter sseEmitter;
private ObjectMapper mapper = new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES);
public AzureOpenAIEventSourceListener(SseEmitter sseEmitter) {
this.sseEmitter = sseEmitter;
}
@ -53,8 +60,31 @@ public class AzureOpenAIEventSourceListener extends EventSourceListener {
sseEmitter.complete();
return;
}
AzureChatCompletions chatCompletions = mapper.readValue(data, AzureChatCompletions.class);
String text = "";
log.info("Model ID={} is created at {}.", chatCompletions.getId(),
chatCompletions.getCreated());
for (AzureChatChoice choice : chatCompletions.getChoices()) {
AzureChatMessage message = choice.getDelta();
if (message != null) {
log.info("Index: {}, Chat Role: {}", choice.getIndex(), message.getRole());
if (message.getContent() != null) {
text = message.getContent();
}
}
}
AzureCompletionsUsage usage = chatCompletions.getUsage();
if (usage != null) {
log.info(
"Usage: number of prompt token is {}, number of completion token is {}, and number of total "
+ "tokens in request and response is {}.%n", usage.getPromptTokens(),
usage.getCompletionTokens(), usage.getTotalTokens());
}
Message message = new Message();
message.setContent(data);
message.setContent(text);
sseEmitter.send(SseEmitter.event()
.id(null)
.data(message)
@ -89,16 +119,19 @@ public class AzureOpenAIEventSourceListener extends EventSourceListener {
return;
}
ResponseBody body = response.body();
String bodyString = null;
String bodyString = Objects.nonNull(t) ? t.getMessage() : "";
if (Objects.nonNull(body)) {
bodyString = body.string();
log.error("Azure OpenAI sse连接异常data{},异常:{}", bodyString, t);
if (StringUtils.isBlank(bodyString) && Objects.nonNull(t)) {
bodyString = t.getMessage();
}
log.error("Azure OpenAI sse response{}", bodyString);
} else {
log.error("Azure OpenAI sse连接异常data{},异常{}", response, t);
log.error("Azure OpenAI sse response{}error{}", response, t);
}
eventSource.cancel();
Message message = new Message();
message.setContent("Azure OpenAI出现异常,请在帮助中查看详细日志" + bodyString);
message.setContent("Azure OpenAI error" + bodyString);
sseEmitter.send(SseEmitter.event()
.id("[ERROR]")
.data(message));