lm-lib: Merged with tp fork;
git-svn-id: svn://home0/lm-lib@3723 71dcc7fd-b1f2-4e5d-bb8e-274146e81173
This commit is contained in:
@@ -15,6 +15,7 @@ import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import de.extio.lmlib.client.Client;
|
||||
import de.extio.lmlib.client.ClientService;
|
||||
|
||||
@Service
|
||||
@@ -47,10 +48,14 @@ public class AgentExecutorService implements InitializingBean, DisposableBean {
|
||||
public List<AgentContext> walkGraph(final Agent agent, final AgentContext context) {
|
||||
LOGGER.info("Agent: {}", agent.name());
|
||||
|
||||
final var client = this.clientService.getClient(agent.modelCategory());
|
||||
final var responses = Collections.synchronizedList(new ArrayList<AgentContext>());
|
||||
Client client = null;
|
||||
if (agent.agentType() != AgentType.PROCESSING_ONLY) {
|
||||
client = this.clientService.getClient(agent.modelCategory());
|
||||
}
|
||||
|
||||
final var branches = agent.execute(client, this.agentExecutorService, context);
|
||||
|
||||
final var responses = Collections.synchronizedList(new ArrayList<AgentContext>());
|
||||
final var branchTasks = new ArrayList<CompletableFuture<?>>(branches.size());
|
||||
for (final var branchContext : branches) {
|
||||
if (!branchContext.isError() && branchContext.getNextAgent() != null) {
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.extio.lmlib.agent;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DecimalFormat;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
@@ -12,18 +13,26 @@ public final class AgentRequestStatistic {
|
||||
|
||||
private final AtomicInteger requests = new AtomicInteger();
|
||||
|
||||
private final AtomicInteger cachedPrompts = new AtomicInteger();
|
||||
|
||||
private final AtomicLong inTokens = new AtomicLong();
|
||||
|
||||
private final AtomicLong outTokens = new AtomicLong();
|
||||
|
||||
private final Instant start = Instant.now();
|
||||
private volatile Instant start = Instant.now();
|
||||
|
||||
private volatile Duration requestDuration = Duration.ofMillis(0L);
|
||||
|
||||
private volatile BigDecimal cost = BigDecimal.ZERO;
|
||||
|
||||
public AtomicInteger getRequests() {
|
||||
return this.requests;
|
||||
}
|
||||
|
||||
public AtomicInteger getCachedPrompts() {
|
||||
return this.cachedPrompts;
|
||||
}
|
||||
|
||||
public AtomicLong getInTokens() {
|
||||
return this.inTokens;
|
||||
}
|
||||
@@ -41,22 +50,44 @@ public final class AgentRequestStatistic {
|
||||
}
|
||||
|
||||
public String getTps() {
|
||||
return new DecimalFormat("#.##").format((double) (this.inTokens.get() + this.outTokens.get()) / (double)this.getEffectiveDuration().toMillis() * 1000.0);
|
||||
return new DecimalFormat("#.##").format((double) (this.inTokens.get() + this.outTokens.get()) / (double) this.getEffectiveDuration().toMillis() * 1000.0);
|
||||
}
|
||||
|
||||
public String getOutTps() {
|
||||
return new DecimalFormat("#.##").format((double) this.outTokens.get() / (double)this.getEffectiveDuration().toMillis() * 1000.0);
|
||||
return new DecimalFormat("#.##").format((double) this.outTokens.get() / (double) this.getEffectiveDuration().toMillis() * 1000.0);
|
||||
}
|
||||
|
||||
public BigDecimal getCost() {
|
||||
return this.cost;
|
||||
}
|
||||
|
||||
public void add(final Completion completion) {
|
||||
this.getRequests().addAndGet(completion.requests());
|
||||
if (completion.cached()) {
|
||||
this.cachedPrompts.incrementAndGet();
|
||||
}
|
||||
else {
|
||||
this.getRequests().addAndGet(completion.requests());
|
||||
}
|
||||
this.getInTokens().addAndGet(completion.inTokens());
|
||||
this.getOutTokens().addAndGet(completion.outTokens());
|
||||
this.addRequestDuration(completion.duration());
|
||||
synchronized (this) {
|
||||
this.requestDuration = this.requestDuration.plus(completion.duration());
|
||||
this.cost = this.cost.add(completion.cost());
|
||||
}
|
||||
}
|
||||
|
||||
public synchronized void addRequestDuration(final Duration duration) {
|
||||
this.requestDuration = this.requestDuration.plus(duration);
|
||||
public void add(final AgentRequestStatistic other) {
|
||||
this.getRequests().addAndGet(other.getRequests().get());
|
||||
this.getCachedPrompts().addAndGet(other.getCachedPrompts().get());
|
||||
this.getInTokens().addAndGet(other.getInTokens().get());
|
||||
this.getOutTokens().addAndGet(other.getOutTokens().get());
|
||||
synchronized (this) {
|
||||
if (other.start.isBefore(this.start)) {
|
||||
this.start = other.start;
|
||||
}
|
||||
this.requestDuration = this.requestDuration.plus(other.getRequestDuration());
|
||||
this.cost = this.cost.add(other.getCost());
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -64,6 +95,8 @@ public final class AgentRequestStatistic {
|
||||
final StringBuilder builder = new StringBuilder();
|
||||
builder.append("AgentRequestStatistic [requests=");
|
||||
builder.append(this.requests);
|
||||
builder.append(", cachedPrompts=");
|
||||
builder.append(this.cachedPrompts);
|
||||
builder.append(", inTokens=");
|
||||
builder.append(this.inTokens);
|
||||
builder.append(", outTokens=");
|
||||
@@ -76,6 +109,8 @@ public final class AgentRequestStatistic {
|
||||
builder.append(this.getTps());
|
||||
builder.append(", getOutTps()=");
|
||||
builder.append(this.getOutTps());
|
||||
builder.append(", getCost()=");
|
||||
builder.append(this.getCost());
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@@ -3,8 +3,11 @@ package de.extio.lmlib.client;
|
||||
import java.util.List;
|
||||
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.stereotype.Service;
|
||||
|
||||
import de.extio.lmlib.client.cached.CachedClient;
|
||||
import de.extio.lmlib.client.cached.CachedClientRepository;
|
||||
import de.extio.lmlib.profile.ModelCategory;
|
||||
import de.extio.lmlib.profile.ModelProfileService;
|
||||
|
||||
@@ -17,11 +20,16 @@ public class ClientService {
|
||||
@Autowired
|
||||
private ModelProfileService modelProfileService;
|
||||
|
||||
@Autowired(required = false)
|
||||
private CachedClientRepository cachedClientRepository;
|
||||
|
||||
@Cacheable("clients")
|
||||
public Client getClient(final ModelCategory category) {
|
||||
final var modelProfile = this.modelProfileService.getModelProfile(category.getModelProfile());
|
||||
return this.clients.stream()
|
||||
.filter(client -> client.getModelProvider() == modelProfile.modelProvider())
|
||||
.findFirst()
|
||||
.map(client -> this.cachedClientRepository == null ? client : new CachedClient(this.cachedClientRepository, this.modelProfileService, client))
|
||||
.orElseThrow(() -> new IllegalStateException("No client found for model profile " + category));
|
||||
}
|
||||
|
||||
|
||||
@@ -1,7 +1,8 @@
|
||||
package de.extio.lmlib.client;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Duration;
|
||||
|
||||
public final record Completion(String response, int requests, Duration duration, long inTokens, long outTokens, boolean cached) {
|
||||
public final record Completion(String response, int requests, Duration duration, long inTokens, long outTokens, BigDecimal cost, boolean cached) {
|
||||
|
||||
}
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.extio.lmlib.client.azureai;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.time.Duration;
|
||||
import java.time.LocalDateTime;
|
||||
import java.util.ArrayDeque;
|
||||
@@ -108,6 +109,7 @@ public final class AzureAiClient implements Client {
|
||||
statistics.duration,
|
||||
statistics.inTokens,
|
||||
statistics.outTokens,
|
||||
new BigDecimal(statistics.inTokens).multiply(modelProfile.costInToken()).add(new BigDecimal(statistics.outTokens).multiply(modelProfile.costOutToken())),
|
||||
false);
|
||||
}
|
||||
|
||||
@@ -120,7 +122,7 @@ public final class AzureAiClient implements Client {
|
||||
Turn assistantTurn = null;
|
||||
boolean first = true;
|
||||
|
||||
final int maxLength = modelProfile.maxContextLength() - modelProfile.maxTokens() - 50; // 50 is a margin for EOT and other special tokens
|
||||
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()) {
|
||||
@@ -165,21 +167,19 @@ public final class AzureAiClient implements Client {
|
||||
}
|
||||
|
||||
private boolean addChatRequestMessage(final String text, final int maxLength, final AtomicInteger tokens, final List<ChatRequestMessage> chat, final ModelProfile modelProfile, final Function<String, ChatRequestMessage> messageFactory) {
|
||||
final int textTokens = this.tokenizer.count(text, modelProfile);
|
||||
|
||||
String trimmedText;
|
||||
String prompt = text;
|
||||
boolean trimmed = false;
|
||||
|
||||
final int textTokens = this.tokenizer.count(prompt, modelProfile);
|
||||
if (tokens.addAndGet(textTokens) > 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!", tokens.get(), maxLength);
|
||||
trimmedText = text.substring(0, text.length() - (tokens.get() - maxLength) / 3);
|
||||
List<Long> 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);
|
||||
trimmed = true;
|
||||
}
|
||||
else {
|
||||
trimmedText = text;
|
||||
}
|
||||
|
||||
chat.add(messageFactory.apply(trimmedText));
|
||||
}
|
||||
chat.add(messageFactory.apply(prompt));
|
||||
|
||||
return trimmed;
|
||||
}
|
||||
@@ -246,7 +246,7 @@ public final class AzureAiClient implements Client {
|
||||
long outTokens;
|
||||
|
||||
void add(final Duration duration, final int inTokens, final int outTokens) {
|
||||
LOGGER.info("Request duration: {}; in: {}; out: {}; tps: {}", duration, inTokens, outTokens, String.format("%.2f", (inTokens + outTokens) / (double) duration.toMillis() * 1000.0));
|
||||
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);
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
package de.extio.lmlib.client.textcompletion;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
import java.text.DecimalFormat;
|
||||
import java.time.Duration;
|
||||
import java.time.Instant;
|
||||
@@ -122,6 +123,7 @@ public class TextCompletionClient implements Client, DisposableBean {
|
||||
statistics.duration,
|
||||
statistics.inTokens,
|
||||
statistics.outTokens,
|
||||
new BigDecimal(statistics.inTokens).multiply(modelProfile.costInToken()).add(new BigDecimal(statistics.outTokens).multiply(modelProfile.costOutToken())),
|
||||
false);
|
||||
}
|
||||
|
||||
@@ -207,9 +209,10 @@ public class TextCompletionClient implements Client, DisposableBean {
|
||||
final var dur = Duration.between(start, LocalDateTime.now());
|
||||
final var in = this.tokenizer.count(promptStr, modelProfile);
|
||||
final var out = this.tokenizer.count(content, modelProfile);
|
||||
LOGGER.debug("Request duration: " + dur + "; in tokens: " + in + "; out tokens: " + out);
|
||||
statistics.add(dur, in, out);
|
||||
this.totalStatistics.add(dur, in, out);
|
||||
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);
|
||||
@@ -247,7 +250,7 @@ public class TextCompletionClient implements Client, DisposableBean {
|
||||
}
|
||||
|
||||
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
|
||||
// TODO: Reactivate when multiple (split) prompts have been implemented again in #createPrompts
|
||||
// LOGGER.debug("Summarizing answers");
|
||||
//
|
||||
// final StringBuilder summary = new StringBuilder();
|
||||
@@ -264,7 +267,7 @@ public class TextCompletionClient implements Client, DisposableBean {
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
if (this.collectStatistics) {
|
||||
if (this.collectStatistics && this.totalStatistics.requests > 0) {
|
||||
LOGGER.info("Client Statistics: {}", this.totalStatistics);
|
||||
}
|
||||
}
|
||||
@@ -273,19 +276,22 @@ public class TextCompletionClient implements Client, DisposableBean {
|
||||
|
||||
private final Instant start = Instant.now();
|
||||
|
||||
volatile int requests;
|
||||
private int requests;
|
||||
|
||||
volatile Duration duration = Duration.ofMillis(0l);
|
||||
private Duration duration = Duration.ofMillis(0l);
|
||||
|
||||
volatile long inTokens;
|
||||
private long inTokens;
|
||||
|
||||
volatile long outTokens;
|
||||
private long outTokens;
|
||||
|
||||
synchronized void add(final Duration duration, final int inTokens, final int outTokens) {
|
||||
private BigDecimal cost = BigDecimal.ZERO;
|
||||
|
||||
synchronized void add(final Duration duration, final int inTokens, final int outTokens, final BigDecimal cost) {
|
||||
this.requests++;
|
||||
this.duration = this.duration.plus(duration);
|
||||
this.inTokens += inTokens;
|
||||
this.outTokens += outTokens;
|
||||
this.cost = this.cost.add(cost);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -300,16 +306,18 @@ public class TextCompletionClient implements Client, DisposableBean {
|
||||
builder.append(", outTokens=");
|
||||
builder.append(this.outTokens);
|
||||
builder.append(", averageTps=");
|
||||
builder.append(new DecimalFormat("#.##").format(((double) (this.outTokens + this.inTokens) / (double) this.requests) / ((double) this.duration.toMillis() / 1000.0 / (double) this.requests)));
|
||||
builder.append(new DecimalFormat("0.##").format(((double) (this.outTokens + this.inTokens) / (double) this.requests) / ((double) this.duration.toMillis() / 1000.0 / (double) this.requests)));
|
||||
builder.append(", averageOutTps=");
|
||||
builder.append(new DecimalFormat("#.##").format((double) this.outTokens / ((double) this.duration.toMillis() / 1000.0)));
|
||||
builder.append(new DecimalFormat("0.##").format((double) this.outTokens / ((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("#.##").format(((double) (this.outTokens + this.inTokens) / (double) this.requests) / ((double) effectiveDuration.toMillis() / 1000.0 / (double) this.requests)));
|
||||
builder.append(new DecimalFormat("0.##").format(((double) (this.outTokens + this.inTokens) / (double) this.requests) / ((double) effectiveDuration.toMillis() / 1000.0 / (double) this.requests)));
|
||||
builder.append(", effectiveOutTps=");
|
||||
builder.append(new DecimalFormat("#.##").format((double) this.outTokens / ((double) effectiveDuration.toMillis() / 1000.0)));
|
||||
builder.append(new DecimalFormat("0.##").format((double) this.outTokens / ((double) effectiveDuration.toMillis() / 1000.0)));
|
||||
builder.append(", cost=");
|
||||
builder.append(new DecimalFormat("0.###").format(cost.doubleValue()));
|
||||
builder.append("]");
|
||||
return builder.toString();
|
||||
}
|
||||
|
||||
@@ -1,5 +1,7 @@
|
||||
package de.extio.lmlib.profile;
|
||||
|
||||
import java.math.BigDecimal;
|
||||
|
||||
public record ModelProfile(
|
||||
String prompt,
|
||||
String tokenEncoding,
|
||||
@@ -11,7 +13,9 @@ public record ModelProfile(
|
||||
ModelProvider modelProvider,
|
||||
String modelName,
|
||||
String url,
|
||||
String apiKey) {
|
||||
String apiKey,
|
||||
BigDecimal costInToken,
|
||||
BigDecimal costOutToken) {
|
||||
|
||||
public static enum ModelProvider {
|
||||
AZURE_AI, OAI_TEXT_COMPLETION;
|
||||
|
||||
@@ -1,6 +1,7 @@
|
||||
package de.extio.lmlib.profile;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.math.BigDecimal;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import java.util.Objects;
|
||||
@@ -12,6 +13,7 @@ import org.slf4j.LoggerFactory;
|
||||
import org.springframework.boot.context.properties.bind.Bindable;
|
||||
import org.springframework.boot.context.properties.bind.Binder;
|
||||
import org.springframework.cache.annotation.Cacheable;
|
||||
import org.springframework.core.env.ConfigurableEnvironment;
|
||||
import org.springframework.core.env.Environment;
|
||||
import org.springframework.core.io.support.ResourcePropertySource;
|
||||
import org.springframework.stereotype.Service;
|
||||
@@ -38,7 +40,9 @@ public class ModelProfileService {
|
||||
throw new IllegalStateException("No model found for profile: " + modelProfile);
|
||||
}
|
||||
try {
|
||||
|
||||
final var resource = new ResourcePropertySource("classpath:" + modelName + ".properties");
|
||||
|
||||
final var promptTemplate = Optional.ofNullable(resource.getProperty("prompts"))
|
||||
.map(Object::toString)
|
||||
.orElseThrow(() -> new IllegalStateException("No model name found for model: " + modelName));
|
||||
@@ -75,11 +79,29 @@ public class ModelProfileService {
|
||||
final var url = Optional.ofNullable(resource.getProperty("url"))
|
||||
.map(Object::toString)
|
||||
.orElseThrow(() -> new IllegalStateException("No model url found for model: " + modelName));
|
||||
final var apiKey = Optional.ofNullable(resource.getProperty("apiKey"))
|
||||
final var costInToken = Optional.ofNullable(resource.getProperty("cost1MInTokens"))
|
||||
.map(Object::toString)
|
||||
.orElseThrow(() -> new IllegalStateException("No model apiKey found for model: " + modelName));
|
||||
.map(value -> new BigDecimal(value))
|
||||
.orElse(BigDecimal.ZERO)
|
||||
.divide(new BigDecimal(1000000.0));
|
||||
final var costOutToken = Optional.ofNullable(resource.getProperty("cost1MOutTokens"))
|
||||
.map(Object::toString)
|
||||
.map(value -> new BigDecimal(value))
|
||||
.orElse(BigDecimal.ZERO)
|
||||
.divide(new BigDecimal(1000000.0));
|
||||
|
||||
return new ModelProfile(promptTemplate, tokenEncoding, maxTokens, maxContextLength, temperature, topP, maxContinuations, modelProvider, modelNameCfg, url, apiKey);
|
||||
//To resolve api key placeholder ${exchangeautodocumentation.azure.apikey} in model-name.properties file
|
||||
String apiKey = null;
|
||||
synchronized (this.environment) {
|
||||
if (this.environment instanceof ConfigurableEnvironment configurableEnvironment) {
|
||||
configurableEnvironment.getPropertySources().addLast(resource);
|
||||
}
|
||||
apiKey = Optional.ofNullable(this.environment.getProperty("apiKey"))
|
||||
.map(Object::toString)
|
||||
.orElseThrow(() -> new IllegalStateException("No model apiKey found for model: " + modelName));
|
||||
}
|
||||
|
||||
return new ModelProfile(promptTemplate, tokenEncoding, maxTokens, maxContextLength, temperature, topP, maxContinuations, modelProvider, modelNameCfg, url, apiKey, costInToken, costOutToken);
|
||||
}
|
||||
catch (final IOException e) {
|
||||
LOGGER.error("Error while reading model profile", e);
|
||||
|
||||
@@ -14,7 +14,7 @@ public class TokenizerAutoConfiguration {
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(name = "tokenizer.strategy", havingValue = "llamaServer")
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnMissingBean(type = "de.extio.lmlib.token.Tokenizer")
|
||||
Tokenizer llamaServerTokenizer(@Qualifier("lmLibWebClientBuilder") final WebClient.Builder webClientBuilder) {
|
||||
return new LlamaServerTokenizer(webClientBuilder);
|
||||
}
|
||||
@@ -22,7 +22,7 @@ public class TokenizerAutoConfiguration {
|
||||
@Configuration(proxyBeanMethods = false)
|
||||
@ConditionalOnClass(name = "com.knuddels.jtokkit.Encodings")
|
||||
@ConditionalOnProperty(name = "tokenizer.strategy", havingValue = "jTokkit")
|
||||
@ConditionalOnMissingBean
|
||||
@ConditionalOnMissingBean(type = "de.extio.lmlib.token.Tokenizer")
|
||||
public static class SomeServiceConfiguration {
|
||||
|
||||
@Bean
|
||||
|
||||
@@ -9,3 +9,5 @@ modelProvider=OAI_TEXT_COMPLETION
|
||||
modelName=
|
||||
url=http://localhost:5001
|
||||
apiKey=
|
||||
cost1MInTokens=0
|
||||
cost1MOutTokens=0
|
||||
|
||||
13
src/test/resources/llama3.3-70b-azure.properties
Normal file
13
src/test/resources/llama3.3-70b-azure.properties
Normal file
@@ -0,0 +1,13 @@
|
||||
prompts=
|
||||
tokenEncoding=cl100k_base
|
||||
maxTokens=1500
|
||||
maxContextLength=32767
|
||||
temperature=0.2
|
||||
topP=1.0
|
||||
maxContinuations=1
|
||||
modelProvider=AZURE_AI
|
||||
modelName=Llama-3.3-70B-Instruct
|
||||
url=XXX
|
||||
apiKey=XXX
|
||||
cost1MInTokens=0.71
|
||||
cost1MOutTokens=0.71
|
||||
Reference in New Issue
Block a user