feat: Add Spring Boot auto-configuration for lm-lib modules

- Introduced auto-configuration classes for profile, client, agent, and prompt modules.
- Updated README to reflect new modular architecture and configuration properties.
- Added selective enabling/disabling of modules via application properties.
- Refactored existing services and strategies to remove unnecessary Spring annotations.
- Enhanced test configurations to utilize auto-configuration.
- Updated META-INF imports to include new auto-configuration classes.
This commit is contained in:
extio-de
2026-01-05 21:58:12 +01:00
parent b17a1fe517
commit d5898e35f5
35 changed files with 428 additions and 90 deletions

View File

@@ -0,0 +1,155 @@
package de.extio.lmlib;
import java.util.List;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
import de.extio.lmlib.agent.AgentExecutorService;
import de.extio.lmlib.client.ClientService;
import de.extio.lmlib.client.oai.ModelNameSupplier;
import de.extio.lmlib.client.oai.completion.chat.ChatCompletionClient;
import de.extio.lmlib.client.oai.completion.text.TextCompletionClient;
import de.extio.lmlib.profile.ModelProfileService;
import de.extio.lmlib.prompt.AlpacaShortPromptStrategy;
import de.extio.lmlib.prompt.ChatMLPromptStrategy;
import de.extio.lmlib.prompt.GemmaPromptStrategy;
import de.extio.lmlib.prompt.GptOssPromptStrategy;
import de.extio.lmlib.prompt.Llama3PromptStrategy;
import de.extio.lmlib.prompt.Llama4PromptStrategy;
import de.extio.lmlib.prompt.MistralNemoPromptStrategy;
import de.extio.lmlib.prompt.MistralPromptStrategy;
import de.extio.lmlib.prompt.NoPromptStrategy;
import de.extio.lmlib.prompt.Phi3PromptStrategy;
import de.extio.lmlib.prompt.Phi4PromptStrategy;
import de.extio.lmlib.prompt.PromptStrategy;
import de.extio.lmlib.prompt.PromptStrategyFactory;
import de.extio.lmlib.prompt.Qwen3PromptStrategy;
import de.extio.lmlib.prompt.VicunaPromptStrategy;
@AutoConfiguration
public class LmLibAutoConfiguration {
@Bean
@ConditionalOnMissingBean
ModelProfileService modelProfileService(final Environment environment) {
return new ModelProfileService(environment);
}
@Bean
@ConditionalOnMissingBean
ClientService clientService() {
return new ClientService();
}
@Bean
@ConditionalOnMissingBean
AgentExecutorService agentExecutorService() {
return new AgentExecutorService();
}
@Bean
@ConditionalOnMissingBean
ModelNameSupplier modelNameSupplier() {
return new ModelNameSupplier();
}
@Bean
@ConditionalOnMissingBean
TextCompletionClient textCompletionClient() {
return new TextCompletionClient();
}
@Bean
@ConditionalOnMissingBean
ChatCompletionClient chatCompletionClient() {
return new ChatCompletionClient();
}
@Bean
@ConditionalOnMissingBean
PromptStrategyFactory promptStrategyFactory(final List<PromptStrategy> strategies) {
return new PromptStrategyFactory(strategies);
}
@Bean
@ConditionalOnMissingBean
AlpacaShortPromptStrategy alpacaShortPromptStrategy() {
return new AlpacaShortPromptStrategy();
}
@Bean
@ConditionalOnMissingBean
ChatMLPromptStrategy chatMLPromptStrategy() {
return new ChatMLPromptStrategy();
}
@Bean
@ConditionalOnMissingBean
GemmaPromptStrategy gemmaPromptStrategy() {
return new GemmaPromptStrategy();
}
@Bean
@ConditionalOnMissingBean
GptOssPromptStrategy gptOssPromptStrategy() {
return new GptOssPromptStrategy();
}
@Bean
@ConditionalOnMissingBean
Llama3PromptStrategy llama3PromptStrategy() {
return new Llama3PromptStrategy();
}
@Bean
@ConditionalOnMissingBean
Llama4PromptStrategy llama4PromptStrategy() {
return new Llama4PromptStrategy();
}
@Bean
@ConditionalOnMissingBean
MistralNemoPromptStrategy mistralNemoPromptStrategy() {
return new MistralNemoPromptStrategy();
}
@Bean
@ConditionalOnMissingBean
MistralPromptStrategy mistralPromptStrategy() {
return new MistralPromptStrategy();
}
@Bean
@ConditionalOnMissingBean
NoPromptStrategy noPromptStrategy() {
return new NoPromptStrategy();
}
@Bean
@ConditionalOnMissingBean
Phi3PromptStrategy phi3PromptStrategy() {
return new Phi3PromptStrategy();
}
@Bean
@ConditionalOnMissingBean
Phi4PromptStrategy phi4PromptStrategy() {
return new Phi4PromptStrategy();
}
@Bean
@ConditionalOnMissingBean
Qwen3PromptStrategy qwen3PromptStrategy() {
return new Qwen3PromptStrategy();
}
@Bean
@ConditionalOnMissingBean
VicunaPromptStrategy vicunaPromptStrategy() {
return new VicunaPromptStrategy();
}
}

