Skip to content

Commit a5fcbde

Browse files
[Backport 2.x] Adding default use cases (#587)
Adding default use cases (#583) * initial default use case addition * adding IT and UT * addresing comments and adding more tests * addressing more comments and adding more UT * addressed more comments and more UT --------- (cherry picked from commit b148eb5) Signed-off-by: Amit Galitzky <amgalitz@amazon.com> Signed-off-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com> Co-authored-by: github-actions[bot] <github-actions[bot]@users.noreply.github.com>
1 parent cedaac8 commit a5fcbde

23 files changed

+1058
-21
lines changed

CHANGELOG.md

+1
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
1717
- Added create ingest pipeline step ([#558](https://github.com/opensearch-project/flow-framework/pull/558))
1818
- Added create search pipeline step ([#569](https://github.com/opensearch-project/flow-framework/pull/569))
1919
- Added create index step ([#574](https://github.com/opensearch-project/flow-framework/pull/574))
20+
- Added default use cases ([#583](https://github.com/opensearch-project/flow-framework/pull/583))
2021

2122
### Enhancements
2223
- Substitute REST path or body parameters in Workflow Steps ([#525](https://github.com/opensearch-project/flow-framework/pull/525))

build.gradle

+1
Original file line numberDiff line numberDiff line change
@@ -179,6 +179,7 @@ dependencies {
179179

180180
// ZipArchive dependencies used for integration tests
181181
zipArchive group: 'org.opensearch.plugin', name:'opensearch-ml-plugin', version: "${opensearch_build}"
182+
182183
secureIntegTestPluginArchive group: 'org.opensearch.plugin', name:'opensearch-security', version: "${opensearch_build}"
183184

184185
configurations.all {

src/main/java/org/opensearch/flowframework/common/CommonValue.java

+2
Original file line numberDiff line numberDiff line change
@@ -72,6 +72,8 @@ private CommonValue() {}
7272
public static final String PROVISION_WORKFLOW = "provision";
7373
/** The field name for workflow steps. This field represents the name of the workflow steps to be fetched. */
7474
public static final String WORKFLOW_STEP = "workflow_step";
75+
/** The param name for default use case, used by the create workflow API */
76+
public static final String USE_CASE = "use_case";
7577

7678
/*
7779
* Constants associated with plugin configuration
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
/*
2+
* Copyright OpenSearch Contributors
3+
* SPDX-License-Identifier: Apache-2.0
4+
*
5+
* The OpenSearch Contributors require contributions made to
6+
* this file be licensed under the Apache-2.0 license or a
7+
* compatible open source license.
8+
*/
9+
package org.opensearch.flowframework.common;
10+
11+
import org.apache.logging.log4j.LogManager;
12+
import org.apache.logging.log4j.Logger;
13+
import org.opensearch.core.rest.RestStatus;
14+
import org.opensearch.flowframework.exception.FlowFrameworkException;
15+
16+
/**
17+
* Enum encapsulating the different default use cases and templates we have stored
18+
*/
19+
public enum DefaultUseCases {
20+
21+
/** defaults file and substitution ready template for OpenAI embedding model */
22+
OPEN_AI_EMBEDDING_MODEL_DEPLOY(
23+
"open_ai_embedding_model_deploy",
24+
"defaults/open-ai-embedding-defaults.json",
25+
"substitutionTemplates/deploy-remote-model-template.json"
26+
),
27+
/** defaults file and substitution ready template for cohere embedding model */
28+
COHERE_EMBEDDING_MODEL_DEPLOY(
29+
"cohere-embedding_model_deploy",
30+
"defaults/cohere-embedding-defaults.json",
31+
"substitutionTemplates/deploy-remote-model-template-extra-params.json"
32+
),
33+
/** defaults file and substitution ready template for local neural sparse model and ingest pipeline*/
34+
LOCAL_NEURAL_SPARSE_SEARCH(
35+
"local_neural_sparse_search",
36+
"defaults/local-sparse-search-defaults.json",
37+
"substitutionTemplates/neural-sparse-local-template.json"
38+
);
39+
40+
private final String useCaseName;
41+
private final String defaultsFile;
42+
private final String substitutionReadyFile;
43+
private static final Logger logger = LogManager.getLogger(DefaultUseCases.class);
44+
45+
DefaultUseCases(String useCaseName, String defaultsFile, String substitutionReadyFile) {
46+
this.useCaseName = useCaseName;
47+
this.defaultsFile = defaultsFile;
48+
this.substitutionReadyFile = substitutionReadyFile;
49+
}
50+
51+
/**
52+
* Returns the useCaseName for the given enum Constant
53+
* @return the useCaseName of this use case.
54+
*/
55+
public String getUseCaseName() {
56+
return useCaseName;
57+
}
58+
59+
/**
60+
* Returns the defaultsFile for the given enum Constant
61+
* @return the defaultsFile of this for the given useCase.
62+
*/
63+
public String getDefaultsFile() {
64+
return defaultsFile;
65+
}
66+
67+
/**
68+
* Returns the substitutionReadyFile for the given enum Constant
69+
* @return the substitutionReadyFile of the given useCase
70+
*/
71+
public String getSubstitutionReadyFile() {
72+
return substitutionReadyFile;
73+
}
74+
75+
/**
76+
* Gets the defaultsFile based on the given use case.
77+
* @param useCaseName name of the given use case
78+
* @return the defaultsFile for that usecase
79+
* @throws FlowFrameworkException if the use case doesn't exist in enum
80+
*/
81+
public static String getDefaultsFileByUseCaseName(String useCaseName) throws FlowFrameworkException {
82+
if (useCaseName != null && !useCaseName.isEmpty()) {
83+
for (DefaultUseCases usecase : values()) {
84+
if (useCaseName.equals(usecase.getUseCaseName())) {
85+
return usecase.getDefaultsFile();
86+
}
87+
}
88+
}
89+
logger.error("Unable to find defaults file for use case: {}", useCaseName);
90+
throw new FlowFrameworkException("Unable to find defaults file for use case: " + useCaseName, RestStatus.BAD_REQUEST);
91+
}
92+
93+
/**
94+
* Gets the substitutionReadyFile based on the given use case
95+
* @param useCaseName name of the given use case
96+
* @return the substitutionReadyFile which has the template
97+
* @throws FlowFrameworkException if the use case doesn't exist in enum
98+
*/
99+
public static String getSubstitutionReadyFileByUseCaseName(String useCaseName) throws FlowFrameworkException {
100+
if (useCaseName != null && !useCaseName.isEmpty()) {
101+
for (DefaultUseCases useCase : values()) {
102+
if (useCase.getUseCaseName().equals(useCaseName)) {
103+
return useCase.getSubstitutionReadyFile();
104+
}
105+
}
106+
}
107+
logger.error("Unable to find substitution ready file for use case: {}", useCaseName);
108+
throw new FlowFrameworkException("Unable to find substitution ready file for use case: " + useCaseName, RestStatus.BAD_REQUEST);
109+
}
110+
}

src/main/java/org/opensearch/flowframework/rest/RestCreateWorkflowAction.java

+65-5
Original file line numberDiff line numberDiff line change
@@ -17,16 +17,19 @@
1717
import org.opensearch.core.xcontent.ToXContent;
1818
import org.opensearch.core.xcontent.XContentBuilder;
1919
import org.opensearch.core.xcontent.XContentParser;
20+
import org.opensearch.flowframework.common.DefaultUseCases;
2021
import org.opensearch.flowframework.common.FlowFrameworkSettings;
2122
import org.opensearch.flowframework.exception.FlowFrameworkException;
2223
import org.opensearch.flowframework.model.Template;
2324
import org.opensearch.flowframework.transport.CreateWorkflowAction;
2425
import org.opensearch.flowframework.transport.WorkflowRequest;
26+
import org.opensearch.flowframework.util.ParseUtils;
2527
import org.opensearch.rest.BaseRestHandler;
2628
import org.opensearch.rest.BytesRestResponse;
2729
import org.opensearch.rest.RestRequest;
2830

2931
import java.io.IOException;
32+
import java.util.Collections;
3033
import java.util.List;
3134
import java.util.Locale;
3235
import java.util.Map;
@@ -35,6 +38,7 @@
3538

3639
import static org.opensearch.core.xcontent.XContentParserUtils.ensureExpectedToken;
3740
import static org.opensearch.flowframework.common.CommonValue.PROVISION_WORKFLOW;
41+
import static org.opensearch.flowframework.common.CommonValue.USE_CASE;
3842
import static org.opensearch.flowframework.common.CommonValue.VALIDATION;
3943
import static org.opensearch.flowframework.common.CommonValue.WORKFLOW_ID;
4044
import static org.opensearch.flowframework.common.CommonValue.WORKFLOW_URI;
@@ -78,6 +82,7 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli
7882
String workflowId = request.param(WORKFLOW_ID);
7983
String[] validation = request.paramAsStringArray(VALIDATION, new String[] { "all" });
8084
boolean provision = request.paramAsBoolean(PROVISION_WORKFLOW, false);
85+
String useCase = request.param(USE_CASE);
8186
// If provisioning, consume all other params and pass to provision transport action
8287
Map<String, String> params = provision
8388
? request.params()
@@ -112,11 +117,63 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli
112117
);
113118
}
114119
try {
115-
XContentParser parser = request.contentParser();
116-
ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser);
117-
Template template = Template.parse(parser);
118120

119-
WorkflowRequest workflowRequest = new WorkflowRequest(workflowId, template, validation, provision, params);
121+
Template template;
122+
Map<String, String> useCaseDefaultsMap = Collections.emptyMap();
123+
if (useCase != null) {
124+
String useCaseTemplateFileInStringFormat = ParseUtils.resourceToString(
125+
"/" + DefaultUseCases.getSubstitutionReadyFileByUseCaseName(useCase)
126+
);
127+
String defaultsFilePath = DefaultUseCases.getDefaultsFileByUseCaseName(useCase);
128+
useCaseDefaultsMap = ParseUtils.parseJsonFileToStringToStringMap("/" + defaultsFilePath);
129+
130+
if (request.hasContent()) {
131+
try {
132+
XContentParser parser = request.contentParser();
133+
ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser);
134+
Map<String, String> userDefaults = ParseUtils.parseStringToStringMap(parser);
135+
// updates the default params with anything user has given that matches
136+
for (Map.Entry<String, String> userDefaultsEntry : userDefaults.entrySet()) {
137+
String key = userDefaultsEntry.getKey();
138+
String value = userDefaultsEntry.getValue();
139+
if (useCaseDefaultsMap.containsKey(key)) {
140+
useCaseDefaultsMap.put(key, value);
141+
}
142+
}
143+
} catch (Exception ex) {
144+
RestStatus status = ex instanceof IOException ? RestStatus.BAD_REQUEST : ExceptionsHelper.status(ex);
145+
String errorMessage = "failure parsing request body when a use case is given";
146+
logger.error(errorMessage, ex);
147+
throw new FlowFrameworkException(errorMessage, status);
148+
}
149+
150+
}
151+
152+
useCaseTemplateFileInStringFormat = (String) ParseUtils.conditionallySubstitute(
153+
useCaseTemplateFileInStringFormat,
154+
null,
155+
useCaseDefaultsMap
156+
);
157+
158+
XContentParser parserTestJson = ParseUtils.jsonToParser(useCaseTemplateFileInStringFormat);
159+
ensureExpectedToken(XContentParser.Token.START_OBJECT, parserTestJson.currentToken(), parserTestJson);
160+
template = Template.parse(parserTestJson);
161+
162+
} else {
163+
XContentParser parser = request.contentParser();
164+
ensureExpectedToken(XContentParser.Token.START_OBJECT, parser.nextToken(), parser);
165+
template = Template.parse(parser);
166+
}
167+
168+
WorkflowRequest workflowRequest = new WorkflowRequest(
169+
workflowId,
170+
template,
171+
validation,
172+
provision,
173+
params,
174+
useCase,
175+
useCaseDefaultsMap
176+
);
120177

121178
return channel -> client.execute(CreateWorkflowAction.INSTANCE, workflowRequest, ActionListener.wrap(response -> {
122179
XContentBuilder builder = response.toXContent(channel.newBuilder(), ToXContent.EMPTY_PARAMS);
@@ -134,11 +191,14 @@ protected RestChannelConsumer prepareRequest(RestRequest request, NodeClient cli
134191
channel.sendResponse(new BytesRestResponse(ExceptionsHelper.status(e), errorMessage));
135192
}
136193
}));
194+
137195
} catch (FlowFrameworkException e) {
196+
logger.error("failed to prepare rest request", e);
138197
return channel -> channel.sendResponse(
139198
new BytesRestResponse(e.getRestStatus(), e.toXContent(channel.newErrorBuilder(), ToXContent.EMPTY_PARAMS))
140199
);
141-
} catch (IOException e) {
200+
} catch (Exception e) {
201+
logger.error("failed to prepare rest request", e);
142202
FlowFrameworkException ex = new FlowFrameworkException(
143203
"IOException: template content invalid for specified Content-Type.",
144204
RestStatus.BAD_REQUEST

src/main/java/org/opensearch/flowframework/transport/WorkflowRequest.java

+46-3
Original file line numberDiff line numberDiff line change
@@ -49,13 +49,23 @@ public class WorkflowRequest extends ActionRequest {
4949
*/
5050
private Map<String, String> params;
5151

52+
/**
53+
* use case flag
54+
*/
55+
private String useCase;
56+
57+
/**
58+
* Deafult params map from use case
59+
*/
60+
private Map<String, String> defaultParams;
61+
5262
/**
5363
* Instantiates a new WorkflowRequest, set validation to all, no provisioning
5464
* @param workflowId the documentId of the workflow
5565
* @param template the use case template which describes the workflow
5666
*/
5767
public WorkflowRequest(@Nullable String workflowId, @Nullable Template template) {
58-
this(workflowId, template, new String[] { "all" }, false, Collections.emptyMap());
68+
this(workflowId, template, new String[] { "all" }, false, Collections.emptyMap(), null, Collections.emptyMap());
5969
}
6070

6171
/**
@@ -65,7 +75,18 @@ public WorkflowRequest(@Nullable String workflowId, @Nullable Template template)
6575
* @param params The parameters from the REST path
6676
*/
6777
public WorkflowRequest(@Nullable String workflowId, @Nullable Template template, Map<String, String> params) {
68-
this(workflowId, template, new String[] { "all" }, true, params);
78+
this(workflowId, template, new String[] { "all" }, true, params, null, Collections.emptyMap());
79+
}
80+
81+
/**
82+
* Instantiates a new WorkflowRequest with params map, set validation to all, provisioning to true
83+
* @param workflowId the documentId of the workflow
84+
* @param template the use case template which describes the workflow
85+
* @param useCase the default use case give by user
86+
* @param defaultParams The parameters from the REST body when a use case is given
87+
*/
88+
public WorkflowRequest(@Nullable String workflowId, @Nullable Template template, String useCase, Map<String, String> defaultParams) {
89+
this(workflowId, template, new String[] { "all" }, false, Collections.emptyMap(), useCase, defaultParams);
6990
}
7091

7192
/**
@@ -75,13 +96,17 @@ public WorkflowRequest(@Nullable String workflowId, @Nullable Template template,
7596
* @param validation flag to indicate if validation is necessary
7697
* @param provision flag to indicate if provision is necessary
7798
* @param params map of REST path params. If provision is false, must be an empty map.
99+
* @param useCase default use case given
100+
* @param defaultParams the params to be used in the substitution based on the default use case.
78101
*/
79102
public WorkflowRequest(
80103
@Nullable String workflowId,
81104
@Nullable Template template,
82105
String[] validation,
83106
boolean provision,
84-
Map<String, String> params
107+
Map<String, String> params,
108+
String useCase,
109+
Map<String, String> defaultParams
85110
) {
86111
this.workflowId = workflowId;
87112
this.template = template;
@@ -91,6 +116,8 @@ public WorkflowRequest(
91116
throw new IllegalArgumentException("Params may only be included when provisioning.");
92117
}
93118
this.params = params;
119+
this.useCase = useCase;
120+
this.defaultParams = defaultParams;
94121
}
95122

96123
/**
@@ -150,6 +177,22 @@ public Map<String, String> getParams() {
150177
return Map.copyOf(this.params);
151178
}
152179

180+
/**
181+
* Gets the use case
182+
* @return the use case
183+
*/
184+
public String getUseCase() {
185+
return this.useCase;
186+
}
187+
188+
/**
189+
* Gets the params map
190+
* @return the params map
191+
*/
192+
public Map<String, String> getDefaultParams() {
193+
return Map.copyOf(this.defaultParams);
194+
}
195+
153196
@Override
154197
public void writeTo(StreamOutput out) throws IOException {
155198
super.writeTo(out);

0 commit comments

Comments
 (0)