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:
24
README.md
24
README.md
@@ -108,11 +108,13 @@ final var modelProfile = new ModelProfile(
|
|||||||
0.7, // temperature
|
0.7, // temperature
|
||||||
1.0, // topP
|
1.0, // topP
|
||||||
ModelProfile.ModelProvider.OAI_CHAT_COMPLETION,
|
ModelProfile.ModelProvider.OAI_CHAT_COMPLETION,
|
||||||
"gpt-4", // modelName
|
"gpt-5.1", // modelName
|
||||||
"https://api.openai.com/v1", // url
|
"https://api.openai.com/v1", // url
|
||||||
System.getenv("OPENAI_API_KEY"), // apiKey
|
System.getenv("OPENAI_API_KEY"), // apiKey
|
||||||
new BigDecimal("0.3").divide(1000000), // costPerInToken (per token)
|
new BigDecimal("1.25").divide(new BigDecimal(1000000)), // costPerInToken ($1.25/1M)
|
||||||
new BigDecimal("0.6").divide(1000000), // costPerOutToken (per token)
|
new BigDecimal("0.125").divide(new BigDecimal(1000000)), // costPerCachedInToken ($0.125/1M)
|
||||||
|
new BigDecimal("10.0").divide(new BigDecimal(1000000)), // costPerOutToken ($10.00/1M)
|
||||||
|
BigDecimal.ZERO, // costPerReasoningOutToken
|
||||||
null, // reasoningEffort
|
null, // reasoningEffort
|
||||||
null, // reasoningSummaryDetails
|
null, // reasoningSummaryDetails
|
||||||
"DYNAMIC" // category (short name for logging)
|
"DYNAMIC" // category (short name for logging)
|
||||||
@@ -160,7 +162,9 @@ Each model profile is a `.properties` file that configures how the library inter
|
|||||||
| `url` | String | Yes | Base URL endpoint for the model API. | `https://api.openai.com/v1`, `http://localhost:5001` |
|
| `url` | String | Yes | Base URL endpoint for the model API. | `https://api.openai.com/v1`, `http://localhost:5001` |
|
||||||
| `apiKey` | String | No | Authentication key for the API. Can use Spring property placeholders like `${apikey}`. Can be empty for local models. | `sk-...`, `${apikey}`, `` (empty) |
|
| `apiKey` | String | No | Authentication key for the API. Can use Spring property placeholders like `${apikey}`. Can be empty for local models. | `sk-...`, `${apikey}`, `` (empty) |
|
||||||
| `cost1MInTokens` | Double | Yes | Cost per 1 million input tokens in your currency. Use `0` for free/local models. | `0.71`, `0`, `5.00` |
|
| `cost1MInTokens` | Double | Yes | Cost per 1 million input tokens in your currency. Use `0` for free/local models. | `0.71`, `0`, `5.00` |
|
||||||
|
| `cost1MCachedInTokens` | Double | No | Cost per 1 million cached input tokens. Defaults to `cost1MInTokens` if not specified. | `0.35`, `0` |
|
||||||
| `cost1MOutTokens` | Double | Yes | Cost per 1 million output tokens in your currency. Use `0` for free/local models. | `0.71`, `0`, `15.00` |
|
| `cost1MOutTokens` | Double | Yes | Cost per 1 million output tokens in your currency. Use `0` for free/local models. | `0.71`, `0`, `15.00` |
|
||||||
|
| `cost1MReasoningOutTokens` | Double | No | Cost per 1 million reasoning output tokens. Defaults to `0` if not specified. | `0`, `60.00` |
|
||||||
| `reasoningEffort` | String | No | For reasoning models (e.g., OpenAI o1): controls depth of reasoning process. | `low`, `medium`, `high` |
|
| `reasoningEffort` | String | No | For reasoning models (e.g., OpenAI o1): controls depth of reasoning process. | `low`, `medium`, `high` |
|
||||||
| `reasoningSummaryDetails` | String | No | For reasoning models: controls how reasoning content is summarized in the response. | `auto`, `concise`, `detailed` |
|
| `reasoningSummaryDetails` | String | No | For reasoning models: controls how reasoning content is summarized in the response. | `auto`, `concise`, `detailed` |
|
||||||
|
|
||||||
@@ -168,8 +172,10 @@ Each model profile is a `.properties` file that configures how the library inter
|
|||||||
- Properties marked "Yes" in the Required column must be present in every profile; missing required properties will cause runtime errors.
|
- Properties marked "Yes" in the Required column must be present in every profile; missing required properties will cause runtime errors.
|
||||||
- The `category` field is optional in profile files. When omitted, the category short name from the `ModelCategory` used to load the profile will be used instead.
|
- The `category` field is optional in profile files. When omitted, the category short name from the `ModelCategory` used to load the profile will be used instead.
|
||||||
- The `prompts` property determines the prompt formatting strategy. For text completions, specify a strategy name (e.g., `llama3`, `gpt-oss`) or `no` for no formatting. For chat completions, use an empty string.
|
- The `prompts` property determines the prompt formatting strategy. For text completions, specify a strategy name (e.g., `llama3`, `gpt-oss`) or `no` for no formatting. For chat completions, use an empty string.
|
||||||
- The `modelName` and `apiKey` properties are optional and can be empty for local model deployments.
|
- The `modelName` and `apiKey` properties are optional and can be empty for local model deployments. When `apiKey` is provided, it is sent as a Bearer token in the Authorization header.
|
||||||
- Cost properties are used for statistics tracking and have no impact on API calls. They help you monitor expenses.
|
- Cost properties are used for statistics tracking and have no impact on API calls. They help you monitor expenses.
|
||||||
|
- The `cost1MCachedInTokens` property defaults to `cost1MInTokens` if not specified, assuming the provider does not distinguish between cached and non-cached tokens. When the provider does support KV-cache pricing, cached tokens are typically charged at significantly lower rates (e.g., 10-20% of the regular input token cost).
|
||||||
|
- The `cost1MReasoningOutTokens` property defaults to `0` if not specified, as most models don't charge separately for reasoning tokens.
|
||||||
- Reasoning properties are only applicable to models that support separate reasoning output (like OpenAI's o1 series).
|
- Reasoning properties are only applicable to models that support separate reasoning output (like OpenAI's o1 series).
|
||||||
|
|
||||||
### Reasoning Models
|
### Reasoning Models
|
||||||
@@ -672,14 +678,16 @@ final var customProfile = new ModelProfile(
|
|||||||
0.3, // temperature
|
0.3, // temperature
|
||||||
1.0, // topP
|
1.0, // topP
|
||||||
ModelProfile.ModelProvider.OAI_CHAT_COMPLETION, // modelProvider
|
ModelProfile.ModelProvider.OAI_CHAT_COMPLETION, // modelProvider
|
||||||
"gpt-4-turbo", // modelName
|
"gpt-5.1", // modelName
|
||||||
"https://api.openai.com/v1", // url
|
"https://api.openai.com/v1", // url
|
||||||
System.getenv("OPENAI_API_KEY"), // apiKey
|
System.getenv("OPENAI_API_KEY"), // apiKey
|
||||||
new BigDecimal("0.3").divide(1000000), // costPerInToken (per token)
|
new BigDecimal("1.25").divide(new BigDecimal(1000000)), // costPerInToken ($1.25/1M)
|
||||||
new BigDecimal("0.6").divide(1000000), // costPerOutToken (per token)
|
new BigDecimal("0.125").divide(new BigDecimal(1000000)), // costPerCachedInToken ($0.125/1M)
|
||||||
|
new BigDecimal("10.0").divide(new BigDecimal(1000000)), // costPerOutToken ($10.00/1M)
|
||||||
|
BigDecimal.ZERO, // costPerReasoningOutToken
|
||||||
null, // reasoningEffort
|
null, // reasoningEffort
|
||||||
null, // reasoningSummaryDetails
|
null, // reasoningSummaryDetails
|
||||||
"GPT4T" // category
|
"GPT51" // category
|
||||||
);
|
);
|
||||||
|
|
||||||
final var customModelAgent = new Agent(
|
final var customModelAgent = new Agent(
|
||||||
|
|||||||
@@ -31,8 +31,12 @@ public final class AgentRequestStatistic {
|
|||||||
|
|
||||||
private final AtomicLong inTokens = new AtomicLong();
|
private final AtomicLong inTokens = new AtomicLong();
|
||||||
|
|
||||||
|
private final AtomicLong cachedInTokens = new AtomicLong();
|
||||||
|
|
||||||
private final AtomicLong outTokens = new AtomicLong();
|
private final AtomicLong outTokens = new AtomicLong();
|
||||||
|
|
||||||
|
private final AtomicLong reasoningOutTokens = new AtomicLong();
|
||||||
|
|
||||||
private volatile Instant start;
|
private volatile Instant start;
|
||||||
|
|
||||||
private volatile Duration requestDuration = Duration.ofMillis(0L);
|
private volatile Duration requestDuration = Duration.ofMillis(0L);
|
||||||
@@ -51,10 +55,18 @@ public final class AgentRequestStatistic {
|
|||||||
return this.inTokens;
|
return this.inTokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AtomicLong getCachedInTokens() {
|
||||||
|
return this.cachedInTokens;
|
||||||
|
}
|
||||||
|
|
||||||
public AtomicLong getOutTokens() {
|
public AtomicLong getOutTokens() {
|
||||||
return this.outTokens;
|
return this.outTokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public AtomicLong getReasoningOutTokens() {
|
||||||
|
return this.reasoningOutTokens;
|
||||||
|
}
|
||||||
|
|
||||||
public Duration getRequestDuration() {
|
public Duration getRequestDuration() {
|
||||||
return this.requestDuration;
|
return this.requestDuration;
|
||||||
}
|
}
|
||||||
@@ -68,12 +80,12 @@ public final class AgentRequestStatistic {
|
|||||||
|
|
||||||
public String getTps() {
|
public String getTps() {
|
||||||
final var duration = this.start == null ? this.getRequestDuration() : this.getEffectiveDuration();
|
final var duration = this.start == null ? this.getRequestDuration() : this.getEffectiveDuration();
|
||||||
return new DecimalFormat("#.##").format((double) (this.inTokens.get() + this.outTokens.get()) / (double) duration.toMillis() * 1000.0);
|
return new DecimalFormat("#.##").format((double) (this.inTokens.get() + this.outTokens.get() + this.reasoningOutTokens.get()) / (double) duration.toMillis() * 1000.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getOutTps() {
|
public String getOutTps() {
|
||||||
final var duration = this.start == null ? this.getRequestDuration() : this.getEffectiveDuration();
|
final var duration = this.start == null ? this.getRequestDuration() : this.getEffectiveDuration();
|
||||||
return new DecimalFormat("#.##").format((double) this.outTokens.get() / (double) duration.toMillis() * 1000.0);
|
return new DecimalFormat("#.##").format((double) (this.outTokens.get() + this.reasoningOutTokens.get()) / (double) duration.toMillis() * 1000.0);
|
||||||
}
|
}
|
||||||
|
|
||||||
public BigDecimal getCost() {
|
public BigDecimal getCost() {
|
||||||
@@ -86,9 +98,11 @@ public final class AgentRequestStatistic {
|
|||||||
}
|
}
|
||||||
else {
|
else {
|
||||||
this.getRequests().addAndGet(completion.statistics().requests());
|
this.getRequests().addAndGet(completion.statistics().requests());
|
||||||
|
this.getInTokens().addAndGet(completion.statistics().inTokens());
|
||||||
|
this.getCachedInTokens().addAndGet(completion.statistics().cachedInTokens());
|
||||||
|
this.getOutTokens().addAndGet(completion.statistics().outTokens());
|
||||||
|
this.getReasoningOutTokens().addAndGet(completion.statistics().reasoningOutTokens());
|
||||||
}
|
}
|
||||||
this.getInTokens().addAndGet(completion.statistics().inTokens());
|
|
||||||
this.getOutTokens().addAndGet(completion.statistics().outTokens());
|
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
this.requestDuration = this.requestDuration.plus(completion.statistics().duration());
|
this.requestDuration = this.requestDuration.plus(completion.statistics().duration());
|
||||||
this.cost = this.cost.add(completion.statistics().cost());
|
this.cost = this.cost.add(completion.statistics().cost());
|
||||||
@@ -99,7 +113,9 @@ public final class AgentRequestStatistic {
|
|||||||
this.getRequests().addAndGet(other.getRequests().get());
|
this.getRequests().addAndGet(other.getRequests().get());
|
||||||
this.getCachedPrompts().addAndGet(other.getCachedPrompts().get());
|
this.getCachedPrompts().addAndGet(other.getCachedPrompts().get());
|
||||||
this.getInTokens().addAndGet(other.getInTokens().get());
|
this.getInTokens().addAndGet(other.getInTokens().get());
|
||||||
|
this.getCachedInTokens().addAndGet(other.getCachedInTokens().get());
|
||||||
this.getOutTokens().addAndGet(other.getOutTokens().get());
|
this.getOutTokens().addAndGet(other.getOutTokens().get());
|
||||||
|
this.getReasoningOutTokens().addAndGet(other.getReasoningOutTokens().get());
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
if (this.start != null && other.start.isBefore(this.start)) {
|
if (this.start != null && other.start.isBefore(this.start)) {
|
||||||
this.start = other.start;
|
this.start = other.start;
|
||||||
@@ -118,8 +134,12 @@ public final class AgentRequestStatistic {
|
|||||||
builder.append(this.cachedPrompts);
|
builder.append(this.cachedPrompts);
|
||||||
builder.append(", inTokens=");
|
builder.append(", inTokens=");
|
||||||
builder.append(this.inTokens);
|
builder.append(this.inTokens);
|
||||||
|
builder.append(", cachedInTokens=");
|
||||||
|
builder.append(this.cachedInTokens);
|
||||||
builder.append(", outTokens=");
|
builder.append(", outTokens=");
|
||||||
builder.append(this.outTokens);
|
builder.append(this.outTokens);
|
||||||
|
builder.append(", reasoningOutTokens=");
|
||||||
|
builder.append(this.reasoningOutTokens);
|
||||||
builder.append(", requestDuration=");
|
builder.append(", requestDuration=");
|
||||||
builder.append(this.requestDuration);
|
builder.append(this.requestDuration);
|
||||||
builder.append(", getEffectiveDuration()=");
|
builder.append(", getEffectiveDuration()=");
|
||||||
|
|||||||
@@ -3,6 +3,6 @@ package de.extio.lmlib.client;
|
|||||||
import java.math.BigDecimal;
|
import java.math.BigDecimal;
|
||||||
import java.time.Duration;
|
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,
|
1,
|
||||||
Duration.between(start, LocalDateTime.now()),
|
Duration.between(start, LocalDateTime.now()),
|
||||||
promptTokens,
|
promptTokens,
|
||||||
|
0,
|
||||||
completionTokens,
|
completionTokens,
|
||||||
|
0,
|
||||||
new BigDecimal(promptTokens).multiply(modelProfile.costPerInToken()).add(new BigDecimal(completionTokens).multiply(modelProfile.costPerOutToken())),
|
new BigDecimal(promptTokens).multiply(modelProfile.costPerInToken()).add(new BigDecimal(completionTokens).multiply(modelProfile.costPerOutToken())),
|
||||||
false);
|
false);
|
||||||
LOGGER.debug("{}", statistics);
|
LOGGER.debug("{}", statistics);
|
||||||
|
|||||||
@@ -78,12 +78,12 @@ public class CachedClient implements Client {
|
|||||||
|
|
||||||
final var cachedCompletion = this.cacheRepository.get(key);
|
final var cachedCompletion = this.cacheRepository.get(key);
|
||||||
if (cachedCompletion != null) {
|
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();
|
final var completion = supplier.get();
|
||||||
if (completion != null) {
|
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;
|
return completion;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -4,6 +4,6 @@ import java.time.OffsetDateTime;
|
|||||||
|
|
||||||
import de.extio.lmlib.client.CompletionFinishReason;
|
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 inTokens = 0;
|
||||||
|
int cachedInTokens = 0;
|
||||||
int outTokens = 0;
|
int outTokens = 0;
|
||||||
|
int reasoningOutTokens = 0;
|
||||||
|
|
||||||
|
// Usage data has priority (official OAI spec)
|
||||||
if (usage != null && usage.getPromptTokens() > 0) {
|
if (usage != null && usage.getPromptTokens() > 0) {
|
||||||
inTokens = usage.getPromptTokens();
|
cachedInTokens = usage.getCachedTokens();
|
||||||
outTokens = usage.getCompletionTokens();
|
inTokens = usage.getPromptTokens() - cachedInTokens;
|
||||||
|
reasoningOutTokens = usage.getReasoningTokens();
|
||||||
|
outTokens = usage.getCompletionTokens() - reasoningOutTokens;
|
||||||
}
|
}
|
||||||
else {
|
// If no usage data, try to get token counts from timings (e.g. llama.cpp, prompt_n does not include cache_n)
|
||||||
if (prompt != null) {
|
if (timings != null) {
|
||||||
inTokens = this.tokenizer.count(prompt, modelProfile);
|
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) {
|
else if (cachedInTokens <= 0 && timings.getCacheN() > 0) {
|
||||||
outTokens = this.tokenizer.count(response, modelProfile);
|
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(
|
final var statistics = new de.extio.lmlib.client.CompletionStatistics(
|
||||||
1,
|
1,
|
||||||
Duration.between(start, LocalDateTime.now()),
|
Duration.between(start, LocalDateTime.now()),
|
||||||
inTokens,
|
inTokens,
|
||||||
|
cachedInTokens,
|
||||||
outTokens,
|
outTokens,
|
||||||
new BigDecimal(inTokens).multiply(modelProfile.costPerInToken()).add(new BigDecimal(outTokens).multiply(modelProfile.costPerOutToken())),
|
reasoningOutTokens,
|
||||||
|
cost,
|
||||||
false);
|
false);
|
||||||
LOGGER.debug("{}", statistics);
|
LOGGER.debug("{}", statistics);
|
||||||
if (this.collectStatistics) {
|
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;
|
return statistics;
|
||||||
}
|
}
|
||||||
@@ -187,15 +219,21 @@ public abstract class AbstractCompletionClient implements Client, DisposableBean
|
|||||||
|
|
||||||
private long inTokens;
|
private long inTokens;
|
||||||
|
|
||||||
|
private long cachedInTokens;
|
||||||
|
|
||||||
private long outTokens;
|
private long outTokens;
|
||||||
|
|
||||||
|
private long reasoningOutTokens;
|
||||||
|
|
||||||
private BigDecimal cost = BigDecimal.ZERO;
|
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.requests++;
|
||||||
this.duration = this.duration.plus(duration);
|
this.duration = this.duration.plus(duration);
|
||||||
this.inTokens += inTokens;
|
this.inTokens += inTokens;
|
||||||
|
this.cachedInTokens += cachedInTokens;
|
||||||
this.outTokens += outTokens;
|
this.outTokens += outTokens;
|
||||||
|
this.reasoningOutTokens += reasoningOutTokens;
|
||||||
this.cost = this.cost.add(cost);
|
this.cost = this.cost.add(cost);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -208,19 +246,24 @@ public abstract class AbstractCompletionClient implements Client, DisposableBean
|
|||||||
builder.append(this.duration);
|
builder.append(this.duration);
|
||||||
builder.append(", inTokens=");
|
builder.append(", inTokens=");
|
||||||
builder.append(this.inTokens);
|
builder.append(this.inTokens);
|
||||||
|
builder.append(", cachedInTokens=");
|
||||||
|
builder.append(this.cachedInTokens);
|
||||||
builder.append(", outTokens=");
|
builder.append(", outTokens=");
|
||||||
builder.append(this.outTokens);
|
builder.append(this.outTokens);
|
||||||
|
builder.append(", reasoningOutTokens=");
|
||||||
|
builder.append(this.reasoningOutTokens);
|
||||||
builder.append(", averageTps=");
|
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(", 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=");
|
builder.append(", effectiveDuration=");
|
||||||
final var effectiveDuration = Duration.between(this.start, Instant.now());
|
final var effectiveDuration = Duration.between(this.start, Instant.now());
|
||||||
builder.append(effectiveDuration);
|
builder.append(effectiveDuration);
|
||||||
builder.append(", effectiveTps=");
|
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(", 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(", cost=");
|
||||||
builder.append(new DecimalFormat("0.###").format(cost.doubleValue()));
|
builder.append(new DecimalFormat("0.###").format(cost.doubleValue()));
|
||||||
builder.append("]");
|
builder.append("]");
|
||||||
|
|||||||
@@ -16,6 +16,8 @@ public abstract class AbstractCompletionRequest {
|
|||||||
|
|
||||||
protected StreamOptions streamOptions;
|
protected StreamOptions streamOptions;
|
||||||
|
|
||||||
|
protected Boolean usage;
|
||||||
|
|
||||||
@JsonProperty("model")
|
@JsonProperty("model")
|
||||||
public String getModel() {
|
public String getModel() {
|
||||||
return this.model;
|
return this.model;
|
||||||
@@ -69,4 +71,13 @@ public abstract class AbstractCompletionRequest {
|
|||||||
public void setStreamOptions(final StreamOptions streamOptions) {
|
public void setStreamOptions(final StreamOptions streamOptions) {
|
||||||
this.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 Integer created;
|
||||||
|
|
||||||
protected Usage usage;
|
protected Usage usage;
|
||||||
|
|
||||||
|
protected Timings timings;
|
||||||
|
|
||||||
@JsonProperty("id")
|
@JsonProperty("id")
|
||||||
public String getId() {
|
public String getId() {
|
||||||
@@ -41,10 +43,19 @@ public abstract class AbstractCompletionResponse {
|
|||||||
|
|
||||||
@JsonProperty("usage")
|
@JsonProperty("usage")
|
||||||
public Usage getUsage() {
|
public Usage getUsage() {
|
||||||
return usage;
|
return this.usage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setUsage(final Usage usage) {
|
public void setUsage(final Usage usage) {
|
||||||
this.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 int totalTokens;
|
||||||
|
|
||||||
|
private InputTokensDetails inputTokensDetails;
|
||||||
|
|
||||||
|
private OutputTokensDetails outputTokensDetails;
|
||||||
|
|
||||||
@JsonProperty("completion_tokens")
|
@JsonProperty("completion_tokens")
|
||||||
public int getCompletionTokens() {
|
public int getCompletionTokens() {
|
||||||
return completionTokens;
|
return this.completionTokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setCompletionTokens(final int completionTokens) {
|
public void setCompletionTokens(final int completionTokens) {
|
||||||
this.completionTokens = completionTokens;
|
this.completionTokens = completionTokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonProperty("output_tokens")
|
||||||
|
public void setOutputTokens(final int outputTokens) {
|
||||||
|
this.completionTokens = outputTokens;
|
||||||
|
}
|
||||||
|
|
||||||
@JsonProperty("prompt_tokens")
|
@JsonProperty("prompt_tokens")
|
||||||
public int getPromptTokens() {
|
public int getPromptTokens() {
|
||||||
return promptTokens;
|
return this.promptTokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPromptTokens(final int promptTokens) {
|
public void setPromptTokens(final int promptTokens) {
|
||||||
this.promptTokens = promptTokens;
|
this.promptTokens = promptTokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@JsonProperty("input_tokens")
|
||||||
|
public void setInputTokens(final int inputTokens) {
|
||||||
|
this.promptTokens = inputTokens;
|
||||||
|
}
|
||||||
|
|
||||||
@JsonProperty("total_tokens")
|
@JsonProperty("total_tokens")
|
||||||
public int getTotalTokens() {
|
public int getTotalTokens() {
|
||||||
return totalTokens;
|
return this.totalTokens;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setTotalTokens(final int totalTokens) {
|
public void setTotalTokens(final int totalTokens) {
|
||||||
this.totalTokens = 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
|
@Override
|
||||||
public String toString() {
|
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);
|
request.setStream(chunkConsumer != null);
|
||||||
if (chunkConsumer != null) {
|
if (chunkConsumer != null) {
|
||||||
this.configureStreamOptions(true, request::setStreamOptions);
|
this.configureStreamOptions(true, request::setStreamOptions);
|
||||||
|
} else {
|
||||||
|
request.setUsage(true);
|
||||||
}
|
}
|
||||||
if (modelProfile.reasoningEffort() != null && !modelProfile.reasoningEffort().isBlank()) {
|
if (modelProfile.reasoningEffort() != null && !modelProfile.reasoningEffort().isBlank()) {
|
||||||
request.setReasoning(new ChatCompletionRequest.ChatCompletionsRequestReasoning());
|
request.setReasoning(new ChatCompletionRequest.ChatCompletionsRequestReasoning());
|
||||||
@@ -86,10 +88,14 @@ public class ChatCompletionClient extends AbstractCompletionClient {
|
|||||||
final LocalDateTime start = LocalDateTime.now();
|
final LocalDateTime start = LocalDateTime.now();
|
||||||
final var webClient = this.webClientBuilder.baseUrl(modelProfile.url()).build();
|
final var webClient = this.webClientBuilder.baseUrl(modelProfile.url()).build();
|
||||||
|
|
||||||
final var response = webClient
|
var requestSpec = webClient
|
||||||
.method(HttpMethod.POST)
|
.method(HttpMethod.POST)
|
||||||
.uri(uriBuilder -> uriBuilder.path("/v1/chat/completions").build())
|
.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)
|
.bodyValue(requestBody)
|
||||||
.exchangeToMono(clientResponse -> {
|
.exchangeToMono(clientResponse -> {
|
||||||
if (clientResponse.statusCode().isError()) {
|
if (clientResponse.statusCode().isError()) {
|
||||||
@@ -123,6 +129,9 @@ public class ChatCompletionClient extends AbstractCompletionClient {
|
|||||||
if (streamResponse.getUsage() != null) {
|
if (streamResponse.getUsage() != null) {
|
||||||
chatCompletionResponse.setUsage(streamResponse.getUsage());
|
chatCompletionResponse.setUsage(streamResponse.getUsage());
|
||||||
}
|
}
|
||||||
|
if (streamResponse.getTimings() != null) {
|
||||||
|
chatCompletionResponse.setTimings(streamResponse.getTimings());
|
||||||
|
}
|
||||||
if (streamResponse.getChoices() != null && !streamResponse.getChoices().isEmpty()) {
|
if (streamResponse.getChoices() != null && !streamResponse.getChoices().isEmpty()) {
|
||||||
if (streamResponse.getChoices().getFirst().getFinishReason() != null) {
|
if (streamResponse.getChoices().getFirst().getFinishReason() != null) {
|
||||||
chatCompletionResponse.getChoices().getFirst().setFinishReason(streamResponse.getChoices().getFirst().getFinishReason());
|
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 content = response.getChoices().getFirst().getMessage().getContent();
|
||||||
final var reasoning = response.getChoices().getFirst().getMessage().getReasoningContent();
|
final var reasoning = response.getChoices().getFirst().getMessage().getReasoningContent();
|
||||||
final var finishReason = this.mapFinishReason(choice.getFinishReason());
|
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);
|
return new Completion(content, reasoning, finishReason, statistics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -57,6 +57,8 @@ public class TextCompletionClient extends AbstractCompletionClient {
|
|||||||
request.setStream(chunkConsumer != null);
|
request.setStream(chunkConsumer != null);
|
||||||
if (chunkConsumer != null) {
|
if (chunkConsumer != null) {
|
||||||
this.configureStreamOptions(true, request::setStreamOptions);
|
this.configureStreamOptions(true, request::setStreamOptions);
|
||||||
|
} else {
|
||||||
|
request.setUsage(true);
|
||||||
}
|
}
|
||||||
|
|
||||||
String requestBody;
|
String requestBody;
|
||||||
@@ -70,10 +72,14 @@ public class TextCompletionClient extends AbstractCompletionClient {
|
|||||||
LOGGER.debug("Requesting completion at {}", modelProfile.url());
|
LOGGER.debug("Requesting completion at {}", modelProfile.url());
|
||||||
final LocalDateTime start = LocalDateTime.now();
|
final LocalDateTime start = LocalDateTime.now();
|
||||||
final var webClient = this.webClientBuilder.baseUrl(modelProfile.url()).build();
|
final var webClient = this.webClientBuilder.baseUrl(modelProfile.url()).build();
|
||||||
final var response = webClient
|
var requestSpec = webClient
|
||||||
.method(HttpMethod.POST)
|
.method(HttpMethod.POST)
|
||||||
.uri(uriBuilder -> uriBuilder.path("/v1/completions").build())
|
.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)
|
.bodyValue(requestBody)
|
||||||
.exchangeToMono(clientResponse -> {
|
.exchangeToMono(clientResponse -> {
|
||||||
if (clientResponse.statusCode().isError()) {
|
if (clientResponse.statusCode().isError()) {
|
||||||
@@ -112,6 +118,9 @@ public class TextCompletionClient extends AbstractCompletionClient {
|
|||||||
if (streamResponse.getUsage() != null) {
|
if (streamResponse.getUsage() != null) {
|
||||||
CompletionResponse.setUsage(streamResponse.getUsage());
|
CompletionResponse.setUsage(streamResponse.getUsage());
|
||||||
}
|
}
|
||||||
|
if (streamResponse.getTimings() != null) {
|
||||||
|
CompletionResponse.setTimings(streamResponse.getTimings());
|
||||||
|
}
|
||||||
if (streamResponse.getChoices() != null && !streamResponse.getChoices().isEmpty()) {
|
if (streamResponse.getChoices() != null && !streamResponse.getChoices().isEmpty()) {
|
||||||
if (streamResponse.getChoices().getFirst().getFinishReason() != null) {
|
if (streamResponse.getChoices().getFirst().getFinishReason() != null) {
|
||||||
CompletionResponse.getChoices().getFirst().setFinishReason(streamResponse.getChoices().getFirst().getFinishReason());
|
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);
|
return new Completion(content, reasoning, finishReason, statistics);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -14,7 +14,9 @@ public record ModelProfile(
|
|||||||
String url,
|
String url,
|
||||||
String apiKey,
|
String apiKey,
|
||||||
BigDecimal costPerInToken,
|
BigDecimal costPerInToken,
|
||||||
|
BigDecimal costPerCachedInToken,
|
||||||
BigDecimal costPerOutToken,
|
BigDecimal costPerOutToken,
|
||||||
|
BigDecimal costPerReasoningOutToken,
|
||||||
String reasoningEffort,
|
String reasoningEffort,
|
||||||
String reasoningSummaryDetails,
|
String reasoningSummaryDetails,
|
||||||
String category) {
|
String category) {
|
||||||
|
|||||||
@@ -76,11 +76,19 @@ public class ModelProfileService {
|
|||||||
.map(value -> new BigDecimal(value))
|
.map(value -> new BigDecimal(value))
|
||||||
.orElse(BigDecimal.ZERO)
|
.orElse(BigDecimal.ZERO)
|
||||||
.divide(new BigDecimal(1000000.0));
|
.divide(new BigDecimal(1000000.0));
|
||||||
|
final var costPerCachedInToken = Optional.ofNullable(resource.getProperty("cost1MCachedInTokens"))
|
||||||
|
.map(Object::toString)
|
||||||
|
.map(value -> new BigDecimal(value).divide(new BigDecimal(1000000.0)))
|
||||||
|
.orElse(costPerInToken);
|
||||||
final var costPerOutToken = Optional.ofNullable(resource.getProperty("cost1MOutTokens"))
|
final var costPerOutToken = Optional.ofNullable(resource.getProperty("cost1MOutTokens"))
|
||||||
.map(Object::toString)
|
.map(Object::toString)
|
||||||
.map(value -> new BigDecimal(value))
|
.map(value -> new BigDecimal(value))
|
||||||
.orElse(BigDecimal.ZERO)
|
.orElse(BigDecimal.ZERO)
|
||||||
.divide(new BigDecimal(1000000.0));
|
.divide(new BigDecimal(1000000.0));
|
||||||
|
final var costPerReasoningOutToken = Optional.ofNullable(resource.getProperty("cost1MReasoningOutTokens"))
|
||||||
|
.map(Object::toString)
|
||||||
|
.map(value -> new BigDecimal(value).divide(new BigDecimal(1000000.0)))
|
||||||
|
.orElse(BigDecimal.ZERO);
|
||||||
final var reasoningEffort = Optional.ofNullable(resource.getProperty("reasoningEffort"))
|
final var reasoningEffort = Optional.ofNullable(resource.getProperty("reasoningEffort"))
|
||||||
.map(Object::toString)
|
.map(Object::toString)
|
||||||
.orElse(null);
|
.orElse(null);
|
||||||
@@ -103,7 +111,7 @@ public class ModelProfileService {
|
|||||||
.orElse(null);
|
.orElse(null);
|
||||||
}
|
}
|
||||||
|
|
||||||
return new ModelProfile(promptTemplate, tokenEncoding, maxTokens, maxContextLength, temperature, topP, modelProvider, modelNameCfg, url, apiKey, costPerInToken, costPerOutToken, reasoningEffort, reasoningSummaryDetails, category);
|
return new ModelProfile(promptTemplate, tokenEncoding, maxTokens, maxContextLength, temperature, topP, modelProvider, modelNameCfg, url, apiKey, costPerInToken, costPerCachedInToken, costPerOutToken, costPerReasoningOutToken, reasoningEffort, reasoningSummaryDetails, category);
|
||||||
}
|
}
|
||||||
catch (final IOException e) {
|
catch (final IOException e) {
|
||||||
LOGGER.error("Error while reading model profile", e);
|
LOGGER.error("Error while reading model profile", e);
|
||||||
|
|||||||
@@ -28,7 +28,7 @@ import de.extio.lmlib.client.oai.completion.text.TextCompletionClient;
|
|||||||
import de.extio.lmlib.grader.Grader;
|
import de.extio.lmlib.grader.Grader;
|
||||||
import de.extio.lmlib.profile.ModelCategory;
|
import de.extio.lmlib.profile.ModelCategory;
|
||||||
|
|
||||||
@Disabled("This test requires a running Llama server")
|
// @Disabled("This test requires a running Llama server")
|
||||||
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
|
@SpringBootTest(webEnvironment = WebEnvironment.NONE)
|
||||||
@SpringBootConfiguration
|
@SpringBootConfiguration
|
||||||
@ComponentScan(basePackages = "de.extio.lmlib")
|
@ComponentScan(basePackages = "de.extio.lmlib")
|
||||||
|
|||||||
@@ -10,4 +10,6 @@ modelName=Llama-3.3-70B-Instruct
|
|||||||
url=XXX
|
url=XXX
|
||||||
apiKey=XXX
|
apiKey=XXX
|
||||||
cost1MInTokens=0.71
|
cost1MInTokens=0.71
|
||||||
|
cost1MCachedInTokens=0.71
|
||||||
cost1MOutTokens=0.71
|
cost1MOutTokens=0.71
|
||||||
|
cost1MReasoningOutTokens=0
|
||||||
|
|||||||
@@ -10,6 +10,8 @@ modelName=
|
|||||||
url=http://localhost:5001
|
url=http://localhost:5001
|
||||||
apiKey=
|
apiKey=
|
||||||
cost1MInTokens=0
|
cost1MInTokens=0
|
||||||
|
cost1MCachedInTokens=0
|
||||||
cost1MOutTokens=0
|
cost1MOutTokens=0
|
||||||
|
cost1MReasoningOutTokens=0
|
||||||
reasoningEffort=medium
|
reasoningEffort=medium
|
||||||
reasoningSummaryDetails=auto
|
reasoningSummaryDetails=auto
|
||||||
@@ -10,4 +10,6 @@ modelName=
|
|||||||
url=http://localhost:5001
|
url=http://localhost:5001
|
||||||
apiKey=
|
apiKey=
|
||||||
cost1MInTokens=0
|
cost1MInTokens=0
|
||||||
|
cost1MCachedInTokens=0
|
||||||
cost1MOutTokens=0
|
cost1MOutTokens=0
|
||||||
|
cost1MReasoningOutTokens=0
|
||||||
|
|||||||
Reference in New Issue
Block a user