Moonshot AI entfernt, Konfiguration auf Umgebungsvariablen umgestellt, Mail-Verschlüsselung auf SSL (Port 465)
- Moonshot AI Provider komplett entfernt (LlmConfig, LlmRestClient, application.properties) - Mail-Konfiguration (Host, Port, User, Passwort, SMTP-Auth, SSL) auf Umgebungsvariablen umgestellt - LM Studio Modell auf Umgebungsvariable LMSTUDIO_MODEL umgestellt - Mail-Verschlüsselung von STARTTLS auf SSL umgestellt (Port 465) - Version auf 0.9.9 erhöht Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -2,5 +2,5 @@
|
|||||||
echo "G8m0T3vz" | docker login registry.assecutor.org -u adsg --password-stdin
|
echo "G8m0T3vz" | docker login registry.assecutor.org -u adsg --password-stdin
|
||||||
|
|
||||||
# Dann ganz normal pushen
|
# Dann ganz normal pushen
|
||||||
docker buildx build --platform linux/amd64 -t registry.assecutor.org/votianlt:0.9.6 --push .
|
docker buildx build --platform linux/amd64 -t registry.assecutor.org/votianlt:0.9.9 --push .
|
||||||
|
|
||||||
|
|||||||
2
pom.xml
2
pom.xml
@@ -6,7 +6,7 @@
|
|||||||
|
|
||||||
<groupId>de.assecutor.votianlt</groupId>
|
<groupId>de.assecutor.votianlt</groupId>
|
||||||
<artifactId>votianlt</artifactId>
|
<artifactId>votianlt</artifactId>
|
||||||
<version>0.9.6</version>
|
<version>0.9.9</version>
|
||||||
|
|
||||||
<packaging>jar</packaging>
|
<packaging>jar</packaging>
|
||||||
|
|
||||||
|
|||||||
@@ -11,17 +11,13 @@ import java.net.URL;
|
|||||||
import java.util.Base64;
|
import java.util.Base64;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configuration for LLM integration. Supports LM Studio and Moonshot AI. Switch
|
* Configuration for LLM integration via LM Studio. The LM Studio instance
|
||||||
* provider via {@code app.ai.provider=lmstudio|moonshot} in
|
* exposes an OpenAI-compatible API at {@code /v1/chat/completions}.
|
||||||
* application.properties.
|
|
||||||
*/
|
*/
|
||||||
@Configuration
|
@Configuration
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class LlmConfig {
|
public class LlmConfig {
|
||||||
|
|
||||||
@Value("${app.ai.provider:lmstudio}")
|
|
||||||
private String provider;
|
|
||||||
|
|
||||||
@Value("${app.ai.lmstudio.base-url}")
|
@Value("${app.ai.lmstudio.base-url}")
|
||||||
private String lmstudioBaseUrl;
|
private String lmstudioBaseUrl;
|
||||||
|
|
||||||
@@ -34,45 +30,29 @@ public class LlmConfig {
|
|||||||
@Value("${app.ai.lmstudio.htaccess-password}")
|
@Value("${app.ai.lmstudio.htaccess-password}")
|
||||||
private String lmstudioHtaccessPassword;
|
private String lmstudioHtaccessPassword;
|
||||||
|
|
||||||
@Value("${app.ai.moonshot.base-url:https://api.moonshot.ai}")
|
|
||||||
private String moonshotBaseUrl;
|
|
||||||
|
|
||||||
@Value("${app.ai.moonshot.api-key:}")
|
|
||||||
private String moonshotApiKey;
|
|
||||||
|
|
||||||
@Value("${app.ai.moonshot.model:moonshot-v1-8k}")
|
|
||||||
private String moonshotModel;
|
|
||||||
|
|
||||||
@PostConstruct
|
@PostConstruct
|
||||||
public void logConfig() {
|
public void logConfig() {
|
||||||
log.info("=== LLM Configuration ===");
|
log.info("=== LLM Configuration ===");
|
||||||
log.info("Provider: {}", provider);
|
log.info("Provider: lmstudio");
|
||||||
if ("moonshot".equalsIgnoreCase(provider)) {
|
|
||||||
log.info("Base URL: {}", moonshotBaseUrl);
|
|
||||||
log.info("Model: {}", moonshotModel);
|
|
||||||
log.info("API Key: {}***", moonshotApiKey.length() > 8 ? moonshotApiKey.substring(0, 8) : "***");
|
|
||||||
testConnection(moonshotBaseUrl, moonshotModel, moonshotApiKey);
|
|
||||||
} else {
|
|
||||||
log.info("Base URL: {}", lmstudioBaseUrl);
|
log.info("Base URL: {}", lmstudioBaseUrl);
|
||||||
log.info("Model: {}", lmstudioModel);
|
log.info("Model: {}", lmstudioModel);
|
||||||
log.info("HTACCESS auth: {}", hasHtaccessCredentials() ? "configured" : "not configured");
|
log.info("HTACCESS auth: {}", hasHtaccessCredentials() ? "configured" : "not configured");
|
||||||
testConnection(lmstudioBaseUrl, lmstudioModel, null);
|
testConnection(lmstudioBaseUrl, lmstudioModel);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testConnection(String baseUrl, String model, String apiKey) {
|
private void testConnection(String baseUrl, String model) {
|
||||||
log.info("Testing LLM connection to: {}", baseUrl);
|
log.info("Testing LLM connection to: {}", baseUrl);
|
||||||
|
|
||||||
// Test 1: Models endpoint
|
// Test 1: Models endpoint
|
||||||
testEndpoint(baseUrl + "/v1/models", "GET", null, apiKey);
|
testEndpoint(baseUrl + "/v1/models", "GET", null);
|
||||||
|
|
||||||
// Test 2: Chat completions (no streaming)
|
// Test 2: Chat completions (no streaming)
|
||||||
String testPayload = "{\"model\":\"" + model
|
String testPayload = "{\"model\":\"" + model
|
||||||
+ "\",\"messages\":[{\"role\":\"user\",\"content\":\"ping\"}],\"max_tokens\":1,\"stream\":false}";
|
+ "\",\"messages\":[{\"role\":\"user\",\"content\":\"ping\"}],\"max_tokens\":1,\"stream\":false}";
|
||||||
testEndpoint(baseUrl + "/v1/chat/completions", "POST", testPayload, apiKey);
|
testEndpoint(baseUrl + "/v1/chat/completions", "POST", testPayload);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void testEndpoint(String endpoint, String method, String payload, String apiKey) {
|
private void testEndpoint(String endpoint, String method, String payload) {
|
||||||
try {
|
try {
|
||||||
log.info("Testing endpoint: {} {}", method, endpoint);
|
log.info("Testing endpoint: {} {}", method, endpoint);
|
||||||
URL url = URI.create(endpoint).toURL();
|
URL url = URI.create(endpoint).toURL();
|
||||||
@@ -85,8 +65,6 @@ public class LlmConfig {
|
|||||||
String credentials = lmstudioHtaccessUsername + ":" + lmstudioHtaccessPassword;
|
String credentials = lmstudioHtaccessUsername + ":" + lmstudioHtaccessPassword;
|
||||||
String encoded = Base64.getEncoder().encodeToString(credentials.getBytes());
|
String encoded = Base64.getEncoder().encodeToString(credentials.getBytes());
|
||||||
connection.setRequestProperty("Authorization", "Basic " + encoded);
|
connection.setRequestProperty("Authorization", "Basic " + encoded);
|
||||||
} else if (apiKey != null && !apiKey.isBlank()) {
|
|
||||||
connection.setRequestProperty("Authorization", "Bearer " + apiKey);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (payload != null) {
|
if (payload != null) {
|
||||||
@@ -123,16 +101,12 @@ public class LlmConfig {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getProvider() {
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getBaseUrl() {
|
public String getBaseUrl() {
|
||||||
return "moonshot".equalsIgnoreCase(provider) ? moonshotBaseUrl : lmstudioBaseUrl;
|
return lmstudioBaseUrl;
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getModel() {
|
public String getModel() {
|
||||||
return "moonshot".equalsIgnoreCase(provider) ? moonshotModel : lmstudioModel;
|
return lmstudioModel;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean hasHtaccessCredentials() {
|
public boolean hasHtaccessCredentials() {
|
||||||
|
|||||||
@@ -16,9 +16,8 @@ import java.util.List;
|
|||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Direct REST client for LLM APIs (LM Studio or Moonshot AI). Provider is
|
* Direct REST client for LM Studio LLM API. Communicates via the
|
||||||
* selected via {@code app.ai.provider} in application.properties. Both
|
* OpenAI-compatible /v1/chat/completions endpoint.
|
||||||
* providers expose an OpenAI-compatible /v1/chat/completions endpoint.
|
|
||||||
*/
|
*/
|
||||||
@Component
|
@Component
|
||||||
@Slf4j
|
@Slf4j
|
||||||
@@ -27,45 +26,30 @@ public class LlmRestClient {
|
|||||||
private final WebClient webClient;
|
private final WebClient webClient;
|
||||||
private final ObjectMapper objectMapper;
|
private final ObjectMapper objectMapper;
|
||||||
private final String model;
|
private final String model;
|
||||||
private final String provider;
|
|
||||||
|
|
||||||
public LlmRestClient(@Value("${app.ai.provider}") String provider,
|
public LlmRestClient(@Value("${app.ai.lmstudio.base-url}") String lmstudioBaseUrl,
|
||||||
@Value("${app.ai.lmstudio.base-url}") String lmstudioBaseUrl,
|
|
||||||
@Value("${app.ai.lmstudio.model}") String lmstudioModel,
|
@Value("${app.ai.lmstudio.model}") String lmstudioModel,
|
||||||
@Value("${app.ai.lmstudio.htaccess-username}") String lmstudioHtaccessUsername,
|
@Value("${app.ai.lmstudio.htaccess-username}") String lmstudioHtaccessUsername,
|
||||||
@Value("${app.ai.lmstudio.htaccess-password}") String lmstudioHtaccessPassword,
|
@Value("${app.ai.lmstudio.htaccess-password}") String lmstudioHtaccessPassword,
|
||||||
@Value("${app.ai.moonshot.base-url}") String moonshotBaseUrl,
|
ObjectMapper objectMapper) {
|
||||||
@Value("${app.ai.moonshot.api-key}") String moonshotApiKey,
|
|
||||||
@Value("${app.ai.moonshot.model}") String moonshotModel, ObjectMapper objectMapper) {
|
|
||||||
|
|
||||||
this.provider = provider.trim().toLowerCase();
|
this.model = lmstudioModel;
|
||||||
this.objectMapper = objectMapper;
|
this.objectMapper = objectMapper;
|
||||||
|
|
||||||
WebClient.Builder builder = WebClient.builder();
|
WebClient.Builder builder = WebClient.builder();
|
||||||
|
|
||||||
if ("moonshot".equals(this.provider)) {
|
|
||||||
this.model = moonshotModel;
|
|
||||||
builder.baseUrl(moonshotBaseUrl + "/v1/chat/completions");
|
|
||||||
if (moonshotApiKey != null && !moonshotApiKey.isBlank()) {
|
|
||||||
builder.defaultHeader(HttpHeaders.AUTHORIZATION, "Bearer " + moonshotApiKey);
|
|
||||||
}
|
|
||||||
log.info("LlmRestClient initialized - Provider: moonshot, URL: {}/v1/chat/completions, Model: {}",
|
|
||||||
moonshotBaseUrl, moonshotModel);
|
|
||||||
} else {
|
|
||||||
this.model = lmstudioModel;
|
|
||||||
builder.baseUrl(lmstudioBaseUrl + "/v1/chat/completions");
|
builder.baseUrl(lmstudioBaseUrl + "/v1/chat/completions");
|
||||||
|
|
||||||
if (lmstudioHtaccessUsername != null && !lmstudioHtaccessUsername.isBlank()
|
if (lmstudioHtaccessUsername != null && !lmstudioHtaccessUsername.isBlank()
|
||||||
&& lmstudioHtaccessPassword != null && !lmstudioHtaccessPassword.isBlank()) {
|
&& lmstudioHtaccessPassword != null && !lmstudioHtaccessPassword.isBlank()) {
|
||||||
String credentials = lmstudioHtaccessUsername + ":" + lmstudioHtaccessPassword;
|
String credentials = lmstudioHtaccessUsername + ":" + lmstudioHtaccessPassword;
|
||||||
String encoded = Base64.getEncoder()
|
String encoded = Base64.getEncoder()
|
||||||
.encodeToString(credentials.getBytes(StandardCharsets.UTF_8));
|
.encodeToString(credentials.getBytes(StandardCharsets.UTF_8));
|
||||||
builder.defaultHeader(HttpHeaders.AUTHORIZATION, "Basic " + encoded);
|
builder.defaultHeader(HttpHeaders.AUTHORIZATION, "Basic " + encoded);
|
||||||
log.info("LlmRestClient initialized - Provider: lmstudio (with HTACCESS auth), "
|
log.info("LlmRestClient initialized (with HTACCESS auth) - URL: {}/v1/chat/completions, Model: {}",
|
||||||
+ "URL: {}/v1/chat/completions, Model: {}", lmstudioBaseUrl, lmstudioModel);
|
lmstudioBaseUrl, lmstudioModel);
|
||||||
} else {
|
} else {
|
||||||
log.info("LlmRestClient initialized - Provider: lmstudio, URL: {}/v1/chat/completions, Model: {}",
|
log.info("LlmRestClient initialized - URL: {}/v1/chat/completions, Model: {}",
|
||||||
lmstudioBaseUrl, lmstudioModel);
|
lmstudioBaseUrl, lmstudioModel);
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
this.webClient = builder.build();
|
this.webClient = builder.build();
|
||||||
@@ -104,7 +88,7 @@ public class LlmRestClient {
|
|||||||
Map.of("role", "user", "content", userMessage)),
|
Map.of("role", "user", "content", userMessage)),
|
||||||
"temperature", temperature, "max_tokens", maxTokens, "stream", false);
|
"temperature", temperature, "max_tokens", maxTokens, "stream", false);
|
||||||
|
|
||||||
log.info("Sending request to LLM [{}] (model: {}, prompt length: {} chars)...", provider, model,
|
log.info("Sending request to LLM (model: {}, prompt length: {} chars)...", model,
|
||||||
userMessage.length());
|
userMessage.length());
|
||||||
long startTime = System.currentTimeMillis();
|
long startTime = System.currentTimeMillis();
|
||||||
|
|
||||||
@@ -118,7 +102,7 @@ public class LlmRestClient {
|
|||||||
return extractContent(response);
|
return extractContent(response);
|
||||||
|
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
log.error("Error calling LLM API [{}]: {} - {}", provider, e.getClass().getSimpleName(), e.getMessage());
|
log.error("Error calling LLM API: {} - {}", e.getClass().getSimpleName(), e.getMessage());
|
||||||
if (log.isDebugEnabled()) {
|
if (log.isDebugEnabled()) {
|
||||||
log.debug("Full stack trace:", e);
|
log.debug("Full stack trace:", e);
|
||||||
}
|
}
|
||||||
@@ -133,10 +117,6 @@ public class LlmRestClient {
|
|||||||
return chat(null, userMessage);
|
return chat(null, userMessage);
|
||||||
}
|
}
|
||||||
|
|
||||||
public String getProvider() {
|
|
||||||
return provider;
|
|
||||||
}
|
|
||||||
|
|
||||||
public String getModel() {
|
public String getModel() {
|
||||||
return model;
|
return model;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -38,12 +38,12 @@ spring.data.mongodb.connect-timeout=10000
|
|||||||
spring.data.mongodb.server-selection-timeout=5000
|
spring.data.mongodb.server-selection-timeout=5000
|
||||||
|
|
||||||
# Mail Configuration (Spring Boot Standard)
|
# Mail Configuration (Spring Boot Standard)
|
||||||
spring.mail.host=mailhub.assecutor.org
|
spring.mail.host=${MAIL_HOST}
|
||||||
spring.mail.port=587
|
spring.mail.port=${MAIL_PORT}
|
||||||
spring.mail.username=noreply@assecutor.org
|
spring.mail.username=${MAIL_USERNAME}
|
||||||
spring.mail.password=OStRIL,_,31
|
spring.mail.password=${MAIL_PASSWORD}
|
||||||
spring.mail.properties.mail.smtp.auth=true
|
spring.mail.properties.mail.smtp.auth=${MAIL_SMTP_AUTH}
|
||||||
spring.mail.properties.mail.smtp.starttls.enable=true
|
spring.mail.properties.mail.smtp.ssl.enable=${MAIL_SMTP_SSL}
|
||||||
|
|
||||||
# HTTP request size limits for large payloads
|
# HTTP request size limits for large payloads
|
||||||
server.max-http-request-header-size=8MB
|
server.max-http-request-header-size=8MB
|
||||||
@@ -73,26 +73,17 @@ app.version=@project.version@
|
|||||||
app.google.maps.api-key=AIzaSyDnbitL06iLp3elmj-WtPudCykX9xvXcVE
|
app.google.maps.api-key=AIzaSyDnbitL06iLp3elmj-WtPudCykX9xvXcVE
|
||||||
|
|
||||||
# ===========================================
|
# ===========================================
|
||||||
# LLM Configuration
|
# LLM Configuration (LM Studio)
|
||||||
# Provider: lmstudio | moonshot
|
|
||||||
# ===========================================
|
# ===========================================
|
||||||
app.ai.provider=moonshot
|
|
||||||
|
|
||||||
# --- LM Studio ---
|
|
||||||
app.ai.lmstudio.base-url=${LMSTUDIO_URL}
|
app.ai.lmstudio.base-url=${LMSTUDIO_URL}
|
||||||
app.ai.lmstudio.model=local-model
|
app.ai.lmstudio.model=${LMSTUDIO_MODEL}
|
||||||
app.ai.lmstudio.htaccess-username=${LMSTUDIO_HTACCESS_USERNAME}
|
app.ai.lmstudio.htaccess-username=${LMSTUDIO_HTACCESS_USERNAME}
|
||||||
app.ai.lmstudio.htaccess-password=${LMSTUDIO_HTACCESS_PASSWORD}
|
app.ai.lmstudio.htaccess-password=${LMSTUDIO_HTACCESS_PASSWORD}
|
||||||
|
|
||||||
# --- Moonshot AI (kimi) ---
|
|
||||||
app.ai.moonshot.base-url=https://api.moonshot.ai
|
|
||||||
app.ai.moonshot.api-key=sk-EfHJfwCsxiZbOoBJ21OLWb9RUJQXSXAFIFGKnOedKke5JYZp
|
|
||||||
app.ai.moonshot.model=moonshot-v1-8k
|
|
||||||
|
|
||||||
# Spring AI OpenAI properties (Pflicht für Auto-Configuration, werden vom LlmRestClient überschrieben)
|
# Spring AI OpenAI properties (Pflicht für Auto-Configuration, werden vom LlmRestClient überschrieben)
|
||||||
spring.ai.openai.base-url=${LMSTUDIO_URL}
|
spring.ai.openai.base-url=${LMSTUDIO_URL}
|
||||||
spring.ai.openai.api-key=not-used
|
spring.ai.openai.api-key=not-used
|
||||||
spring.ai.openai.chat.options.model=local-model
|
spring.ai.openai.chat.options.model=${LMSTUDIO_MODEL}
|
||||||
spring.ai.openai.chat.options.temperature=0.7
|
spring.ai.openai.chat.options.temperature=0.7
|
||||||
spring.ai.openai.chat.options.stream=false
|
spring.ai.openai.chat.options.stream=false
|
||||||
spring.ai.openai.connect-timeout=10s
|
spring.ai.openai.connect-timeout=10s
|
||||||
|
|||||||
Reference in New Issue
Block a user