refactor: Simplify model name loading and update ModelNameSupplier to use ModelProfile
This commit is contained in:
@@ -1,62 +1,44 @@
|
||||
package de.extio.lmlib.client.oai;
|
||||
|
||||
import java.time.Duration;
|
||||
import java.util.function.Consumer;
|
||||
import java.util.Optional;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
import org.slf4j.LoggerFactory;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import reactor.util.retry.Retry;
|
||||
|
||||
class ModelNameLoader implements Runnable {
|
||||
class ModelNameLoader {
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ModelNameLoader.class);
|
||||
|
||||
private final WebClient webClient;
|
||||
|
||||
private final Consumer<String> consumer;
|
||||
|
||||
public ModelNameLoader(@Qualifier("textCompletionWebClientBuilder") final WebClient webClient, final Consumer<String> consumer) {
|
||||
this.consumer = consumer;
|
||||
public ModelNameLoader(final WebClient webClient) {
|
||||
this.webClient = webClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
String modelName = null;
|
||||
do {
|
||||
try {
|
||||
LOGGER.info("Querying model name");
|
||||
|
||||
modelName = this.webClient
|
||||
.method(HttpMethod.GET)
|
||||
.uri(uriBuilder -> uriBuilder.path("/v1/models").build())
|
||||
.retrieve()
|
||||
.bodyToMono(ModelsResponse.class)
|
||||
.retryWhen(Retry.backoff(99, Duration.ofSeconds(1)).maxBackoff(Duration.ofSeconds(10)).jitter(0.25d).doAfterRetry(rs -> LOGGER.warn("Request failed " + rs.failure().getMessage())))
|
||||
.block()
|
||||
.getData()
|
||||
.stream()
|
||||
.map(Model::getId)
|
||||
.findFirst()
|
||||
.orElseThrow();
|
||||
}
|
||||
catch (final Exception e) {
|
||||
LOGGER.error(e.getMessage(), e);
|
||||
try {
|
||||
Thread.sleep(1000l);
|
||||
}
|
||||
catch (final InterruptedException e1) {
|
||||
throw new RuntimeException("Interrupted", e1);
|
||||
}
|
||||
}
|
||||
} while (modelName == null);
|
||||
|
||||
LOGGER.info("Model name: {}", modelName);
|
||||
this.consumer.accept(modelName);
|
||||
public Optional<String> loadModelName() {
|
||||
try {
|
||||
LOGGER.info("Querying model name");
|
||||
|
||||
final var modelName = this.webClient
|
||||
.method(HttpMethod.GET)
|
||||
.uri(uriBuilder -> uriBuilder.path("/v1/models").build())
|
||||
.retrieve()
|
||||
.bodyToMono(ModelsResponse.class)
|
||||
.block()
|
||||
.getData()
|
||||
.stream()
|
||||
.map(Model::getId)
|
||||
.findFirst();
|
||||
|
||||
modelName.ifPresent(name -> LOGGER.info("Model name: {}", name));
|
||||
return modelName;
|
||||
}
|
||||
catch (final Exception e) {
|
||||
LOGGER.warn("Failed to load model name: {}", e.getMessage());
|
||||
return Optional.empty();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -1,61 +1,40 @@
|
||||
package de.extio.lmlib.client.oai;
|
||||
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.CompletableFuture;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.beans.factory.annotation.Qualifier;
|
||||
import org.springframework.stereotype.Component;
|
||||
import org.springframework.web.reactive.function.client.WebClient;
|
||||
|
||||
import de.extio.lmlib.profile.ModelProfileService;
|
||||
import de.extio.lmlib.profile.ModelProfile;
|
||||
import de.extio.lmlib.profile.ModelProfile.ModelProvider;
|
||||
|
||||
@Component
|
||||
public class ModelNameSupplier implements InitializingBean {
|
||||
public class ModelNameSupplier {
|
||||
|
||||
@Autowired
|
||||
@Qualifier("lmLibWebClientBuilder")
|
||||
private WebClient.Builder webClientBuilder;
|
||||
|
||||
private final Map<String, CompletableFuture<String>> modelNames = new ConcurrentHashMap<>();
|
||||
private final Map<String, String> resolvedModelNames = new ConcurrentHashMap<>();
|
||||
|
||||
private final Map<String, CountDownLatch> countDownLatches = new ConcurrentHashMap<>();
|
||||
|
||||
@Autowired
|
||||
private ModelProfileService modelProfileService;
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
final var urls = this.modelProfileService.getModelProfileUrls(ModelProvider.OAI_TEXT_COMPLETION, ModelProvider.OAI_CHAT_COMPLETION);
|
||||
for (final var url : urls) {
|
||||
this.countDownLatches.put(url, new CountDownLatch(1));
|
||||
this.fetchModelName(url);
|
||||
public String getModelName(final ModelProfile modelProfile) {
|
||||
if (modelProfile.modelName() != null && !modelProfile.modelName().isBlank()) {
|
||||
return modelProfile.modelName();
|
||||
}
|
||||
}
|
||||
|
||||
private void fetchModelName(final String url) {
|
||||
final var webClient = this.webClientBuilder.baseUrl(url).build();
|
||||
CompletableFuture.runAsync(() -> {
|
||||
final var modelNameLoader = new ModelNameLoader(webClient, modelName -> {
|
||||
this.modelNames.put(url, CompletableFuture.completedFuture(modelName));
|
||||
this.countDownLatches.get(url).countDown();
|
||||
});
|
||||
modelNameLoader.run();
|
||||
|
||||
if (modelProfile.modelProvider() != ModelProvider.OAI_TEXT_COMPLETION && modelProfile.modelProvider() != ModelProvider.OAI_CHAT_COMPLETION) {
|
||||
return "";
|
||||
}
|
||||
|
||||
final var cacheKey = modelProfile.category() + "|" + modelProfile.url();
|
||||
return this.resolvedModelNames.computeIfAbsent(cacheKey, key -> {
|
||||
final var webClient = this.webClientBuilder.baseUrl(modelProfile.url()).build();
|
||||
final var loader = new ModelNameLoader(webClient);
|
||||
return loader.loadModelName().orElse("");
|
||||
});
|
||||
}
|
||||
|
||||
public String getModelName(final String url) {
|
||||
try {
|
||||
this.countDownLatches.get(url).await();
|
||||
}
|
||||
catch (final InterruptedException e) {
|
||||
throw new RuntimeException("Interrupted", e);
|
||||
}
|
||||
return this.modelNames.get(url).join();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@@ -20,7 +20,6 @@ import de.extio.lmlib.client.Completion;
|
||||
import de.extio.lmlib.client.Conversation;
|
||||
import de.extio.lmlib.client.Conversation.Turn;
|
||||
import de.extio.lmlib.client.oai.completion.AbstractCompletionClient;
|
||||
import de.extio.lmlib.profile.ModelCategory;
|
||||
import de.extio.lmlib.profile.ModelProfile;
|
||||
import de.extio.lmlib.profile.ModelProfile.ModelProvider;
|
||||
import reactor.core.publisher.Mono;
|
||||
@@ -44,7 +43,7 @@ public class ChatCompletionClient extends AbstractCompletionClient {
|
||||
final var chat = createChats(conversation, modelProfile);
|
||||
|
||||
final var request = new ChatCompletionRequest();
|
||||
request.setModel(this.modelNameSupplier.getModelName(modelProfile.url()));
|
||||
request.setModel(this.modelNameSupplier.getModelName(modelProfile));
|
||||
request.setMessages(chat);
|
||||
request.setMaxTokens(modelProfile.maxTokens());
|
||||
request.setMaxCompletionTokens(modelProfile.maxTokens());
|
||||
|
||||
@@ -49,7 +49,7 @@ public class TextCompletionClient extends AbstractCompletionClient {
|
||||
final var prompt = this.createPrompt(conversation, modelProfile, promptStrategy);
|
||||
|
||||
final var request = new CompletionRequest();
|
||||
request.setModel(this.modelNameSupplier.getModelName(modelProfile.url()));
|
||||
request.setModel(this.modelNameSupplier.getModelName(modelProfile));
|
||||
request.setPrompt(prompt);
|
||||
request.setMaxTokens(modelProfile.maxTokens());
|
||||
request.setTemperature(modelProfile.temperature());
|
||||
|
||||
@@ -2,16 +2,10 @@ 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;
|
||||
import java.util.Optional;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.slf4j.Logger;
|
||||
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;
|
||||
@@ -25,8 +19,6 @@ public class ModelProfileService {
|
||||
|
||||
private final Environment environment;
|
||||
|
||||
private static final String MODEL_PROFILE_PREFIX = "profile";
|
||||
|
||||
private static final Logger LOGGER = LoggerFactory.getLogger(ModelProfileService.class);
|
||||
|
||||
public ModelProfileService(final Environment environment) {
|
||||
@@ -122,31 +114,4 @@ public class ModelProfileService {
|
||||
public ModelProfile getModelProfile(final ModelCategory modelCategory) {
|
||||
return this.getModelProfile(modelCategory.getModelProfile(), modelCategory);
|
||||
}
|
||||
|
||||
public List<String> getModelProfileUrls(final ModelProvider... modelProviders) {
|
||||
final var modelProfiles = Binder.get(this.environment)
|
||||
.bind(MODEL_PROFILE_PREFIX, Bindable.mapOf(String.class, String.class))
|
||||
.orElse(Collections.emptyMap());
|
||||
|
||||
return modelProfiles.values().stream()
|
||||
.filter(model -> model != null && !model.isEmpty())
|
||||
.map(model -> {
|
||||
try {
|
||||
final var resource = new ResourcePropertySource("classpath:" + model + ".properties");
|
||||
for (final ModelProvider modelProvider : modelProviders) {
|
||||
if (modelProvider.name().equals(resource.getProperty("modelProvider"))) {
|
||||
return resource.getProperty("url") != null ? resource.getProperty("url").toString() : null;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
catch (final IOException e) {
|
||||
LOGGER.error("Error while reading model profile for: " + model, e);
|
||||
return null;
|
||||
}
|
||||
})
|
||||
.filter(Objects::nonNull)
|
||||
.distinct()
|
||||
.collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user