diff --git a/server/libs/modules/components/ai/ai-text-analysis/src/main/java/com/bytechef/component/ai/text/analysis/AiTextAnalysisComponentHandler.java b/server/libs/modules/components/ai/ai-text-analysis/src/main/java/com/bytechef/component/ai/text/analysis/AiTextAnalysisComponentHandler.java index 402a8270930..ed7d75add38 100644 --- a/server/libs/modules/components/ai/ai-text-analysis/src/main/java/com/bytechef/component/ai/text/analysis/AiTextAnalysisComponentHandler.java +++ b/server/libs/modules/components/ai/ai-text-analysis/src/main/java/com/bytechef/component/ai/text/analysis/AiTextAnalysisComponentHandler.java @@ -21,6 +21,7 @@ import com.bytechef.component.ComponentHandler; import com.bytechef.component.ai.text.analysis.action.ClassifyTextAction; +import com.bytechef.component.ai.text.analysis.action.SentimentAnalysisAction; import com.bytechef.component.ai.text.analysis.action.SummarizeTextAction; import com.bytechef.component.definition.ComponentCategory; import com.bytechef.component.definition.ComponentDefinition; @@ -60,6 +61,7 @@ private AiTextAnalysisComponentDefinitionImpl(ApplicationProperties.Ai.Component .categories(ComponentCategory.ARTIFICIAL_INTELLIGENCE) .actions( new ClassifyTextAction(component).actionDefinition, + new SentimentAnalysisAction(component).actionDefinition, new SummarizeTextAction(component).actionDefinition)); } } diff --git a/server/libs/modules/components/ai/ai-text-analysis/src/main/java/com/bytechef/component/ai/text/analysis/action/SentimentAnalysisAction.java b/server/libs/modules/components/ai/ai-text-analysis/src/main/java/com/bytechef/component/ai/text/analysis/action/SentimentAnalysisAction.java new file mode 100644 index 00000000000..6d682df6421 --- /dev/null +++ b/server/libs/modules/components/ai/ai-text-analysis/src/main/java/com/bytechef/component/ai/text/analysis/action/SentimentAnalysisAction.java @@ -0,0 +1,81 @@ +/* + * Copyright 2023-present ByteChef Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.bytechef.component.ai.text.analysis.action; + +import static com.bytechef.component.ai.llm.constant.LLMConstants.MAX_TOKENS_PROPERTY; +import static com.bytechef.component.ai.llm.constant.LLMConstants.MODEL; +import static com.bytechef.component.ai.llm.constant.LLMConstants.TEMPERATURE_PROPERTY; +import static com.bytechef.component.ai.text.analysis.constant.AiTextAnalysisConstants.MODEL_NO_OPTIONS_PROPERTY; +import static com.bytechef.component.ai.text.analysis.constant.AiTextAnalysisConstants.MODEL_OPTIONS_PROPERTY; +import static com.bytechef.component.ai.text.analysis.constant.AiTextAnalysisConstants.MODEL_PROVIDER_PROPERTY; +import static com.bytechef.component.ai.text.analysis.constant.AiTextAnalysisConstants.MODEL_URL_PROPERTY; +import static com.bytechef.component.ai.text.analysis.constant.AiTextAnalysisConstants.TEXT; +import static com.bytechef.component.definition.ComponentDsl.action; +import static com.bytechef.component.definition.ComponentDsl.string; + +import com.bytechef.component.ai.text.analysis.action.definition.AiTextAnalysisActionDefinition; +import com.bytechef.component.ai.text.analysis.constant.AiTextAnalysisConstants; +import com.bytechef.component.definition.Parameters; +import com.bytechef.config.ApplicationProperties; +import com.bytechef.platform.component.definition.ParametersFactory; +import java.util.HashMap; +import java.util.List; +import java.util.Map; + +public class SentimentAnalysisAction implements AiTextAnalysisAction { + public final AiTextAnalysisActionDefinition actionDefinition; + + public SentimentAnalysisAction(ApplicationProperties.Ai.Component component) { + this.actionDefinition = getActionDefinition(component); + } + + private AiTextAnalysisActionDefinition getActionDefinition(ApplicationProperties.Ai.Component component) { + return new AiTextAnalysisActionDefinition( + action(AiTextAnalysisConstants.SENTIMENT_ANALYSIS) + .title("Sentiment Analysis") + .description("The sentiment of the text is typically categorized as POSITIVE, NEGATIVE, or NEUTRAL.") + .properties( + MODEL_PROVIDER_PROPERTY, + MODEL_OPTIONS_PROPERTY, + MODEL_NO_OPTIONS_PROPERTY, + MODEL_URL_PROPERTY, + string(TEXT) + .label("Text") + .description("The text that is to be classified.") + .required(true), + MAX_TOKENS_PROPERTY, + TEMPERATURE_PROPERTY) + .output(), + component, this); + } + + public Parameters createParameters(Parameters inputParameters) { + Map modelInputParametersMap = new HashMap<>(); + + String systemPrompt = + "You are a sentiment analysis specialist. I will give you some text and you will only reply with 'POSITIVE', 'NEGATIVE' or 'NEUTRAL'."; + + String userBuilder = "Text: " + inputParameters.getString(TEXT); + + modelInputParametersMap.put( + "messages", + List.of(Map.of("content", systemPrompt, "role", "system"), Map.of("content", userBuilder, "role", "user"))); + modelInputParametersMap.put("model", inputParameters.getString(MODEL)); + + return ParametersFactory.createParameters(modelInputParametersMap); + } +} diff --git a/server/libs/modules/components/ai/ai-text-analysis/src/main/java/com/bytechef/component/ai/text/analysis/constant/AiTextAnalysisConstants.java b/server/libs/modules/components/ai/ai-text-analysis/src/main/java/com/bytechef/component/ai/text/analysis/constant/AiTextAnalysisConstants.java index c8e4e80a43a..09bccb1a0ae 100644 --- a/server/libs/modules/components/ai/ai-text-analysis/src/main/java/com/bytechef/component/ai/text/analysis/constant/AiTextAnalysisConstants.java +++ b/server/libs/modules/components/ai/ai-text-analysis/src/main/java/com/bytechef/component/ai/text/analysis/constant/AiTextAnalysisConstants.java @@ -40,6 +40,7 @@ public class AiTextAnalysisConstants { public static final String MODEL_PROVIDER = "modelProvider"; public static final String SUMMARIZE_TEXT = "summarizeText"; public static final String CLASSIFY_TEXT = "classifyText"; + public static final String SENTIMENT_ANALYSIS = "sentimentAnalysis"; public static final ModifiableIntegerProperty MODEL_PROVIDER_PROPERTY = integer(MODEL_PROVIDER) .label("Model provider") diff --git a/server/libs/modules/components/ai/ai-text-analysis/src/main/java/com/bytechef/component/ai/text/analysis/task/handler/AiTextAnalysisClassifyTextTaskHandler.java b/server/libs/modules/components/ai/ai-text-analysis/src/main/java/com/bytechef/component/ai/text/analysis/task/handler/AiTextAnalysisClassifyTextTaskHandler.java new file mode 100644 index 00000000000..6035499ac07 --- /dev/null +++ b/server/libs/modules/components/ai/ai-text-analysis/src/main/java/com/bytechef/component/ai/text/analysis/task/handler/AiTextAnalysisClassifyTextTaskHandler.java @@ -0,0 +1,35 @@ +/* + * Copyright 2023-present ByteChef Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.bytechef.component.ai.text.analysis.task.handler; + +import static com.bytechef.component.ai.text.analysis.constant.AiTextAnalysisConstants.CLASSIFY_TEXT; +import static com.bytechef.platform.component.definition.AiComponentDefinition.AI_TEXT_ANALYSIS; + +import com.bytechef.platform.component.facade.ActionDefinitionFacade; +import com.bytechef.platform.component.task.handler.AbstractTaskHandler; +import org.springframework.stereotype.Component; + +/** + * @author Ivica Cardic + */ +@Component(AI_TEXT_ANALYSIS + "/v1/" + CLASSIFY_TEXT) +public class AiTextAnalysisClassifyTextTaskHandler extends AbstractTaskHandler { + + public AiTextAnalysisClassifyTextTaskHandler(ActionDefinitionFacade actionDefinitionFacade) { + super(AI_TEXT_ANALYSIS, 1, CLASSIFY_TEXT, actionDefinitionFacade); + } +} diff --git a/server/libs/modules/components/ai/ai-text-analysis/src/main/java/com/bytechef/component/ai/text/analysis/task/handler/AiTextAnalysisSentimentAnalysisTaskHandler.java b/server/libs/modules/components/ai/ai-text-analysis/src/main/java/com/bytechef/component/ai/text/analysis/task/handler/AiTextAnalysisSentimentAnalysisTaskHandler.java new file mode 100644 index 00000000000..d8c069c567d --- /dev/null +++ b/server/libs/modules/components/ai/ai-text-analysis/src/main/java/com/bytechef/component/ai/text/analysis/task/handler/AiTextAnalysisSentimentAnalysisTaskHandler.java @@ -0,0 +1,35 @@ +/* + * Copyright 2023-present ByteChef Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package com.bytechef.component.ai.text.analysis.task.handler; + +import static com.bytechef.component.ai.text.analysis.constant.AiTextAnalysisConstants.SENTIMENT_ANALYSIS; +import static com.bytechef.platform.component.definition.AiComponentDefinition.AI_TEXT_ANALYSIS; + +import com.bytechef.platform.component.facade.ActionDefinitionFacade; +import com.bytechef.platform.component.task.handler.AbstractTaskHandler; +import org.springframework.stereotype.Component; + +/** + * @author Ivica Cardic + */ +@Component(AI_TEXT_ANALYSIS + "/v1/" + SENTIMENT_ANALYSIS) +public class AiTextAnalysisSentimentAnalysisTaskHandler extends AbstractTaskHandler { + + public AiTextAnalysisSentimentAnalysisTaskHandler(ActionDefinitionFacade actionDefinitionFacade) { + super(AI_TEXT_ANALYSIS, 1, SENTIMENT_ANALYSIS, actionDefinitionFacade); + } +} diff --git a/server/libs/modules/components/ai/ai-text-analysis/src/test/resources/definition/ai_text_analysis_v1.json b/server/libs/modules/components/ai/ai-text-analysis/src/test/resources/definition/ai_text_analysis_v1.json index bfdabe1321e..626306d754f 100644 --- a/server/libs/modules/components/ai/ai-text-analysis/src/test/resources/definition/ai_text_analysis_v1.json +++ b/server/libs/modules/components/ai/ai-text-analysis/src/test/resources/definition/ai_text_analysis_v1.json @@ -2,10 +2,10 @@ "actions" : [ { "batch" : null, "deprecated" : null, - "description" : "AI reads, analyzes and summarizes your text into a shorter format.", + "description" : "AI reads, analyzes and classifies your text into one of defined categories.", "help" : null, "metadata" : null, - "name" : "summarizeText", + "name" : "classifyText", "properties" : [ { "advancedOption" : null, "description" : null, @@ -146,7 +146,7 @@ "optionsDataSource" : null }, { "advancedOption" : null, - "description" : "The text that is to be summarized.", + "description" : "The text that is to be classified.", "displayCondition" : null, "expressionEnabled" : null, "hidden" : null, @@ -161,61 +161,300 @@ "controlType" : "TEXT", "languageId" : null, "maxLength" : null, - "minLength" : 100, + "minLength" : null, "options" : null, "optionsDataSource" : null }, { "advancedOption" : null, - "description" : "In what format do you wish the text summarized?", + "description" : "A list of categories that the model can choose from.", "displayCondition" : null, "expressionEnabled" : null, "hidden" : null, "metadata" : { }, "required" : true, - "name" : "format", + "name" : "categories", + "type" : "ARRAY", + "defaultValue" : null, + "exampleValue" : null, + "label" : "Categories", + "placeholder" : null, + "items" : [ { + "advancedOption" : null, + "description" : null, + "displayCondition" : null, + "expressionEnabled" : null, + "hidden" : null, + "metadata" : { }, + "required" : null, + "name" : null, + "type" : "STRING", + "defaultValue" : null, + "exampleValue" : null, + "label" : null, + "placeholder" : null, + "controlType" : "TEXT", + "languageId" : null, + "maxLength" : null, + "minLength" : null, + "options" : null, + "optionsDataSource" : null + } ], + "maxItems" : null, + "minItems" : null, + "multipleValues" : null, + "options" : null, + "controlType" : "ARRAY_BUILDER", + "optionsDataSource" : null + }, { + "advancedOption" : null, + "description" : "You can classify a few samples, to guide your model on how to classify the real data.", + "displayCondition" : null, + "expressionEnabled" : null, + "hidden" : null, + "metadata" : { }, + "required" : null, + "name" : "examples", + "type" : "OBJECT", + "defaultValue" : null, + "exampleValue" : null, + "label" : "Examples", + "placeholder" : null, + "additionalProperties" : [ { + "advancedOption" : null, + "description" : null, + "displayCondition" : null, + "expressionEnabled" : null, + "hidden" : null, + "metadata" : { }, + "required" : null, + "name" : null, + "type" : "STRING", + "defaultValue" : null, + "exampleValue" : null, + "label" : null, + "placeholder" : null, + "controlType" : "TEXT", + "languageId" : null, + "maxLength" : null, + "minLength" : null, + "options" : null, + "optionsDataSource" : null + } ], + "multipleValues" : null, + "options" : null, + "properties" : null, + "controlType" : "OBJECT_BUILDER", + "optionsDataSource" : null + }, { + "advancedOption" : true, + "description" : "The maximum number of tokens to generate in the chat completion.", + "displayCondition" : null, + "expressionEnabled" : null, + "hidden" : null, + "metadata" : { }, + "required" : null, + "name" : "maxTokens", "type" : "INTEGER", "defaultValue" : null, "exampleValue" : null, - "label" : "Format", + "label" : "Max Tokens", + "placeholder" : null, + "maxValue" : null, + "minValue" : null, + "options" : null, + "controlType" : "INTEGER", + "optionsDataSource" : null + }, { + "advancedOption" : true, + "description" : "Controls randomness: Higher values will make the output more random, while lower values like will make it more focused and deterministic.", + "displayCondition" : null, + "expressionEnabled" : null, + "hidden" : null, + "metadata" : { }, + "required" : null, + "name" : "temperature", + "type" : "NUMBER", + "defaultValue" : 1.0, + "exampleValue" : null, + "label" : "Temperature", + "placeholder" : null, + "maxNumberPrecision" : null, + "maxValue" : 2.0, + "minNumberPrecision" : null, + "minValue" : 0.0, + "numberPrecision" : null, + "options" : null, + "controlType" : "NUMBER", + "optionsDataSource" : null + } ], + "title" : "Classify Text", + "perform" : { }, + "processErrorResponse" : null, + "workflowNodeDescription" : null, + "outputDefinition" : { + "output" : null, + "outputResponse" : null, + "outputSchema" : null, + "sampleOutput" : null + } + }, { + "batch" : null, + "deprecated" : null, + "description" : "The sentiment of the text is typically categorized as POSITIVE, NEGATIVE, or NEUTRAL.", + "help" : null, + "metadata" : null, + "name" : "sentimentAnalysis", + "properties" : [ { + "advancedOption" : null, + "description" : null, + "displayCondition" : null, + "expressionEnabled" : null, + "hidden" : null, + "metadata" : { }, + "required" : true, + "name" : "modelProvider", + "type" : "INTEGER", + "defaultValue" : null, + "exampleValue" : null, + "label" : "Model provider", "placeholder" : null, "maxValue" : null, "minValue" : null, "options" : [ { "description" : null, - "label" : "A structured summary with sections", + "label" : "Amazon Bedrock: Anthropic 2", "value" : 0 }, { "description" : null, - "label" : "A brief title summarizing the content in 4-7 words", + "label" : "Amazon Bedrock: Anthropic 3", "value" : 1 }, { "description" : null, - "label" : "A single, concise sentence", + "label" : "Amazon Bedrock: Cohere", "value" : 2 }, { "description" : null, - "label" : "A bulleted list recap", + "label" : "Amazon Bedrock: Jurassic 2", "value" : 3 }, { "description" : null, - "label" : "Custom Prompt", + "label" : "Amazon Bedrock: Llama", "value" : 4 + }, { + "description" : null, + "label" : "Amazon Bedrock: Titan", + "value" : 5 + }, { + "description" : null, + "label" : "Anthropic", + "value" : 6 + }, { + "description" : null, + "label" : "Azure Open AI", + "value" : 7 + }, { + "description" : null, + "label" : "Groq", + "value" : 8 + }, { + "description" : null, + "label" : "NVIDIA", + "value" : 9 + }, { + "description" : null, + "label" : "Hugging Face", + "value" : 10 + }, { + "description" : null, + "label" : "Mistral", + "value" : 11 + }, { + "description" : null, + "label" : "Open AI", + "value" : 12 + }, { + "description" : null, + "label" : "Vertex Gemini", + "value" : 13 } ], "controlType" : "SELECT", "optionsDataSource" : null }, { "advancedOption" : null, - "description" : "Write your prompt for summarizing text.", - "displayCondition" : "format == 4", + "description" : "ID of the model to use.", + "displayCondition" : "modelProvider <= 6 || (modelProvider >= 11 && modelProvider <= 13)", "expressionEnabled" : null, "hidden" : null, "metadata" : { }, "required" : true, - "name" : "prompt", + "name" : "model", "type" : "STRING", "defaultValue" : null, "exampleValue" : null, - "label" : "Custom Prompt", + "label" : "Model", + "placeholder" : null, + "controlType" : "SELECT", + "languageId" : null, + "maxLength" : null, + "minLength" : null, + "options" : null, + "optionsDataSource" : { + "optionsLookupDependsOn" : [ "modelProvider" ], + "options" : { } + } + }, { + "advancedOption" : null, + "description" : "ID of the model to use.", + "displayCondition" : "modelProvider >= 7 && modelProvider <= 9", + "expressionEnabled" : null, + "hidden" : null, + "metadata" : { }, + "required" : true, + "name" : "model", + "type" : "STRING", + "defaultValue" : null, + "exampleValue" : null, + "label" : "Model", + "placeholder" : null, + "controlType" : "TEXT", + "languageId" : null, + "maxLength" : null, + "minLength" : null, + "options" : null, + "optionsDataSource" : null + }, { + "advancedOption" : null, + "description" : "Url of the inference endpoint.", + "displayCondition" : "modelProvider == 10", + "expressionEnabled" : null, + "hidden" : null, + "metadata" : { }, + "required" : true, + "name" : "model", + "type" : "STRING", + "defaultValue" : null, + "exampleValue" : null, + "label" : "URL", + "placeholder" : null, + "controlType" : "TEXT", + "languageId" : null, + "maxLength" : null, + "minLength" : null, + "options" : null, + "optionsDataSource" : null + }, { + "advancedOption" : null, + "description" : "The text that is to be classified.", + "displayCondition" : null, + "expressionEnabled" : null, + "hidden" : null, + "metadata" : { }, + "required" : true, + "name" : "text", + "type" : "STRING", + "defaultValue" : null, + "exampleValue" : null, + "label" : "Text", "placeholder" : null, "controlType" : "TEXT", "languageId" : null, @@ -265,23 +504,23 @@ "controlType" : "NUMBER", "optionsDataSource" : null } ], - "title" : "Summarize Text", + "title" : "Sentiment Analysis", "perform" : { }, "processErrorResponse" : null, + "workflowNodeDescription" : null, "outputDefinition" : { "output" : null, "outputResponse" : null, "outputSchema" : null, "sampleOutput" : null - }, - "workflowNodeDescription" : null + } }, { "batch" : null, "deprecated" : null, - "description" : "AI reads, analyzes and classifies your text into one of defined categories.", + "description" : "AI reads, analyzes and summarizes your text into a shorter format.", "help" : null, "metadata" : null, - "name" : "classifyText", + "name" : "summarizeText", "properties" : [ { "advancedOption" : null, "description" : null, @@ -422,7 +661,7 @@ "optionsDataSource" : null }, { "advancedOption" : null, - "description" : "The text that is to be classified.", + "description" : "The text that is to be summarized.", "displayCondition" : null, "expressionEnabled" : null, "hidden" : null, @@ -437,89 +676,67 @@ "controlType" : "TEXT", "languageId" : null, "maxLength" : null, - "minLength" : null, + "minLength" : 100, "options" : null, "optionsDataSource" : null }, { "advancedOption" : null, - "description" : "A list of categories that the model can choose from.", + "description" : "In what format do you wish the text summarized?", "displayCondition" : null, "expressionEnabled" : null, "hidden" : null, "metadata" : { }, "required" : true, - "name" : "categories", - "type" : "ARRAY", + "name" : "format", + "type" : "INTEGER", "defaultValue" : null, "exampleValue" : null, - "label" : "Categories", + "label" : "Format", "placeholder" : null, - "items" : [ { - "advancedOption" : null, + "maxValue" : null, + "minValue" : null, + "options" : [ { "description" : null, - "displayCondition" : null, - "expressionEnabled" : null, - "hidden" : null, - "metadata" : { }, - "required" : null, - "name" : null, - "type" : "STRING", - "defaultValue" : null, - "exampleValue" : null, - "label" : null, - "placeholder" : null, - "controlType" : "TEXT", - "languageId" : null, - "maxLength" : null, - "minLength" : null, - "options" : null, - "optionsDataSource" : null + "label" : "A structured summary with sections", + "value" : 0 + }, { + "description" : null, + "label" : "A brief title summarizing the content in 4-7 words", + "value" : 1 + }, { + "description" : null, + "label" : "A single, concise sentence", + "value" : 2 + }, { + "description" : null, + "label" : "A bulleted list recap", + "value" : 3 + }, { + "description" : null, + "label" : "Custom Prompt", + "value" : 4 } ], - "maxItems" : null, - "minItems" : null, - "multipleValues" : null, - "options" : null, - "controlType" : "ARRAY_BUILDER", + "controlType" : "SELECT", "optionsDataSource" : null }, { "advancedOption" : null, - "description" : "You can classify a few samples, to guide your model on how to classify the real data.", - "displayCondition" : null, + "description" : "Write your prompt for summarizing text.", + "displayCondition" : "format == 4", "expressionEnabled" : null, "hidden" : null, "metadata" : { }, - "required" : null, - "name" : "examples", - "type" : "OBJECT", + "required" : true, + "name" : "prompt", + "type" : "STRING", "defaultValue" : null, "exampleValue" : null, - "label" : "Examples", + "label" : "Custom Prompt", "placeholder" : null, - "additionalProperties" : [ { - "advancedOption" : null, - "description" : null, - "displayCondition" : null, - "expressionEnabled" : null, - "hidden" : null, - "metadata" : { }, - "required" : null, - "name" : null, - "type" : "STRING", - "defaultValue" : null, - "exampleValue" : null, - "label" : null, - "placeholder" : null, - "controlType" : "TEXT", - "languageId" : null, - "maxLength" : null, - "minLength" : null, - "options" : null, - "optionsDataSource" : null - } ], - "multipleValues" : null, + "controlType" : "TEXT", + "languageId" : null, + "maxLength" : null, + "minLength" : null, "options" : null, - "properties" : null, - "controlType" : "OBJECT_BUILDER", "optionsDataSource" : null }, { "advancedOption" : true, @@ -563,16 +780,16 @@ "controlType" : "NUMBER", "optionsDataSource" : null } ], - "title" : "Classify Text", + "title" : "Summarize Text", "perform" : { }, "processErrorResponse" : null, + "workflowNodeDescription" : null, "outputDefinition" : { "output" : null, "outputResponse" : null, "outputSchema" : null, "sampleOutput" : null - }, - "workflowNodeDescription" : null + } } ], "categories" : [ { "key" : "artificial-intelligence",