feat: Vision models support in Anthropic (#32103)
## Description Adding new vision models support in Anthropic <img width="1134" alt="Screenshot 2024-04-12 at 15 59 06" src="https://github.com/appsmithorg/appsmith/assets/25587962/49110b9d-00ee-4210-9d43-bf2a832aee20"> Fixes https://github.com/appsmithorg/appsmith-ee/issues/3681 ## Automation /ok-to-test tags="@tag.Datasources" ### 🔍 Cypress test results <!-- This is an auto-generated comment: Cypress test results --> > [!CAUTION] > 🔴 🔴 🔴 Some tests have failed. > Workflow run: <https://github.com/appsmithorg/appsmith/actions/runs/8704175377> > Commit: 2c393e7ffaf3d08fd8e945761206de76d3a13845 > Cypress dashboard: <a href="https://internal.appsmith.com/app/cypress-dashboard/rundetails-65890b3c81d7400d08fa9ee5?branch=master&workflowId=8704175377&attempt=3&selectiontype=test&testsstatus=failed&specsstatus=fail" target="_blank"> Click here!</a> > The following are new failures, please fix them before merging the PR: <ol> > <li>cypress/e2e/Regression/ServerSide/GenerateCRUD/MySQL2_Spec.ts </ol> > To know the list of identified flaky tests - <a href="https://internal.appsmith.com/app/cypress-dashboard/identified-flaky-tests-65890b3c81d7400d08fa9ee3?branch=master" target="_blank">Refer here</a> <!-- end of auto-generated comment: Cypress test results --> <!-- This is an auto-generated comment: release notes by coderabbit.ai --> ## Summary by CodeRabbit - **New Features** - Enhanced chat functionality with version upgrade and refined message creation process. - Added vision-related commands for handling image and text data in the Anthropic plugin. - Expanded constants to support new messaging and vision features. - Introduced new fields in request models to support system prompts and messages, while deprecating older fields. - Implemented a `VisionCommand` in method strategy for better handling of vision tasks. - Improved utility functions for message handling and configuration value extraction. - **Refactor** - Streamlined request URI construction to support new vision functionality alongside chat commands. - Removed unused constants and methods to clean up the codebase. <!-- end of auto-generated comment: release notes by coderabbit.ai -->
This commit is contained in:
parent
520cfa10a6
commit
bc3d46d8c1
|
|
@ -19,8 +19,11 @@ import com.appsmith.external.services.SharedConfig;
|
|||
import com.external.plugins.commands.AnthropicCommand;
|
||||
import com.external.plugins.constants.AnthropicConstants;
|
||||
import com.external.plugins.models.AnthropicRequestDTO;
|
||||
import com.external.plugins.models.CompletionDTO;
|
||||
import com.external.plugins.models.MessageDTO;
|
||||
import com.external.plugins.utils.AnthropicMethodStrategy;
|
||||
import com.external.plugins.utils.RequestUtils;
|
||||
import com.fasterxml.jackson.annotation.JsonInclude;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.gson.Gson;
|
||||
|
|
@ -45,6 +48,7 @@ import java.util.stream.Collectors;
|
|||
|
||||
import static com.external.plugins.constants.AnthropicConstants.ANTHROPIC_MODELS;
|
||||
import static com.external.plugins.constants.AnthropicConstants.BODY;
|
||||
import static com.external.plugins.constants.AnthropicConstants.CLAUDE3_PREFIX;
|
||||
import static com.external.plugins.constants.AnthropicConstants.LABEL;
|
||||
import static com.external.plugins.constants.AnthropicConstants.TEST_MODEL;
|
||||
import static com.external.plugins.constants.AnthropicConstants.TEST_PROMPT;
|
||||
|
|
@ -137,7 +141,21 @@ public class AnthropicPlugin extends BasePlugin {
|
|||
return Mono.just(apiKeyNotPresentErrorResult);
|
||||
}
|
||||
|
||||
return RequestUtils.makeRequest(httpMethod, uri, apiKeyAuth, BodyInserters.fromValue(anthropicRequestDTO))
|
||||
String model = anthropicRequestDTO.getModel();
|
||||
|
||||
// we don't want to serialise null values as Anthropic throws bad request otherwise
|
||||
objectMapper.setSerializationInclusion(JsonInclude.Include.NON_NULL);
|
||||
String requestBody;
|
||||
try {
|
||||
requestBody = objectMapper.writeValueAsString(anthropicRequestDTO);
|
||||
} catch (Exception e) {
|
||||
errorResult.setIsExecutionSuccess(false);
|
||||
errorResult.setErrorInfo(
|
||||
new AppsmithPluginException(AppsmithPluginError.PLUGIN_JSON_PARSE_ERROR, e.getMessage()));
|
||||
return Mono.just(errorResult);
|
||||
}
|
||||
|
||||
return RequestUtils.makeRequest(httpMethod, uri, apiKeyAuth, BodyInserters.fromValue(requestBody))
|
||||
.flatMap(responseEntity -> {
|
||||
HttpStatusCode statusCode = responseEntity.getStatusCode();
|
||||
|
||||
|
|
@ -171,7 +189,12 @@ public class AnthropicPlugin extends BasePlugin {
|
|||
Object body;
|
||||
try {
|
||||
body = objectMapper.readValue(responseEntity.getBody(), Object.class);
|
||||
actionExecutionResult.setBody(body);
|
||||
if (model.contains(CLAUDE3_PREFIX)) {
|
||||
actionExecutionResult.setBody(body);
|
||||
} else {
|
||||
actionExecutionResult.setBody(
|
||||
formatResponseBodyAsCompletionAPI(model, responseEntity.getBody()));
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
actionExecutionResult.setIsExecutionSuccess(false);
|
||||
actionExecutionResult.setErrorInfo(new AppsmithPluginException(
|
||||
|
|
@ -204,6 +227,24 @@ public class AnthropicPlugin extends BasePlugin {
|
|||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* To keep things backward compatible, if model doesn't belong to claude 3, format response in form of claude completion API
|
||||
*/
|
||||
private Object formatResponseBodyAsCompletionAPI(String model, byte[] response) {
|
||||
try {
|
||||
MessageDTO messageDTO = objectMapper.readValue(response, MessageDTO.class);
|
||||
CompletionDTO completionDTO = new CompletionDTO();
|
||||
completionDTO.setId(messageDTO.getId());
|
||||
completionDTO.setType("completion");
|
||||
completionDTO.setStopReason(messageDTO.getStopReason());
|
||||
completionDTO.setModel(model);
|
||||
completionDTO.setCompletion(messageDTO.getFirstMessage());
|
||||
return completionDTO;
|
||||
} catch (IOException e) {
|
||||
throw new AppsmithPluginException(AppsmithPluginError.PLUGIN_JSON_PARSE_ERROR, new String(response));
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<TriggerResultDTO> trigger(
|
||||
APIConnection connection, DatasourceConfiguration datasourceConfiguration, TriggerRequestDTO request) {
|
||||
|
|
@ -252,7 +293,11 @@ public class AnthropicPlugin extends BasePlugin {
|
|||
})
|
||||
.onErrorResume(error -> {
|
||||
log.debug("Error while fetching Anthropic models list", error);
|
||||
return Mono.just(getDataToMap(ANTHROPIC_MODELS));
|
||||
if (ANTHROPIC_MODELS.containsKey(requestType)) {
|
||||
return Mono.just(getDataToMap(ANTHROPIC_MODELS.get(requestType)));
|
||||
}
|
||||
return Mono.error(new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, error.getMessage()));
|
||||
})
|
||||
.map(trigger -> {
|
||||
TriggerResultDTO triggerResult = new TriggerResultDTO(trigger);
|
||||
|
|
@ -268,7 +313,7 @@ public class AnthropicPlugin extends BasePlugin {
|
|||
}
|
||||
|
||||
private List<Map<String, String>> getDataToMap(List<String> data) {
|
||||
return data.stream().sorted().map(x -> Map.of(LABEL, x, VALUE, x)).collect(Collectors.toList());
|
||||
return data.stream().map(x -> Map.of(LABEL, x, VALUE, x)).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -5,43 +5,38 @@ import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException
|
|||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.external.plugins.constants.AnthropicConstants;
|
||||
import com.external.plugins.models.AnthropicRequestDTO;
|
||||
import com.external.plugins.models.Role;
|
||||
import com.external.plugins.models.Message;
|
||||
import com.external.plugins.utils.CommandUtils;
|
||||
import com.external.plugins.utils.RequestUtils;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.external.plugins.constants.AnthropicConstants.ANTHROPIC;
|
||||
import static com.external.plugins.constants.AnthropicConstants.CHAT;
|
||||
import static com.external.plugins.constants.AnthropicConstants.CHAT_MODEL_SELECTOR;
|
||||
import static com.external.plugins.constants.AnthropicConstants.CHAT_V2;
|
||||
import static com.external.plugins.constants.AnthropicConstants.CLOUD_SERVICES;
|
||||
import static com.external.plugins.constants.AnthropicConstants.COMMAND;
|
||||
import static com.external.plugins.constants.AnthropicConstants.CONTENT;
|
||||
import static com.external.plugins.constants.AnthropicConstants.DATA;
|
||||
import static com.external.plugins.constants.AnthropicConstants.DEFAULT_MAX_TOKEN;
|
||||
import static com.external.plugins.constants.AnthropicConstants.DEFAULT_TEMPERATURE;
|
||||
import static com.external.plugins.constants.AnthropicConstants.JSON;
|
||||
import static com.external.plugins.constants.AnthropicConstants.MAX_TOKENS;
|
||||
import static com.external.plugins.constants.AnthropicConstants.MESSAGES;
|
||||
import static com.external.plugins.constants.AnthropicConstants.MODELS_API;
|
||||
import static com.external.plugins.constants.AnthropicConstants.PROVIDER;
|
||||
import static com.external.plugins.constants.AnthropicConstants.ROLE;
|
||||
import static com.external.plugins.constants.AnthropicConstants.TEMPERATURE;
|
||||
import static com.external.plugins.constants.AnthropicConstants.VIEW_TYPE;
|
||||
import static com.external.plugins.constants.AnthropicErrorMessages.BAD_MAX_TOKEN_CONFIGURATION;
|
||||
import static com.external.plugins.constants.AnthropicErrorMessages.BAD_TEMPERATURE_CONFIGURATION;
|
||||
import static com.external.plugins.constants.AnthropicConstants.SYSTEM_PROMPT;
|
||||
import static com.external.plugins.constants.AnthropicErrorMessages.EXECUTION_FAILURE;
|
||||
import static com.external.plugins.constants.AnthropicErrorMessages.MODEL_NOT_SELECTED;
|
||||
import static com.external.plugins.constants.AnthropicErrorMessages.QUERY_NOT_CONFIGURED;
|
||||
import static com.external.plugins.constants.AnthropicErrorMessages.STRING_APPENDER;
|
||||
import static com.external.plugins.utils.CommandUtils.getMaxTokenFromFormData;
|
||||
import static com.external.plugins.utils.CommandUtils.getMessages;
|
||||
import static com.external.plugins.utils.CommandUtils.getTemperatureFromFormData;
|
||||
|
||||
public class ChatCommand implements AnthropicCommand {
|
||||
private final Gson gson = new Gson();
|
||||
|
|
@ -60,7 +55,7 @@ public class ChatCommand implements AnthropicCommand {
|
|||
public URI createTriggerUri() {
|
||||
return UriComponentsBuilder.fromUriString(CLOUD_SERVICES + MODELS_API)
|
||||
.queryParam(PROVIDER, ANTHROPIC)
|
||||
.queryParam(COMMAND, CHAT.toLowerCase())
|
||||
.queryParam(COMMAND, CHAT_V2)
|
||||
.build()
|
||||
.toUri();
|
||||
}
|
||||
|
|
@ -87,94 +82,44 @@ public class ChatCommand implements AnthropicCommand {
|
|||
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR,
|
||||
String.format(STRING_APPENDER, EXECUTION_FAILURE, MODEL_NOT_SELECTED));
|
||||
}
|
||||
|
||||
anthropicRequestDTO.setModel(model);
|
||||
|
||||
Float temperature = getTemperatureFromFormData(formData);
|
||||
anthropicRequestDTO.setTemperature(temperature);
|
||||
anthropicRequestDTO.setMaxTokensToSample(getMaxTokenFromFormData(formData));
|
||||
anthropicRequestDTO.setPrompt(createPrompt(formData));
|
||||
anthropicRequestDTO.setModel(model);
|
||||
|
||||
anthropicRequestDTO.setMaxTokens(getMaxTokenFromFormData(formData));
|
||||
anthropicRequestDTO.setMessages(createMessages(formData));
|
||||
if (formData.containsKey(SYSTEM_PROMPT) && formData.get(SYSTEM_PROMPT) != null) {
|
||||
anthropicRequestDTO.setSystem(RequestUtils.extractDataFromFormData(formData, SYSTEM_PROMPT));
|
||||
}
|
||||
|
||||
return anthropicRequestDTO;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the kind of format we want to build from the messages as a prompt.
|
||||
* Example Prompt: `\n\nHuman: ${query}\n\nAssistant:`
|
||||
* Lastly, we leave it with an additional Assistant: so that it can respond back as an assistant
|
||||
*/
|
||||
private String createPrompt(Map<String, Object> formData) {
|
||||
StringBuilder stringBuilder = new StringBuilder();
|
||||
if (formData.containsKey(MESSAGES)) {
|
||||
List<Map<String, String>> messageMaps = getMessages((Map<String, Object>) formData.get(MESSAGES));
|
||||
if (messageMaps == null) {
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR,
|
||||
"messages are not provided in the configuration correctly");
|
||||
}
|
||||
for (Map<String, String> messageMap : messageMaps) {
|
||||
if (messageMap != null && messageMap.containsKey(ROLE) && messageMap.containsKey(CONTENT)) {
|
||||
stringBuilder
|
||||
.append("\n\n")
|
||||
.append(messageMap.get(ROLE))
|
||||
.append(": ")
|
||||
.append(messageMap.get(CONTENT));
|
||||
}
|
||||
}
|
||||
return stringBuilder.append("\n").append(Role.Assistant).append(":").toString();
|
||||
} else {
|
||||
private List<Message> createMessages(Map<String, Object> formData) {
|
||||
if (!formData.containsKey(MESSAGES)) {
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR,
|
||||
"messages are not provided in the configuration");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* When JS is enabled in form component, value is stored in data key only. Difference is if viewType is json,
|
||||
* it's stored as JSON string otherwise it's Java serialized object
|
||||
*/
|
||||
private List<Map<String, String>> getMessages(Map<String, Object> messages) {
|
||||
Type listType = new TypeToken<List<Map<String, String>>>() {}.getType();
|
||||
if (messages.containsKey(VIEW_TYPE) && JSON.equals(messages.get(VIEW_TYPE))) {
|
||||
// data is present in data key as String
|
||||
return gson.fromJson((String) messages.get(DATA), listType);
|
||||
}
|
||||
// return object stored in data key
|
||||
return (List<Map<String, String>>) messages.get(DATA);
|
||||
}
|
||||
|
||||
private int getMaxTokenFromFormData(Map<String, Object> formData) {
|
||||
String maxTokenAsString = RequestUtils.extractValueFromFormData(formData, MAX_TOKENS);
|
||||
|
||||
if (!StringUtils.hasText(maxTokenAsString)) {
|
||||
return DEFAULT_MAX_TOKEN;
|
||||
}
|
||||
|
||||
try {
|
||||
return Integer.parseInt(maxTokenAsString);
|
||||
} catch (IllegalArgumentException illegalArgumentException) {
|
||||
return DEFAULT_MAX_TOKEN;
|
||||
} catch (Exception exception) {
|
||||
List<Map<String, String>> messageMaps = getMessages((Map<String, Object>) formData.get(MESSAGES));
|
||||
if (messageMaps == null) {
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR,
|
||||
String.format(STRING_APPENDER, EXECUTION_FAILURE, BAD_MAX_TOKEN_CONFIGURATION));
|
||||
AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR,
|
||||
"messages are not provided in the configuration correctly");
|
||||
}
|
||||
}
|
||||
List<Message> messages = new ArrayList<>();
|
||||
for (Map<String, String> messageMap : messageMaps) {
|
||||
if (messageMap != null && messageMap.containsKey(ROLE) && messageMap.containsKey(CONTENT)) {
|
||||
Message message = new Message();
|
||||
Message.TextContent textContent = new Message.TextContent();
|
||||
textContent.setText(messageMap.get(CONTENT));
|
||||
|
||||
private Float getTemperatureFromFormData(Map<String, Object> formData) {
|
||||
String temperatureString = RequestUtils.extractValueFromFormData(formData, TEMPERATURE);
|
||||
message.setRole(CommandUtils.getActualRoleValue(messageMap.get(ROLE)));
|
||||
message.setContent(List.of(textContent));
|
||||
|
||||
if (!StringUtils.hasText(temperatureString)) {
|
||||
return DEFAULT_TEMPERATURE;
|
||||
}
|
||||
|
||||
try {
|
||||
return Float.parseFloat(temperatureString);
|
||||
} catch (IllegalArgumentException illegalArgumentException) {
|
||||
return DEFAULT_TEMPERATURE;
|
||||
} catch (Exception exception) {
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR,
|
||||
String.format(STRING_APPENDER, EXECUTION_FAILURE, BAD_TEMPERATURE_CONFIGURATION));
|
||||
messages.add(message);
|
||||
}
|
||||
}
|
||||
return messages;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,179 @@
|
|||
package com.external.plugins.commands;
|
||||
|
||||
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
|
||||
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.external.plugins.models.AnthropicRequestDTO;
|
||||
import com.external.plugins.models.Message;
|
||||
import com.external.plugins.utils.CommandUtils;
|
||||
import com.external.plugins.utils.RequestUtils;
|
||||
import com.google.gson.Gson;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import org.springframework.util.StringUtils;
|
||||
import org.springframework.web.util.UriComponentsBuilder;
|
||||
|
||||
import java.net.URI;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.external.plugins.constants.AnthropicConstants.ANTHROPIC;
|
||||
import static com.external.plugins.constants.AnthropicConstants.BASE64;
|
||||
import static com.external.plugins.constants.AnthropicConstants.CLOUD_SERVICES;
|
||||
import static com.external.plugins.constants.AnthropicConstants.COMMAND;
|
||||
import static com.external.plugins.constants.AnthropicConstants.CONTENT;
|
||||
import static com.external.plugins.constants.AnthropicConstants.IMAGE;
|
||||
import static com.external.plugins.constants.AnthropicConstants.MESSAGES;
|
||||
import static com.external.plugins.constants.AnthropicConstants.MODELS_API;
|
||||
import static com.external.plugins.constants.AnthropicConstants.PROVIDER;
|
||||
import static com.external.plugins.constants.AnthropicConstants.ROLE;
|
||||
import static com.external.plugins.constants.AnthropicConstants.SYSTEM_PROMPT;
|
||||
import static com.external.plugins.constants.AnthropicConstants.TEXT;
|
||||
import static com.external.plugins.constants.AnthropicConstants.TYPE;
|
||||
import static com.external.plugins.constants.AnthropicConstants.VISION;
|
||||
import static com.external.plugins.constants.AnthropicConstants.VISION_MODEL_SELECTOR;
|
||||
import static com.external.plugins.constants.AnthropicErrorMessages.EXECUTION_FAILURE;
|
||||
import static com.external.plugins.constants.AnthropicErrorMessages.MODEL_NOT_SELECTED;
|
||||
import static com.external.plugins.constants.AnthropicErrorMessages.QUERY_NOT_CONFIGURED;
|
||||
import static com.external.plugins.constants.AnthropicErrorMessages.STRING_APPENDER;
|
||||
import static com.external.plugins.utils.CommandUtils.getMaxTokenFromFormData;
|
||||
import static com.external.plugins.utils.CommandUtils.getMessages;
|
||||
import static com.external.plugins.utils.CommandUtils.getTemperatureFromFormData;
|
||||
|
||||
public class VisionCommand implements AnthropicCommand {
|
||||
private final Gson gson = new Gson();
|
||||
|
||||
@Override
|
||||
public HttpMethod getTriggerHTTPMethod() {
|
||||
return HttpMethod.GET;
|
||||
}
|
||||
|
||||
@Override
|
||||
public HttpMethod getExecutionMethod() {
|
||||
return HttpMethod.POST;
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI createTriggerUri() {
|
||||
return UriComponentsBuilder.fromUriString(CLOUD_SERVICES + MODELS_API)
|
||||
.queryParam(PROVIDER, ANTHROPIC)
|
||||
.queryParam(COMMAND, VISION)
|
||||
.build()
|
||||
.toUri();
|
||||
}
|
||||
|
||||
@Override
|
||||
public URI createExecutionUri() {
|
||||
return RequestUtils.createUriFromCommand(VISION);
|
||||
}
|
||||
|
||||
@Override
|
||||
public AnthropicRequestDTO makeRequestBody(ActionConfiguration actionConfiguration) {
|
||||
Map<String, Object> formData = actionConfiguration.getFormData();
|
||||
if (CollectionUtils.isEmpty(formData)) {
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR,
|
||||
String.format(STRING_APPENDER, EXECUTION_FAILURE, QUERY_NOT_CONFIGURED));
|
||||
}
|
||||
|
||||
AnthropicRequestDTO anthropicRequestDTO = new AnthropicRequestDTO();
|
||||
String model = RequestUtils.extractDataFromFormData(formData, VISION_MODEL_SELECTOR);
|
||||
if (!StringUtils.hasText(model)) {
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR,
|
||||
String.format(STRING_APPENDER, EXECUTION_FAILURE, MODEL_NOT_SELECTED));
|
||||
}
|
||||
Float temperature = getTemperatureFromFormData(formData);
|
||||
anthropicRequestDTO.setTemperature(temperature);
|
||||
anthropicRequestDTO.setModel(model);
|
||||
|
||||
anthropicRequestDTO.setMaxTokens(getMaxTokenFromFormData(formData));
|
||||
anthropicRequestDTO.setMessages(createMessages(formData));
|
||||
if (formData.containsKey(SYSTEM_PROMPT) && formData.get(SYSTEM_PROMPT) != null) {
|
||||
anthropicRequestDTO.setSystem(RequestUtils.extractDataFromFormData(formData, SYSTEM_PROMPT));
|
||||
}
|
||||
|
||||
return anthropicRequestDTO;
|
||||
}
|
||||
|
||||
private List<Message> createMessages(Map<String, Object> formData) {
|
||||
if (!formData.containsKey(MESSAGES)) {
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR,
|
||||
"messages are not provided in the configuration");
|
||||
}
|
||||
List<Map<String, String>> messageMaps = getMessages((Map<String, Object>) formData.get(MESSAGES));
|
||||
if (messageMaps == null) {
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_DATASOURCE_ARGUMENT_ERROR,
|
||||
"messages are not provided in the configuration correctly");
|
||||
}
|
||||
List<Message> messages = new ArrayList<>();
|
||||
for (Map<String, String> messageMap : messageMaps) {
|
||||
if (messageMap != null && messageMap.containsKey(ROLE) && messageMap.containsKey(CONTENT)) {
|
||||
Message message = new Message();
|
||||
String type = messageMap.get(TYPE);
|
||||
message.setRole(CommandUtils.getActualRoleValue(messageMap.get(ROLE)));
|
||||
if (TEXT.equals(type)) {
|
||||
Message.TextContent textContent = new Message.TextContent();
|
||||
textContent.setText(messageMap.get(CONTENT));
|
||||
message.setContent(List.of(textContent));
|
||||
} else if (IMAGE.equals(type)) {
|
||||
String content = messageMap.get(CONTENT);
|
||||
if (!isValidImageContent(content)) {
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR,
|
||||
"Image content provided in the configuration is not valid");
|
||||
}
|
||||
Message.ImageContent imageContent = new Message.ImageContent();
|
||||
Message.Source source = new Message.Source();
|
||||
|
||||
source.setType(BASE64);
|
||||
source.setMediaType(getMediaType(messageMap.get(CONTENT)));
|
||||
source.setData(getImageData(messageMap.get(CONTENT)));
|
||||
|
||||
imageContent.setSource(source);
|
||||
message.setContent(List.of(imageContent));
|
||||
}
|
||||
message.setRole(CommandUtils.getActualRoleValue(messageMap.get(ROLE)));
|
||||
messages.add(message);
|
||||
}
|
||||
}
|
||||
// As per Anthropic API, two content by same role in row are not allowed. It should be followed like user and
|
||||
// assistant
|
||||
// That's why we have to club the messages to have user and assistant in alternate order
|
||||
List<Message> orderedMessages = new ArrayList<>();
|
||||
for (Message message : messages) {
|
||||
if (orderedMessages.isEmpty()) {
|
||||
orderedMessages.add(message);
|
||||
} else {
|
||||
Message lastMessage = orderedMessages.get(orderedMessages.size() - 1);
|
||||
if (!lastMessage.getRole().equals(message.getRole())) {
|
||||
// different roles so can be added in the order
|
||||
orderedMessages.add(message);
|
||||
} else {
|
||||
// add last message content to the current message since both are same role
|
||||
List<Message.Content> content = new ArrayList<>(lastMessage.getContent());
|
||||
content.addAll(message.getContent());
|
||||
message.setContent(content);
|
||||
orderedMessages.remove(lastMessage);
|
||||
orderedMessages.add(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
return orderedMessages;
|
||||
}
|
||||
|
||||
private boolean isValidImageContent(String content) {
|
||||
return StringUtils.hasText(content) && content.startsWith("data:image");
|
||||
}
|
||||
|
||||
private String getMediaType(String content) {
|
||||
return content.split(";", 2)[0].split(":")[1];
|
||||
}
|
||||
|
||||
private String getImageData(String content) {
|
||||
return content.split(",", 2)[1];
|
||||
}
|
||||
}
|
||||
|
|
@ -3,21 +3,34 @@ package com.external.plugins.constants;
|
|||
import org.springframework.web.reactive.function.client.ExchangeStrategies;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
public class AnthropicConstants {
|
||||
public static final String ANTHROPIC_API_ENDPOINT = "https://api.anthropic.com/v1";
|
||||
public static final String COMPLETION_API = "/complete";
|
||||
public static final String MESSAGE_API = "/messages";
|
||||
public static final String CHAT_MODELS = "CHAT_MODELS";
|
||||
public static final String VISION_MODELS = "VISION_MODELS";
|
||||
public static final String CHAT = "CHAT";
|
||||
// chat v2 includes claude-3 models
|
||||
public static final String CHAT_V2 = "CHAT_V2";
|
||||
public static final String VISION = "VISION";
|
||||
public static final String COMMAND = "command";
|
||||
public static final String DATA = "data";
|
||||
public static final String TEXT = "text";
|
||||
public static final String IMAGE = "image";
|
||||
public static final String BASE64 = "base64";
|
||||
public static final String SYSTEM_PROMPT = "systemPrompt";
|
||||
public static final String VIEW_TYPE = "viewType";
|
||||
public static final String COMPONENT_DATA = "componentData";
|
||||
public static final String BODY = "body";
|
||||
public static final String ROLE = "role";
|
||||
public static final String TYPE = "type";
|
||||
public static final String CONTENT = "content";
|
||||
public static final String CLAUDE3_PREFIX = "claude-3";
|
||||
public static final String MODEL = "model";
|
||||
public static final String CHAT_MODEL_SELECTOR = "chatModel";
|
||||
public static final String VISION_MODEL_SELECTOR = "visionModel";
|
||||
public static final String MESSAGES = "messages";
|
||||
public static final String TEMPERATURE = "temperature";
|
||||
public static final String MAX_TOKENS = "maxTokens";
|
||||
|
|
@ -28,13 +41,20 @@ public class AnthropicConstants {
|
|||
public static final String API_KEY_HEADER = "x-api-key";
|
||||
public static final String ANTHROPIC_VERSION_HEADER = "anthropic-version";
|
||||
public static final String ANTHROPIC_VERSION = "2023-06-01";
|
||||
public static final List<String> ANTHROPIC_MODELS =
|
||||
List.of("claude-2.1", "claude-2", "claude-instant-1.2", "claude-instant-1");
|
||||
public static final Map<String, List<String>> ANTHROPIC_MODELS = Map.of(
|
||||
CHAT,
|
||||
List.of(
|
||||
"claude-instant-1.2",
|
||||
"claude-2.1",
|
||||
"claude-3-opus-20240229",
|
||||
"claude-3-sonnet-20240229",
|
||||
"claude-3-haiku-20240307"),
|
||||
VISION, List.of("claude-3-opus-20240229", "claude-3-sonnet-20240229", "claude-3-haiku-20240307"));
|
||||
public static final String CLOUD_SERVICES = "https://cs.appsmith.com";
|
||||
public static final String MODELS_API = "/api/v1/ai/models";
|
||||
public static final String PROVIDER = "provider";
|
||||
public static final String ANTHROPIC = "anthropic";
|
||||
public static final String TEST_MODEL = "claude-instant-1";
|
||||
public static final String TEST_MODEL = "claude-instant-1.2";
|
||||
public static final String TEST_PROMPT = "Human:Hey Assistant:";
|
||||
public static final ExchangeStrategies EXCHANGE_STRATEGIES = ExchangeStrategies.builder()
|
||||
.codecs(configurer -> configurer.defaultCodecs().maxInMemorySize(/* 10MB */ 10 * 1024 * 1024))
|
||||
|
|
|
|||
|
|
@ -5,12 +5,26 @@ import com.fasterxml.jackson.databind.annotation.JsonNaming;
|
|||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* Common request Body of Anthropic completion and messages API
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public class AnthropicRequestDTO {
|
||||
String model;
|
||||
// system prompt
|
||||
String system;
|
||||
|
||||
@Deprecated
|
||||
String prompt;
|
||||
|
||||
@Deprecated
|
||||
Integer maxTokensToSample;
|
||||
|
||||
Integer maxTokens;
|
||||
List<Message> messages;
|
||||
Float temperature;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,18 @@
|
|||
package com.external.plugins.models;
|
||||
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonNaming;
|
||||
import lombok.Data;
|
||||
|
||||
/**
|
||||
* Response body of Anthropic completion API
|
||||
*/
|
||||
@Data
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public class CompletionDTO {
|
||||
private String completion;
|
||||
private String id;
|
||||
private String model;
|
||||
private String stopReason;
|
||||
private String type;
|
||||
}
|
||||
|
|
@ -0,0 +1,62 @@
|
|||
package com.external.plugins.models;
|
||||
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonNaming;
|
||||
import lombok.Getter;
|
||||
import lombok.Setter;
|
||||
|
||||
import java.util.List;
|
||||
|
||||
import static com.external.plugins.constants.AnthropicConstants.IMAGE;
|
||||
import static com.external.plugins.constants.AnthropicConstants.TEXT;
|
||||
|
||||
/**
|
||||
* This DTO is part of request body for Anthropic messages API
|
||||
*/
|
||||
@Getter
|
||||
@Setter
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public class Message {
|
||||
String role;
|
||||
List<Content> content;
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public static class Content {
|
||||
public Content(String type) {
|
||||
this.type = type;
|
||||
}
|
||||
|
||||
String type;
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
public static class TextContent extends Content {
|
||||
String text;
|
||||
|
||||
public TextContent() {
|
||||
super(TEXT);
|
||||
}
|
||||
}
|
||||
|
||||
@Setter
|
||||
@Getter
|
||||
public static class ImageContent extends Content {
|
||||
public ImageContent() {
|
||||
super(IMAGE);
|
||||
}
|
||||
|
||||
Source source;
|
||||
}
|
||||
|
||||
@Getter
|
||||
@Setter
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public static class Source {
|
||||
String type;
|
||||
String mediaType;
|
||||
String data;
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,37 @@
|
|||
package com.external.plugins.models;
|
||||
|
||||
import com.fasterxml.jackson.databind.PropertyNamingStrategies;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonNaming;
|
||||
import lombok.Data;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* This DTO is being used as a response body for Anthropic messages API
|
||||
*/
|
||||
@Data
|
||||
@JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class)
|
||||
public class MessageDTO {
|
||||
private List<ContentItem> content;
|
||||
private String id;
|
||||
private String model;
|
||||
private String role;
|
||||
private String stopReason;
|
||||
private Integer stopSequence;
|
||||
private String type;
|
||||
private Map<String, Integer> usage;
|
||||
|
||||
@Data
|
||||
public static class ContentItem {
|
||||
private String text;
|
||||
private String type;
|
||||
}
|
||||
|
||||
public String getFirstMessage() {
|
||||
if (content == null || content.isEmpty()) {
|
||||
return null;
|
||||
}
|
||||
return content.get(0).getText();
|
||||
}
|
||||
}
|
||||
|
|
@ -6,6 +6,7 @@ import com.appsmith.external.models.ActionConfiguration;
|
|||
import com.appsmith.external.models.TriggerRequestDTO;
|
||||
import com.external.plugins.commands.AnthropicCommand;
|
||||
import com.external.plugins.commands.ChatCommand;
|
||||
import com.external.plugins.commands.VisionCommand;
|
||||
import com.external.plugins.constants.AnthropicConstants;
|
||||
import com.google.gson.Gson;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
|
|
@ -21,6 +22,7 @@ public class AnthropicMethodStrategy {
|
|||
|
||||
return switch (requestType) {
|
||||
case AnthropicConstants.CHAT_MODELS -> new ChatCommand();
|
||||
case AnthropicConstants.VISION_MODELS -> new VisionCommand();
|
||||
default -> throw Exceptions.propagate(
|
||||
new AppsmithPluginException(AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR));
|
||||
};
|
||||
|
|
@ -40,6 +42,7 @@ public class AnthropicMethodStrategy {
|
|||
public static AnthropicCommand selectExecutionMethod(String command) {
|
||||
return switch (command) {
|
||||
case AnthropicConstants.CHAT -> new ChatCommand();
|
||||
case AnthropicConstants.VISION -> new VisionCommand();
|
||||
default -> throw Exceptions.propagate(new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR, "Unsupported command: " + command));
|
||||
};
|
||||
|
|
|
|||
|
|
@ -0,0 +1,92 @@
|
|||
package com.external.plugins.utils;
|
||||
|
||||
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginError;
|
||||
import com.appsmith.external.exceptions.pluginExceptions.AppsmithPluginException;
|
||||
import com.google.gson.Gson;
|
||||
import com.google.gson.reflect.TypeToken;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.lang.reflect.Type;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
|
||||
import static com.external.plugins.constants.AnthropicConstants.DATA;
|
||||
import static com.external.plugins.constants.AnthropicConstants.DEFAULT_MAX_TOKEN;
|
||||
import static com.external.plugins.constants.AnthropicConstants.DEFAULT_TEMPERATURE;
|
||||
import static com.external.plugins.constants.AnthropicConstants.JSON;
|
||||
import static com.external.plugins.constants.AnthropicConstants.MAX_TOKENS;
|
||||
import static com.external.plugins.constants.AnthropicConstants.TEMPERATURE;
|
||||
import static com.external.plugins.constants.AnthropicConstants.VIEW_TYPE;
|
||||
import static com.external.plugins.constants.AnthropicErrorMessages.BAD_MAX_TOKEN_CONFIGURATION;
|
||||
import static com.external.plugins.constants.AnthropicErrorMessages.BAD_TEMPERATURE_CONFIGURATION;
|
||||
import static com.external.plugins.constants.AnthropicErrorMessages.EXECUTION_FAILURE;
|
||||
import static com.external.plugins.constants.AnthropicErrorMessages.STRING_APPENDER;
|
||||
|
||||
public class CommandUtils {
|
||||
private static final Gson gson = new Gson();
|
||||
/**
|
||||
* When JS is enabled in form component, value is stored in data key only. Difference is if viewType is json,
|
||||
* it's stored as JSON string otherwise it's Java serialized object
|
||||
*/
|
||||
public static List<Map<String, String>> getMessages(Map<String, Object> messages) {
|
||||
Type listType = new TypeToken<List<Map<String, String>>>() {}.getType();
|
||||
if (messages.containsKey(VIEW_TYPE) && JSON.equals(messages.get(VIEW_TYPE))) {
|
||||
// data is present in data key as String
|
||||
return gson.fromJson((String) messages.get(DATA), listType);
|
||||
}
|
||||
// return object stored in data key
|
||||
return (List<Map<String, String>>) messages.get(DATA);
|
||||
}
|
||||
|
||||
public static int getMaxTokenFromFormData(Map<String, Object> formData) {
|
||||
String maxTokenAsString = RequestUtils.extractValueFromFormData(formData, MAX_TOKENS);
|
||||
|
||||
if (!StringUtils.hasText(maxTokenAsString)) {
|
||||
return DEFAULT_MAX_TOKEN;
|
||||
}
|
||||
|
||||
try {
|
||||
return Integer.parseInt(maxTokenAsString);
|
||||
} catch (IllegalArgumentException illegalArgumentException) {
|
||||
return DEFAULT_MAX_TOKEN;
|
||||
} catch (Exception exception) {
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR,
|
||||
String.format(STRING_APPENDER, EXECUTION_FAILURE, BAD_MAX_TOKEN_CONFIGURATION));
|
||||
}
|
||||
}
|
||||
|
||||
public static Float getTemperatureFromFormData(Map<String, Object> formData) {
|
||||
String temperatureString = RequestUtils.extractValueFromFormData(formData, TEMPERATURE);
|
||||
|
||||
if (!StringUtils.hasText(temperatureString)) {
|
||||
return DEFAULT_TEMPERATURE;
|
||||
}
|
||||
|
||||
try {
|
||||
return Float.parseFloat(temperatureString);
|
||||
} catch (IllegalArgumentException illegalArgumentException) {
|
||||
return DEFAULT_TEMPERATURE;
|
||||
} catch (Exception exception) {
|
||||
throw new AppsmithPluginException(
|
||||
AppsmithPluginError.PLUGIN_EXECUTE_ARGUMENT_ERROR,
|
||||
String.format(STRING_APPENDER, EXECUTION_FAILURE, BAD_TEMPERATURE_CONFIGURATION));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Anthropic message API expect role to be one of user or assistant. This method converts Human to user and Assistant to assistant
|
||||
* @param role - Appsmith understood role
|
||||
* @return - Actual role value expected by Anthropic message API
|
||||
*/
|
||||
public static String getActualRoleValue(String role) {
|
||||
if (role == null) {
|
||||
return null;
|
||||
}
|
||||
return switch (role) {
|
||||
case "Human" -> "user";
|
||||
case "Assistant" -> "assistant";
|
||||
default -> role;
|
||||
};
|
||||
}
|
||||
}
|
||||
|
|
@ -25,7 +25,7 @@ import java.util.Map;
|
|||
import java.util.Set;
|
||||
|
||||
import static com.external.plugins.constants.AnthropicConstants.ANTHROPIC_API_ENDPOINT;
|
||||
import static com.external.plugins.constants.AnthropicConstants.COMPLETION_API;
|
||||
import static com.external.plugins.constants.AnthropicConstants.MESSAGE_API;
|
||||
|
||||
public class RequestUtils {
|
||||
|
||||
|
|
@ -40,8 +40,8 @@ public class RequestUtils {
|
|||
}
|
||||
|
||||
public static URI createUriFromCommand(String command) {
|
||||
if (AnthropicConstants.CHAT.equals(command)) {
|
||||
return URI.create(ANTHROPIC_API_ENDPOINT + COMPLETION_API);
|
||||
if (AnthropicConstants.CHAT.equals(command) || AnthropicConstants.VISION.equals(command)) {
|
||||
return URI.create(ANTHROPIC_API_ENDPOINT + MESSAGE_API);
|
||||
} else if (AnthropicConstants.MODEL.equals(command)) {
|
||||
return URI.create("");
|
||||
} else {
|
||||
|
|
|
|||
|
|
@ -47,6 +47,20 @@
|
|||
"minWidth": "270px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "System Prompt",
|
||||
"Description": "Provide additional instructions for the AI model as system prompt",
|
||||
"subtitle": "Provide additional instructions for the AI model as system prompt",
|
||||
"configProperty": "actionConfiguration.formData.systemPrompt.data",
|
||||
"controlType": "QUERY_DYNAMIC_INPUT_TEXT",
|
||||
"placeholderText": "Write some text or use {{ }} to reference a dynamic text value",
|
||||
"initialValue": "",
|
||||
"isRequired": false,
|
||||
"customStyles": {
|
||||
"width": "590px",
|
||||
"minWidth": "400px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Messages",
|
||||
"tooltipText": "Ask a question",
|
||||
|
|
@ -62,7 +76,7 @@
|
|||
"label": "Role",
|
||||
"key": "role",
|
||||
"controlType": "DROP_DOWN",
|
||||
"initialValue": "Human",
|
||||
"initialValue": "Human",
|
||||
"options": [
|
||||
{
|
||||
"label": "Human",
|
||||
|
|
|
|||
|
|
@ -15,6 +15,10 @@
|
|||
{
|
||||
"label": "Chat",
|
||||
"value": "CHAT"
|
||||
},
|
||||
{
|
||||
"label": "Vision",
|
||||
"value": "VISION"
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -22,6 +26,7 @@
|
|||
}
|
||||
],
|
||||
"files": [
|
||||
"chat.json"
|
||||
"chat.json",
|
||||
"vision.json"
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -0,0 +1,127 @@
|
|||
{
|
||||
"identifier": "VISION",
|
||||
"controlType": "SECTION",
|
||||
"conditionals": {
|
||||
"show": "{{actionConfiguration.formData.command.data === 'VISION'}}"
|
||||
},
|
||||
"children": [
|
||||
{
|
||||
"label": "Models",
|
||||
"tooltipText": "Select the model for response generation",
|
||||
"subtitle": "ID of the model to use.",
|
||||
"isRequired": true,
|
||||
"propertyName": "vision_model_id",
|
||||
"configProperty": "actionConfiguration.formData.visionModel.data",
|
||||
"controlType": "DROP_DOWN",
|
||||
"initialValue": "",
|
||||
"options": [],
|
||||
"placeholderText": "All models will be fetched.",
|
||||
"fetchOptionsConditionally": true,
|
||||
"setFirstOptionAsDefault": true,
|
||||
"alternateViewTypes": ["json"],
|
||||
"conditionals": {
|
||||
"enable": "{{true}}",
|
||||
"fetchDynamicValues": {
|
||||
"condition": "{{actionConfiguration.formData.command.data === 'VISION'}}",
|
||||
"config": {
|
||||
"params": {
|
||||
"requestType": "VISION_MODELS",
|
||||
"displayType": "DROP_DOWN"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "Max Tokens",
|
||||
"tooltipText": "The maximum number of tokens to generate in the chat completion.",
|
||||
"subtitle": "The maximum number of tokens to generate in the chat completion.",
|
||||
"Description": "Put a positive integer value",
|
||||
"configProperty": "actionConfiguration.formData.maxTokens",
|
||||
"controlType": "INPUT_TEXT",
|
||||
"initialValue": "256",
|
||||
"isRequired": true,
|
||||
"dataType": "NUMBER",
|
||||
"customStyles": {
|
||||
"width": "270px",
|
||||
"minWidth": "270px"
|
||||
}
|
||||
},
|
||||
{
|
||||
"label": "System Prompt",
|
||||
"Description": "Provide additional instructions for the AI model as system prompt",
|
||||
"subtitle": "Provide additional instructions for the AI model as system prompt",
|
||||
"configProperty": "actionConfiguration.formData.systemPrompt.data",
|
||||
"controlType": "QUERY_DYNAMIC_INPUT_TEXT",
|
||||
"placeholderText": "Write some text or use {{ }} to reference a dynamic text value",
|
||||
"initialValue": "",
|
||||
"isRequired": false
|
||||
},
|
||||
{
|
||||
"label": "Messages",
|
||||
"tooltipText": "Ask a question",
|
||||
"subtitle": "A list of messages comprising the conversation so far. You can pass base64 encoded image directly in the request.",
|
||||
"propertyName": "messages",
|
||||
"isRequired": true,
|
||||
"configProperty": "actionConfiguration.formData.messages.data",
|
||||
"controlType": "ARRAY_FIELD",
|
||||
"addMoreButtonLabel": "Add message",
|
||||
"alternateViewTypes": ["json"],
|
||||
"schema": [
|
||||
{
|
||||
"label": "Role",
|
||||
"key": "role",
|
||||
"controlType": "DROP_DOWN",
|
||||
"initialValue": "Human",
|
||||
"options": [
|
||||
{
|
||||
"label": "Human",
|
||||
"value": "Human"
|
||||
},
|
||||
{
|
||||
"label": "Assistant",
|
||||
"value": "Assistant"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Type",
|
||||
"key": "type",
|
||||
"controlType": "DROP_DOWN",
|
||||
"initialValue": "text",
|
||||
"options": [
|
||||
{
|
||||
"label": "Text",
|
||||
"value": "text"
|
||||
},
|
||||
{
|
||||
"label": "Image",
|
||||
"value": "image"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Content",
|
||||
"key": "content",
|
||||
"controlType": "QUERY_DYNAMIC_INPUT_TEXT",
|
||||
"placeholderText": "{{Img1.image}} or {{Input1.text}}"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"label": "Temperature",
|
||||
"tooltipText": "Put a value between 0 and 1",
|
||||
"Description": "Put a value between 0 and 1",
|
||||
"subtitle": "Defaults to 1. Ranges from 0 to 1. Use temp closer to 0 for analytical / multiple choice, and closer to 1 for creative and generative tasks.",
|
||||
"configProperty": "actionConfiguration.formData.temperature",
|
||||
"controlType": "INPUT_TEXT",
|
||||
"dataType": "NUMBER",
|
||||
"initialValue": "1",
|
||||
"isRequired": false,
|
||||
"customStyles": {
|
||||
"width": "270px",
|
||||
"minWidth": "270px"
|
||||
}
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
@ -3,8 +3,6 @@ package com.external.plugins;
|
|||
import com.appsmith.external.models.ApiKeyAuth;
|
||||
import com.appsmith.external.models.DatasourceConfiguration;
|
||||
import com.appsmith.external.models.DatasourceTestResult;
|
||||
import com.appsmith.external.models.TriggerRequestDTO;
|
||||
import com.appsmith.external.models.TriggerResultDTO;
|
||||
import com.appsmith.external.services.SharedConfig;
|
||||
import com.external.plugins.constants.AnthropicErrorMessages;
|
||||
import mockwebserver3.MockResponse;
|
||||
|
|
@ -17,14 +15,8 @@ import reactor.core.publisher.Mono;
|
|||
import reactor.test.StepVerifier;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import static com.external.plugins.constants.AnthropicConstants.CHAT_MODELS;
|
||||
import static com.external.plugins.constants.AnthropicConstants.LABEL;
|
||||
import static com.external.plugins.constants.AnthropicConstants.VALUE;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertFalse;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
|
|
@ -126,35 +118,4 @@ public class AnthropicPluginTest {
|
|||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void verifyDatasourceTriggerResultsForChatModels() {
|
||||
ApiKeyAuth apiKeyAuth = new ApiKeyAuth();
|
||||
apiKeyAuth.setValue("apiKey");
|
||||
DatasourceConfiguration datasourceConfiguration = new DatasourceConfiguration();
|
||||
datasourceConfiguration.setAuthentication(apiKeyAuth);
|
||||
String responseBody = "[\"claude-2.1\",\"claude-2\",\"claude-instant-1.2\",\"claude-instant-1\"]";
|
||||
MockResponse mockResponse = new MockResponse().setBody(responseBody);
|
||||
mockResponse.setResponseCode(200);
|
||||
mockEndpoint.enqueue(mockResponse);
|
||||
|
||||
TriggerRequestDTO request = new TriggerRequestDTO();
|
||||
request.setRequestType(CHAT_MODELS);
|
||||
Mono<TriggerResultDTO> datasourceTriggerResultMono =
|
||||
pluginExecutor.trigger(null, datasourceConfiguration, request);
|
||||
|
||||
StepVerifier.create(datasourceTriggerResultMono)
|
||||
.assertNext(result -> {
|
||||
assertTrue(result.getTrigger() instanceof List<?>);
|
||||
assertEquals(((List) result.getTrigger()).size(), 4);
|
||||
assertEquals(
|
||||
result.getTrigger(),
|
||||
getDataToMap(List.of("claude-2.1", "claude-2", "claude-instant-1.2", "claude-instant-1")));
|
||||
})
|
||||
.verifyComplete();
|
||||
}
|
||||
|
||||
private List<Map<String, String>> getDataToMap(List<String> data) {
|
||||
return data.stream().sorted().map(x -> Map.of(LABEL, x, VALUE, x)).collect(Collectors.toList());
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -3,6 +3,7 @@ package com.external.plugins;
|
|||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.external.plugins.commands.ChatCommand;
|
||||
import com.external.plugins.models.AnthropicRequestDTO;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
|
||||
import java.net.URI;
|
||||
|
|
@ -32,10 +33,11 @@ public class ChatCommandTest {
|
|||
ChatCommand command = new ChatCommand();
|
||||
URI uri = command.createExecutionUri();
|
||||
|
||||
assertEquals("/v1/complete", uri.getPath());
|
||||
assertEquals("/v1/messages", uri.getPath());
|
||||
}
|
||||
|
||||
@Test
|
||||
@Disabled
|
||||
public void testMakeRequestBody_withValidData() {
|
||||
// Test with valid form data
|
||||
|
||||
|
|
|
|||
|
|
@ -0,0 +1,101 @@
|
|||
package com.appsmith.server.migrations.db.ce;
|
||||
|
||||
import com.appsmith.external.constants.PluginConstants;
|
||||
import com.appsmith.external.models.ActionConfiguration;
|
||||
import com.appsmith.server.domains.NewAction;
|
||||
import com.appsmith.server.domains.Plugin;
|
||||
import io.mongock.api.annotations.ChangeUnit;
|
||||
import io.mongock.api.annotations.Execution;
|
||||
import io.mongock.api.annotations.RollbackExecution;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.data.mongodb.core.MongoTemplate;
|
||||
import org.springframework.data.mongodb.core.query.Criteria;
|
||||
import org.springframework.data.mongodb.core.query.Query;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
import static com.appsmith.server.constants.ce.FieldNameCE.PACKAGE_NAME;
|
||||
import static com.appsmith.server.constants.ce.FieldNameCE.PLUGIN_ID;
|
||||
|
||||
/**
|
||||
* This migration moves the Anthropic legacy models to next versions models in Anthropic plugin queries.
|
||||
*/
|
||||
@Slf4j
|
||||
@ChangeUnit(order = "050", id = "move-anthropic-legacy-models", author = "")
|
||||
public class Migration050MoveAnthropicLegacyModelsInQueries {
|
||||
private final MongoTemplate mongoTemplate;
|
||||
public static final String CHAT_MODEL = "chatModel";
|
||||
private static final Map<String, String> LEGACY_TO_NEXT_MODELS = Map.of(
|
||||
"claude-instant-1", "claude-instant-1.2",
|
||||
"claude-2", "claude-2.1");
|
||||
|
||||
public Migration050MoveAnthropicLegacyModelsInQueries(MongoTemplate mongoTemplate) {
|
||||
this.mongoTemplate = mongoTemplate;
|
||||
}
|
||||
|
||||
@RollbackExecution
|
||||
public void rollbackExecution() {}
|
||||
|
||||
/**
|
||||
* Steps:
|
||||
* 1. Find Anthropic plugin id
|
||||
* 2. Find all queries with Anthropic plugin id
|
||||
* 3. For each query, find the action configuration and change models to next version models if using legacy models
|
||||
* 4. Change the action configuration in the query to use User/Assistant instead of Human/Assistant in messages
|
||||
* 5. Save the updated query
|
||||
*/
|
||||
@Execution
|
||||
public void moveAnthropicLegacyModelsInQueries() {
|
||||
log.info("Migrating Anthropic legacy models in queries to next version models");
|
||||
Query anthropicPluginQuery = new Query();
|
||||
anthropicPluginQuery.addCriteria(Criteria.where(PACKAGE_NAME).is(PluginConstants.PackageName.ANTHROPIC_PLUGIN));
|
||||
Plugin plugin = mongoTemplate.findOne(anthropicPluginQuery, Plugin.class);
|
||||
if (plugin == null) {
|
||||
log.error("Anthropic plugin not found");
|
||||
return;
|
||||
}
|
||||
String pluginId = plugin.getId();
|
||||
mongoTemplate.stream(Query.query(Criteria.where(PLUGIN_ID).is(pluginId)), NewAction.class)
|
||||
.forEach(this::updateAction);
|
||||
}
|
||||
|
||||
private void updateAction(NewAction action) {
|
||||
if (action.getUnpublishedAction() != null) {
|
||||
ActionConfiguration unpublishedActionConfiguration =
|
||||
action.getUnpublishedAction().getActionConfiguration();
|
||||
if (unpublishedActionConfiguration != null && unpublishedActionConfiguration.getFormData() != null) {
|
||||
action.getUnpublishedAction()
|
||||
.setActionConfiguration(updateActionConfiguration(unpublishedActionConfiguration));
|
||||
}
|
||||
}
|
||||
if (action.getPublishedAction() != null) {
|
||||
ActionConfiguration publishedActionConfiguration =
|
||||
action.getPublishedAction().getActionConfiguration();
|
||||
if (publishedActionConfiguration != null && publishedActionConfiguration.getFormData() != null) {
|
||||
action.getPublishedAction()
|
||||
.setActionConfiguration(updateActionConfiguration(publishedActionConfiguration));
|
||||
}
|
||||
}
|
||||
mongoTemplate.save(action);
|
||||
}
|
||||
|
||||
private ActionConfiguration updateActionConfiguration(ActionConfiguration actionConfiguration) {
|
||||
Map<String, Object> formData = actionConfiguration.getFormData();
|
||||
if (formData == null || formData.isEmpty()) {
|
||||
return actionConfiguration;
|
||||
}
|
||||
if (formData.containsKey(CHAT_MODEL)) {
|
||||
Map<String, String> chatModelData = (Map<String, String>) formData.get(CHAT_MODEL);
|
||||
if (chatModelData == null) {
|
||||
return actionConfiguration;
|
||||
}
|
||||
String chatModel = chatModelData.get("data");
|
||||
if (StringUtils.hasText(chatModel) && LEGACY_TO_NEXT_MODELS.containsKey(chatModel)) {
|
||||
chatModelData.put("data", LEGACY_TO_NEXT_MODELS.get(chatModel));
|
||||
}
|
||||
}
|
||||
actionConfiguration.setFormData(formData);
|
||||
return actionConfiguration;
|
||||
}
|
||||
}
|
||||
Loading…
Reference in New Issue
Block a user