View File

@@ -0,0 +1,22 @@
package de.extio.lmlib.agent;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import de.extio.lmlib.client.ClientAutoConfiguration;
@AutoConfiguration
@AutoConfigureAfter(ClientAutoConfiguration.class)
@ConditionalOnProperty(prefix = "lmlib.agent", name = "enabled", havingValue = "true", matchIfMissing = true)
public class AgentAutoConfiguration {
@Bean
@ConditionalOnMissingBean
AgentExecutorService agentExecutorService() {
return new AgentExecutorService();
}
}

View File

@@ -15,12 +15,10 @@ import org.springframework.beans.factory.DisposableBean;
import org.springframework.beans.factory.InitializingBean;
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
public class AgentExecutorService implements InitializingBean, DisposableBean {
private static final Logger LOGGER = LoggerFactory.getLogger(AgentExecutorService.class);

View File

@@ -0,0 +1,44 @@
package de.extio.lmlib.client;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import de.extio.lmlib.client.oai.ModelNameSupplier;
import de.extio.lmlib.client.oai.completion.chat.ChatCompletionClient;
import de.extio.lmlib.client.oai.completion.text.TextCompletionClient;
import de.extio.lmlib.profile.ProfileAutoConfiguration;
import de.extio.lmlib.token.TokenizerAutoConfiguration;
@AutoConfiguration
@AutoConfigureAfter({ProfileAutoConfiguration.class, TokenizerAutoConfiguration.class, RestClientConfiguration.class})
@ConditionalOnProperty(prefix = "lmlib.client", name = "enabled", havingValue = "true", matchIfMissing = true)
public class ClientAutoConfiguration {
@Bean
@ConditionalOnMissingBean
ClientService clientService() {
return new ClientService();
}
@Bean
@ConditionalOnMissingBean
ModelNameSupplier modelNameSupplier() {
return new ModelNameSupplier();
}
@Bean
@ConditionalOnMissingBean
TextCompletionClient textCompletionClient() {
return new TextCompletionClient();
}
@Bean
@ConditionalOnMissingBean
ChatCompletionClient chatCompletionClient() {
return new ChatCompletionClient();
}
}

View File

@@ -5,7 +5,6 @@ import java.util.function.Consumer;
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;
@@ -14,7 +13,6 @@ import de.extio.lmlib.profile.ModelProfile;
import de.extio.lmlib.profile.ModelProfile.ModelProvider;
import de.extio.lmlib.profile.ModelProfileService;
@Service
public class ClientService {
@Autowired

View File

@@ -19,8 +19,9 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.beans.factory.annotation.Value;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.http.HttpHeaders;
import org.springframework.http.MediaType;
import org.springframework.http.client.ClientHttpRequestInterceptor;
@@ -30,7 +31,8 @@ import org.springframework.util.backoff.BackOffExecution;
import org.springframework.util.backoff.ExponentialBackOff;
import org.springframework.web.client.RestClient;
@Configuration
@AutoConfiguration
@ConditionalOnProperty(prefix = "lmlib.restclient", name = "enabled", havingValue = "true", matchIfMissing = true)
public class RestClientConfiguration {
private static final Logger LOGGER = LoggerFactory.getLogger(RestClientConfiguration.class);

View File

@@ -5,13 +5,11 @@ import java.util.concurrent.ConcurrentHashMap;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
import de.extio.lmlib.profile.ModelProfile;
import de.extio.lmlib.profile.ModelProfile.ModelProvider;
@Component
public class ModelNameSupplier {
@Autowired

View File

@@ -14,7 +14,6 @@ import java.util.function.Consumer;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
import tools.jackson.core.JacksonException;
@@ -27,7 +26,6 @@ import de.extio.lmlib.client.oai.completion.AbstractCompletionClient;
import de.extio.lmlib.profile.ModelProfile;
import de.extio.lmlib.profile.ModelProfile.ModelProvider;
@Component
public class ChatCompletionClient extends AbstractCompletionClient {
private static final Logger LOGGER = LoggerFactory.getLogger(ChatCompletionClient.class);

View File

@@ -13,7 +13,6 @@ import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.http.HttpMethod;
import org.springframework.stereotype.Component;
import org.springframework.web.client.RestClient;
import tools.jackson.core.JacksonException;
@@ -29,7 +28,6 @@ import de.extio.lmlib.profile.ModelProfile.ModelProvider;
import de.extio.lmlib.prompt.PromptStrategy;
import de.extio.lmlib.prompt.PromptStrategyFactory;
@Component
public class TextCompletionClient extends AbstractCompletionClient {
private static final Logger LOGGER = LoggerFactory.getLogger(TextCompletionClient.class);

View File

@@ -10,11 +10,9 @@ 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;
import de.extio.lmlib.profile.ModelProfile.ModelProvider;
@Service
public class ModelProfileService {
private final Environment environment;

View File

@@ -0,0 +1,19 @@
package de.extio.lmlib.profile;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.core.env.Environment;
@AutoConfiguration
@ConditionalOnProperty(prefix = "lmlib.profile", name = "enabled", havingValue = "true", matchIfMissing = true)
public class ProfileAutoConfiguration {
@Bean
@ConditionalOnMissingBean
ModelProfileService modelProfileService(final Environment environment) {
return new ModelProfileService(environment);
}
}

View File

@@ -1,8 +1,5 @@
package de.extio.lmlib.prompt;
import org.springframework.stereotype.Component;
@Component
public class ChatMLPromptStrategy implements PromptStrategy {
@Override

View File

@@ -1,12 +1,5 @@
package de.extio.lmlib.prompt;
import org.springframework.stereotype.Component;
/**
* Works for both Gemma2 and Gemma3
* @see https://ai.google.dev/gemma/docs/core/prompt-structure
*/
@Component
public class GemmaPromptStrategy implements PromptStrategy {
@Override

View File

@@ -1,8 +1,5 @@
package de.extio.lmlib.prompt;
import org.springframework.stereotype.Component;
@Component
public class GptOssPromptStrategy implements PromptStrategy {
private static final String finalMarker = "<|channel|>final<|message|>";

View File

@@ -1,11 +1,5 @@
package de.extio.lmlib.prompt;
import org.springframework.stereotype.Component;
/**
* @see https://llama.meta.com/docs/model-cards-and-prompt-formats/meta-llama-3
*/
@Component
public class Llama3PromptStrategy implements PromptStrategy {
@Override

View File

@@ -1,11 +1,6 @@
package de.extio.lmlib.prompt;
import org.springframework.stereotype.Component;
/**
* @see https://www.llama.com/docs/model-cards-and-prompt-formats/llama4/
*/
@Component
public class Llama4PromptStrategy implements PromptStrategy {
@Override

View File

@@ -1,8 +1,5 @@
package de.extio.lmlib.prompt;
import org.springframework.stereotype.Component;
@Component
public class MistralNemoPromptStrategy implements PromptStrategy {
@Override

View File

@@ -1,8 +1,5 @@
package de.extio.lmlib.prompt;
import org.springframework.stereotype.Component;
@Component
public class MistralPromptStrategy implements PromptStrategy {
@Override

View File

@@ -1,8 +1,5 @@
package de.extio.lmlib.prompt;
import org.springframework.stereotype.Component;
@Component
public class NoPromptStrategy implements PromptStrategy {
@Override

View File

@@ -1,8 +1,5 @@
package de.extio.lmlib.prompt;
import org.springframework.stereotype.Component;
@Component
public class Phi3PromptStrategy implements PromptStrategy {
@Override

View File

@@ -1,8 +1,5 @@
package de.extio.lmlib.prompt;
import org.springframework.stereotype.Component;
@Component
public class Phi4PromptStrategy implements PromptStrategy {
@Override

View File

@@ -0,0 +1,98 @@
package de.extio.lmlib.prompt;
import java.util.List;
import org.springframework.boot.autoconfigure.AutoConfiguration;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
@AutoConfiguration
@ConditionalOnProperty(prefix = "lmlib.prompt", name = "enabled", havingValue = "true", matchIfMissing = true)
public class PromptAutoConfiguration {
@Bean
@ConditionalOnMissingBean
PromptStrategyFactory promptStrategyFactory(final List<PromptStrategy> strategies) {
return new PromptStrategyFactory(strategies);
}
@Bean
@ConditionalOnMissingBean
AlpacaShortPromptStrategy alpacaShortPromptStrategy() {
return new AlpacaShortPromptStrategy();
}
@Bean
@ConditionalOnMissingBean
ChatMLPromptStrategy chatMLPromptStrategy() {
return new ChatMLPromptStrategy();
}
@Bean
@ConditionalOnMissingBean
GemmaPromptStrategy gemmaPromptStrategy() {
return new GemmaPromptStrategy();
}
@Bean
@ConditionalOnMissingBean
GptOssPromptStrategy gptOssPromptStrategy() {
return new GptOssPromptStrategy();
}
@Bean
@ConditionalOnMissingBean
Llama3PromptStrategy llama3PromptStrategy() {
return new Llama3PromptStrategy();
}
@Bean
@ConditionalOnMissingBean
Llama4PromptStrategy llama4PromptStrategy() {
return new Llama4PromptStrategy();
}
@Bean
@ConditionalOnMissingBean
MistralNemoPromptStrategy mistralNemoPromptStrategy() {
return new MistralNemoPromptStrategy();
}
@Bean
@ConditionalOnMissingBean
MistralPromptStrategy mistralPromptStrategy() {
return new MistralPromptStrategy();
}
@Bean
@ConditionalOnMissingBean
NoPromptStrategy noPromptStrategy() {
return new NoPromptStrategy();
}
@Bean
@ConditionalOnMissingBean
Phi3PromptStrategy phi3PromptStrategy() {
return new Phi3PromptStrategy();
}
@Bean
@ConditionalOnMissingBean
Phi4PromptStrategy phi4PromptStrategy() {
return new Phi4PromptStrategy();
}
@Bean
@ConditionalOnMissingBean
Qwen3PromptStrategy qwen3PromptStrategy() {
return new Qwen3PromptStrategy();
}
@Bean
@ConditionalOnMissingBean
VicunaPromptStrategy vicunaPromptStrategy() {
return new VicunaPromptStrategy();
}
}

View File

@@ -4,9 +4,6 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import org.springframework.stereotype.Component;
@Component
public class PromptStrategyFactory {
private final Map<String, PromptStrategy> strategies = new HashMap<>();

View File

@@ -1,8 +1,5 @@
package de.extio.lmlib.prompt;
import org.springframework.stereotype.Component;
@Component
public class Qwen3PromptStrategy implements PromptStrategy {
private static final String IM_START = "<|im_start|>";

View File

@@ -1,8 +1,5 @@
package de.extio.lmlib.prompt;
import org.springframework.stereotype.Component;
@Component
public class VicunaPromptStrategy implements PromptStrategy {
@Override

View File

@@ -6,10 +6,10 @@ import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.web.client.RestClient;
@AutoConfiguration
@ConditionalOnProperty(prefix = "lmlib.tokenizer", name = "enabled", havingValue = "true", matchIfMissing = true)
public class TokenizerAutoConfiguration {
@Bean
@@ -19,17 +19,12 @@ public class TokenizerAutoConfiguration {
return new LlamaServerTokenizer(restClientBuilder);
}
@Configuration(proxyBeanMethods = false)
@Bean
@ConditionalOnClass(name = "com.knuddels.jtokkit.Encodings")
@ConditionalOnProperty(name = "tokenizer.strategy", havingValue = "jTokkit")
@ConditionalOnProperty(name = "tokenizer.strategy", havingValue = "jTokkit", matchIfMissing = true)
@ConditionalOnMissingBean(type = "de.extio.lmlib.token.Tokenizer")
public static class SomeServiceConfiguration {
@Bean
Tokenizer jTokkitTokenizer() {
return new JTokkitTokenizer();
}
Tokenizer jTokkitTokenizer() {
return new JTokkitTokenizer();
}
}