Skip to content

Commit

Permalink
feat: code completion for "Custom OpenAI Service" (#476)
Browse files Browse the repository at this point in the history
* Add code completion setting states for custom service

* Add settings for code completion in Custom OpenAI service

* Move code completion section to the bottom

* Create test testFetchCodeCompletionCustomService

* Add Custom OpenAI to the "Enable/Disable Completion" actions

* New configuration UI separating /v1/chat/completions from /v1/completions

* Code completion for Custom Service

* Formatting fixes

* Move prefix and suffix to templates in body

* Message updates

* New tabbed UI for Chat and Code Completions

* convert to kotlin, improve ui and other minor changes

* fix test connection for chat completions

* add help tooltips

* allow backward compatibility

* support prefix and suffix placeholders

* fix initial state loading

---------

Co-authored-by: Jack Boswell (boswelja) <[email protected]>
Co-authored-by: Carl-Robert Linnupuu <[email protected]>
  • Loading branch information
3 people authored Apr 20, 2024
1 parent c8181a6 commit 14f3254
Show file tree
Hide file tree
Showing 26 changed files with 1,001 additions and 592 deletions.
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@ Receive single-line or whole-function autocomplete suggestions as you type.

![Code Completions](https://github.com/carlrobertoh/CodeGPT-docs/blob/main/images/new/inline-completion.png?raw=true)

> **Note**: Currently supported only on GPT-3.5 and locally-hosted models.
> **Note**: Currently only supported with OpenAI, Custom OpenAI, or LLaMA.
### Chat (with Vision)

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import static ee.carlrobert.codegpt.credentials.CredentialsStore.CredentialKey.CUSTOM_SERVICE_API_KEY;
import static ee.carlrobert.codegpt.util.file.FileUtil.getResourceContent;
import static java.lang.String.format;
import static java.util.Objects.requireNonNull;
import static java.util.stream.Collectors.joining;
import static java.util.stream.Collectors.toList;

Expand All @@ -23,8 +24,9 @@
import ee.carlrobert.codegpt.settings.configuration.ConfigurationSettings;
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.CustomServiceSettingsState;
import ee.carlrobert.codegpt.settings.service.custom.CustomServiceState;
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings;
import ee.carlrobert.codegpt.settings.service.you.YouSettings;
Expand Down Expand Up @@ -114,16 +116,27 @@ public static OpenAIChatCompletionRequest buildOpenAILookupCompletionRequest(Str

public static Request buildCustomOpenAICompletionRequest(String system, String context) {
return buildCustomOpenAIChatCompletionRequest(
CustomServiceSettings.getCurrentState(),
ApplicationManager.getApplication().getService(CustomServiceState.class)
.getChatCompletionSettings(),
List.of(
new OpenAIChatCompletionStandardMessage("system", system),
new OpenAIChatCompletionStandardMessage("user", context)),
true);
}

public static Request buildCustomOpenAICompletionRequest(String input) {
return buildCustomOpenAIChatCompletionRequest(
ApplicationManager.getApplication().getService(CustomServiceSettings.class)
.getState()
.getChatCompletionSettings(),
List.of(new OpenAIChatCompletionStandardMessage("user", input)),
true);
}

public static Request buildCustomOpenAILookupCompletionRequest(String context) {
return buildCustomOpenAIChatCompletionRequest(
CustomServiceSettings.getCurrentState(),
ApplicationManager.getApplication().getService(CustomServiceState.class)
.getChatCompletionSettings(),
List.of(
new OpenAIChatCompletionStandardMessage(
"system",
Expand Down Expand Up @@ -199,29 +212,29 @@ public OpenAIChatCompletionRequest buildOpenAIChatCompletionRequest(
}

public Request buildCustomOpenAIChatCompletionRequest(
CustomServiceSettingsState customConfiguration,
CustomServiceChatCompletionSettingsState settings,
CallParameters callParameters) {
return buildCustomOpenAIChatCompletionRequest(
customConfiguration,
settings,
buildMessages(callParameters),
true);
}

private static Request buildCustomOpenAIChatCompletionRequest(
CustomServiceSettingsState customConfiguration,
CustomServiceChatCompletionSettingsState settings,
List<OpenAIChatCompletionMessage> messages,
boolean streamRequest) {
var requestBuilder = new Request.Builder().url(customConfiguration.getUrl().trim());
var requestBuilder = new Request.Builder().url(requireNonNull(settings.getUrl()).trim());
var credential = CredentialsStore.INSTANCE.getCredential(CUSTOM_SERVICE_API_KEY);
for (var entry : customConfiguration.getHeaders().entrySet()) {
for (var entry : settings.getHeaders().entrySet()) {
String value = entry.getValue();
if (credential != null && value.contains("$CUSTOM_SERVICE_API_KEY")) {
value = value.replace("$CUSTOM_SERVICE_API_KEY", credential);
}
requestBuilder.addHeader(entry.getKey(), value);
}

var body = customConfiguration.getBody().entrySet().stream()
var body = settings.getBody().entrySet().stream()
.collect(Collectors.toMap(
Map.Entry::getKey,
entry -> {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@
import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionStandardMessage;
import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest;
import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionEventSourceListener;
import ee.carlrobert.llm.client.openai.completion.OpenAITextCompletionEventSourceListener;
import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionRequest;
import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionStandardMessage;
import ee.carlrobert.llm.client.openai.completion.response.OpenAIChatCompletionResponse;
Expand All @@ -55,6 +56,15 @@ public static CompletionRequestService getInstance() {
return ApplicationManager.getApplication().getService(CompletionRequestService.class);
}

public EventSource getCustomOpenAICompletionAsync(
Request customRequest,
CompletionEventListener<String> eventListener) {
var httpClient = CompletionClientProvider.getDefaultClientBuilder().build();
return EventSources.createFactory(httpClient).newEventSource(
customRequest,
new OpenAITextCompletionEventSourceListener(eventListener));
}

public EventSource getCustomOpenAIChatCompletionAsync(
Request customRequest,
CompletionEventListener<String> eventListener) {
Expand All @@ -76,7 +86,10 @@ public EventSource getChatCompletionAsync(
eventListener);
case CUSTOM_OPENAI -> getCustomOpenAIChatCompletionAsync(
requestProvider.buildCustomOpenAIChatCompletionRequest(
CustomServiceSettings.getCurrentState(),
ApplicationManager.getApplication()
.getService(CustomServiceSettings.class)
.getState()
.getChatCompletionSettings(),
callParameters),
eventListener);
case ANTHROPIC -> CompletionClientProvider.getClaudeClient().getCompletionAsync(
Expand All @@ -99,14 +112,18 @@ public EventSource getChatCompletionAsync(
public EventSource getCodeCompletionAsync(
InfillRequestDetails requestDetails,
CompletionEventListener<String> eventListener) {
var httpClient = CompletionClientProvider.getDefaultClientBuilder().build();
return switch (GeneralSettings.getCurrentState().getSelectedService()) {
case OPENAI -> CompletionClientProvider.getOpenAIClient()
.getCompletionAsync(
CodeCompletionRequestFactory.INSTANCE.buildOpenAIRequest(requestDetails),
CodeCompletionRequestFactory.buildOpenAIRequest(requestDetails),
eventListener);
case CUSTOM_OPENAI -> EventSources.createFactory(httpClient).newEventSource(
CodeCompletionRequestFactory.buildCustomRequest(requestDetails),
new OpenAITextCompletionEventSourceListener(eventListener));
case LLAMA_CPP -> CompletionClientProvider.getLlamaClient()
.getChatCompletionAsync(
CodeCompletionRequestFactory.INSTANCE.buildLlamaRequest(requestDetails),
CodeCompletionRequestFactory.buildLlamaRequest(requestDetails),
eventListener);
default ->
throw new IllegalArgumentException("Code completion not supported for selected service");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,8 +12,18 @@
import com.intellij.ui.components.JBTextField;
import com.intellij.util.ui.FormBuilder;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.settings.service.ServiceSelectionForm;
import ee.carlrobert.codegpt.settings.service.ServiceType;
import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings;
import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettingsForm;
import ee.carlrobert.codegpt.settings.service.azure.AzureSettings;
import ee.carlrobert.codegpt.settings.service.azure.AzureSettingsForm;
import ee.carlrobert.codegpt.settings.service.custom.CustomServiceForm;
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
import ee.carlrobert.codegpt.settings.service.llama.form.LlamaSettingsForm;
import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings;
import ee.carlrobert.codegpt.settings.service.openai.OpenAISettingsForm;
import ee.carlrobert.codegpt.settings.service.you.YouSettings;
import ee.carlrobert.codegpt.settings.service.you.YouSettingsForm;
import java.awt.CardLayout;
import java.awt.Component;
import java.awt.Container;
Expand All @@ -29,21 +39,30 @@ public class GeneralSettingsComponent {
private final JPanel mainPanel;
private final JBTextField displayNameField;
private final ComboBox<ServiceType> serviceComboBox;
private final ServiceSelectionForm serviceSelectionForm;
private final OpenAISettingsForm openAISettingsForm;
private final CustomServiceForm customConfigurationSettingsForm;
private final AnthropicSettingsForm anthropicSettingsForm;
private final AzureSettingsForm azureSettingsForm;
private final YouSettingsForm youSettingsForm;
private final LlamaSettingsForm llamaSettingsForm;

public GeneralSettingsComponent(Disposable parentDisposable, GeneralSettings settings) {
displayNameField = new JBTextField(settings.getState().getDisplayName(), 20);
serviceSelectionForm = new ServiceSelectionForm(parentDisposable);
openAISettingsForm = new OpenAISettingsForm(OpenAISettings.getCurrentState());
customConfigurationSettingsForm = new CustomServiceForm();
anthropicSettingsForm = new AnthropicSettingsForm(AnthropicSettings.getCurrentState());
azureSettingsForm = new AzureSettingsForm(AzureSettings.getCurrentState());
youSettingsForm = new YouSettingsForm(YouSettings.getCurrentState(), parentDisposable);
llamaSettingsForm = new LlamaSettingsForm(LlamaSettings.getCurrentState());

var cardLayout = new DynamicCardLayout();
var cards = new JPanel(cardLayout);
cards.add(serviceSelectionForm.getOpenAISettingsForm().getForm(), OPENAI.getCode());
cards.add(
serviceSelectionForm.getCustomConfigurationSettingsForm().getForm(),
CUSTOM_OPENAI.getCode());
cards.add(serviceSelectionForm.getAnthropicSettingsForm().getForm(), ANTHROPIC.getCode());
cards.add(serviceSelectionForm.getAzureSettingsForm().getForm(), AZURE.getCode());
cards.add(serviceSelectionForm.getYouSettingsForm(), YOU.getCode());
cards.add(serviceSelectionForm.getLlamaSettingsForm(), LLAMA_CPP.getCode());
cards.add(openAISettingsForm.getForm(), OPENAI.getCode());
cards.add(customConfigurationSettingsForm.getForm(), CUSTOM_OPENAI.getCode());
cards.add(anthropicSettingsForm.getForm(), ANTHROPIC.getCode());
cards.add(azureSettingsForm.getForm(), AZURE.getCode());
cards.add(youSettingsForm, YOU.getCode());
cards.add(llamaSettingsForm, LLAMA_CPP.getCode());
var serviceComboBoxModel = new DefaultComboBoxModel<ServiceType>();
serviceComboBoxModel.addAll(Arrays.stream(ServiceType.values()).toList());
serviceComboBox = new ComboBox<>(serviceComboBoxModel);
Expand All @@ -63,6 +82,30 @@ public GeneralSettingsComponent(Disposable parentDisposable, GeneralSettings set
.getPanel();
}

public OpenAISettingsForm getOpenAISettingsForm() {
return openAISettingsForm;
}

public CustomServiceForm getCustomConfigurationSettingsForm() {
return customConfigurationSettingsForm;
}

public AnthropicSettingsForm getAnthropicSettingsForm() {
return anthropicSettingsForm;
}

public AzureSettingsForm getAzureSettingsForm() {
return azureSettingsForm;
}

public LlamaSettingsForm getLlamaSettingsForm() {
return llamaSettingsForm;
}

public YouSettingsForm getYouSettingsForm() {
return youSettingsForm;
}

public ServiceType getSelectedService() {
return serviceComboBox.getItem();
}
Expand All @@ -79,10 +122,6 @@ public JComponent getPreferredFocusedComponent() {
return displayNameField;
}

public ServiceSelectionForm getServiceSelectionForm() {
return serviceSelectionForm;
}

public String getDisplayName() {
return displayNameField.getText();
}
Expand All @@ -91,6 +130,15 @@ public void setDisplayName(String displayName) {
displayNameField.setText(displayName);
}

public void resetForms() {
openAISettingsForm.resetForm();
customConfigurationSettingsForm.resetForm();
anthropicSettingsForm.resetForm();
azureSettingsForm.resetForm();
youSettingsForm.resetForm();
llamaSettingsForm.resetForm();
}

static class DynamicCardLayout extends CardLayout {

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,6 @@
import ee.carlrobert.codegpt.settings.service.azure.AzureSettings;
import ee.carlrobert.codegpt.settings.service.azure.AzureSettingsForm;
import ee.carlrobert.codegpt.settings.service.custom.CustomServiceForm;
import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettings;
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
import ee.carlrobert.codegpt.settings.service.llama.form.LlamaSettingsForm;
import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings;
Expand Down Expand Up @@ -61,17 +60,15 @@ public JComponent createComponent() {
@Override
public boolean isModified() {
var settings = GeneralSettings.getCurrentState();
var serviceSelectionForm = component.getServiceSelectionForm();

return !component.getDisplayName().equals(settings.getDisplayName())
|| component.getSelectedService() != settings.getSelectedService()
|| OpenAISettings.getInstance().isModified(serviceSelectionForm.getOpenAISettingsForm())
|| CustomServiceSettings.getInstance()
.isModified(serviceSelectionForm.getCustomConfigurationSettingsForm())
|| AnthropicSettings.getInstance()
.isModified(serviceSelectionForm.getAnthropicSettingsForm())
|| AzureSettings.getInstance().isModified(serviceSelectionForm.getAzureSettingsForm())
|| YouSettings.getInstance().isModified(serviceSelectionForm.getYouSettingsForm())
|| LlamaSettings.getInstance().isModified(serviceSelectionForm.getLlamaSettingsForm());
|| OpenAISettings.getInstance().isModified(component.getOpenAISettingsForm())
|| component.getCustomConfigurationSettingsForm().isModified()
|| AnthropicSettings.getInstance().isModified(component.getAnthropicSettingsForm())
|| AzureSettings.getInstance().isModified(component.getAzureSettingsForm())
|| YouSettings.getInstance().isModified(component.getYouSettingsForm())
|| LlamaSettings.getInstance().isModified(component.getLlamaSettingsForm());
}

@Override
Expand All @@ -80,14 +77,13 @@ public void apply() {
settings.setDisplayName(component.getDisplayName());
settings.setSelectedService(component.getSelectedService());

var serviceSelectionForm = component.getServiceSelectionForm();
var openAISettingsForm = serviceSelectionForm.getOpenAISettingsForm();
var openAISettingsForm = component.getOpenAISettingsForm();
applyOpenAISettings(openAISettingsForm);
applyCustomOpenAISettings(serviceSelectionForm.getCustomConfigurationSettingsForm());
applyAnthropicSettings(serviceSelectionForm.getAnthropicSettingsForm());
applyAzureSettings(serviceSelectionForm.getAzureSettingsForm());
applyYouSettings(serviceSelectionForm.getYouSettingsForm());
applyLlamaSettings(serviceSelectionForm.getLlamaSettingsForm());
applyCustomOpenAISettings(component.getCustomConfigurationSettingsForm());
applyAnthropicSettings(component.getAnthropicSettingsForm());
applyAzureSettings(component.getAzureSettingsForm());
applyYouSettings(component.getYouSettingsForm());
applyLlamaSettings(component.getLlamaSettingsForm());

var serviceChanged = component.getSelectedService() != settings.getSelectedService();
var modelChanged = !OpenAISettings.getCurrentState().getModel()
Expand All @@ -109,7 +105,7 @@ private void applyOpenAISettings(OpenAISettingsForm form) {

private void applyCustomOpenAISettings(CustomServiceForm form) {
CredentialsStore.INSTANCE.setCredential(CUSTOM_SERVICE_API_KEY, form.getApiKey());
CustomServiceSettings.getInstance().loadState(form.getCurrentState());
form.applyChanges();
}

private void applyLlamaSettings(LlamaSettingsForm form) {
Expand Down Expand Up @@ -142,7 +138,7 @@ public void reset() {
var settings = GeneralSettings.getCurrentState();
component.setDisplayName(settings.getDisplayName());
component.setSelectedService(settings.getSelectedService());
component.getServiceSelectionForm().resetForms();
component.resetForms();
}

@Override
Expand Down
Loading

0 comments on commit 14f3254

Please sign in to comment.