feat: Update model profile and statistics tracking
- Updated model profile to support new cost parameters: costPerCachedInToken and costPerReasoningOutToken. - Enhanced AgentRequestStatistic to track cached input tokens and reasoning output tokens. - Modified CompletionStatistics to include cached input tokens and reasoning output tokens. - Updated AzureAiClient, CachedClient, and other clients to handle new statistics fields. - Introduced Timings class to capture detailed timing metrics for requests. - Adjusted Usage class to include input and output token details. - Updated properties files to include new cost parameters for cached and reasoning tokens.
This commit is contained in:
@@ -3,6 +3,6 @@ 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) {
|
||||
public record CompletionStatistics(int requests, Duration duration, long inTokens, long cachedInTokens, long outTokens, long reasoningOutTokens, BigDecimal cost, boolean cached) {
|
||||
|
||||
}
|
||||
|
||||
@@ -290,7 +290,9 @@ public final class AzureAiClient implements Client {
|
||||
1,
|
||||
Duration.between(start, LocalDateTime.now()),
|
||||
promptTokens,
|
||||
0,
|
||||
completionTokens,
|
||||
0,
|
||||
new BigDecimal(promptTokens).multiply(modelProfile.costPerInToken()).add(new BigDecimal(completionTokens).multiply(modelProfile.costPerOutToken())),
|
||||
false);
|
||||
LOGGER.debug("{}", statistics);
|
||||
|
||||
@@ -78,12 +78,12 @@ public class CachedClient implements Client {
|
||||
|
||||
final var cachedCompletion = this.cacheRepository.get(key);
|
||||
if (cachedCompletion != null) {
|
||||
return new Completion(cachedCompletion.response(), cachedCompletion.reasoning(), CompletionFinishReason.DONE, new CompletionStatistics(0, Duration.ZERO, cachedCompletion.inTokens(), cachedCompletion.outTokens(), BigDecimal.ZERO, true));
|
||||
return new Completion(cachedCompletion.response(), cachedCompletion.reasoning(), CompletionFinishReason.DONE, new CompletionStatistics(0, Duration.ZERO, cachedCompletion.inTokens(), cachedCompletion.cachedInTokens(), cachedCompletion.outTokens(), cachedCompletion.reasoningOutTokens(), BigDecimal.ZERO, true));
|
||||
}
|
||||
|
||||
final var completion = supplier.get();
|
||||
if (completion != null) {
|
||||
this.cacheRepository.put(key, new CachedCompletion(completion.response(), completion.reasoning(), completion.finishReason(), completion.statistics().inTokens(), completion.statistics().outTokens(), OffsetDateTime.now()));
|
||||
this.cacheRepository.put(key, new CachedCompletion(completion.response(), completion.reasoning(), completion.finishReason(), completion.statistics().inTokens(), completion.statistics().cachedInTokens(), completion.statistics().outTokens(), completion.statistics().reasoningOutTokens(), OffsetDateTime.now()));
|
||||
}
|
||||
return completion;
|
||||
}
|
||||
|
||||
@@ -4,6 +4,6 @@ import java.time.OffsetDateTime;
|
||||
|
||||
import de.extio.lmlib.client.CompletionFinishReason;
|
||||
|
||||
public record CachedCompletion(String response, String reasoning, CompletionFinishReason finishReason, long inTokens, long outTokens, OffsetDateTime created) {
|
||||
public record CachedCompletion(String response, String reasoning, CompletionFinishReason finishReason, long inTokens, long cachedInTokens, long outTokens, long reasoningOutTokens, OffsetDateTime created) {
|
||||
|
||||
}
|
||||
|
||||
@@ -140,32 +140,64 @@ public abstract class AbstractCompletionClient implements Client, DisposableBean
|
||||
}
|
||||
}
|
||||
|
||||
protected de.extio.lmlib.client.CompletionStatistics createCompletionStatistics(final ModelProfile modelProfile, final LocalDateTime start, final Usage usage, final String prompt, final String response) {
|
||||
protected de.extio.lmlib.client.CompletionStatistics createCompletionStatistics(final ModelProfile modelProfile, final LocalDateTime start, final Usage usage, final Timings timings, final String prompt, final String response, final String reasoning) {
|
||||
int inTokens = 0;
|
||||
int cachedInTokens = 0;
|
||||
int outTokens = 0;
|
||||
int reasoningOutTokens = 0;
|
||||
|
||||
// Usage data has priority (official OAI spec)
|
||||
if (usage != null && usage.getPromptTokens() > 0) {
|
||||
inTokens = usage.getPromptTokens();
|
||||
outTokens = usage.getCompletionTokens();
|
||||
cachedInTokens = usage.getCachedTokens();
|
||||
inTokens = usage.getPromptTokens() - cachedInTokens;
|
||||
reasoningOutTokens = usage.getReasoningTokens();
|
||||
outTokens = usage.getCompletionTokens() - reasoningOutTokens;
|
||||
}
|
||||
else {
|
||||
if (prompt != null) {
|
||||
inTokens = this.tokenizer.count(prompt, modelProfile);
|
||||
// If no usage data, try to get token counts from timings (e.g. llama.cpp, prompt_n does not include cache_n)
|
||||
if (timings != null) {
|
||||
if (inTokens <= 0 && cachedInTokens <= 0 && (timings.getPromptN() > 0 || timings.getCacheN() > 0)) {
|
||||
inTokens = Math.max(0, timings.getPromptN());
|
||||
cachedInTokens = Math.max(0, timings.getCacheN());
|
||||
}
|
||||
if (response != null) {
|
||||
outTokens = this.tokenizer.count(response, modelProfile);
|
||||
else if (cachedInTokens <= 0 && timings.getCacheN() > 0) {
|
||||
cachedInTokens = Math.max(0, timings.getCacheN());
|
||||
inTokens -= cachedInTokens;
|
||||
}
|
||||
if (outTokens <= 0 && timings.getPredictedN() > 0) {
|
||||
outTokens = timings.getPredictedN();
|
||||
}
|
||||
}
|
||||
// Fallback to tokenizer estimation
|
||||
if (inTokens <= 0 && prompt != null && !prompt.isBlank()) {
|
||||
inTokens = this.tokenizer.count(prompt, modelProfile);
|
||||
}
|
||||
if (reasoningOutTokens <= 0 && reasoning != null && !reasoning.isBlank()) {
|
||||
reasoningOutTokens = this.tokenizer.count(reasoning, modelProfile);
|
||||
outTokens -= reasoningOutTokens;
|
||||
}
|
||||
if (outTokens <= 0 && response != null && !response.isBlank()) {
|
||||
outTokens = this.tokenizer.count(response, modelProfile);
|
||||
}
|
||||
|
||||
// Calculate cost - inTokens and outTokens are already exclusive of cached/reasoning tokens
|
||||
BigDecimal cost = BigDecimal.ZERO;
|
||||
cost = cost.add(new BigDecimal(inTokens).multiply(modelProfile.costPerInToken()));
|
||||
cost = cost.add(new BigDecimal(cachedInTokens).multiply(modelProfile.costPerCachedInToken()));
|
||||
cost = cost.add(new BigDecimal(outTokens).multiply(modelProfile.costPerOutToken()));
|
||||
cost = cost.add(new BigDecimal(reasoningOutTokens).multiply(modelProfile.costPerReasoningOutToken()));
|
||||
|
||||
final var statistics = new de.extio.lmlib.client.CompletionStatistics(
|
||||
1,
|
||||
Duration.between(start, LocalDateTime.now()),
|
||||
inTokens,
|
||||
cachedInTokens,
|
||||
outTokens,
|
||||
new BigDecimal(inTokens).multiply(modelProfile.costPerInToken()).add(new BigDecimal(outTokens).multiply(modelProfile.costPerOutToken())),
|
||||
reasoningOutTokens,
|
||||
cost,
|
||||
false);
|
||||
LOGGER.debug("{}", statistics);
|
||||
if (this.collectStatistics) {
|
||||
this.totalStatistics.add(statistics.duration(), inTokens, outTokens, statistics.cost());
|
||||
this.totalStatistics.add(statistics.duration(), inTokens, cachedInTokens, outTokens, reasoningOutTokens, statistics.cost());
|
||||
}
|
||||
return statistics;
|
||||
}
|
||||
@@ -187,15 +219,21 @@ public abstract class AbstractCompletionClient implements Client, DisposableBean
|
||||
|
||||
private long inTokens;
|
||||
|
||||
private long cachedInTokens;
|
||||
|
||||
private long outTokens;
|
||||
|
||||
private long reasoningOutTokens;
|
||||
|
||||
private BigDecimal cost = BigDecimal.ZERO;
|
||||
|
||||
synchronized void add(final Duration duration, final int inTokens, final int outTokens, final BigDecimal cost) {
|
||||
synchronized void add(final Duration duration, final int inTokens, final int cachedInTokens, final int outTokens, final int reasoningOutTokens, final BigDecimal cost) {
|
||||
this.requests++;
|
||||
this.duration = this.duration.plus(duration);
|
||||
this.inTokens += inTokens;
|
||||
this.cachedInTokens += cachedInTokens;
|
||||
this.outTokens += outTokens;
|
||||
this.reasoningOutTokens += reasoningOutTokens;
|
||||
this.cost = this.cost.add(cost);
|
||||
}
|
||||
|
||||
@@ -208,19 +246,24 @@ public abstract class AbstractCompletionClient implements Client, DisposableBean
|
||||
builder.append(this.duration);
|
||||
builder.append(", inTokens=");
|
||||
builder.append(this.inTokens);
|
||||
builder.append(", cachedInTokens=");
|
||||
builder.append(this.cachedInTokens);
|
||||
builder.append(", outTokens=");
|
||||
builder.append(this.outTokens);
|
||||
builder.append(", reasoningOutTokens=");
|
||||
builder.append(this.reasoningOutTokens);
|
||||
builder.append(", averageTps=");
|
||||
builder.append(new DecimalFormat("0.##").format(((double) (this.outTokens + this.inTokens) / (double) this.requests) / ((double) this.duration.toMillis() / 1000.0 / (double) this.requests)));
|
||||
final long totalOutTokens = this.outTokens + this.reasoningOutTokens;
|
||||
builder.append(new DecimalFormat("0.##").format(((double) (totalOutTokens + inTokens) / (double) this.requests) / ((double) this.duration.toMillis() / 1000.0 / (double) this.requests)));
|
||||
builder.append(", averageOutTps=");
|
||||
builder.append(new DecimalFormat("0.##").format((double) this.outTokens / ((double) this.duration.toMillis() / 1000.0)));
|
||||
builder.append(new DecimalFormat("0.##").format((double) totalOutTokens / ((double) this.duration.toMillis() / 1000.0)));
|
||||
builder.append(", effectiveDuration=");
|
||||
final var effectiveDuration = Duration.between(this.start, Instant.now());
|
||||
builder.append(effectiveDuration);
|
||||
builder.append(", effectiveTps=");
|
||||
builder.append(new DecimalFormat("0.##").format(((double) (this.outTokens + this.inTokens) / (double) this.requests) / ((double) effectiveDuration.toMillis() / 1000.0 / (double) this.requests)));
|
||||
builder.append(new DecimalFormat("0.##").format(((double) (totalOutTokens + inTokens) / (double) this.requests) / ((double) effectiveDuration.toMillis() / 1000.0 / (double) this.requests)));
|
||||
builder.append(", effectiveOutTps=");
|
||||
builder.append(new DecimalFormat("0.##").format((double) this.outTokens / ((double) effectiveDuration.toMillis() / 1000.0)));
|
||||
builder.append(new DecimalFormat("0.##").format((double) totalOutTokens / ((double) effectiveDuration.toMillis() / 1000.0)));
|
||||
builder.append(", cost=");
|
||||
builder.append(new DecimalFormat("0.###").format(cost.doubleValue()));
|
||||
builder.append("]");
|
||||
|
||||
@@ -16,6 +16,8 @@ public abstract class AbstractCompletionRequest {
|
||||
|
||||
protected StreamOptions streamOptions;
|
||||
|
||||
protected Boolean usage;
|
||||
|
||||
@JsonProperty("model")
|
||||
public String getModel() {
|
||||
return this.model;
|
||||
@@ -69,4 +71,13 @@ public abstract class AbstractCompletionRequest {
|
||||
public void setStreamOptions(final StreamOptions streamOptions) {
|
||||
this.streamOptions = streamOptions;
|
||||
}
|
||||
|
||||
@JsonProperty("usage")
|
||||
public Boolean getUsage() {
|
||||
return this.usage;
|
||||
}
|
||||
|
||||
public void setUsage(final Boolean usage) {
|
||||
this.usage = usage;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -11,6 +11,8 @@ public abstract class AbstractCompletionResponse {
|
||||
protected Integer created;
|
||||
|
||||
protected Usage usage;
|
||||
|
||||
protected Timings timings;
|
||||
|
||||
@JsonProperty("id")
|
||||
public String getId() {
|
||||
@@ -41,10 +43,19 @@ public abstract class AbstractCompletionResponse {
|
||||
|
||||
@JsonProperty("usage")
|
||||
public Usage getUsage() {
|
||||
return usage;
|
||||
return this.usage;
|
||||
}
|
||||
|
||||
public void setUsage(final Usage usage) {
|
||||
this.usage = usage;
|
||||
}
|
||||
|
||||
@JsonProperty("timings")
|
||||
public Timings getTimings() {
|
||||
return this.timings;
|
||||
}
|
||||
|
||||
public void setTimings(final Timings timings) {
|
||||
this.timings = timings;
|
||||
}
|
||||
}
|
||||
|
||||
111
src/main/java/de/extio/lmlib/client/oai/completion/Timings.java
Normal file
111
src/main/java/de/extio/lmlib/client/oai/completion/Timings.java
Normal file
@@ -0,0 +1,111 @@
|
||||
package de.extio.lmlib.client.oai.completion;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
|
||||
public final class Timings {
|
||||
|
||||
private int cacheN;
|
||||
|
||||
private int predictedN;
|
||||
|
||||
private double predictedMs;
|
||||
|
||||
private double predictedPerTokenMs;
|
||||
|
||||
private double predictedPerSecond;
|
||||
|
||||
private int promptN;
|
||||
|
||||
private double promptMs;
|
||||
|
||||
private double promptPerTokenMs;
|
||||
|
||||
private double promptPerSecond;
|
||||
|
||||
@JsonProperty("cache_n")
|
||||
public int getCacheN() {
|
||||
return this.cacheN;
|
||||
}
|
||||
|
||||
public void setCacheN(final int cacheN) {
|
||||
this.cacheN = cacheN;
|
||||
}
|
||||
|
||||
@JsonProperty("predicted_n")
|
||||
public int getPredictedN() {
|
||||
return this.predictedN;
|
||||
}
|
||||
|
||||
public void setPredictedN(final int predictedN) {
|
||||
this.predictedN = predictedN;
|
||||
}
|
||||
|
||||
@JsonProperty("predicted_ms")
|
||||
public double getPredictedMs() {
|
||||
return this.predictedMs;
|
||||
}
|
||||
|
||||
public void setPredictedMs(final double predictedMs) {
|
||||
this.predictedMs = predictedMs;
|
||||
}
|
||||
|
||||
@JsonProperty("predicted_per_token_ms")
|
||||
public double getPredictedPerTokenMs() {
|
||||
return this.predictedPerTokenMs;
|
||||
}
|
||||
|
||||
public void setPredictedPerTokenMs(final double predictedPerTokenMs) {
|
||||
this.predictedPerTokenMs = predictedPerTokenMs;
|
||||
}
|
||||
|
||||
@JsonProperty("predicted_per_second")
|
||||
public double getPredictedPerSecond() {
|
||||
return this.predictedPerSecond;
|
||||
}
|
||||
|
||||
public void setPredictedPerSecond(final double predictedPerSecond) {
|
||||
this.predictedPerSecond = predictedPerSecond;
|
||||
}
|
||||
|
||||
@JsonProperty("prompt_n")
|
||||
public int getPromptN() {
|
||||
return this.promptN;
|
||||
}
|
||||
|
||||
public void setPromptN(final int promptN) {
|
||||
this.promptN = promptN;
|
||||
}
|
||||
|
||||
@JsonProperty("prompt_ms")
|
||||
public double getPromptMs() {
|
||||
return this.promptMs;
|
||||
}
|
||||
|
||||
public void setPromptMs(final double promptMs) {
|
||||
this.promptMs = promptMs;
|
||||
}
|
||||
|
||||
@JsonProperty("prompt_per_token_ms")
|
||||
public double getPromptPerTokenMs() {
|
||||
return this.promptPerTokenMs;
|
||||
}
|
||||
|
||||
public void setPromptPerTokenMs(final double promptPerTokenMs) {
|
||||
this.promptPerTokenMs = promptPerTokenMs;
|
||||
}
|
||||
|
||||
@JsonProperty("prompt_per_second")
|
||||
public double getPromptPerSecond() {
|
||||
return this.promptPerSecond;
|
||||
}
|
||||
|
||||
public void setPromptPerSecond(final double promptPerSecond) {
|
||||
this.promptPerSecond = promptPerSecond;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Timings [cacheN=" + this.cacheN + ", predictedN=" + this.predictedN + ", predictedMs=" + this.predictedMs + ", predictedPerTokenMs=" + this.predictedPerTokenMs + ", predictedPerSecond=" + this.predictedPerSecond + ", promptN=" + this.promptN + ", promptMs=" + this.promptMs + ", promptPerTokenMs=" + this.promptPerTokenMs + ", promptPerSecond=" + this.promptPerSecond + "]";
|
||||
}
|
||||
|
||||
}
|
||||
@@ -10,36 +10,114 @@ public final class Usage {
|
||||
|
||||
private int totalTokens;
|
||||
|
||||
private InputTokensDetails inputTokensDetails;
|
||||
|
||||
private OutputTokensDetails outputTokensDetails;
|
||||
|
||||
@JsonProperty("completion_tokens")
|
||||
public int getCompletionTokens() {
|
||||
return completionTokens;
|
||||
return this.completionTokens;
|
||||
}
|
||||
|
||||
public void setCompletionTokens(final int completionTokens) {
|
||||
this.completionTokens = completionTokens;
|
||||
}
|
||||
|
||||
@JsonProperty("output_tokens")
|
||||
public void setOutputTokens(final int outputTokens) {
|
||||
this.completionTokens = outputTokens;
|
||||
}
|
||||
|
||||
@JsonProperty("prompt_tokens")
|
||||
public int getPromptTokens() {
|
||||
return promptTokens;
|
||||
return this.promptTokens;
|
||||
}
|
||||
|
||||
public void setPromptTokens(final int promptTokens) {
|
||||
this.promptTokens = promptTokens;
|
||||
}
|
||||
|
||||
@JsonProperty("input_tokens")
|
||||
public void setInputTokens(final int inputTokens) {
|
||||
this.promptTokens = inputTokens;
|
||||
}
|
||||
|
||||
@JsonProperty("total_tokens")
|
||||
public int getTotalTokens() {
|
||||
return totalTokens;
|
||||
return this.totalTokens;
|
||||
}
|
||||
|
||||
public void setTotalTokens(final int totalTokens) {
|
||||
this.totalTokens = totalTokens;
|
||||
}
|
||||
|
||||
@JsonProperty("input_tokens_details")
|
||||
public InputTokensDetails getInputTokensDetails() {
|
||||
return this.inputTokensDetails;
|
||||
}
|
||||
|
||||
public void setInputTokensDetails(final InputTokensDetails inputTokensDetails) {
|
||||
this.inputTokensDetails = inputTokensDetails;
|
||||
}
|
||||
|
||||
@JsonProperty("output_tokens_details")
|
||||
public OutputTokensDetails getOutputTokensDetails() {
|
||||
return this.outputTokensDetails;
|
||||
}
|
||||
|
||||
public void setOutputTokensDetails(final OutputTokensDetails outputTokensDetails) {
|
||||
this.outputTokensDetails = outputTokensDetails;
|
||||
}
|
||||
|
||||
public int getCachedTokens() {
|
||||
return this.inputTokensDetails != null ? this.inputTokensDetails.getCachedTokens() : 0;
|
||||
}
|
||||
|
||||
public int getReasoningTokens() {
|
||||
return this.outputTokensDetails != null ? this.outputTokensDetails.getReasoningTokens() : 0;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "Usage [completionTokens=" + completionTokens + ", promptTokens=" + promptTokens + ", totalTokens=" + totalTokens + "]";
|
||||
return "Usage [completionTokens=" + this.completionTokens + ", promptTokens=" + this.promptTokens + ", totalTokens=" + this.totalTokens + ", inputTokensDetails=" + this.inputTokensDetails + ", outputTokensDetails=" + this.outputTokensDetails + "]";
|
||||
}
|
||||
|
||||
public static final class InputTokensDetails {
|
||||
|
||||
private int cachedTokens;
|
||||
|
||||
@JsonProperty("cached_tokens")
|
||||
public int getCachedTokens() {
|
||||
return this.cachedTokens;
|
||||
}
|
||||
|
||||
public void setCachedTokens(final int cachedTokens) {
|
||||
this.cachedTokens = cachedTokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "InputTokensDetails [cachedTokens=" + this.cachedTokens + "]";
|
||||
}
|
||||
}
|
||||
|
||||
public static final class OutputTokensDetails {
|
||||
|
||||
private int reasoningTokens;
|
||||
|
||||
@JsonProperty("reasoning_tokens")
|
||||
public int getReasoningTokens() {
|
||||
return this.reasoningTokens;
|
||||
}
|
||||
|
||||
public void setReasoningTokens(final int reasoningTokens) {
|
||||
this.reasoningTokens = reasoningTokens;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return "OutputTokensDetails [reasoningTokens=" + this.reasoningTokens + "]";
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -52,6 +52,8 @@ public class ChatCompletionClient extends AbstractCompletionClient {
|
||||
request.setStream(chunkConsumer != null);
|
||||
if (chunkConsumer != null) {
|
||||
this.configureStreamOptions(true, request::setStreamOptions);
|
||||
} else {
|
||||
request.setUsage(true);
|
||||
}
|
||||
if (modelProfile.reasoningEffort() != null && !modelProfile.reasoningEffort().isBlank()) {
|
||||
request.setReasoning(new ChatCompletionRequest.ChatCompletionsRequestReasoning());
|
||||
@@ -86,10 +88,14 @@ public class ChatCompletionClient extends AbstractCompletionClient {
|
||||
final LocalDateTime start = LocalDateTime.now();
|
||||
final var webClient = this.webClientBuilder.baseUrl(modelProfile.url()).build();
|
||||
|
||||
final var response = webClient
|
||||
var requestSpec = webClient
|
||||
.method(HttpMethod.POST)
|
||||
.uri(uriBuilder -> uriBuilder.path("/v1/chat/completions").build())
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Content-Type", "application/json");
|
||||
if (modelProfile.apiKey() != null && !modelProfile.apiKey().isBlank()) {
|
||||
requestSpec = requestSpec.header("Authorization", "Bearer " + modelProfile.apiKey());
|
||||
}
|
||||
final var response = requestSpec
|
||||
.bodyValue(requestBody)
|
||||
.exchangeToMono(clientResponse -> {
|
||||
if (clientResponse.statusCode().isError()) {
|
||||
@@ -123,6 +129,9 @@ public class ChatCompletionClient extends AbstractCompletionClient {
|
||||
if (streamResponse.getUsage() != null) {
|
||||
chatCompletionResponse.setUsage(streamResponse.getUsage());
|
||||
}
|
||||
if (streamResponse.getTimings() != null) {
|
||||
chatCompletionResponse.setTimings(streamResponse.getTimings());
|
||||
}
|
||||
if (streamResponse.getChoices() != null && !streamResponse.getChoices().isEmpty()) {
|
||||
if (streamResponse.getChoices().getFirst().getFinishReason() != null) {
|
||||
chatCompletionResponse.getChoices().getFirst().setFinishReason(streamResponse.getChoices().getFirst().getFinishReason());
|
||||
@@ -165,7 +174,7 @@ public class ChatCompletionClient extends AbstractCompletionClient {
|
||||
final var content = response.getChoices().getFirst().getMessage().getContent();
|
||||
final var reasoning = response.getChoices().getFirst().getMessage().getReasoningContent();
|
||||
final var finishReason = this.mapFinishReason(choice.getFinishReason());
|
||||
final var statistics = createCompletionStatistics(modelProfile, start, response.getUsage(), null, content);
|
||||
final var statistics = createCompletionStatistics(modelProfile, start, response.getUsage(), response.getTimings(), null, content, reasoning);
|
||||
return new Completion(content, reasoning, finishReason, statistics);
|
||||
}
|
||||
|
||||
|
||||
@@ -57,6 +57,8 @@ public class TextCompletionClient extends AbstractCompletionClient {
|
||||
request.setStream(chunkConsumer != null);
|
||||
if (chunkConsumer != null) {
|
||||
this.configureStreamOptions(true, request::setStreamOptions);
|
||||
} else {
|
||||
request.setUsage(true);
|
||||
}
|
||||
|
||||
String requestBody;
|
||||
@@ -70,10 +72,14 @@ public class TextCompletionClient extends AbstractCompletionClient {
|
||||
LOGGER.debug("Requesting completion at {}", modelProfile.url());
|
||||
final LocalDateTime start = LocalDateTime.now();
|
||||
final var webClient = this.webClientBuilder.baseUrl(modelProfile.url()).build();
|
||||
final var response = webClient
|
||||
var requestSpec = webClient
|
||||
.method(HttpMethod.POST)
|
||||
.uri(uriBuilder -> uriBuilder.path("/v1/completions").build())
|
||||
.header("Content-Type", "application/json")
|
||||
.header("Content-Type", "application/json");
|
||||
if (modelProfile.apiKey() != null && !modelProfile.apiKey().isBlank()) {
|
||||
requestSpec = requestSpec.header("Authorization", "Bearer " + modelProfile.apiKey());
|
||||
}
|
||||
final var response = requestSpec
|
||||
.bodyValue(requestBody)
|
||||
.exchangeToMono(clientResponse -> {
|
||||
if (clientResponse.statusCode().isError()) {
|
||||
@@ -112,6 +118,9 @@ public class TextCompletionClient extends AbstractCompletionClient {
|
||||
if (streamResponse.getUsage() != null) {
|
||||
CompletionResponse.setUsage(streamResponse.getUsage());
|
||||
}
|
||||
if (streamResponse.getTimings() != null) {
|
||||
CompletionResponse.setTimings(streamResponse.getTimings());
|
||||
}
|
||||
if (streamResponse.getChoices() != null && !streamResponse.getChoices().isEmpty()) {
|
||||
if (streamResponse.getChoices().getFirst().getFinishReason() != null) {
|
||||
CompletionResponse.getChoices().getFirst().setFinishReason(streamResponse.getChoices().getFirst().getFinishReason());
|
||||
@@ -172,7 +181,7 @@ public class TextCompletionClient extends AbstractCompletionClient {
|
||||
}
|
||||
}
|
||||
|
||||
final var statistics = createCompletionStatistics(modelProfile, start, response.getUsage(), prompt, response.getContent());
|
||||
final var statistics = createCompletionStatistics(modelProfile, start, response.getUsage(), response.getTimings(), prompt, content, reasoning);
|
||||
return new Completion(content, reasoning, finishReason, statistics);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user