feat: support claude completions (#398)

This commit is contained in:
Carl-Robert 2024-03-06 12:48:29 +02:00 committed by GitHub
parent 20c31de21d
commit 9706a357d2
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
24 changed files with 283 additions and 10 deletions

View file

@ -8,6 +8,7 @@ public final class Icons {
public static final Icon Default = IconLoader.getIcon("/icons/codegpt.svg", Icons.class);
public static final Icon DefaultSmall =
IconLoader.getIcon("/icons/codegpt-small.svg", Icons.class);
public static final Icon Anthropic = IconLoader.getIcon("/icons/anthropic.svg", Icons.class);
public static final Icon Azure = IconLoader.getIcon("/icons/azure.svg", Icons.class);
public static final Icon Llama = IconLoader.getIcon("/icons/llama.svg", Icons.class);
public static final Icon OpenAI = IconLoader.getIcon("/icons/openai.svg", Icons.class);

View file

@ -4,13 +4,16 @@ import static java.lang.String.format;
import ee.carlrobert.codegpt.CodeGPTPlugin;
import ee.carlrobert.codegpt.completions.you.YouUserManager;
import ee.carlrobert.codegpt.credentials.AnthropicCredentialsManager;
import ee.carlrobert.codegpt.credentials.AzureCredentialsManager;
import ee.carlrobert.codegpt.credentials.LlamaCredentialManager;
import ee.carlrobert.codegpt.credentials.OpenAICredentialManager;
import ee.carlrobert.codegpt.settings.advanced.AdvancedSettings;
import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings;
import ee.carlrobert.codegpt.settings.service.azure.AzureSettings;
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings;
import ee.carlrobert.llm.client.anthropic.ClaudeClient;
import ee.carlrobert.llm.client.azure.AzureClient;
import ee.carlrobert.llm.client.azure.AzureCompletionRequestParams;
import ee.carlrobert.llm.client.llama.LlamaClient;
@ -31,6 +34,13 @@ public class CompletionClientProvider {
.build(getDefaultClientBuilder());
}
public static ClaudeClient getClaudeClient() {
return new ClaudeClient(
AnthropicCredentialsManager.getInstance().getCredential(),
AnthropicSettings.getCurrentState().getApiVersion(),
getDefaultClientBuilder());
}
public static AzureClient getAzureClient() {
var settings = AzureSettings.getCurrentState();
var params = new AzureCompletionRequestParams(

View file

@ -21,6 +21,7 @@ import ee.carlrobert.codegpt.settings.GeneralSettings;
import ee.carlrobert.codegpt.settings.IncludedFilesSettings;
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.CustomServiceSettings;
import ee.carlrobert.codegpt.settings.service.custom.CustomServiceSettingsState;
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
@ -30,6 +31,8 @@ import ee.carlrobert.codegpt.telemetry.core.configuration.TelemetryConfiguration
import ee.carlrobert.codegpt.telemetry.core.service.UserId;
import ee.carlrobert.embedding.EmbeddingsService;
import ee.carlrobert.embedding.ReferencedFile;
import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionRequest;
import ee.carlrobert.llm.client.anthropic.completion.ClaudeCompletionRequestMessage;
import ee.carlrobert.llm.client.llama.completion.LlamaCompletionRequest;
import ee.carlrobert.llm.client.openai.completion.OpenAIChatCompletionModel;
import ee.carlrobert.llm.client.openai.completion.request.OpenAIChatCompletionMessage;
@ -44,6 +47,7 @@ import java.util.NoSuchElementException;
import java.util.Objects;
import java.util.UUID;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import okhttp3.Request;
import okhttp3.RequestBody;
import org.jetbrains.annotations.Nullable;
@ -236,7 +240,29 @@ public class CompletionRequestProvider {
}
}
public List<OpenAIChatCompletionMessage> buildMessages(
public ClaudeCompletionRequest buildAnthropicChatCompletionRequest(
CallParameters callParameters) {
var configuration = ConfigurationSettings.getCurrentState();
var settings = AnthropicSettings.getCurrentState();
var request = new ClaudeCompletionRequest();
request.setModel(settings.getModel());
request.setMaxTokens(configuration.getMaxTokens());
request.setStream(true);
request.setSystem(COMPLETION_SYSTEM_PROMPT);
var messages = conversation.getMessages().stream()
.filter(prevMessage -> prevMessage.getResponse() != null
&& !prevMessage.getResponse().isEmpty())
.flatMap(prevMessage -> Stream.of(
new ClaudeCompletionRequestMessage("user", prevMessage.getPrompt()),
new ClaudeCompletionRequestMessage("assistant", prevMessage.getResponse())))
.collect(toList());
messages.add(
new ClaudeCompletionRequestMessage("user", callParameters.getMessage().getPrompt()));
request.setMessages(messages);
return request;
}
private List<OpenAIChatCompletionMessage> buildMessages(
CallParameters callParameters,
boolean useContextualSearch) {
var message = callParameters.getMessage();

View file

@ -75,6 +75,10 @@ public final class CompletionRequestService {
customConfiguration,
callParameters),
eventListener);
case ANTHROPIC:
return CompletionClientProvider.getClaudeClient().getCompletionAsync(
requestProvider.buildAnthropicChatCompletionRequest(callParameters),
eventListener);
case AZURE:
var azureSettings = AzureSettings.getCurrentState();
return CompletionClientProvider.getAzureClient().getChatCompletionAsync(

View file

@ -8,6 +8,7 @@ import ee.carlrobert.codegpt.completions.CallParameters;
import ee.carlrobert.codegpt.conversations.message.Message;
import ee.carlrobert.codegpt.settings.GeneralSettings;
import ee.carlrobert.codegpt.settings.service.ServiceType;
import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings;
import ee.carlrobert.codegpt.settings.service.azure.AzureSettings;
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings;
@ -192,6 +193,8 @@ public final class ConversationService {
return OpenAISettings.getCurrentState().getModel();
case CUSTOM_OPENAI:
return "CustomService";
case ANTHROPIC:
return AnthropicSettings.getCurrentState().getModel();
case AZURE:
return AzureSettings.getCurrentState().getDeploymentId();
case YOU:

View file

@ -0,0 +1,16 @@
package ee.carlrobert.codegpt.credentials;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.components.Service;
@Service
public final class AnthropicCredentialsManager extends SingleCredentialManager {
private AnthropicCredentialsManager() {
super("ANTHROPIC_API_KEY");
}
public static AnthropicCredentialsManager getInstance() {
return ApplicationManager.getApplication().getService(AnthropicCredentialsManager.class);
}
}

View file

@ -8,6 +8,7 @@ import ee.carlrobert.codegpt.completions.HuggingFaceModel;
import ee.carlrobert.codegpt.completions.llama.LlamaModel;
import ee.carlrobert.codegpt.conversations.Conversation;
import ee.carlrobert.codegpt.settings.service.ServiceType;
import ee.carlrobert.codegpt.settings.service.anthropic.AnthropicSettings;
import ee.carlrobert.codegpt.settings.service.azure.AzureSettings;
import ee.carlrobert.codegpt.settings.service.llama.LlamaSettings;
import ee.carlrobert.codegpt.settings.service.openai.OpenAISettings;
@ -43,6 +44,10 @@ public class GeneralSettings implements PersistentStateComponent<GeneralSettings
state.setSelectedService(ServiceType.OPENAI);
OpenAISettings.getCurrentState().setModel(conversation.getModel());
}
if ("anthropic.chat.completion".equals(clientCode)) {
state.setSelectedService(ServiceType.ANTHROPIC);
AnthropicSettings.getCurrentState().setModel(conversation.getModel());
}
if ("azure.chat.completion".equals(clientCode)) {
state.setSelectedService(ServiceType.AZURE);
}
@ -67,6 +72,8 @@ public class GeneralSettings implements PersistentStateComponent<GeneralSettings
switch (state.getSelectedService()) {
case OPENAI:
return OpenAISettings.getCurrentState().getModel();
case ANTHROPIC:
return AnthropicSettings.getCurrentState().getModel();
case AZURE:
return AzureSettings.getCurrentState().getDeploymentId();
case YOU:

View file

@ -1,5 +1,6 @@
package ee.carlrobert.codegpt.settings;
import static ee.carlrobert.codegpt.settings.service.ServiceType.ANTHROPIC;
import static ee.carlrobert.codegpt.settings.service.ServiceType.AZURE;
import static ee.carlrobert.codegpt.settings.service.ServiceType.CUSTOM_OPENAI;
import static ee.carlrobert.codegpt.settings.service.ServiceType.LLAMA_CPP;
@ -40,6 +41,7 @@ public class GeneralSettingsComponent {
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());

View file

@ -5,10 +5,13 @@ import com.intellij.openapi.options.Configurable;
import com.intellij.openapi.util.Disposer;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.conversations.ConversationsState;
import ee.carlrobert.codegpt.credentials.AnthropicCredentialsManager;
import ee.carlrobert.codegpt.credentials.AzureCredentialsManager;
import ee.carlrobert.codegpt.credentials.CustomServiceCredentialManager;
import ee.carlrobert.codegpt.credentials.LlamaCredentialManager;
import ee.carlrobert.codegpt.credentials.OpenAICredentialManager;
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;
@ -61,6 +64,8 @@ public class GeneralSettingsConfigurable implements Configurable {
|| 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());
@ -76,6 +81,7 @@ public class GeneralSettingsConfigurable implements Configurable {
var openAISettingsForm = serviceSelectionForm.getOpenAISettingsForm();
applyOpenAISettings(openAISettingsForm);
applyCustomOpenAISettings(serviceSelectionForm.getCustomConfigurationSettingsForm());
applyAnthropicSettings(serviceSelectionForm.getAnthropicSettingsForm());
applyAzureSettings(serviceSelectionForm.getAzureSettingsForm());
applyYouSettings(serviceSelectionForm.getYouSettingsForm());
applyLlamaSettings(serviceSelectionForm.getLlamaSettingsForm());
@ -113,6 +119,11 @@ public class GeneralSettingsConfigurable implements Configurable {
YouSettings.getInstance().loadState(form.getCurrentState());
}
private void applyAnthropicSettings(AnthropicSettingsForm form) {
AnthropicSettings.getInstance().loadState(form.getCurrentState());
AnthropicCredentialsManager.getInstance().setCredential(form.getApiKey());
}
private void applyAzureSettings(AzureSettingsForm form) {
AzureSettings.getInstance().loadState(form.getCurrentState());
var azureCredentials = AzureCredentialsManager.getInstance();

View file

@ -1,6 +1,8 @@
package ee.carlrobert.codegpt.settings.service;
import com.intellij.openapi.Disposable;
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;
@ -16,6 +18,7 @@ public class ServiceSelectionForm {
private final OpenAISettingsForm openAISettingsForm;
private final CustomServiceForm customServiceForm;
private final AnthropicSettingsForm anthropicSettingsForm;
private final AzureSettingsForm azureSettingsForm;
private final LlamaSettingsForm llamaSettingsForm;
private final YouSettingsForm youSettingsForm;
@ -24,6 +27,7 @@ public class ServiceSelectionForm {
openAISettingsForm = new OpenAISettingsForm(OpenAISettings.getCurrentState());
customServiceForm = new CustomServiceForm(
CustomServiceSettings.getCurrentState());
anthropicSettingsForm = new AnthropicSettingsForm(AnthropicSettings.getCurrentState());
azureSettingsForm = new AzureSettingsForm(AzureSettings.getCurrentState());
youSettingsForm = new YouSettingsForm(YouSettings.getCurrentState(), parentDisposable);
llamaSettingsForm = new LlamaSettingsForm(LlamaSettings.getCurrentState());
@ -37,6 +41,10 @@ public class ServiceSelectionForm {
return customServiceForm;
}
public AnthropicSettingsForm getAnthropicSettingsForm() {
return anthropicSettingsForm;
}
public AzureSettingsForm getAzureSettingsForm() {
return azureSettingsForm;
}
@ -52,6 +60,7 @@ public class ServiceSelectionForm {
public void resetForms() {
openAISettingsForm.resetForm();
customServiceForm.resetForm();
anthropicSettingsForm.resetForm();
azureSettingsForm.resetForm();
youSettingsForm.resetForm();
llamaSettingsForm.resetForm();

View file

@ -5,6 +5,7 @@ import ee.carlrobert.codegpt.CodeGPTBundle;
public enum ServiceType {
OPENAI("OPENAI", "service.openai.title", "chat.completion"),
CUSTOM_OPENAI("CUSTOM_OPENAI", "service.custom.openai.title", "custom.openai.chat.completion"),
ANTHROPIC("ANTHROPIC", "service.anthropic.title", "anthropic.chat.completion"),
AZURE("AZURE", "service.azure.title", "azure.chat.completion"),
YOU("YOU", "service.you.title", "you.chat.completion"),
LLAMA_CPP("LLAMA_CPP", "service.llama.title", "llama.chat.completion");

View file

@ -0,0 +1,41 @@
package ee.carlrobert.codegpt.settings.service.anthropic;
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 ee.carlrobert.codegpt.credentials.AnthropicCredentialsManager;
import org.apache.commons.lang3.StringUtils;
import org.jetbrains.annotations.NotNull;
@State(name = "CodeGPT_AnthropicSettings", storages = @Storage("CodeGPT_AnthropicSettings.xml"))
public class AnthropicSettings implements PersistentStateComponent<AnthropicSettingsState> {
private AnthropicSettingsState state = new AnthropicSettingsState();
@Override
@NotNull
public AnthropicSettingsState getState() {
return state;
}
@Override
public void loadState(@NotNull AnthropicSettingsState state) {
this.state = state;
}
public static AnthropicSettingsState getCurrentState() {
return getInstance().getState();
}
public static AnthropicSettings getInstance() {
return ApplicationManager.getApplication().getService(AnthropicSettings.class);
}
public boolean isModified(AnthropicSettingsForm form) {
return !form.getCurrentState().equals(state)
|| !StringUtils.equals(
form.getApiKey(),
AnthropicCredentialsManager.getInstance().getCredential());
}
}

View file

@ -0,0 +1,74 @@
package ee.carlrobert.codegpt.settings.service.anthropic;
import static ee.carlrobert.codegpt.ui.UIUtil.withEmptyLeftBorder;
import com.intellij.ui.TitledSeparator;
import com.intellij.ui.components.JBPasswordField;
import com.intellij.ui.components.JBTextField;
import com.intellij.util.ui.FormBuilder;
import com.intellij.util.ui.UI;
import ee.carlrobert.codegpt.CodeGPTBundle;
import ee.carlrobert.codegpt.credentials.AnthropicCredentialsManager;
import ee.carlrobert.codegpt.ui.UIUtil;
import javax.swing.JPanel;
import org.jetbrains.annotations.Nullable;
public class AnthropicSettingsForm {
private final JBPasswordField apiKeyField;
private final JBTextField apiVersionField;
private final JBTextField modelField;
public AnthropicSettingsForm(AnthropicSettingsState settings) {
apiKeyField = new JBPasswordField();
apiKeyField.setColumns(30);
apiKeyField.setText(AnthropicCredentialsManager.getInstance().getCredential());
apiVersionField = new JBTextField(settings.getApiVersion(), 35);
modelField = new JBTextField(settings.getModel(), 35);
}
public JPanel getForm() {
return FormBuilder.createFormBuilder()
.addComponent(new TitledSeparator(CodeGPTBundle.get("shared.configuration")))
.addComponent(withEmptyLeftBorder(UI.PanelFactory.grid()
.add(UI.PanelFactory.panel(apiKeyField)
.withLabel(CodeGPTBundle.get("settingsConfigurable.shared.apiKey.label"))
.resizeX(false)
.withComment(
CodeGPTBundle.get("settingsConfigurable.service.anthropic.apiKey.comment"))
.withCommentHyperlinkListener(UIUtil::handleHyperlinkClicked))
.add(UI.PanelFactory.panel(apiVersionField)
.withLabel(CodeGPTBundle.get("shared.apiVersion"))
.withComment(CodeGPTBundle.get(
"settingsConfigurable.service.anthropic.apiVersion.comment"))
.withCommentHyperlinkListener(UIUtil::handleHyperlinkClicked)
.resizeX(false))
.add(UI.PanelFactory.panel(modelField)
.withLabel(CodeGPTBundle.get("settingsConfigurable.shared.model.label"))
.withComment(CodeGPTBundle.get(
"settingsConfigurable.service.anthropic.model.comment"))
.resizeX(false))
.createPanel()))
.addComponentFillVertically(new JPanel(), 0)
.getPanel();
}
public AnthropicSettingsState getCurrentState() {
var state = new AnthropicSettingsState();
state.setModel(modelField.getText());
state.setApiVersion(apiVersionField.getText());
return state;
}
public void resetForm() {
var state = AnthropicSettings.getCurrentState();
apiKeyField.setText(AnthropicCredentialsManager.getInstance().getCredential());
apiVersionField.setText(state.getApiVersion());
modelField.setText(state.getModel());
}
public @Nullable String getApiKey() {
var apiKey = new String(apiKeyField.getPassword());
return apiKey.isEmpty() ? null : apiKey;
}
}

View file

@ -0,0 +1,42 @@
package ee.carlrobert.codegpt.settings.service.anthropic;
import java.util.Objects;
public class AnthropicSettingsState {
private String apiVersion = "";
private String model = "claude-3-opus-20240229";
public String getApiVersion() {
return apiVersion;
}
public void setApiVersion(String apiVersion) {
this.apiVersion = apiVersion;
}
public String getModel() {
return model;
}
public void setModel(String model) {
this.model = model;
}
@Override
public boolean equals(Object o) {
if (this == o) {
return true;
}
if (o == null || getClass() != o.getClass()) {
return false;
}
AnthropicSettingsState that = (AnthropicSettingsState) o;
return Objects.equals(apiVersion, that.apiVersion) && Objects.equals(model, that.model);
}
@Override
public int hashCode() {
return Objects.hash(apiVersion, model);
}
}

View file

@ -91,7 +91,7 @@ public class AzureSettingsForm {
"settingsConfigurable.service.azure.deploymentId.comment")))
.add(UI.PanelFactory.panel(azureApiVersionField)
.withLabel(CodeGPTBundle.get(
"settingsConfigurable.service.azure.apiVersion.label"))
"shared.apiVersion"))
.resizeX(false)
.withComment(CodeGPTBundle.get(
"settingsConfigurable.service.azure.apiVersion.comment")))

View file

@ -92,8 +92,7 @@ public class CustomServiceForm {
.getPanel();
return FormBuilder.createFormBuilder()
.addComponent(new TitledSeparator(
CodeGPTBundle.get("settingsConfigurable.service.openai.configuration.title")))
.addComponent(new TitledSeparator(CodeGPTBundle.get("shared.configuration")))
.addComponent(withEmptyLeftBorder(form))
.addComponentFillVertically(new JPanel(), 0)
.getPanel();

View file

@ -51,8 +51,7 @@ public class OpenAISettingsForm {
.createPanel();
return FormBuilder.createFormBuilder()
.addComponent(new TitledSeparator(
CodeGPTBundle.get("settingsConfigurable.service.openai.configuration.title")))
.addComponent(new TitledSeparator(CodeGPTBundle.get("shared.configuration")))
.addComponent(withEmptyLeftBorder(configurationGrid))
.addComponentFillVertically(new JPanel(), 0)
.getPanel();

View file

@ -73,6 +73,12 @@ public class ModelComboBoxAction extends ComboBoxAction {
Icons.OpenAI,
presentation));
actionGroup.addSeparator();
actionGroup.add(createModelAction(
ServiceType.ANTHROPIC,
"Anthropic (Claude)",
Icons.Anthropic,
presentation));
actionGroup.addSeparator();
actionGroup.add(
createModelAction(ServiceType.AZURE, "Azure OpenAI", Icons.Azure, presentation));
actionGroup.addSeparator();
@ -105,6 +111,10 @@ public class ModelComboBoxAction extends ComboBoxAction {
.getTemplate()
.getName());
break;
case ANTHROPIC:
templatePresentation.setIcon(Icons.Anthropic);
templatePresentation.setText("Anthropic (Claude)");
break;
case AZURE:
templatePresentation.setIcon(Icons.Azure);
templatePresentation.setText("Azure OpenAI");

View file

@ -18,6 +18,9 @@ public class ModelIconLabel extends JBLabel {
if ("chat.completion".equals(clientCode)) {
setIcon(Icons.OpenAI);
}
if ("anthropic.chat.completion".equals(clientCode)) {
setIcon(Icons.Anthropic);
}
if ("azure.chat.completion".equals(clientCode)) {
setIcon(Icons.Azure);
}