feat: cloud AI chat provider via OpenAI-compatible API
This commit is contained in:
parent
7c721cd75f
commit
75275f992d
@ -1,13 +1,17 @@
|
||||
package ai.decompile.ai.config;
|
||||
|
||||
import io.micrometer.observation.ObservationRegistry;
|
||||
import java.time.Duration;
|
||||
import org.springframework.ai.ollama.OllamaChatModel;
|
||||
import org.springframework.ai.ollama.OllamaEmbeddingModel;
|
||||
import org.springframework.ai.ollama.api.OllamaApi;
|
||||
import org.springframework.ai.ollama.api.OllamaChatOptions;
|
||||
import org.springframework.ai.ollama.api.OllamaEmbeddingOptions;
|
||||
import org.springframework.ai.ollama.management.ModelManagementOptions;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatOptions;
|
||||
import org.springframework.beans.factory.annotation.Value;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Configuration;
|
||||
@ -29,12 +33,25 @@ public class AiConfig {
|
||||
@Value("${spring.ai.ollama.embedding.options.model:qwen3-embedding:latest}")
|
||||
private String ollamaEmbeddingModel;
|
||||
|
||||
@Value("${AI_CLOUD_API_KEY:}")
|
||||
private String cloudApiKey;
|
||||
|
||||
@Value("${AI_CLOUD_BASE_URL:http://localhost:11434/v1}")
|
||||
private String cloudBaseUrl;
|
||||
|
||||
@Value("${AI_CLOUD_CHAT_MODEL:gemma4:12b}")
|
||||
private String cloudChatModel;
|
||||
|
||||
@Bean
|
||||
OllamaApi ollamaApi() {
|
||||
return OllamaApi.builder().baseUrl(ollamaBaseUrl).build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnProperty(
|
||||
name = "app.ai.default-chat-provider",
|
||||
havingValue = "ollama",
|
||||
matchIfMissing = true)
|
||||
OllamaChatModel ollamaChatModel(OllamaApi ollamaApi, ObservationRegistry observationRegistry) {
|
||||
return OllamaChatModel.builder()
|
||||
.ollamaApi(ollamaApi)
|
||||
@ -57,4 +74,20 @@ public class AiConfig {
|
||||
.modelManagementOptions(ModelManagementOptions.defaults())
|
||||
.build();
|
||||
}
|
||||
|
||||
@Bean
|
||||
@ConditionalOnExpression(
|
||||
"'${app.ai.default-chat-provider:ollama}' != 'ollama' && '${AI_CLOUD_API_KEY:}' != ''")
|
||||
OpenAiChatModel openAiChatModel(ObservationRegistry observationRegistry) {
|
||||
return OpenAiChatModel.builder()
|
||||
.options(
|
||||
OpenAiChatOptions.builder()
|
||||
.baseUrl(cloudBaseUrl)
|
||||
.apiKey(cloudApiKey)
|
||||
.model(cloudChatModel)
|
||||
.timeout(Duration.ofMinutes(5))
|
||||
.build())
|
||||
.observationRegistry(observationRegistry)
|
||||
.build();
|
||||
}
|
||||
}
|
||||
|
||||
@ -45,7 +45,7 @@ public class ChatController {
|
||||
public ResponseEntity<SessionResponse> createSession(
|
||||
@PathVariable UUID binaryId,
|
||||
@RequestParam(required = false) UUID projectId,
|
||||
@RequestParam(required = false, defaultValue = "gemma4:12b") String model) {
|
||||
@RequestParam(required = false) String model) {
|
||||
if (projectId == null) {
|
||||
return ResponseEntity.badRequest().build();
|
||||
}
|
||||
|
||||
@ -30,8 +30,14 @@ public class ChatSessionService {
|
||||
private final StaticAnalysisRepository analysisRepository;
|
||||
private final StreamingModelProvider streamingModelProvider;
|
||||
|
||||
@Value("${app.ai.chat.default-model:gemma4:12b}")
|
||||
private String defaultModel;
|
||||
@Value("${AI_CLOUD_CHAT_MODEL:gemma4:12b}")
|
||||
private String cloudChatModel;
|
||||
|
||||
@Value("${spring.ai.ollama.chat.options.model:gemma4:12b}")
|
||||
private String ollamaChatModel;
|
||||
|
||||
@Value("${app.ai.default-chat-provider:ollama}")
|
||||
private String chatProvider;
|
||||
|
||||
@Transactional
|
||||
public ChatSession createSession(UUID binaryId, UUID projectId, String model) {
|
||||
@ -46,7 +52,7 @@ public class ChatSessionService {
|
||||
+ "' has no static analysis yet. Run static analysis before starting a chat session.");
|
||||
}
|
||||
|
||||
var effectiveModel = Objects.nonNull(model) ? model : defaultModel;
|
||||
var effectiveModel = Objects.nonNull(model) ? model : resolveDefaultModel();
|
||||
streamingModelProvider.resolve();
|
||||
|
||||
var session =
|
||||
@ -60,6 +66,14 @@ public class ChatSessionService {
|
||||
return sessionRepository.save(session);
|
||||
}
|
||||
|
||||
private String resolveDefaultModel() {
|
||||
if ("ollama".equals(chatProvider)) {
|
||||
return ollamaChatModel;
|
||||
}
|
||||
|
||||
return cloudChatModel;
|
||||
}
|
||||
|
||||
@Transactional(readOnly = true)
|
||||
public List<ChatSession> getSessions(UUID binaryId) {
|
||||
return sessionRepository.findByBinaryIdOrderByUpdatedAtDesc(binaryId);
|
||||
|
||||
@ -6,6 +6,7 @@ import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.ai.chat.model.StreamingChatModel;
|
||||
import org.springframework.ai.ollama.OllamaChatModel;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@ -13,6 +14,7 @@ import org.springframework.stereotype.Component;
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(name = "app.ai.enabled", havingValue = "true", matchIfMissing = true)
|
||||
@ConditionalOnExpression("'${app.ai.default-chat-provider:ollama}' == 'ollama'")
|
||||
public class OllamaStreamingModelProvider implements StreamingModelProvider {
|
||||
private final ObjectProvider<OllamaChatModel> ollamaChatModelProvider;
|
||||
|
||||
|
||||
@ -0,0 +1,32 @@
|
||||
package ai.decompile.ai.service;
|
||||
|
||||
import java.util.Objects;
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import lombok.extern.log4j.Log4j2;
|
||||
import org.springframework.ai.chat.model.StreamingChatModel;
|
||||
import org.springframework.ai.openai.OpenAiChatModel;
|
||||
import org.springframework.beans.factory.ObjectProvider;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnExpression;
|
||||
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
|
||||
import org.springframework.stereotype.Component;
|
||||
|
||||
@Log4j2
|
||||
@Component
|
||||
@RequiredArgsConstructor
|
||||
@ConditionalOnProperty(name = "app.ai.enabled", havingValue = "true", matchIfMissing = true)
|
||||
@ConditionalOnExpression("'${app.ai.default-chat-provider:ollama}' != 'ollama'")
|
||||
public class OpenAiStreamingModelProvider implements StreamingModelProvider {
|
||||
private final ObjectProvider<OpenAiChatModel> openAiChatModelProvider;
|
||||
|
||||
@Override
|
||||
public StreamingChatModel resolve() {
|
||||
var model = openAiChatModelProvider.getIfAvailable();
|
||||
if (Objects.isNull(model)) {
|
||||
throw new IllegalStateException(
|
||||
"Cloud chat is not configured. Set AI_CLOUD_API_KEY in your .env file.");
|
||||
}
|
||||
|
||||
log.info("Using cloud chat provider with model: {}", model);
|
||||
return model;
|
||||
}
|
||||
}
|
||||
@ -29,13 +29,18 @@ spring:
|
||||
base-url: http://localhost:11434
|
||||
embedding:
|
||||
options:
|
||||
model: ${AI_EMBEDDING_MODEL:qwen3-embedding:latest}
|
||||
model: ${AI_EMBEDDING_MODEL:nomic-embed-text}
|
||||
chat:
|
||||
options:
|
||||
model: ${AI_CHAT_MODEL_OLLAMA:gemma4:12b}
|
||||
temperature: 0.3
|
||||
top-p: 0.95
|
||||
top-k: 64
|
||||
openai:
|
||||
base-url: ${AI_CLOUD_BASE_URL:http://localhost:11434/v1}
|
||||
chat:
|
||||
options:
|
||||
model: ${AI_CLOUD_CHAT_MODEL:gemma4:12b}
|
||||
|
||||
app:
|
||||
storage:
|
||||
@ -63,7 +68,7 @@ app:
|
||||
ai:
|
||||
default-chat-provider: ${AI_DEFAULT_CHAT_PROVIDER:ollama}
|
||||
embedding:
|
||||
dimension: ${AI_EMBEDDING_DIMENSION:4096}
|
||||
dimension: ${AI_EMBEDDING_DIMENSION:768}
|
||||
chunk-max-chars: 3000
|
||||
batch-size: 20
|
||||
rag:
|
||||
|
||||
@ -23,6 +23,7 @@ spring:
|
||||
autoconfigure:
|
||||
exclude:
|
||||
- org.springframework.ai.autoconfigure.ollama.OllamaAutoConfiguration
|
||||
- org.springframework.ai.autoconfigure.openai.OpenAiAutoConfiguration
|
||||
|
||||
app:
|
||||
storage:
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user