From 58937432f52934c9fefec82ca0f66b9db4696019 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Wed, 20 Nov 2024 12:07:59 -0800 Subject: [PATCH] Refactor model input/output transform configurations (#492) (#493) (cherry picked from commit c2f05e435ae489643796c71a5f0a508c6e452d50) Signed-off-by: Tyler Ohlsen Signed-off-by: github-actions[bot] Co-authored-by: github-actions[bot] --- .../ml_processor_inputs/index.ts | 7 + .../ml_processor_inputs.tsx | 89 ++------- .../modals/configure_prompt_modal.tsx | 4 +- .../{ => ml_processor_inputs}/modals/index.ts | 1 + .../modals/input_transform_modal.tsx | 12 +- .../modals/output_transform_modal.tsx | 12 +- .../modals/override_query_modal.tsx | 6 +- .../ml_processor_inputs/model_inputs.tsx | 185 ++++++++++++++++++ .../ml_processor_inputs/model_outputs.tsx | 85 ++++++++ 9 files changed, 311 insertions(+), 90 deletions(-) create mode 100644 public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/index.ts rename public/pages/workflow_detail/workflow_inputs/processor_inputs/{ => ml_processor_inputs}/ml_processor_inputs.tsx (83%) rename public/pages/workflow_detail/workflow_inputs/processor_inputs/{ => ml_processor_inputs}/modals/configure_prompt_modal.tsx (99%) rename public/pages/workflow_detail/workflow_inputs/processor_inputs/{ => ml_processor_inputs}/modals/index.ts (83%) rename public/pages/workflow_detail/workflow_inputs/processor_inputs/{ => ml_processor_inputs}/modals/input_transform_modal.tsx (99%) rename public/pages/workflow_detail/workflow_inputs/processor_inputs/{ => ml_processor_inputs}/modals/output_transform_modal.tsx (98%) rename public/pages/workflow_detail/workflow_inputs/processor_inputs/{ => ml_processor_inputs}/modals/override_query_modal.tsx (98%) create mode 100644 public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_inputs.tsx create mode 100644 public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_outputs.tsx diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/index.ts b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/index.ts new file mode 100644 index 00000000..ff3459f4 --- /dev/null +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/index.ts @@ -0,0 +1,7 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './ml_processor_inputs'; +export * from './modals'; diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs.tsx b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/ml_processor_inputs.tsx similarity index 83% rename from public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs.tsx rename to public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/ml_processor_inputs.tsx index cd70d468..90c525e7 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/ml_processor_inputs.tsx @@ -31,25 +31,25 @@ import { MapArrayFormValue, MapEntry, MapFormValue, - REQUEST_PREFIX, - REQUEST_PREFIX_WITH_JSONPATH_ROOT_SELECTOR, -} from '../../../../../common'; -import { MapArrayField, ModelField } from '../input_fields'; +} from '../../../../../../common'; +import { ModelField } from '../../input_fields'; import { ConfigurePromptModal, InputTransformModal, OutputTransformModal, + OverrideQueryModal, } from './modals'; -import { AppState, getMappings, useAppDispatch } from '../../../../store'; +import { ModelInputs } from './model_inputs'; +import { AppState, getMappings, useAppDispatch } from '../../../../../store'; import { formikToPartialPipeline, getDataSourceId, parseModelInputs, parseModelOutputs, sanitizeJSONPath, -} from '../../../../utils'; -import { ConfigFieldList } from '../config_field_list'; -import { OverrideQueryModal } from './modals/override_query_modal'; +} from '../../../../../utils'; +import { ConfigFieldList } from '../../config_field_list'; +import { ModelOutputs } from './model_outputs'; interface MLProcessorInputsProps { uiConfig: WorkflowConfig; @@ -83,10 +83,6 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) { const inputMapValue = getIn(values, inputMapFieldPath); const outputMapFieldPath = `${props.baseConfigPath}.${props.config.id}.output_map`; const outputMapValue = getIn(values, outputMapFieldPath); - const fullResponsePath = getIn( - values, - `${props.baseConfigPath}.${props.config.id}.full_response_path` - ); // contains a configurable prompt field or not. if so, expose some extra // dedicated UI @@ -390,44 +386,10 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) { - @@ -458,29 +420,10 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) { - {inputMapValue.length !== outputMapValue.length && diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/configure_prompt_modal.tsx b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/configure_prompt_modal.tsx similarity index 99% rename from public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/configure_prompt_modal.tsx rename to public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/configure_prompt_modal.tsx index 12dc9ca1..0a64c752 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/configure_prompt_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/configure_prompt_modal.tsx @@ -38,11 +38,11 @@ import { PromptPreset, WorkflowFormValues, customStringify, -} from '../../../../../../common'; +} from '../../../../../../../common'; import { parseModelInputs, parseModelInputsObj, -} from '../../../../../utils/utils'; +} from '../../../../../../utils/utils'; interface ConfigurePromptModalProps { config: IProcessorConfig; diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/index.ts b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/index.ts similarity index 83% rename from public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/index.ts rename to public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/index.ts index 790872f3..6874447e 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/index.ts +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/index.ts @@ -6,3 +6,4 @@ export * from './input_transform_modal'; export * from './output_transform_modal'; export * from './configure_prompt_modal'; +export * from './override_query_modal'; diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/input_transform_modal.tsx b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/input_transform_modal.tsx similarity index 99% rename from public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/input_transform_modal.tsx rename to public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/input_transform_modal.tsx index 292fa8a0..a40015b3 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/input_transform_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/input_transform_modal.tsx @@ -48,7 +48,7 @@ import { customStringify, REQUEST_PREFIX, REQUEST_PREFIX_WITH_JSONPATH_ROOT_SELECTOR, -} from '../../../../../../common'; +} from '../../../../../../../common'; import { formikToPartialPipeline, generateTransform, @@ -56,20 +56,20 @@ import { getInitialValue, prepareDocsForSimulate, unwrapTransformedDocs, -} from '../../../../../utils'; +} from '../../../../../../utils'; import { searchIndex, simulatePipeline, useAppDispatch, -} from '../../../../../store'; -import { getCore } from '../../../../../services'; +} from '../../../../../../store'; +import { getCore } from '../../../../../../services'; import { generateArrayTransform, getDataSourceId, parseModelInputs, parseModelInputsObj, -} from '../../../../../utils/utils'; -import { BooleanField, MapArrayField } from '../../input_fields'; +} from '../../../../../../utils/utils'; +import { BooleanField, MapArrayField } from '../../../input_fields'; interface InputTransformModalProps { uiConfig: WorkflowConfig; diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/output_transform_modal.tsx b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/output_transform_modal.tsx similarity index 98% rename from public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/output_transform_modal.tsx rename to public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/output_transform_modal.tsx index 7d9b7c41..9d1dc7a4 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/output_transform_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/output_transform_modal.tsx @@ -46,7 +46,7 @@ import { WorkflowConfig, WorkflowFormValues, customStringify, -} from '../../../../../../common'; +} from '../../../../../../../common'; import { formikToPartialPipeline, generateTransform, @@ -54,19 +54,19 @@ import { getInitialValue, prepareDocsForSimulate, unwrapTransformedDocs, -} from '../../../../../utils'; +} from '../../../../../../utils'; import { searchIndex, simulatePipeline, useAppDispatch, -} from '../../../../../store'; -import { getCore } from '../../../../../services'; -import { BooleanField, MapArrayField } from '../../input_fields'; +} from '../../../../../../store'; +import { getCore } from '../../../../../../services'; +import { BooleanField, MapArrayField } from '../../../input_fields'; import { getDataSourceId, parseModelOutputs, parseModelOutputsObj, -} from '../../../../../utils/utils'; +} from '../../../../../../utils/utils'; interface OutputTransformModalProps { uiConfig: WorkflowConfig; diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/override_query_modal.tsx b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/override_query_modal.tsx similarity index 98% rename from public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/override_query_modal.tsx rename to public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/override_query_modal.tsx index 57a1d001..d4257556 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/override_query_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/modals/override_query_modal.tsx @@ -39,9 +39,9 @@ import { VECTOR_FIELD_PATTERN, VECTOR_PATTERN, WorkflowFormValues, -} from '../../../../../../common'; -import { parseModelOutputs } from '../../../../../utils/utils'; -import { JsonField } from '../../input_fields'; +} from '../../../../../../../common'; +import { parseModelOutputs } from '../../../../../../utils/utils'; +import { JsonField } from '../../../input_fields'; interface OverrideQueryModalProps { config: IProcessorConfig; diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_inputs.tsx b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_inputs.tsx new file mode 100644 index 00000000..4d18aa44 --- /dev/null +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_inputs.tsx @@ -0,0 +1,185 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState, useEffect } from 'react'; +import { useFormikContext, getIn } from 'formik'; +import { isEmpty } from 'lodash'; +import { useSelector } from 'react-redux'; +import { flattie } from 'flattie'; +import { + IProcessorConfig, + IConfigField, + PROCESSOR_CONTEXT, + WorkflowFormValues, + ModelInterface, + IndexMappings, + REQUEST_PREFIX, + REQUEST_PREFIX_WITH_JSONPATH_ROOT_SELECTOR, +} from '../../../../../../common'; +import { MapArrayField } from '../../input_fields'; +import { AppState, getMappings, useAppDispatch } from '../../../../../store'; +import { + getDataSourceId, + parseModelInputs, + sanitizeJSONPath, +} from '../../../../../utils'; + +interface ModelInputsProps { + config: IProcessorConfig; + baseConfigPath: string; + context: PROCESSOR_CONTEXT; +} + +/** + * Base component to configure ML inputs. + */ +export function ModelInputs(props: ModelInputsProps) { + const dispatch = useAppDispatch(); + const dataSourceId = getDataSourceId(); + const { models } = useSelector((state: AppState) => state.ml); + const indices = useSelector((state: AppState) => state.opensearch.indices); + const { values } = useFormikContext(); + + // get some current form & config values + const modelField = props.config.fields.find( + (field) => field.type === 'model' + ) as IConfigField; + const modelFieldPath = `${props.baseConfigPath}.${props.config.id}.${modelField.id}`; + const inputMapFieldPath = `${props.baseConfigPath}.${props.config.id}.input_map`; + + // model interface state + const [modelInterface, setModelInterface] = useState< + ModelInterface | undefined + >(undefined); + + // on initial load of the models, update model interface states + useEffect(() => { + if (!isEmpty(models)) { + const modelId = getIn(values, modelFieldPath)?.id; + if (modelId) { + setModelInterface(models[modelId]?.interface); + } + } + }, [models]); + + // persisting doc/query/index mapping fields to collect a list + // of options to display in the dropdowns when configuring input / output maps + const [docFields, setDocFields] = useState<{ label: string }[]>([]); + const [queryFields, setQueryFields] = useState<{ label: string }[]>([]); + const [indexMappingFields, setIndexMappingFields] = useState< + { label: string }[] + >([]); + useEffect(() => { + try { + const docObjKeys = Object.keys( + flattie((JSON.parse(values.ingest.docs) as {}[])[0]) + ); + if (docObjKeys.length > 0) { + setDocFields( + docObjKeys.map((key) => { + return { + label: + // ingest inputs can handle dot notation, and hence don't need + // sanitizing to handle JSONPath edge cases. The other contexts + // only support JSONPath, and hence need some post-processing/sanitizing. + props.context === PROCESSOR_CONTEXT.INGEST + ? key + : sanitizeJSONPath(key), + }; + }) + ); + } + } catch {} + }, [values?.ingest?.docs]); + useEffect(() => { + try { + const queryObjKeys = Object.keys( + flattie(JSON.parse(values.search.request)) + ); + if (queryObjKeys.length > 0) { + setQueryFields( + queryObjKeys.map((key) => { + return { + label: + // ingest inputs can handle dot notation, and hence don't need + // sanitizing to handle JSONPath edge cases. The other contexts + // only support JSONPath, and hence need some post-processing/sanitizing. + props.context === PROCESSOR_CONTEXT.INGEST + ? key + : sanitizeJSONPath(key), + }; + }) + ); + } + } catch {} + }, [values?.search?.request]); + useEffect(() => { + const indexName = values?.search?.index?.name as string | undefined; + if (indexName !== undefined && indices[indexName] !== undefined) { + dispatch( + getMappings({ + index: indexName, + dataSourceId, + }) + ) + .unwrap() + .then((resp: IndexMappings) => { + const mappingsObjKeys = Object.keys(resp.properties); + if (mappingsObjKeys.length > 0) { + setIndexMappingFields( + mappingsObjKeys.map((key) => { + return { + label: key, + type: resp.properties[key]?.type, + }; + }) + ); + } + }); + } + }, [values?.search?.index?.name]); + + return ( + + ); +} diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_outputs.tsx b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_outputs.tsx new file mode 100644 index 00000000..6d143dee --- /dev/null +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/ml_processor_inputs/model_outputs.tsx @@ -0,0 +1,85 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +import React, { useState, useEffect } from 'react'; +import { getIn, useFormikContext } from 'formik'; +import { isEmpty } from 'lodash'; +import { useSelector } from 'react-redux'; +import { + IProcessorConfig, + IConfigField, + PROCESSOR_CONTEXT, + WorkflowFormValues, + ModelInterface, +} from '../../../../../../common'; +import { MapArrayField } from '../../input_fields'; + +import { AppState } from '../../../../../store'; +import { parseModelOutputs } from '../../../../../utils'; + +interface ModelOutputsProps { + config: IProcessorConfig; + baseConfigPath: string; + context: PROCESSOR_CONTEXT; +} + +/** + * Base component to configure ML outputs. + */ +export function ModelOutputs(props: ModelOutputsProps) { + const { models } = useSelector((state: AppState) => state.ml); + const { values } = useFormikContext(); + + // get some current form & config values + const modelField = props.config.fields.find( + (field) => field.type === 'model' + ) as IConfigField; + const modelFieldPath = `${props.baseConfigPath}.${props.config.id}.${modelField.id}`; + const outputMapFieldPath = `${props.baseConfigPath}.${props.config.id}.output_map`; + const fullResponsePath = getIn( + values, + `${props.baseConfigPath}.${props.config.id}.full_response_path` + ); + + // model interface state + const [modelInterface, setModelInterface] = useState< + ModelInterface | undefined + >(undefined); + + // on initial load of the models, update model interface states + useEffect(() => { + if (!isEmpty(models)) { + const modelId = getIn(values, modelFieldPath)?.id; + if (modelId) { + setModelInterface(models[modelId]?.interface); + } + } + }, [models]); + + return ( + + ); +}