feat: Implement Ollama as a high-level service (#510)

* Initial implementation of Ollama as a service

* Fix model selector in tool window

* Enable image attachment

* Rewrite OllamaSettingsForm in Kt

* Create OllamaInlineCompletionModel and use it for building completion template

* Add support for blocking code completion on models that we don't know support it

* Allow disabling code completion settings

* Disable code completion settings when an unsupported model is entered

* Track FIM template in settings as a derived state

* Update llm-client

* Initial implementation of model combo box

* Add Ollama icon and display models as list

* Make OllamaSettingsState immutable & convert OllamaSettings to Kotlin

* Add refresh models button

* Distinguish between empty/needs refresh/loading

* Avoid storing any model if the combo box is empty

* Fix icon size

* Back to mutable settings
There were some bugs with immutable settings

* Store available models in settings state

* Expose available models in model dropdown

* Add dark icon

* Cleanups for CompletionRequestProvider

* Fix checkstyle issues

* refactor: migrate to SimplePersistentStateComponent

* fix: add code completion stop tokens

* fix: display only one item in the model popup action group

* fix: add back multi model selection

---------

Co-authored-by: Carl-Robert Linnupuu <carlrobertoh@gmail.com>
This commit is contained in:
Jack Boswell 2024-05-08 10:11:13 +12:00 committed by Carl-Robert Linnupuu
parent 7b386686b0
commit 5fb6589dc6
20 changed files with 541 additions and 30 deletions

View file

@ -26,8 +26,8 @@ import ee.carlrobert.codegpt.settings.service.ServiceType;
import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings;
import ee.carlrobert.codegpt.settings.service.custom.CustomServiceChatCompletionSettingsState;
import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettings;
import ee.carlrobert.codegpt.settings.service.custom.CustomServiceState;
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
import ee.carlrobert.codegpt.settings.service.ollama.OllamaSettings;
import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings;
import ee.carlrobert.codegpt.settings.service.you.YouSettings;
import ee.carlrobert.codegpt.telemetry.core.configuration.TelemetryConfiguration;
@ -41,6 +41,8 @@ import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionStandardMes
import ee.carlrobert.llm.client.anthropic.completion.ClaudeMessageImageContent;
import ee.carlrobert.llm.client.anthropic.completion.ClaudeMessageTextContent;
import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest;
import ee.carlrobert.llm.client.ollama.completion.request.OllamaChatCompletionMessage;
import ee.carlrobert.llm.client.ollama.completion.request.OllamaChatCompletionRequest;
import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionModel;
import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionDetailedMessage;
import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionMessage;
@ -56,6 +58,7 @@ import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.ArrayList;
import java.util.Base64;
import java.util.List;
import java.util.Map;
import java.util.NoSuchElementException;
@ -140,7 +143,8 @@ public class CompletionRequestProvider {
public static Request buildCustomOpenAILookupCompletionRequest(String context) {
return buildCustomOpenAIChatCompletionRequest(
ApplicationManager.getApplication().getService(CustomServiceState.class)
ApplicationManager.getApplication().getService(CustomServiceSettings.class)
.getState()
.getChatCompletionSettings(),
List.of(
new OpenAIChatCompletionStandardMessage(
@ -210,7 +214,7 @@ public class CompletionRequestProvider {
@Nullable String model,
CallParameters callParameters) {
var configuration = ConfigurationSettings.getCurrentState();
return new OpenAIChatCompletionRequest.Builder(buildMessages(model, callParameters))
return new OpenAIChatCompletionRequest.Builder(buildOpenAIMessages(model, callParameters))
.setModel(model)
.setMaxTokens(configuration.getMaxTokens())
.setStream(true)
@ -222,7 +226,7 @@ public class CompletionRequestProvider {
CallParameters callParameters) {
return buildCustomOpenAIChatCompletionRequest(
settings,
buildMessages(callParameters),
buildOpenAIMessages(callParameters),
true);
}
@ -307,7 +311,68 @@ public class CompletionRequestProvider {
return request;
}
private List<OpenAIChatCompletionMessage> buildMessages(CallParameters callParameters) {
public OllamaChatCompletionRequest buildOllamaChatCompletionRequest(
CallParameters callParameters
) {
var settings = ApplicationManager.getApplication().getService(OllamaSettings.class).getState();
return new OllamaChatCompletionRequest
.Builder(settings.getModel(), buildOllamaMessages(callParameters))
.build();
}
private List<OllamaChatCompletionMessage> buildOllamaMessages(CallParameters callParameters) {
var message = callParameters.getMessage();
var messages = new ArrayList<OllamaChatCompletionMessage>();
if (callParameters.getConversationType() == ConversationType.DEFAULT) {
String systemPrompt = ConfigurationSettings.getCurrentState().getSystemPrompt();
messages.add(new OllamaChatCompletionMessage("system", systemPrompt, null));
}
if (callParameters.getConversationType() == ConversationType.FIX_COMPILE_ERRORS) {
messages.add(
new OllamaChatCompletionMessage("system", FIX_COMPILE_ERRORS_SYSTEM_PROMPT, null)
);
}
for (var prevMessage : conversation.getMessages()) {
if (callParameters.isRetry() && prevMessage.getId().equals(message.getId())) {
break;
}
var prevMessageImageFilePath = prevMessage.getImageFilePath();
if (prevMessageImageFilePath != null && !prevMessageImageFilePath.isEmpty()) {
try {
var imageFilePath = Path.of(prevMessageImageFilePath);
var imageBytes = Files.readAllBytes(imageFilePath);
var imageBase64 = Base64.getEncoder().encodeToString(imageBytes);
messages.add(
new OllamaChatCompletionMessage(
"user", prevMessage.getPrompt(), List.of(imageBase64)
)
);
} catch (IOException e) {
throw new RuntimeException(e);
}
} else {
messages.add(
new OllamaChatCompletionMessage("user", prevMessage.getPrompt(), null)
);
}
messages.add(
new OllamaChatCompletionMessage("assistant", prevMessage.getResponse(), null)
);
}
if (callParameters.getImageMediaType() != null && callParameters.getImageData().length > 0) {
var imageBase64 = Base64.getEncoder().encodeToString(callParameters.getImageData());
messages.add(
new OllamaChatCompletionMessage("user", message.getPrompt(), List.of(imageBase64))
);
} else {
messages.add(new OllamaChatCompletionMessage("user", message.getPrompt(), null));
}
return messages;
}
private List<OpenAIChatCompletionMessage> buildOpenAIMessages(CallParameters callParameters) {
var message = callParameters.getMessage();
var messages = new ArrayList<OpenAIChatCompletionMessage>();
if (callParameters.getConversationType() == ConversationType.DEFAULT) {
@ -339,7 +404,9 @@ public class CompletionRequestProvider {
} else {
messages.add(new OpenAIChatCompletionStandardMessage("user", prevMessage.getPrompt()));
}
messages.add(new OpenAIChatCompletionStandardMessage("assistant", prevMessage.getResponse()));
messages.add(
new OpenAIChatCompletionStandardMessage("assistant", prevMessage.getResponse())
);
}
if (callParameters.getImageMediaType() != null && callParameters.getImageData().length > 0) {
@ -355,10 +422,10 @@ public class CompletionRequestProvider {
return messages;
}
private List<OpenAIChatCompletionMessage> buildMessages(
private List<OpenAIChatCompletionMessage> buildOpenAIMessages(
@Nullable String model,
CallParameters callParameters) {
var messages = buildMessages(callParameters);
var messages = buildOpenAIMessages(callParameters);
if (model == null
|| GeneralSettings.getCurrentState().getSelectedService() == ServiceType.YOU) {