mirror of
https://github.com/carlrobertoh/ProxyAI.git
synced 2026-05-21 19:13:38 +00:00
feat: add native mistral client support
This commit is contained in:
parent
a6ff38e52c
commit
d50ad140b0
27 changed files with 371 additions and 29 deletions
|
|
@ -12,7 +12,7 @@ jsoup = "1.19.1"
|
|||
jtokkit = "1.1.0"
|
||||
junit = "5.12.1"
|
||||
kotlin = "2.1.20"
|
||||
llm-client = "0.8.44"
|
||||
llm-client = "0.8.46"
|
||||
okio = "3.10.2"
|
||||
tree-sitter = "0.24.5"
|
||||
grpc = "1.71.0"
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ import ee.carlrobert.llm.client.anthropic.ClaudeClient;
|
|||
import ee.carlrobert.llm.client.codegpt.CodeGPTClient;
|
||||
import ee.carlrobert.llm.client.google.GoogleClient;
|
||||
import ee.carlrobert.llm.client.llama.LlamaClient;
|
||||
import ee.carlrobert.llm.client.mistral.MistralClient;
|
||||
import ee.carlrobert.llm.client.ollama.OllamaClient;
|
||||
import ee.carlrobert.llm.client.openai.OpenAIClient;
|
||||
import java.net.InetSocketAddress;
|
||||
|
|
@ -73,6 +74,10 @@ public class CompletionClientProvider {
|
|||
.build(getDefaultClientBuilder());
|
||||
}
|
||||
|
||||
public static MistralClient getMistralClient() {
|
||||
return new MistralClient(getCredential(CredentialKey.MistralApiKey.INSTANCE), getDefaultClientBuilder());
|
||||
}
|
||||
|
||||
public static OkHttpClient.Builder getDefaultClientBuilder() {
|
||||
OkHttpClient.Builder builder = new OkHttpClient.Builder();
|
||||
CertificateManager certificateManager = CertificateManager.getInstance();
|
||||
|
|
|
|||
|
|
@ -122,6 +122,8 @@ public final class CompletionRequestService {
|
|||
.getChatCompletionAsync(completionRequest, eventListener);
|
||||
case OLLAMA -> CompletionClientProvider.getOllamaClient()
|
||||
.getChatCompletionAsync(completionRequest, eventListener);
|
||||
case MISTRAL -> CompletionClientProvider.getMistralClient()
|
||||
.getChatCompletionAsync(completionRequest, eventListener);
|
||||
default -> throw new RuntimeException("Unknown service selected");
|
||||
};
|
||||
}
|
||||
|
|
@ -152,17 +154,16 @@ public final class CompletionRequestService {
|
|||
throw new IllegalStateException("Unknown request type: " + request.getClass());
|
||||
}
|
||||
|
||||
public String getChatCompletion(CompletionRequest request, ServiceType serviceType) {
|
||||
return getChatCompletion(request, serviceType, FeatureType.CHAT);
|
||||
}
|
||||
|
||||
public String getChatCompletion(CompletionRequest request, ServiceType serviceType, FeatureType featureType) {
|
||||
public String getChatCompletion(CompletionRequest request, ServiceType serviceType,
|
||||
FeatureType featureType) {
|
||||
if (request instanceof OpenAIChatCompletionRequest completionRequest) {
|
||||
var response = switch (serviceType) {
|
||||
case OPENAI -> CompletionClientProvider.getOpenAIClient()
|
||||
.getChatCompletion(completionRequest);
|
||||
case OLLAMA -> CompletionClientProvider.getOllamaClient()
|
||||
.getChatCompletion(completionRequest);
|
||||
case MISTRAL -> CompletionClientProvider.getMistralClient()
|
||||
.getChatCompletion(completionRequest);
|
||||
default -> throw new RuntimeException("Unknown service selected");
|
||||
};
|
||||
return tryExtractContent(response).orElseThrow();
|
||||
|
|
@ -227,6 +228,8 @@ public final class CompletionRequestService {
|
|||
CredentialKey.AnthropicApiKey.INSTANCE
|
||||
);
|
||||
case GOOGLE -> CredentialsStore.INSTANCE.isCredentialSet(CredentialKey.GoogleApiKey.INSTANCE);
|
||||
case MISTRAL ->
|
||||
CredentialsStore.INSTANCE.isCredentialSet(CredentialKey.MistralApiKey.INSTANCE);
|
||||
case PROXYAI, CUSTOM_OPENAI, LLAMA_CPP, OLLAMA -> true;
|
||||
};
|
||||
}
|
||||
|
|
|
|||
|
|
@ -10,6 +10,7 @@ public enum ServiceType {
|
|||
CUSTOM_OPENAI("CUSTOM_OPENAI", "service.custom.openai.title", "custom.openai.chat.completion"),
|
||||
ANTHROPIC("ANTHROPIC", "service.anthropic.title", "anthropic.chat.completion"),
|
||||
GOOGLE("GOOGLE", "service.google.title", "google.chat.completion"),
|
||||
MISTRAL("MISTRAL", "service.mistral.title", "mistral.chat.completion"),
|
||||
LLAMA_CPP("LLAMA_CPP", "service.llama.title", "llama.chat.completion"),
|
||||
OLLAMA("OLLAMA", "service.ollama.title", "ollama.chat.completion");
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,32 @@
|
|||
package ee.carlrobert.codegpt.settings.service.mistral;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.openapi.components.PersistentStateComponent;
|
||||
import com.intellij.openapi.components.State;
|
||||
import com.intellij.openapi.components.Storage;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
@State(name = "CodeGPT_MistralSettings", storages = @Storage("CodeGPT_MistralSettings.xml"))
|
||||
public class MistralSettings implements PersistentStateComponent<MistralSettingsState> {
|
||||
|
||||
private MistralSettingsState state = new MistralSettingsState();
|
||||
|
||||
@Override
|
||||
@NotNull
|
||||
public MistralSettingsState getState() {
|
||||
return state;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadState(@NotNull MistralSettingsState state) {
|
||||
this.state = state;
|
||||
}
|
||||
|
||||
public static MistralSettingsState getCurrentState() {
|
||||
return getInstance().getState();
|
||||
}
|
||||
|
||||
public static MistralSettings getInstance() {
|
||||
return ApplicationManager.getApplication().getService(MistralSettings.class);
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,59 @@
|
|||
package ee.carlrobert.codegpt.settings.service.mistral;
|
||||
|
||||
import com.intellij.openapi.application.ApplicationManager;
|
||||
import com.intellij.ui.components.JBPasswordField;
|
||||
import com.intellij.util.ui.FormBuilder;
|
||||
import com.intellij.util.ui.UI;
|
||||
import ee.carlrobert.codegpt.CodeGPTBundle;
|
||||
import ee.carlrobert.codegpt.credentials.CredentialsStore;
|
||||
import ee.carlrobert.codegpt.ui.UIUtil;
|
||||
import javax.swing.JPanel;
|
||||
import javax.swing.SwingUtilities;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
|
||||
public class MistralSettingsForm {
|
||||
|
||||
private final JBPasswordField apiKeyField;
|
||||
|
||||
public MistralSettingsForm(MistralSettingsState settings) {
|
||||
apiKeyField = new JBPasswordField();
|
||||
apiKeyField.setColumns(30);
|
||||
ApplicationManager.getApplication().executeOnPooledThread(() -> {
|
||||
var apiKey = CredentialsStore.getCredential(
|
||||
CredentialsStore.CredentialKey.MistralApiKey.INSTANCE
|
||||
);
|
||||
SwingUtilities.invokeLater(() -> apiKeyField.setText(apiKey));
|
||||
});
|
||||
}
|
||||
|
||||
public JPanel getForm() {
|
||||
return FormBuilder.createFormBuilder()
|
||||
.addComponent(UI.PanelFactory.grid()
|
||||
.add(UI.PanelFactory.panel(apiKeyField)
|
||||
.withLabel(CodeGPTBundle.get("settingsConfigurable.shared.apiKey.label"))
|
||||
.resizeX(false)
|
||||
.withComment(
|
||||
"You can find the API key in your <a href=\"https://console.mistral.ai/api-keys\">Mistral Console</a>.")
|
||||
.withCommentHyperlinkListener(UIUtil::handleHyperlinkClicked))
|
||||
.createPanel())
|
||||
.addComponentFillVertically(new JPanel(), 0)
|
||||
.getPanel();
|
||||
}
|
||||
|
||||
public MistralSettingsState getCurrentState() {
|
||||
var state = new MistralSettingsState();
|
||||
return state;
|
||||
}
|
||||
|
||||
public void resetForm() {
|
||||
var state = MistralSettings.getCurrentState();
|
||||
apiKeyField.setText(
|
||||
CredentialsStore.getCredential(CredentialsStore.CredentialKey.MistralApiKey.INSTANCE)
|
||||
);
|
||||
}
|
||||
|
||||
public @Nullable String getApiKey() {
|
||||
var apiKey = new String(apiKeyField.getPassword());
|
||||
return apiKey.isEmpty() ? null : apiKey;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,33 @@
|
|||
package ee.carlrobert.codegpt.settings.service.mistral;
|
||||
|
||||
import java.util.Objects;
|
||||
|
||||
public class MistralSettingsState {
|
||||
|
||||
private boolean codeCompletionsEnabled = true;
|
||||
|
||||
public boolean isCodeCompletionsEnabled() {
|
||||
return codeCompletionsEnabled;
|
||||
}
|
||||
|
||||
public void setCodeCompletionsEnabled(boolean codeCompletionsEnabled) {
|
||||
this.codeCompletionsEnabled = codeCompletionsEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean equals(Object o) {
|
||||
if (this == o) {
|
||||
return true;
|
||||
}
|
||||
if (o == null || getClass() != o.getClass()) {
|
||||
return false;
|
||||
}
|
||||
MistralSettingsState that = (MistralSettingsState) o;
|
||||
return codeCompletionsEnabled == that.codeCompletionsEnabled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int hashCode() {
|
||||
return Objects.hash(codeCompletionsEnabled);
|
||||
}
|
||||
}
|
||||
|
|
@ -5,6 +5,7 @@ import static ee.carlrobert.codegpt.settings.service.ServiceType.ANTHROPIC;
|
|||
import static ee.carlrobert.codegpt.settings.service.ServiceType.CUSTOM_OPENAI;
|
||||
import static ee.carlrobert.codegpt.settings.service.ServiceType.GOOGLE;
|
||||
import static ee.carlrobert.codegpt.settings.service.ServiceType.LLAMA_CPP;
|
||||
import static ee.carlrobert.codegpt.settings.service.ServiceType.MISTRAL;
|
||||
import static ee.carlrobert.codegpt.settings.service.ServiceType.OLLAMA;
|
||||
import static ee.carlrobert.codegpt.settings.service.ServiceType.OPENAI;
|
||||
import static ee.carlrobert.codegpt.settings.service.ServiceType.PROXYAI;
|
||||
|
|
@ -199,7 +200,7 @@ public class ModelComboBoxAction extends ComboBoxAction {
|
|||
List.of(
|
||||
GoogleModel.GEMINI_2_5_PRO_PREVIEW,
|
||||
GoogleModel.GEMINI_2_5_FLASH_PREVIEW,
|
||||
GoogleModel.GEMINI_2_5_PRO_EXP,
|
||||
GoogleModel.GEMINI_2_5_PRO,
|
||||
GoogleModel.GEMINI_2_0_PRO_EXP,
|
||||
GoogleModel.GEMINI_2_0_FLASH_THINKING_EXP,
|
||||
GoogleModel.GEMINI_2_0_FLASH,
|
||||
|
|
@ -208,6 +209,17 @@ public class ModelComboBoxAction extends ComboBoxAction {
|
|||
actionGroup.add(googleGroup);
|
||||
}
|
||||
|
||||
if (availableProviders.contains(MISTRAL)) {
|
||||
var mistralGroup = DefaultActionGroup.createPopupGroup(() -> "Mistral");
|
||||
mistralGroup.getTemplatePresentation().setIcon(Icons.Mistral);
|
||||
List.of(
|
||||
ModelRegistry.DEVSTRAL_MEDIUM_2507,
|
||||
ModelRegistry.MISTRAL_LARGE_2411,
|
||||
ModelRegistry.CODESTRAL_LATEST)
|
||||
.forEach(model -> mistralGroup.add(createMistralModelAction(model, presentation)));
|
||||
actionGroup.add(mistralGroup);
|
||||
}
|
||||
|
||||
if (availableProviders.contains(LLAMA_CPP) || availableProviders.contains(OLLAMA)) {
|
||||
actionGroup.addSeparator("Offline");
|
||||
|
||||
|
|
@ -302,6 +314,10 @@ public class ModelComboBoxAction extends ComboBoxAction {
|
|||
templatePresentation.setText(getGooglePresentationText());
|
||||
templatePresentation.setIcon(Icons.Google);
|
||||
break;
|
||||
case MISTRAL:
|
||||
templatePresentation.setText(getMistralPresentationText());
|
||||
templatePresentation.setIcon(Icons.Mistral);
|
||||
break;
|
||||
default:
|
||||
break;
|
||||
}
|
||||
|
|
@ -458,4 +474,22 @@ public class ModelComboBoxAction extends ComboBoxAction {
|
|||
.setModel(FeatureType.CHAT,
|
||||
LlamaSettings.getCurrentState().getHuggingFaceModel().getCode(), LLAMA_CPP));
|
||||
}
|
||||
|
||||
private AnAction createMistralModelAction(String modelCode, Presentation comboBoxPresentation) {
|
||||
var modelName = ModelRegistry.getInstance().getModelDisplayName(MISTRAL, modelCode);
|
||||
return createModelAction(
|
||||
MISTRAL,
|
||||
modelName,
|
||||
Icons.Mistral,
|
||||
comboBoxPresentation,
|
||||
() -> ApplicationManager.getApplication().getService(ModelSettings.class)
|
||||
.setModel(FeatureType.CHAT, modelCode, MISTRAL));
|
||||
}
|
||||
|
||||
private String getMistralPresentationText() {
|
||||
var chatModel = ApplicationManager.getApplication().getService(ModelSettings.class).getState()
|
||||
.getModelSelection(FeatureType.CHAT);
|
||||
var modelCode = chatModel != null ? chatModel.getModel() : null;
|
||||
return ModelRegistry.getInstance().getModelDisplayName(MISTRAL, modelCode);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -45,7 +45,8 @@ abstract class CodeCompletionFeatureToggleActions(
|
|||
}
|
||||
|
||||
ANTHROPIC,
|
||||
GOOGLE -> {
|
||||
GOOGLE,
|
||||
MISTRAL -> {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -61,7 +62,8 @@ abstract class CodeCompletionFeatureToggleActions(
|
|||
OPENAI,
|
||||
CUSTOM_OPENAI,
|
||||
LLAMA_CPP,
|
||||
OLLAMA -> true
|
||||
OLLAMA,
|
||||
MISTRAL -> true
|
||||
|
||||
ANTHROPIC,
|
||||
GOOGLE -> false
|
||||
|
|
|
|||
|
|
@ -7,12 +7,12 @@ import ee.carlrobert.codegpt.completions.llama.LlamaModel
|
|||
import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey
|
||||
import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential
|
||||
import ee.carlrobert.codegpt.settings.Placeholder.*
|
||||
import ee.carlrobert.codegpt.settings.service.FeatureType
|
||||
import ee.carlrobert.codegpt.settings.service.ModelSelectionService
|
||||
import ee.carlrobert.codegpt.settings.service.custom.CustomServicesSettings
|
||||
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings
|
||||
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettingsState
|
||||
import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings
|
||||
import ee.carlrobert.codegpt.settings.service.ModelSelectionService
|
||||
import ee.carlrobert.codegpt.settings.service.FeatureType
|
||||
import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest
|
||||
import ee.carlrobert.llm.client.ollama.completion.request.OllamaCompletionRequest
|
||||
import ee.carlrobert.llm.client.ollama.completion.request.OllamaParameters
|
||||
|
|
@ -40,6 +40,9 @@ object CodeCompletionRequestFactory {
|
|||
@JvmStatic
|
||||
fun buildOpenAIRequest(details: InfillRequest): OpenAITextCompletionRequest {
|
||||
return OpenAITextCompletionRequest.Builder(details.prefix)
|
||||
.setModel(
|
||||
ModelSelectionService.getInstance().getModelForFeature(FeatureType.CODE_COMPLETION)
|
||||
)
|
||||
.setSuffix(details.suffix)
|
||||
.setStream(true)
|
||||
.setMaxTokens(MAX_TOKENS)
|
||||
|
|
|
|||
|
|
@ -8,13 +8,14 @@ import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildL
|
|||
import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildOllamaRequest
|
||||
import ee.carlrobert.codegpt.codecompletions.CodeCompletionRequestFactory.buildOpenAIRequest
|
||||
import ee.carlrobert.codegpt.completions.CompletionClientProvider
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType.*
|
||||
import ee.carlrobert.codegpt.settings.service.FeatureType
|
||||
import ee.carlrobert.codegpt.settings.service.ModelSelectionService
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType.*
|
||||
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings
|
||||
import ee.carlrobert.codegpt.settings.service.custom.CustomServicesSettings
|
||||
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings
|
||||
import ee.carlrobert.codegpt.settings.service.mistral.MistralSettings
|
||||
import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings
|
||||
import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings
|
||||
import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionEventSourceListener
|
||||
|
|
@ -30,13 +31,16 @@ class CodeCompletionService(private val project: Project) {
|
|||
return ModelSelectionService.getInstance().getModelForFeature(FeatureType.CODE_COMPLETION)
|
||||
}
|
||||
|
||||
fun isCodeCompletionsEnabled(): Boolean = isCodeCompletionsEnabled(ModelSelectionService.getInstance().getServiceForFeature(FeatureType.CODE_COMPLETION))
|
||||
fun isCodeCompletionsEnabled(): Boolean = isCodeCompletionsEnabled(
|
||||
ModelSelectionService.getInstance().getServiceForFeature(FeatureType.CODE_COMPLETION)
|
||||
)
|
||||
|
||||
fun isCodeCompletionsEnabled(selectedService: ServiceType): Boolean =
|
||||
when (selectedService) {
|
||||
PROXYAI -> service<CodeGPTServiceSettings>().state.codeCompletionSettings.codeCompletionsEnabled
|
||||
OPENAI -> OpenAISettings.getCurrentState().isCodeCompletionsEnabled
|
||||
CUSTOM_OPENAI -> service<CustomServicesSettings>().state.active.codeCompletionSettings.codeCompletionsEnabled
|
||||
MISTRAL -> MistralSettings.getCurrentState().isCodeCompletionsEnabled
|
||||
LLAMA_CPP -> LlamaSettings.isCodeCompletionsPossible()
|
||||
OLLAMA -> service<OllamaSettings>().state.codeCompletionsEnabled
|
||||
else -> false
|
||||
|
|
@ -46,7 +50,8 @@ class CodeCompletionService(private val project: Project) {
|
|||
infillRequest: InfillRequest,
|
||||
eventListener: CompletionEventListener<String>
|
||||
): EventSource {
|
||||
return when (val selectedService = ModelSelectionService.getInstance().getServiceForFeature(FeatureType.CODE_COMPLETION)) {
|
||||
return when (val selectedService =
|
||||
ModelSelectionService.getInstance().getServiceForFeature(FeatureType.CODE_COMPLETION)) {
|
||||
OPENAI -> CompletionClientProvider.getOpenAIClient()
|
||||
.getCompletionAsync(buildOpenAIRequest(infillRequest), eventListener)
|
||||
|
||||
|
|
@ -61,6 +66,9 @@ class CodeCompletionService(private val project: Project) {
|
|||
}
|
||||
)
|
||||
|
||||
MISTRAL -> CompletionClientProvider.getMistralClient()
|
||||
.getCodeCompletionAsync(buildOpenAIRequest(infillRequest), eventListener)
|
||||
|
||||
OLLAMA -> CompletionClientProvider.getOllamaClient()
|
||||
.getCompletionAsync(buildOllamaRequest(infillRequest), eventListener)
|
||||
|
||||
|
|
|
|||
|
|
@ -112,6 +112,7 @@ class DebouncedCodeCompletionProvider : DebouncedInlineCompletionProvider() {
|
|||
ServiceType.CUSTOM_OPENAI -> service<CustomServicesSettings>().state.active.codeCompletionSettings.codeCompletionsEnabled
|
||||
ServiceType.LLAMA_CPP -> LlamaSettings.isCodeCompletionsPossible()
|
||||
ServiceType.OLLAMA -> service<OllamaSettings>().state.codeCompletionsEnabled
|
||||
ServiceType.MISTRAL -> true // Mistral supports code completions
|
||||
ServiceType.ANTHROPIC,
|
||||
ServiceType.GOOGLE,
|
||||
null -> false
|
||||
|
|
|
|||
|
|
@ -29,6 +29,7 @@ interface CompletionRequestFactory {
|
|||
ServiceType.CUSTOM_OPENAI -> CustomOpenAIRequestFactory()
|
||||
ServiceType.ANTHROPIC -> ClaudeRequestFactory()
|
||||
ServiceType.GOOGLE -> GoogleRequestFactory()
|
||||
ServiceType.MISTRAL -> MistralRequestFactory()
|
||||
ServiceType.OLLAMA -> OllamaRequestFactory()
|
||||
ServiceType.LLAMA_CPP -> LlamaRequestFactory()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,50 @@
|
|||
package ee.carlrobert.codegpt.completions.factory
|
||||
|
||||
import com.intellij.openapi.components.service
|
||||
import ee.carlrobert.codegpt.completions.BaseRequestFactory
|
||||
import ee.carlrobert.codegpt.completions.ChatCompletionParameters
|
||||
import ee.carlrobert.codegpt.completions.factory.OpenAIRequestFactory.Companion.buildOpenAIMessages
|
||||
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings
|
||||
import ee.carlrobert.codegpt.settings.service.FeatureType
|
||||
import ee.carlrobert.codegpt.settings.service.ModelSelectionService
|
||||
import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionRequest
|
||||
|
||||
class MistralRequestFactory : BaseRequestFactory() {
|
||||
|
||||
override fun createChatRequest(params: ChatCompletionParameters): OpenAIChatCompletionRequest {
|
||||
val model = ModelSelectionService.getInstance().getModelForFeature(FeatureType.CHAT)
|
||||
val configuration = service<ConfigurationSettings>().state
|
||||
|
||||
return OpenAIChatCompletionRequest.Builder(
|
||||
buildOpenAIMessages(
|
||||
model = model,
|
||||
callParameters = params,
|
||||
referencedFiles = params.referencedFiles,
|
||||
conversationsHistory = params.history,
|
||||
psiStructure = params.psiStructure,
|
||||
)
|
||||
)
|
||||
.setModel(model)
|
||||
.setMaxTokens(configuration.maxTokens)
|
||||
.setMaxCompletionTokens(null)
|
||||
.setStream(true)
|
||||
.setTemperature(configuration.temperature.toDouble())
|
||||
.build()
|
||||
}
|
||||
|
||||
override fun createBasicCompletionRequest(
|
||||
systemPrompt: String,
|
||||
userPrompt: String,
|
||||
maxTokens: Int,
|
||||
stream: Boolean,
|
||||
featureType: FeatureType
|
||||
): OpenAIChatCompletionRequest {
|
||||
val model = ModelSelectionService.getInstance().getModelForFeature(featureType)
|
||||
return OpenAIRequestFactory.createBasicCompletionRequest(
|
||||
systemPrompt,
|
||||
userPrompt,
|
||||
model = model,
|
||||
isStream = stream
|
||||
)
|
||||
}
|
||||
}
|
||||
|
|
@ -68,5 +68,9 @@ object CredentialsStore {
|
|||
data object OllamaApikey : CredentialKey() {
|
||||
override val value: String = "OLLAMA_API_KEY"
|
||||
}
|
||||
|
||||
data object MistralApiKey : CredentialKey() {
|
||||
override val value: String = "MISTRAL_API_KEY"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -9,7 +9,6 @@ import ee.carlrobert.codegpt.settings.models.ModelSettingsState
|
|||
import ee.carlrobert.codegpt.settings.service.FeatureType
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType
|
||||
import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings
|
||||
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTAvailableModels
|
||||
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTServiceSettings
|
||||
import ee.carlrobert.codegpt.settings.service.custom.CustomServicesSettings
|
||||
import ee.carlrobert.codegpt.settings.service.google.GoogleSettings
|
||||
|
|
@ -74,7 +73,7 @@ object LegacySettingsMigration {
|
|||
|
||||
ServiceType.GOOGLE -> {
|
||||
service<GoogleSettings>().state.model
|
||||
?: GoogleModel.GEMINI_PRO.code
|
||||
?: GoogleModel.GEMINI_2_5_PRO.code
|
||||
}
|
||||
|
||||
ServiceType.OLLAMA -> {
|
||||
|
|
@ -96,6 +95,10 @@ object LegacySettingsMigration {
|
|||
.map { it.chatCompletionSettings.body["model"] as String }
|
||||
.lastOrNull() ?: ""
|
||||
}
|
||||
|
||||
ServiceType.MISTRAL -> {
|
||||
ModelRegistry.CODESTRAL_LATEST
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Could not get legacy model for service $serviceType, using default", e)
|
||||
|
|
@ -140,6 +143,10 @@ object LegacySettingsMigration {
|
|||
.map { it.codeCompletionSettings.body["model"] as String }
|
||||
.lastOrNull() ?: ""
|
||||
}
|
||||
|
||||
ServiceType.MISTRAL -> {
|
||||
ModelRegistry.CODESTRAL_LATEST
|
||||
}
|
||||
}
|
||||
} catch (e: Exception) {
|
||||
logger.warn("Could not get legacy model for service $serviceType, using default", e)
|
||||
|
|
@ -153,6 +160,7 @@ object LegacySettingsMigration {
|
|||
ServiceType.OPENAI -> ModelRegistry.GPT_4O
|
||||
ServiceType.ANTHROPIC -> ModelRegistry.CLAUDE_SONNET_4_20250514
|
||||
ServiceType.GOOGLE -> ModelRegistry.GEMINI_2_0_FLASH
|
||||
ServiceType.MISTRAL -> ModelRegistry.DEVSTRAL_MEDIUM_2507
|
||||
ServiceType.OLLAMA -> ModelRegistry.LLAMA_3_2
|
||||
ServiceType.LLAMA_CPP -> ModelRegistry.LLAMA_3_2_3B_INSTRUCT
|
||||
ServiceType.CUSTOM_OPENAI -> ModelRegistry.GPT_4O
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ object ModelIcons {
|
|||
ServiceType.OPENAI -> Icons.OpenAI
|
||||
ServiceType.ANTHROPIC -> Icons.Anthropic
|
||||
ServiceType.GOOGLE -> Icons.Google
|
||||
ServiceType.MISTRAL -> Icons.Mistral
|
||||
ServiceType.OLLAMA -> Icons.Ollama
|
||||
ServiceType.CUSTOM_OPENAI -> Icons.OpenAI
|
||||
ServiceType.LLAMA_CPP -> Icons.Llama
|
||||
|
|
|
|||
|
|
@ -8,9 +8,7 @@ import ee.carlrobert.codegpt.Icons
|
|||
import ee.carlrobert.codegpt.completions.llama.LlamaModel
|
||||
import ee.carlrobert.codegpt.settings.service.FeatureType
|
||||
import ee.carlrobert.codegpt.settings.service.ServiceType
|
||||
import ee.carlrobert.codegpt.settings.service.codegpt.CodeGPTAvailableModels
|
||||
import ee.carlrobert.codegpt.settings.service.custom.CustomServicesSettings
|
||||
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings
|
||||
import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings
|
||||
import ee.carlrobert.llm.client.codegpt.PricingPlan
|
||||
import ee.carlrobert.llm.client.google.models.GoogleModel
|
||||
|
|
@ -63,6 +61,11 @@ class ModelRegistry {
|
|||
setOf(FeatureType.CHAT, FeatureType.AUTO_APPLY, FeatureType.COMMIT_MESSAGE,
|
||||
FeatureType.EDIT_CODE, FeatureType.LOOKUP)
|
||||
),
|
||||
ServiceType.MISTRAL to ModelCapability(
|
||||
ServiceType.MISTRAL,
|
||||
setOf(FeatureType.CHAT, FeatureType.CODE_COMPLETION, FeatureType.AUTO_APPLY,
|
||||
FeatureType.COMMIT_MESSAGE, FeatureType.EDIT_CODE, FeatureType.LOOKUP)
|
||||
),
|
||||
ServiceType.OLLAMA to ModelCapability(
|
||||
ServiceType.OLLAMA,
|
||||
setOf(FeatureType.CHAT, FeatureType.CODE_COMPLETION, FeatureType.AUTO_APPLY,
|
||||
|
|
@ -168,6 +171,7 @@ class ModelRegistry {
|
|||
addAll(getOpenAIChatModels())
|
||||
addAll(getAnthropicModels())
|
||||
addAll(getGoogleModels())
|
||||
addAll(getMistralModels())
|
||||
addAll(getLlamaModels())
|
||||
addAll(getOllamaModels())
|
||||
addAll(getCustomOpenAIModels())
|
||||
|
|
@ -178,6 +182,7 @@ class ModelRegistry {
|
|||
return buildList {
|
||||
addAll(getProxyAICodeModels())
|
||||
add(getOpenAICodeModel())
|
||||
addAll(getMistralCodeModels())
|
||||
addAll(getLlamaModels())
|
||||
addAll(getCustomOpenAICodeModels())
|
||||
addAll(getOllamaModels())
|
||||
|
|
@ -207,9 +212,7 @@ class ModelRegistry {
|
|||
}
|
||||
|
||||
private fun getNextEditModels(): List<ModelSelection> {
|
||||
return listOf(
|
||||
ModelSelection(ServiceType.PROXYAI, ZETA, "Zeta")
|
||||
)
|
||||
return listOf(ModelSelection(ServiceType.PROXYAI, ZETA, "Zeta"))
|
||||
}
|
||||
|
||||
fun getProxyAIChatModels(): List<ModelSelection> {
|
||||
|
|
@ -274,7 +277,7 @@ class ModelRegistry {
|
|||
return listOf(
|
||||
ModelSelection(ServiceType.GOOGLE, GoogleModel.GEMINI_2_5_PRO_PREVIEW.code, GoogleModel.GEMINI_2_5_PRO_PREVIEW.description),
|
||||
ModelSelection(ServiceType.GOOGLE, GoogleModel.GEMINI_2_5_FLASH_PREVIEW.code, GoogleModel.GEMINI_2_5_FLASH_PREVIEW.description),
|
||||
ModelSelection(ServiceType.GOOGLE, GoogleModel.GEMINI_2_5_PRO_EXP.code, GoogleModel.GEMINI_2_5_PRO_EXP.description),
|
||||
ModelSelection(ServiceType.GOOGLE, GoogleModel.GEMINI_2_5_PRO.code, GoogleModel.GEMINI_2_5_PRO.description),
|
||||
ModelSelection(ServiceType.GOOGLE, GoogleModel.GEMINI_2_0_PRO_EXP.code, GoogleModel.GEMINI_2_0_PRO_EXP.description),
|
||||
ModelSelection(ServiceType.GOOGLE, GoogleModel.GEMINI_2_0_FLASH_THINKING_EXP.code, GoogleModel.GEMINI_2_0_FLASH_THINKING_EXP.description),
|
||||
ModelSelection(ServiceType.GOOGLE, GoogleModel.GEMINI_2_0_FLASH.code, GoogleModel.GEMINI_2_0_FLASH.description),
|
||||
|
|
@ -282,6 +285,18 @@ class ModelRegistry {
|
|||
)
|
||||
}
|
||||
|
||||
private fun getMistralModels(): List<ModelSelection> {
|
||||
return listOf(
|
||||
ModelSelection(ServiceType.MISTRAL, DEVSTRAL_MEDIUM_2507, "Devstral Medium"),
|
||||
ModelSelection(ServiceType.MISTRAL, MISTRAL_LARGE_2411, "Mistral Large"),
|
||||
ModelSelection(ServiceType.MISTRAL, CODESTRAL_LATEST, "Codestral"),
|
||||
)
|
||||
}
|
||||
|
||||
private fun getMistralCodeModels(): List<ModelSelection> {
|
||||
return listOf(ModelSelection(ServiceType.MISTRAL, CODESTRAL_LATEST, "Codestral"))
|
||||
}
|
||||
|
||||
private fun getOllamaModels(): List<ModelSelection> {
|
||||
return try {
|
||||
val ollamaSettings = service<OllamaSettings>()
|
||||
|
|
@ -365,12 +380,17 @@ class ModelRegistry {
|
|||
// Google Models
|
||||
const val GEMINI_2_5_PRO_PREVIEW = "gemini-pro-2.5-preview"
|
||||
const val GEMINI_2_5_FLASH_PREVIEW = "gemini-flash-2.5-preview"
|
||||
const val GEMINI_2_5_PRO_EXP = "gemini-pro-2.5-exp"
|
||||
const val GEMINI_2_5_PRO = "gemini-2.5-pro"
|
||||
const val GEMINI_2_0_PRO_EXP = "gemini-pro-2.0-exp"
|
||||
const val GEMINI_2_0_FLASH_THINKING_EXP = "gemini-flash-thinking-2.0-exp"
|
||||
const val GEMINI_2_0_FLASH = "gemini-2.0-flash"
|
||||
const val GEMINI_1_5_PRO = "gemini-1.5-pro"
|
||||
|
||||
// Mistral Models
|
||||
const val MISTRAL_LARGE_2411 = "mistral-large-2411"
|
||||
const val DEVSTRAL_MEDIUM_2507 = "devstral-medium-2507"
|
||||
const val CODESTRAL_LATEST = "codestral-latest"
|
||||
|
||||
// Ollama default models
|
||||
const val LLAMA_3_2 = "llama3.2"
|
||||
|
||||
|
|
|
|||
|
|
@ -79,13 +79,23 @@ class ModelSettings : SimplePersistentStateComponent<ModelSettingsState>(ModelSe
|
|||
}
|
||||
|
||||
fun getModelSelection(featureType: FeatureType): ModelSelection {
|
||||
val details = getModelDetailsState(featureType) ?: throw IllegalStateException("No model selected")
|
||||
val details = getModelDetailsState(featureType)
|
||||
|
||||
if (details == null) {
|
||||
val defaultModel = ModelRegistry.getInstance().getDefaultModelForFeature(featureType)
|
||||
state.setModelSelection(featureType, defaultModel.model, defaultModel.provider)
|
||||
return defaultModel
|
||||
}
|
||||
|
||||
return details.model?.let { model ->
|
||||
details.provider?.let { provider ->
|
||||
ModelRegistry.getInstance().findModel(provider, model)
|
||||
}
|
||||
} ?: throw IllegalStateException("No model found")
|
||||
} ?: run {
|
||||
val defaultModel = ModelRegistry.getInstance().getDefaultModelForFeature(featureType)
|
||||
state.setModelSelection(featureType, defaultModel.model, defaultModel.provider)
|
||||
defaultModel
|
||||
}
|
||||
}
|
||||
|
||||
fun getOrCreateModelSelection(featureType: FeatureType, ): ModelSelection {
|
||||
|
|
|
|||
|
|
@ -67,7 +67,8 @@ class SettingsModelComboBoxAction(
|
|||
ServiceType.ANTHROPIC,
|
||||
ServiceType.OPENAI,
|
||||
ServiceType.CUSTOM_OPENAI,
|
||||
ServiceType.GOOGLE
|
||||
ServiceType.GOOGLE,
|
||||
ServiceType.MISTRAL
|
||||
)
|
||||
val hasCloudProviders = cloudProviders.any { groupedModels.containsKey(it) }
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,40 @@
|
|||
package ee.carlrobert.codegpt.settings.service
|
||||
|
||||
import com.intellij.openapi.components.service
|
||||
import com.intellij.openapi.options.Configurable
|
||||
import ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.MistralApiKey
|
||||
import ee.carlrobert.codegpt.credentials.CredentialsStore.getCredential
|
||||
import ee.carlrobert.codegpt.credentials.CredentialsStore.setCredential
|
||||
import ee.carlrobert.codegpt.settings.service.mistral.MistralSettings
|
||||
import ee.carlrobert.codegpt.settings.service.mistral.MistralSettingsForm
|
||||
import javax.swing.JComponent
|
||||
|
||||
class MistralServiceConfigurable : Configurable {
|
||||
|
||||
private lateinit var component: MistralSettingsForm
|
||||
|
||||
override fun getDisplayName(): String {
|
||||
return "ProxyAI: Mistral Service"
|
||||
}
|
||||
|
||||
override fun createComponent(): JComponent {
|
||||
component = MistralSettingsForm(service<MistralSettings>().state)
|
||||
return component.form
|
||||
}
|
||||
|
||||
override fun isModified(): Boolean {
|
||||
return component.getCurrentState() != service<MistralSettings>().state
|
||||
|| component.getApiKey() != getCredential(MistralApiKey)
|
||||
}
|
||||
|
||||
override fun apply() {
|
||||
setCredential(MistralApiKey, component.getApiKey())
|
||||
service<MistralSettings>().loadState(component.getCurrentState())
|
||||
|
||||
ModelReplacementDialog.showDialogIfNeeded(ServiceType.MISTRAL)
|
||||
}
|
||||
|
||||
override fun reset() {
|
||||
component.resetForm()
|
||||
}
|
||||
}
|
||||
|
|
@ -17,7 +17,15 @@ class ModelSelectionService {
|
|||
pricingPlan: PricingPlan? = null
|
||||
): ModelSelection {
|
||||
return try {
|
||||
service<ModelSettings>().getModelSelection(featureType)
|
||||
val modelDetailsState = service<ModelSettings>().state.getModelSelection(featureType)
|
||||
if (modelDetailsState != null && modelDetailsState.model != null && modelDetailsState.provider != null) {
|
||||
val foundModel = service<ModelRegistry>().findModel(modelDetailsState.provider!!, modelDetailsState.model!!)
|
||||
if (foundModel != null) {
|
||||
return foundModel
|
||||
}
|
||||
}
|
||||
|
||||
service<ModelRegistry>().getDefaultModelForFeature(featureType, pricingPlan)
|
||||
} catch (exception: Exception) {
|
||||
logger.warn(
|
||||
"Error getting model selection for feature: $featureType, using default",
|
||||
|
|
|
|||
|
|
@ -35,6 +35,7 @@ class ServiceConfigurableComponent {
|
|||
"Custom OpenAI" to CustomServiceConfigurable::class.java,
|
||||
"Anthropic" to AnthropicServiceConfigurable::class.java,
|
||||
"Google" to GoogleSettingsConfigurable::class.java,
|
||||
"Mistral" to MistralServiceConfigurable::class.java,
|
||||
"LLaMA C/C++" to LlamaServiceConfigurable::class.java,
|
||||
"Ollama" to OllamaSettingsConfigurable::class.java,
|
||||
).entries.forEach { (name, configurableClass) ->
|
||||
|
|
|
|||
|
|
@ -8,5 +8,5 @@ import ee.carlrobert.llm.client.google.models.GoogleModel
|
|||
class GoogleSettings : SimplePersistentStateComponent<GoogleSettingsState>(GoogleSettingsState())
|
||||
|
||||
class GoogleSettingsState : BaseState() {
|
||||
var model by string(GoogleModel.GEMINI_PRO.code)
|
||||
var model by string(GoogleModel.GEMINI_2_5_PRO.code)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -42,6 +42,8 @@
|
|||
instance="ee.carlrobert.codegpt.settings.service.AnthropicServiceConfigurable"/>
|
||||
<applicationConfigurable id="settings.codegpt.services.google" parentId="settings.codegpt.services" displayName="Google"
|
||||
instance="ee.carlrobert.codegpt.settings.service.google.GoogleSettingsConfigurable"/>
|
||||
<applicationConfigurable id="settings.codegpt.services.mistral" parentId="settings.codegpt.services" displayName="Mistral"
|
||||
instance="ee.carlrobert.codegpt.settings.service.MistralServiceConfigurable"/>
|
||||
<applicationConfigurable id="settings.codegpt.services.llama_cpp" parentId="settings.codegpt.services" displayName="LLaMA C/C++ (Offline)"
|
||||
instance="ee.carlrobert.codegpt.settings.service.LlamaServiceConfigurable"/>
|
||||
<applicationConfigurable id="settings.codegpt.services.ollama" parentId="settings.codegpt.services" displayName="Ollama (Offline)"
|
||||
|
|
@ -68,6 +70,7 @@
|
|||
<applicationService serviceImplementation="ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings"/>
|
||||
<applicationService serviceImplementation="ee.carlrobert.codegpt.settings.service.openai.OpenAISettings"/>
|
||||
<applicationService serviceImplementation="ee.carlrobert.codegpt.settings.service.you.YouSettings"/>
|
||||
<applicationService serviceImplementation="ee.carlrobert.codegpt.settings.service.mistral.MistralSettings"/>
|
||||
<applicationService serviceImplementation="ee.carlrobert.codegpt.settings.service.llama.LlamaSettings"/>
|
||||
<applicationService serviceImplementation="ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings"/>
|
||||
<applicationService serviceImplementation="ee.carlrobert.codegpt.settings.models.ModelSettings"/>
|
||||
|
|
|
|||
|
|
@ -229,6 +229,7 @@ service.custom.openai.title=Custom OpenAI
|
|||
service.anthropic.title=Anthropic
|
||||
service.azure.title=Azure
|
||||
service.google.title=Google
|
||||
service.mistral.title=Mistral
|
||||
service.llama.title=LLaMA C/C++
|
||||
service.ollama.title=Ollama
|
||||
validation.error.model.notExists='%s' is not available, please select another model
|
||||
|
|
|
|||
|
|
@ -70,6 +70,7 @@ class ModelRegistryTest : IntegrationTest() {
|
|||
assertThat(result).anyMatch { it.provider == ServiceType.OPENAI && it.model == "gpt-4.1" }
|
||||
assertThat(result).anyMatch { it.provider == ServiceType.ANTHROPIC && it.model == "claude-sonnet-4-20250514" }
|
||||
assertThat(result).anyMatch { it.provider == ServiceType.GOOGLE && it.model.contains("gemini") }
|
||||
assertThat(result).anyMatch { it.provider == ServiceType.MISTRAL && it.model == "codestral-latest" }
|
||||
assertThat(result).noneMatch { it.provider == ServiceType.ANTHROPIC && it.model == "qwen-2.5-32b-code" }
|
||||
}
|
||||
|
||||
|
|
@ -78,6 +79,7 @@ class ModelRegistryTest : IntegrationTest() {
|
|||
|
||||
assertThat(result).anyMatch { it.provider == ServiceType.PROXYAI && it.model == "qwen-2.5-32b-code" }
|
||||
assertThat(result).anyMatch { it.provider == ServiceType.OPENAI && it.model == "gpt-3.5-turbo-instruct" }
|
||||
assertThat(result).anyMatch { it.provider == ServiceType.MISTRAL && it.model == "codestral-latest" }
|
||||
assertThat(result).noneMatch { it.provider == ServiceType.ANTHROPIC }
|
||||
assertThat(result).noneMatch { it.provider == ServiceType.GOOGLE }
|
||||
}
|
||||
|
|
@ -97,6 +99,7 @@ class ModelRegistryTest : IntegrationTest() {
|
|||
ServiceType.OPENAI,
|
||||
ServiceType.ANTHROPIC,
|
||||
ServiceType.GOOGLE,
|
||||
ServiceType.MISTRAL,
|
||||
ServiceType.OLLAMA,
|
||||
ServiceType.LLAMA_CPP,
|
||||
ServiceType.CUSTOM_OPENAI
|
||||
|
|
@ -109,6 +112,7 @@ class ModelRegistryTest : IntegrationTest() {
|
|||
assertThat(result).containsExactlyInAnyOrder(
|
||||
ServiceType.PROXYAI,
|
||||
ServiceType.OPENAI,
|
||||
ServiceType.MISTRAL,
|
||||
ServiceType.OLLAMA,
|
||||
ServiceType.LLAMA_CPP,
|
||||
ServiceType.CUSTOM_OPENAI
|
||||
|
|
@ -158,6 +162,15 @@ class ModelRegistryTest : IntegrationTest() {
|
|||
assertThat(result.displayName).isEqualTo("Claude Sonnet 4")
|
||||
}
|
||||
|
||||
fun `test findModel with existing mistral model returns model selection`() {
|
||||
val result = modelRegistry.findModel(ServiceType.MISTRAL, "codestral-latest")
|
||||
|
||||
assertThat(result).isNotNull
|
||||
assertThat(result!!.provider).isEqualTo(ServiceType.MISTRAL)
|
||||
assertThat(result.model).isEqualTo("codestral-latest")
|
||||
assertThat(result.displayName).isEqualTo("Codestral")
|
||||
}
|
||||
|
||||
fun `test findModel with non-existing model returns null`() {
|
||||
val result = modelRegistry.findModel(ServiceType.OPENAI, "non-existing-model")
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue