refactor: Simplify model name loading and update ModelNameSupplier to use ModelProfile

This commit is contained in:
extio-de
2025-10-29 13:02:14 +01:00
parent d0b1eeae75
commit 179755df45
6 changed files with 44 additions and 119 deletions

View File

@@ -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();
}
}
}

View File

@@ -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();
}
}

View File

@@ -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());

View File

@@ -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());