Refactored llm clients, removed maxContinuations feature, exposed finishReason instead;
This commit is contained in:
@@ -1,8 +1,5 @@
|
||||
package de.extio.lmlib.client;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Duration;
|
||||
public final record Completion(String response, CompletionFinishReason finishReason, CompletionStatistics statistics) {
|
||||
|
||||
public final record Completion(String response, int requests, Duration duration, long inTokens, long outTokens, BigDecimal cost, boolean cached) {
|
||||
|
||||
}
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
package de.extio.lmlib.client;
|
||||
|
||||
public enum CompletionFinishReason {
|
||||
|
||||
/**
|
||||
* Regular completion
|
||||
*/
|
||||
DONE,
|
||||
|
||||
/**
|
||||
* Completion was stopped because the token limit was reached. You may continue the conversation.
|
||||
*/
|
||||
TOKEN_LIMIT_REACHED,
|
||||
|
||||
/**
|
||||
* Completion was stopped because the content was filtered.
|
||||
*/
|
||||
CONTENT_FILTERED
|
||||
|
||||
}
|
||||
@@ -0,0 +1,8 @@
|
||||
package de.extio.lmlib.client;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Duration;
|
||||
|
||||
public record CompletionStatistics(int requests, Duration duration, long inTokens, long outTokens, BigDecimal cost, boolean cached) {
|
||||
|
||||
}
|
||||
@@ -13,14 +13,12 @@ import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import java.util.function.Function;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
|
||||
import com.azure.ai.inference.ChatCompletionsClient;
|
||||
import com.azure.ai.inference.ChatCompletionsClientBuilder;
|
||||
import com.azure.ai.inference.models.ChatCompletions;
|
||||
import com.azure.ai.inference.models.ChatCompletionsOptions;
|
||||
import com.azure.ai.inference.models.ChatRequestAssistantMessage;
|
||||
import com.azure.ai.inference.models.ChatRequestMessage;
|
||||
@@ -35,6 +33,8 @@ import com.azure.core.http.policy.RetryOptions;
|
||||
|
||||
import de.extio.lmlib.client.Client;
|
||||
import de.extio.lmlib.client.Completion;
|
||||
import de.extio.lmlib.client.CompletionFinishReason;
|
||||
import de.extio.lmlib.client.CompletionStatistics;
|
||||
import de.extio.lmlib.client.Conversation;
|
||||
import de.extio.lmlib.client.Conversation.Turn;
|
||||
import de.extio.lmlib.profile.ModelCategory;
|
||||
@@ -42,7 +42,6 @@ import de.extio.lmlib.profile.ModelProfile;
|
||||
import de.extio.lmlib.profile.ModelProfile.ModelProvider;
|
||||
import de.extio.lmlib.profile.ModelProfileService;
|
||||
import de.extio.lmlib.token.Tokenizer;
|
||||
|
||||
import okhttp3.Authenticator;
|
||||
import okhttp3.Challenge;
|
||||
import okhttp3.Credentials;
|
||||
@@ -56,6 +55,7 @@ public final class AzureAiClient implements Client {
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(AzureAiClient.class);
|
||||
|
||||
private static final int TIMEOUT = 300000;
|
||||
|
||||
private static final int CONNECT_TIMEOUT = 10000;
|
||||
|
||||
private final ConcurrentMap<ModelCategory, ChatCompletionsClient> chatCompletionsClients = new ConcurrentHashMap<>();
|
||||
@@ -104,32 +104,12 @@ public final class AzureAiClient implements Client {
|
||||
throw new IllegalArgumentException("ModelProfile " + modelCategory.getModelProfile());
|
||||
}
|
||||
|
||||
final List<List<ChatRequestMessage>> chats = this.createChats(conversation, modelProfile);
|
||||
final var chat = this.createChats(conversation, modelProfile);
|
||||
|
||||
final List<StringBuilder> answers = new ArrayList<>();
|
||||
final CompletionStatistics statistics = new CompletionStatistics();
|
||||
for (final List<ChatRequestMessage> chat : chats) {
|
||||
final StringBuilder answer = new StringBuilder();
|
||||
this.requestCompletionsContinuations(chat, answer, statistics, modelCategory, modelProfile);
|
||||
answers.add(answer);
|
||||
}
|
||||
|
||||
// TODO: Reactivate when multiple (split) prompts have been implemented again in #addChatRequestMessage
|
||||
// if (answers.size() > 1) {
|
||||
// this.summarize(answers, modelProfile, promptStrategy, statistics);
|
||||
// }
|
||||
|
||||
return new Completion(
|
||||
answers.stream().collect(Collectors.joining()),
|
||||
statistics.requests,
|
||||
statistics.duration,
|
||||
statistics.inTokens,
|
||||
statistics.outTokens,
|
||||
new BigDecimal(statistics.inTokens).multiply(modelProfile.costInToken()).add(new BigDecimal(statistics.outTokens).multiply(modelProfile.costOutToken())),
|
||||
false);
|
||||
return this.requestCompletionsContinuations(chat, modelCategory, modelProfile);
|
||||
}
|
||||
|
||||
private List<List<ChatRequestMessage>> createChats(final Conversation conversation, final ModelProfile modelProfile) {
|
||||
private List<ChatRequestMessage> createChats(final Conversation conversation, final ModelProfile modelProfile) {
|
||||
final List<ChatRequestMessage> chat = new ArrayList<>();
|
||||
|
||||
final Deque<Turn> turns = new ArrayDeque<>(conversation.getConversation());
|
||||
@@ -138,7 +118,6 @@ public final class AzureAiClient implements Client {
|
||||
Turn assistantTurn = null;
|
||||
boolean first = true;
|
||||
|
||||
final int maxLength = modelProfile.maxContextLength() - modelProfile.maxTokens() - 15; // 15 is a margin for EOT and other special tokens
|
||||
final AtomicInteger tokens = new AtomicInteger();
|
||||
|
||||
while (!turns.isEmpty()) {
|
||||
@@ -161,17 +140,17 @@ public final class AzureAiClient implements Client {
|
||||
if (systemTurn != null) {
|
||||
chat.add(new ChatRequestSystemMessage(systemTurn.text()));
|
||||
}
|
||||
if (this.addChatRequestMessage(userTurn.text(), maxLength, tokens, chat, modelProfile, ChatRequestUserMessage::new)) {
|
||||
if (this.addChatRequestMessage(userTurn.text(), tokens, chat, modelProfile, ChatRequestUserMessage::new)) {
|
||||
break;
|
||||
}
|
||||
userTurn = null;
|
||||
first = false;
|
||||
}
|
||||
else if (!first && assistantTurn != null && userTurn != null) {
|
||||
if (this.addChatRequestMessage(assistantTurn.text(), maxLength, tokens, chat, modelProfile, ChatRequestAssistantMessage::new)) {
|
||||
if (this.addChatRequestMessage(assistantTurn.text(), tokens, chat, modelProfile, ChatRequestAssistantMessage::new)) {
|
||||
break;
|
||||
}
|
||||
if (this.addChatRequestMessage(userTurn.text(), maxLength, tokens, chat, modelProfile, ChatRequestUserMessage::new)) {
|
||||
if (this.addChatRequestMessage(userTurn.text(), tokens, chat, modelProfile, ChatRequestUserMessage::new)) {
|
||||
break;
|
||||
}
|
||||
userTurn = null;
|
||||
@@ -179,17 +158,17 @@ public final class AzureAiClient implements Client {
|
||||
}
|
||||
}
|
||||
|
||||
return List.of(chat);
|
||||
return chat;
|
||||
}
|
||||
|
||||
private boolean addChatRequestMessage(final String text, final int maxLength, final AtomicInteger tokens, final List<ChatRequestMessage> chat, final ModelProfile modelProfile, final Function<String, ChatRequestMessage> messageFactory) {
|
||||
String prompt = text;
|
||||
boolean trimmed = false;
|
||||
private boolean addChatRequestMessage(final String text, final AtomicInteger tokens, final List<ChatRequestMessage> chat, final ModelProfile modelProfile, final Function<String, ChatRequestMessage> messageFactory) {
|
||||
var prompt = text;
|
||||
var trimmed = false;
|
||||
|
||||
final int textTokens = this.tokenizer.count(prompt, modelProfile);
|
||||
final var textTokens = this.tokenizer.count(prompt, modelProfile);
|
||||
final int maxLength = modelProfile.maxContextLength() - modelProfile.maxTokens() - 15; // 15 is a margin for EOT and other special tokens
|
||||
if (tokens.addAndGet(textTokens) > maxLength) {
|
||||
// TODO: Cut smarter, e.g. split conversation into parts while keeping system prompt
|
||||
List<Long> tokenized = this.tokenizer.tokenize(prompt, modelProfile);
|
||||
var tokenized = this.tokenizer.tokenize(prompt, modelProfile);
|
||||
LOGGER.warn("Prompt too long: {} tokens / {} max. Prompt will be cut!", tokenized.size(), maxLength);
|
||||
tokenized = tokenized.subList(0, tokenized.size() - (tokens.get() - maxLength));
|
||||
prompt = this.tokenizer.detokenize(tokenized, modelProfile);
|
||||
@@ -201,42 +180,40 @@ public final class AzureAiClient implements Client {
|
||||
return trimmed;
|
||||
}
|
||||
|
||||
private void requestCompletionsContinuations(final List<ChatRequestMessage> chat, final StringBuilder answer, final CompletionStatistics statistics, final ModelCategory modelCategory, final ModelProfile modelProfile) {
|
||||
for (int continuation = 0; continuation < modelProfile.maxContinuations(); continuation++) {
|
||||
final LocalDateTime start = LocalDateTime.now();
|
||||
|
||||
final var response = this.requestCompletions(chat, modelCategory, modelProfile);
|
||||
|
||||
final var choice = response.getChoice();
|
||||
|
||||
statistics.add(Duration.between(start, LocalDateTime.now()), response.getUsage().getPromptTokens(), response.getUsage().getCompletionTokens());
|
||||
|
||||
answer.append(choice.getMessage().getContent());
|
||||
|
||||
final var complete = !(choice.getFinishReason() == CompletionsFinishReason.TOKEN_LIMIT_REACHED);
|
||||
if (complete) {
|
||||
LOGGER.debug("Prompt response complete");
|
||||
break;
|
||||
}
|
||||
|
||||
answer.append('\n');
|
||||
chat.add(new ChatRequestAssistantMessage(choice.getMessage().getContent()));
|
||||
chat.add(new ChatRequestUserMessage("continue"));
|
||||
}
|
||||
}
|
||||
|
||||
private ChatCompletions requestCompletions(final List<ChatRequestMessage> chat, final ModelCategory modelCategory, final ModelProfile modelProfile) {
|
||||
private Completion requestCompletionsContinuations(final List<ChatRequestMessage> chat, final ModelCategory modelCategory, final ModelProfile modelProfile) {
|
||||
LOGGER.debug("Requesting ChatCompletion at {}", modelProfile.modelName());
|
||||
final LocalDateTime start = LocalDateTime.now();
|
||||
|
||||
final var client = this.createHttpClient(modelCategory, modelProfile);
|
||||
|
||||
final var options = new ChatCompletionsOptions(chat);
|
||||
options.setModel(modelProfile.modelName());
|
||||
options.setMaxTokens(modelProfile.maxTokens());
|
||||
options.setTemperature(modelProfile.temperature());
|
||||
options.setTopP(modelProfile.topP());
|
||||
|
||||
return client.complete(options);
|
||||
final var client = this.createHttpClient(modelCategory, modelProfile);
|
||||
final var response = client.complete(options);
|
||||
|
||||
final CompletionFinishReason finishReason;
|
||||
if (CompletionsFinishReason.CONTENT_FILTERED.equals(response.getChoice().getFinishReason())) {
|
||||
finishReason = CompletionFinishReason.CONTENT_FILTERED;
|
||||
}
|
||||
else if (CompletionsFinishReason.TOKEN_LIMIT_REACHED.equals(response.getChoice().getFinishReason())) {
|
||||
finishReason = CompletionFinishReason.TOKEN_LIMIT_REACHED;
|
||||
}
|
||||
else {
|
||||
finishReason = CompletionFinishReason.DONE;
|
||||
}
|
||||
|
||||
final var statistics = new CompletionStatistics(
|
||||
1,
|
||||
Duration.between(start, LocalDateTime.now()),
|
||||
response.getUsage().getPromptTokens(),
|
||||
response.getUsage().getCompletionTokens(),
|
||||
new BigDecimal(response.getUsage().getPromptTokens()).multiply(modelProfile.costInToken()).add(new BigDecimal(response.getUsage().getCompletionTokens()).multiply(modelProfile.costOutToken())),
|
||||
false);
|
||||
LOGGER.debug("{}", statistics);
|
||||
|
||||
return new Completion(response.getChoice().getMessage().getContent(), finishReason, statistics);
|
||||
}
|
||||
|
||||
private ChatCompletionsClient createHttpClient(final ModelCategory modelCategory, final ModelProfile modelProfile) {
|
||||
@@ -258,19 +235,20 @@ public final class AzureAiClient implements Client {
|
||||
}
|
||||
return false;
|
||||
});
|
||||
|
||||
|
||||
final OkHttpClient.Builder nativeOkHttpClientBuilder = new OkHttpClient.Builder();
|
||||
|
||||
|
||||
if (this.proxyEnabled) {
|
||||
LOGGER.info("Proxy is enabled. Configuring proxy settings for host: {} port: {} user: {}", this.proxyHost, this.proxyPort, this.proxyUser);
|
||||
|
||||
|
||||
final Proxy proxy = new Proxy(Proxy.Type.HTTP, new InetSocketAddress(this.proxyHost, this.proxyPort));
|
||||
nativeOkHttpClientBuilder.proxy(proxy);
|
||||
|
||||
|
||||
if (this.proxyUser != null && this.proxyPassword != null) {
|
||||
final Authenticator proxyAuthenticator = new Authenticator() {
|
||||
|
||||
@Override
|
||||
public Request authenticate(final Route route, final Response response) {
|
||||
public Request authenticate(final Route route, final Response response) {
|
||||
for (final Challenge challenge : response.challenges()) {
|
||||
if (challenge.scheme().equalsIgnoreCase("OkHttp-Preemptive")) {
|
||||
final String credential = Credentials.basic(proxyUser, proxyPassword);
|
||||
@@ -283,13 +261,15 @@ public final class AzureAiClient implements Client {
|
||||
}
|
||||
};
|
||||
nativeOkHttpClientBuilder.proxyAuthenticator(proxyAuthenticator);
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
LOGGER.warn("Proxy user or password is not set. Skipping proxy authentication.");
|
||||
}
|
||||
} else {
|
||||
}
|
||||
else {
|
||||
LOGGER.info("Proxy is not enabled. Skipping proxy configuration.");
|
||||
}
|
||||
|
||||
|
||||
final OkHttpClient nativeOkHttpClient = nativeOkHttpClientBuilder.build();
|
||||
final OkHttpAsyncHttpClientBuilder okHttpClientBuilder = new OkHttpAsyncHttpClientBuilder(nativeOkHttpClient);
|
||||
final HttpClient okHttpClient = okHttpClientBuilder
|
||||
@@ -313,24 +293,4 @@ public final class AzureAiClient implements Client {
|
||||
return statusCode == 408 || statusCode == 429 || (statusCode >= 500 && statusCode < 600);
|
||||
}
|
||||
|
||||
static class CompletionStatistics {
|
||||
|
||||
int requests;
|
||||
|
||||
Duration duration = Duration.ofMillis(0l);
|
||||
|
||||
long inTokens;
|
||||
|
||||
long outTokens;
|
||||
|
||||
void add(final Duration duration, final int inTokens, final int outTokens) {
|
||||
LOGGER.debug("Request duration: {}; in: {}; out: {}; tps: {}", duration, inTokens, outTokens, String.format("%.2f", (inTokens + outTokens) / (double) duration.toMillis() * 1000.0));
|
||||
|
||||
this.requests++;
|
||||
this.duration = this.duration.plus(duration);
|
||||
this.inTokens += inTokens;
|
||||
this.outTokens += outTokens;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ import java.util.function.Supplier;
|
||||
|
||||
import de.extio.lmlib.client.Client;
|
||||
import de.extio.lmlib.client.Completion;
|
||||
import de.extio.lmlib.client.CompletionFinishReason;
|
||||
import de.extio.lmlib.client.CompletionStatistics;
|
||||
import de.extio.lmlib.client.Conversation;
|
||||
import de.extio.lmlib.profile.ModelCategory;
|
||||
import de.extio.lmlib.profile.ModelProfile.ModelProvider;
|
||||
@@ -50,12 +52,12 @@ public class CachedClient implements Client {
|
||||
|
||||
final var cachedCompletion = this.cacheRepository.get(key);
|
||||
if (cachedCompletion != null) {
|
||||
return new Completion(cachedCompletion.response(), 0, Duration.ZERO, cachedCompletion.inTokens(), cachedCompletion.outTokens(), BigDecimal.ZERO, true);
|
||||
return new Completion(cachedCompletion.response(), CompletionFinishReason.DONE, new CompletionStatistics(0, Duration.ZERO, cachedCompletion.inTokens(), cachedCompletion.outTokens(), BigDecimal.ZERO, true));
|
||||
}
|
||||
|
||||
final var completion = supplier.get();
|
||||
if (completion != null) {
|
||||
this.cacheRepository.put(key, new CachedCompletion(completion.response(), completion.inTokens(), completion.outTokens(), OffsetDateTime.now()));
|
||||
this.cacheRepository.put(key, new CachedCompletion(completion.response(), completion.finishReason(), completion.statistics().inTokens(), completion.statistics().outTokens(), OffsetDateTime.now()));
|
||||
}
|
||||
return completion;
|
||||
}
|
||||
@@ -76,7 +78,7 @@ public class CachedClient implements Client {
|
||||
digest.update(String.valueOf(modelProfile.modelProvider()).getBytes());
|
||||
digest.update(String.valueOf(modelProfile.maxTokens()).getBytes());
|
||||
digest.update(String.valueOf(modelProfile.maxContextLength()).getBytes());
|
||||
digest.update(String.valueOf(modelProfile.maxContinuations()).getBytes());
|
||||
digest.update(String.valueOf("1").getBytes());
|
||||
digest.update(String.valueOf(modelProfile.temperature()).getBytes());
|
||||
digest.update(String.valueOf(modelProfile.topP()).getBytes());
|
||||
for (final var k : keys) {
|
||||
|
||||
@@ -2,6 +2,8 @@ package de.extio.lmlib.client.cached;
|
||||
|
||||
import java.time.OffsetDateTime;
|
||||
|
||||
public record CachedCompletion(String response, long inTokens, long outTokens, OffsetDateTime created) {
|
||||
import de.extio.lmlib.client.CompletionFinishReason;
|
||||
|
||||
public record CachedCompletion(String response, CompletionFinishReason finishReason, long inTokens, long outTokens, OffsetDateTime created) {
|
||||
|
||||
}
|
||||
|
||||
@@ -6,10 +6,8 @@ import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Deque;
|
||||
import java.util.List;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
@@ -23,6 +21,7 @@ import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import de.extio.lmlib.client.Client;
|
||||
import de.extio.lmlib.client.Completion;
|
||||
import de.extio.lmlib.client.CompletionFinishReason;
|
||||
import de.extio.lmlib.client.Conversation;
|
||||
import de.extio.lmlib.client.Conversation.Turn;
|
||||
import de.extio.lmlib.client.oai.ModelNameSupplier;
|
||||
@@ -33,7 +32,6 @@ import de.extio.lmlib.profile.ModelProfileService;
|
||||
import de.extio.lmlib.prompt.PromptStrategy;
|
||||
import de.extio.lmlib.prompt.PromptStrategyFactory;
|
||||
import de.extio.lmlib.token.Tokenizer;
|
||||
|
||||
import reactor.util.retry.Retry;
|
||||
|
||||
@Component
|
||||
@@ -87,37 +85,17 @@ public class TextCompletionClient implements Client, DisposableBean {
|
||||
throw new IllegalArgumentException("Prompt strategy not found: " + modelProfile.prompt());
|
||||
}
|
||||
|
||||
final List<StringBuilder> prompts = this.createPrompts(conversation, modelCategory, modelProfile, promptStrategy);
|
||||
final var prompt = this.createPrompt(conversation, modelCategory, modelProfile, promptStrategy);
|
||||
|
||||
final List<StringBuilder> answers = new ArrayList<>();
|
||||
final CompletionStatistics statistics = new CompletionStatistics();
|
||||
for (final StringBuilder prompt : prompts) {
|
||||
final StringBuilder answer = new StringBuilder();
|
||||
this.requestCompletionsContinuations(prompt, answer, statistics, modelProfile, promptStrategy);
|
||||
answers.add(answer);
|
||||
}
|
||||
|
||||
if (answers.size() > 1) {
|
||||
this.summarize(answers, modelProfile, promptStrategy, statistics);
|
||||
}
|
||||
|
||||
return new Completion(
|
||||
answers.stream().collect(Collectors.joining()),
|
||||
statistics.requests,
|
||||
statistics.duration,
|
||||
statistics.inTokens,
|
||||
statistics.outTokens,
|
||||
new BigDecimal(statistics.inTokens).multiply(modelProfile.costInToken()).add(new BigDecimal(statistics.outTokens).multiply(modelProfile.costOutToken())),
|
||||
false);
|
||||
return this.requestCompletion(prompt, modelProfile, promptStrategy);
|
||||
}
|
||||
|
||||
private List<StringBuilder> createPrompts(final Conversation conversation, final ModelCategory modelCategory, final ModelProfile modelProfile, final PromptStrategy promptStrategy) {
|
||||
private String createPrompt(final Conversation conversation, final ModelCategory modelCategory, final ModelProfile modelProfile, final PromptStrategy promptStrategy) {
|
||||
StringBuilder prompt = this.formatConversation(conversation, promptStrategy);
|
||||
|
||||
List<Long> tokenized = this.tokenizer.tokenize(prompt.toString(), modelProfile);
|
||||
final int maxLength = modelProfile.maxContextLength() - modelProfile.maxTokens() - 15; // 15 is a margin for EOT and other special tokens
|
||||
if (tokenized.size() > maxLength) {
|
||||
// TODO: Cut smarter, e.g. split conversation into parts while keeping system prompt
|
||||
LOGGER.warn("Prompt too long: {} tokens / {} max. Prompt will be cut!", tokenized.size(), maxLength);
|
||||
tokenized = tokenized.subList(0, maxLength);
|
||||
prompt = new StringBuilder(this.tokenizer.detokenize(tokenized, modelProfile));
|
||||
@@ -125,7 +103,7 @@ public class TextCompletionClient implements Client, DisposableBean {
|
||||
|
||||
LOGGER.info("Completion request for {}. Input tokens: {}", modelCategory, tokenized.size());
|
||||
|
||||
return List.of(prompt);
|
||||
return prompt.toString();
|
||||
}
|
||||
|
||||
private StringBuilder formatConversation(final Conversation conversation, final PromptStrategy promptStrategy) {
|
||||
@@ -168,51 +146,9 @@ public class TextCompletionClient implements Client, DisposableBean {
|
||||
return prompt;
|
||||
}
|
||||
|
||||
private void requestCompletionsContinuations(final StringBuilder prompt, final StringBuilder answer, final CompletionStatistics statistics,
|
||||
final ModelProfile modelProfile, final PromptStrategy promptStrategy) {
|
||||
for (int continuation = 0; continuation < modelProfile.maxContinuations(); continuation++) {
|
||||
final LocalDateTime start = LocalDateTime.now();
|
||||
|
||||
final String promptStr = prompt.toString();
|
||||
|
||||
final var response = this.requestCompletions(promptStr, modelProfile);
|
||||
|
||||
String content = null;
|
||||
var complete = false;
|
||||
if (response.getContent() != null) {
|
||||
content = promptStrategy.removeEOT(response.getContent());
|
||||
complete = response.isStoppedEos();
|
||||
}
|
||||
else {
|
||||
final var choice = response.getChoices().getFirst();
|
||||
content = promptStrategy.removeEOT(choice.getText());
|
||||
complete = !Choice.FINISH_REASON_LENGTH.equals(choice.getFinishReason());
|
||||
}
|
||||
|
||||
if (this.collectStatistics) {
|
||||
final var dur = Duration.between(start, LocalDateTime.now());
|
||||
final var in = this.tokenizer.count(promptStr, modelProfile);
|
||||
final var out = this.tokenizer.count(content, modelProfile);
|
||||
final var cost = new BigDecimal(in).multiply(modelProfile.costInToken()).add(new BigDecimal(out).multiply(modelProfile.costOutToken()));
|
||||
LOGGER.debug("Request duration: " + dur + "; in tokens: " + in + "; out tokens: " + out + "; cost: " + new DecimalFormat("0.####").format(cost));
|
||||
statistics.add(dur, in, out, cost);
|
||||
this.totalStatistics.add(dur, in, out, cost);
|
||||
}
|
||||
|
||||
answer.append(content);
|
||||
|
||||
if (complete) {
|
||||
LOGGER.debug("Prompt response complete");
|
||||
break;
|
||||
}
|
||||
|
||||
answer.append('\n');
|
||||
promptStrategy.continue_(prompt, content);
|
||||
}
|
||||
}
|
||||
|
||||
private CompletionResponse requestCompletions(final String prompt, final ModelProfile modelProfile) {
|
||||
private Completion requestCompletion(final String prompt, final ModelProfile modelProfile, final PromptStrategy promptStrategy) {
|
||||
LOGGER.debug("Requesting completion at {}", modelProfile.url());
|
||||
final LocalDateTime start = LocalDateTime.now();
|
||||
|
||||
final var request = new CompletionRequest();
|
||||
request.setModel(this.modelNameSupplier.getModelName(modelProfile.url()));
|
||||
@@ -223,7 +159,7 @@ public class TextCompletionClient implements Client, DisposableBean {
|
||||
request.setStream(false);
|
||||
|
||||
final var webClient = this.webClientBuilder.baseUrl(modelProfile.url()).build();
|
||||
return webClient
|
||||
final var response = webClient
|
||||
.method(HttpMethod.POST)
|
||||
.uri(uriBuilder -> uriBuilder.path("/v1/completions").build())
|
||||
.bodyValue(request)
|
||||
@@ -231,22 +167,38 @@ public class TextCompletionClient implements Client, DisposableBean {
|
||||
.bodyToMono(CompletionResponse.class)
|
||||
.retryWhen(Retry.backoff(99, Duration.ofSeconds(1)).maxBackoff(Duration.ofSeconds(10)).jitter(0.25d).doAfterRetry(rs -> LOGGER.warn("Request failed " + rs.failure().getMessage())))
|
||||
.block();
|
||||
}
|
||||
|
||||
private void summarize(final List<StringBuilder> answers, final ModelProfile modelProfile, final PromptStrategy promptStrategy, final CompletionStatistics statistics) {
|
||||
// TODO: Reactivate when multiple (split) prompts have been implemented again in #createPrompts
|
||||
// LOGGER.debug("Summarizing answers");
|
||||
//
|
||||
// final StringBuilder summary = new StringBuilder();
|
||||
//
|
||||
// final StringBuilder prompt = promptStrategy.start(instruction, question, "");
|
||||
// answers.forEach(answer -> promptStrategy.next(prompt, answer.toString(), "Continue"));
|
||||
// promptStrategy.next(prompt, "", "Generate now a full summary");
|
||||
//
|
||||
// this.requestCompletionsContinuations(prompt, summary, statistics, modelProfile, promptStrategy);
|
||||
//
|
||||
// answers.clear();
|
||||
// answers.add(summary);
|
||||
|
||||
String content;
|
||||
CompletionFinishReason finishReason;
|
||||
if (response.getContent() != null) {
|
||||
content = promptStrategy.removeEOT(response.getContent());
|
||||
finishReason = response.isStoppedEos() ? CompletionFinishReason.DONE : CompletionFinishReason.TOKEN_LIMIT_REACHED;
|
||||
}
|
||||
else {
|
||||
final var choice = response.getChoices().getFirst();
|
||||
content = promptStrategy.removeEOT(choice.getText());
|
||||
finishReason = switch (choice.getFinishReason()) {
|
||||
case Choice.FINISH_REASON_CONTENT_FILTER -> CompletionFinishReason.CONTENT_FILTERED;
|
||||
case Choice.FINISH_REASON_LENGTH -> CompletionFinishReason.TOKEN_LIMIT_REACHED;
|
||||
default -> CompletionFinishReason.DONE;
|
||||
};
|
||||
}
|
||||
|
||||
final var in = this.tokenizer.count(prompt, modelProfile);
|
||||
final var out = this.tokenizer.count(content, modelProfile);
|
||||
final var statistics = new de.extio.lmlib.client.CompletionStatistics(
|
||||
1,
|
||||
Duration.between(start, LocalDateTime.now()),
|
||||
in,
|
||||
out,
|
||||
new BigDecimal(in).multiply(modelProfile.costInToken()).add(new BigDecimal(out).multiply(modelProfile.costOutToken())),
|
||||
false);
|
||||
LOGGER.debug("{}", statistics);
|
||||
if (this.collectStatistics) {
|
||||
this.totalStatistics.add(statistics.duration(), in, out, statistics.cost());
|
||||
}
|
||||
|
||||
return new Completion(content, finishReason, statistics);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
||||
Reference in New Issue
Block a user