Skip to content

Commit d5ba601

Browse files
authored
Support basic semantic search with preset workflow template (#121)
Signed-off-by: Tyler Ohlsen <ohltyler@amazon.com>
1 parent 3eb94a5 commit d5ba601

34 files changed

+588
-203
lines changed

common/constants.ts

+13-1
Original file line numberDiff line numberDiff line change
@@ -6,13 +6,20 @@
66
export const PLUGIN_ID = 'flow-framework';
77

88
/**
9-
* BACKEND/CLUSTER APIs
9+
* BACKEND FLOW FRAMEWORK APIs
1010
*/
1111
export const FLOW_FRAMEWORK_API_ROUTE_PREFIX = '/_plugins/_flow_framework';
1212
export const FLOW_FRAMEWORK_WORKFLOW_ROUTE_PREFIX = `${FLOW_FRAMEWORK_API_ROUTE_PREFIX}/workflow`;
1313
export const FLOW_FRAMEWORK_SEARCH_WORKFLOWS_ROUTE = `${FLOW_FRAMEWORK_WORKFLOW_ROUTE_PREFIX}/_search`;
1414
export const FLOW_FRAMEWORK_SEARCH_WORKFLOW_STATE_ROUTE = `${FLOW_FRAMEWORK_WORKFLOW_ROUTE_PREFIX}/state/_search`;
1515

16+
/**
17+
* BACKEND ML PLUGIN APIs
18+
*/
19+
export const ML_API_ROUTE_PREFIX = '/_plugins/_ml';
20+
export const ML_MODEL_ROUTE_PREFIX = `${ML_API_ROUTE_PREFIX}/models`;
21+
export const ML_SEARCH_MODELS_ROUTE = `${ML_MODEL_ROUTE_PREFIX}/_search`;
22+
1623
/**
1724
* NODE APIs
1825
*/
@@ -31,11 +38,16 @@ export const CREATE_WORKFLOW_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/cre
3138
export const DELETE_WORKFLOW_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/delete`;
3239
export const GET_PRESET_WORKFLOWS_NODE_API_PATH = `${BASE_WORKFLOW_NODE_API_PATH}/presets`;
3340

41+
// ML Plugin node APIs
42+
export const BASE_MODEL_NODE_API_PATH = `${BASE_NODE_API_PATH}/model`;
43+
export const SEARCH_MODELS_NODE_API_PATH = `${BASE_MODEL_NODE_API_PATH}/search`;
44+
3445
/**
3546
* MISCELLANEOUS
3647
*/
3748
export const NEW_WORKFLOW_ID_URL = 'new';
3849
export const START_FROM_SCRATCH_WORKFLOW_NAME = 'Start From Scratch';
3950
export const DEFAULT_NEW_WORKFLOW_NAME = 'new_workflow';
51+
export const DEFAULT_NEW_WORKFLOW_DESCRIPTION = 'My new workflow';
4052
export const DATE_FORMAT_PATTERN = 'MM/DD/YY hh:mm A';
4153
export const EMPTY_FIELD_STRING = '--';

common/interfaces.ts

+20-3
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,10 @@ type ReactFlowViewport = {
2828
zoom: number;
2929
};
3030

31+
export type UIState = {
32+
workspaceFlow: WorkspaceFlowState;
33+
};
34+
3135
export type WorkspaceFlowState = {
3236
nodes: ReactFlowComponent[];
3337
edges: ReactFlowEdge[];
@@ -51,7 +55,8 @@ export type TemplateEdge = {
5155
};
5256

5357
export type TemplateFlow = {
54-
user_params?: Map<string, any>;
58+
user_inputs?: Map<string, any>;
59+
previous_node_inputs?: Map<string, any>;
5560
nodes: TemplateNode[];
5661
edges?: TemplateEdge[];
5762
};
@@ -69,14 +74,14 @@ export type WorkflowTemplate = {
6974
// https://github.com/opensearch-project/flow-framework/issues/526
7075
version: any;
7176
workflows: TemplateFlows;
77+
// UI state and any ReactFlow state may not exist if a workflow is created via API/backend-only.
78+
ui_metadata?: UIState;
7279
};
7380

7481
// An instance of a workflow based on a workflow template
7582
export type Workflow = WorkflowTemplate & {
7683
// won't exist until created in backend
7784
id?: string;
78-
// ReactFlow state may not exist if a workflow is created via API/backend-only.
79-
workspaceFlowState?: WorkspaceFlowState;
8085
// won't exist until created in backend
8186
lastUpdated?: number;
8287
// won't exist until launched/provisioned in backend
@@ -89,6 +94,14 @@ export enum USE_CASE {
8994
PROVISION = 'PROVISION',
9095
}
9196

97+
/**
98+
********** ML PLUGIN TYPES/INTERFACES **********
99+
*/
100+
export type Model = {
101+
id: string;
102+
algorithm: string;
103+
};
104+
92105
/**
93106
********** MISC TYPES/INTERFACES ************
94107
*/
@@ -111,3 +124,7 @@ export enum WORKFLOW_STATE {
111124
export type WorkflowDict = {
112125
[workflowId: string]: Workflow;
113126
};
127+
128+
export type ModelDict = {
129+
[modelId: string]: Model;
130+
};

common/utils.ts

+94-35
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
*/
55

66
import moment from 'moment';
7+
import { MarkerType } from 'reactflow';
78
import {
89
WorkspaceFlowState,
910
ReactFlowComponent,
@@ -17,21 +18,88 @@ import {
1718
DATE_FORMAT_PATTERN,
1819
COMPONENT_CATEGORY,
1920
NODE_CATEGORY,
21+
WorkspaceFormValues,
2022
} from './';
2123

2224
// TODO: implement this and remove hardcoded return values
2325
/**
24-
* Converts a ReactFlow workspace flow to a backend-compatible set of ingest and/or search sub-workflows,
25-
* along with a provision sub-workflow if resources are to be created.
26+
* Given a ReactFlow workspace flow and the set of current form values within such flow,
27+
* generate a backend-compatible set of sub-workflows.
28+
*
2629
*/
2730
export function toTemplateFlows(
28-
workspaceFlow: WorkspaceFlowState
31+
workspaceFlow: WorkspaceFlowState,
32+
formValues: WorkspaceFormValues
2933
): TemplateFlows {
34+
const textEmbeddingTransformerNodeId = Object.keys(formValues).find((key) =>
35+
key.includes('text_embedding')
36+
) as string;
37+
const knnIndexerNodeId = Object.keys(formValues).find((key) =>
38+
key.includes('knn')
39+
) as string;
40+
const textEmbeddingFields = formValues[textEmbeddingTransformerNodeId];
41+
const knnIndexerFields = formValues[knnIndexerNodeId];
42+
3043
return {
3144
provision: {
32-
user_params: {} as Map<string, any>,
33-
nodes: [],
34-
edges: [],
45+
nodes: [
46+
{
47+
id: 'create_ingest_pipeline',
48+
type: 'create_ingest_pipeline',
49+
user_inputs: {
50+
pipeline_id: 'test-pipeline',
51+
model_id: textEmbeddingFields['modelId'],
52+
input_field: textEmbeddingFields['inputField'],
53+
output_field: textEmbeddingFields['vectorField'],
54+
configurations: {
55+
description: 'A text embedding ingest pipeline',
56+
processors: [
57+
{
58+
text_embedding: {
59+
model_id: textEmbeddingFields['modelId'],
60+
field_map: {
61+
[textEmbeddingFields['inputField']]:
62+
textEmbeddingFields['vectorField'],
63+
},
64+
},
65+
},
66+
],
67+
},
68+
},
69+
},
70+
{
71+
id: 'create_index',
72+
type: 'create_index',
73+
previous_node_inputs: {
74+
create_ingest_pipeline: 'pipeline_id',
75+
},
76+
user_inputs: {
77+
index_name: knnIndexerFields['indexName'],
78+
configurations: {
79+
settings: {
80+
default_pipeline: '${{create_ingest_pipeline.pipeline_id}}',
81+
},
82+
mappings: {
83+
properties: {
84+
[textEmbeddingFields['vectorField']]: {
85+
type: 'knn_vector',
86+
dimension: 768,
87+
method: {
88+
engine: 'lucene',
89+
space_type: 'l2',
90+
name: 'hnsw',
91+
parameters: {},
92+
},
93+
},
94+
[textEmbeddingFields['inputField']]: {
95+
type: 'text',
96+
},
97+
},
98+
},
99+
},
100+
},
101+
},
102+
],
35103
},
36104
};
37105
}
@@ -47,10 +115,8 @@ export function toWorkspaceFlow(
47115
const ingestId1 = generateId('text_embedding_processor');
48116
const ingestId2 = generateId('knn_index');
49117
const ingestGroupId = generateId(COMPONENT_CATEGORY.INGEST);
50-
51-
const searchId1 = generateId('text_embedding_processor');
52-
const searchId2 = generateId('knn_index');
53118
const searchGroupId = generateId(COMPONENT_CATEGORY.SEARCH);
119+
const edgeId = generateId('edge');
54120

55121
const ingestNodes = [
56122
{
@@ -61,11 +127,10 @@ export function toWorkspaceFlow(
61127
style: {
62128
width: 900,
63129
height: 400,
64-
overflowX: 'auto',
65-
overflowY: 'auto',
66130
},
67131
className: 'reactflow__group-node__ingest',
68132
selectable: true,
133+
deletable: false,
69134
},
70135
{
71136
id: ingestId1,
@@ -78,6 +143,7 @@ export function toWorkspaceFlow(
78143
parentNode: ingestGroupId,
79144
extent: 'parent',
80145
draggable: true,
146+
deletable: false,
81147
},
82148
{
83149
id: ingestId2,
@@ -87,6 +153,7 @@ export function toWorkspaceFlow(
87153
parentNode: ingestGroupId,
88154
extent: 'parent',
89155
draggable: true,
156+
deletable: false,
90157
},
91158
] as ReactFlowComponent[];
92159

@@ -99,38 +166,30 @@ export function toWorkspaceFlow(
99166
style: {
100167
width: 900,
101168
height: 400,
102-
overflowX: 'auto',
103-
overflowY: 'auto',
104169
},
105170
className: 'reactflow__group-node__search',
106171
selectable: true,
107-
},
108-
{
109-
id: searchId1,
110-
position: { x: 100, y: 70 },
111-
data: initComponentData(
112-
new TextEmbeddingTransformer().toObj(),
113-
searchId1
114-
),
115-
type: NODE_CATEGORY.CUSTOM,
116-
parentNode: searchGroupId,
117-
extent: 'parent',
118-
draggable: true,
119-
},
120-
{
121-
id: searchId2,
122-
position: { x: 500, y: 70 },
123-
data: initComponentData(new KnnIndexer().toObj(), searchId2),
124-
type: NODE_CATEGORY.CUSTOM,
125-
parentNode: searchGroupId,
126-
extent: 'parent',
127-
draggable: true,
172+
deletable: false,
128173
},
129174
] as ReactFlowComponent[];
130175

131176
return {
132177
nodes: [...ingestNodes, ...searchNodes],
133-
edges: [] as ReactFlowEdge[],
178+
edges: [
179+
{
180+
id: edgeId,
181+
key: edgeId,
182+
source: ingestId1,
183+
target: ingestId2,
184+
markerEnd: {
185+
type: MarkerType.ArrowClosed,
186+
width: 20,
187+
height: 20,
188+
},
189+
zIndex: 2,
190+
deletable: false,
191+
},
192+
] as ReactFlowEdge[],
134193
};
135194
}
136195

public/component_types/indexer/indexer.ts

+13-19
Original file line numberDiff line numberDiff line change
@@ -25,7 +25,6 @@ export class Indexer extends BaseComponent {
2525
// TODO: may need to change to be looser. it should be able to take
2626
// in other component types
2727
baseClass: COMPONENT_CLASS.TRANSFORMER,
28-
optional: false,
2928
acceptMultiple: false,
3029
},
3130
];
@@ -34,32 +33,27 @@ export class Indexer extends BaseComponent {
3433
label: 'Index Name',
3534
name: 'indexName',
3635
type: 'select',
37-
optional: false,
38-
advanced: false,
3936
},
4037
];
4138
this.createFields = [
4239
{
4340
label: 'Index Name',
4441
name: 'indexName',
4542
type: 'string',
46-
optional: false,
47-
advanced: false,
48-
},
49-
{
50-
label: 'Mappings',
51-
name: 'indexMappings',
52-
type: 'json',
53-
placeholder: 'Enter an index mappings JSON blob...',
54-
optional: false,
55-
advanced: false,
56-
},
57-
];
58-
this.outputs = [
59-
{
60-
label: this.label,
61-
baseClasses: this.baseClasses,
6243
},
44+
// {
45+
// label: 'Mappings',
46+
// name: 'indexMappings',
47+
// type: 'json',
48+
// placeholder: 'Enter an index mappings JSON blob...',
49+
// },
6350
];
51+
// this.outputs = [
52+
// {
53+
// label: this.label,
54+
// baseClasses: this.baseClasses,
55+
// },
56+
// ];
57+
this.outputs = [];
6458
}
6559
}

public/component_types/indexer/knn_indexer.ts

+6-8
Original file line numberDiff line numberDiff line change
@@ -17,14 +17,12 @@ export class KnnIndexer extends Indexer {
1717
// @ts-ignore
1818
...this.createFields,
1919
// TODO: finalize what to expose / what to have for defaults here
20-
{
21-
label: 'K-NN Settings',
22-
name: 'knnSettings',
23-
type: 'json',
24-
placeholder: 'Enter K-NN settings JSON blob...',
25-
optional: false,
26-
advanced: false,
27-
},
20+
// {
21+
// label: 'K-NN Settings',
22+
// name: 'knnSettings',
23+
// type: 'json',
24+
// placeholder: 'Enter K-NN settings JSON blob...',
25+
// },
2826
];
2927
}
3028
}

0 commit comments

Comments
 (0)