From 60997369e0b7da618cb8ffdbf323e9cac55821bc Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Tue, 17 Sep 2024 16:42:41 -0700 Subject: [PATCH 1/4] Add basic prompt modal (partially working) Signed-off-by: Tyler Ohlsen --- .../processor_inputs/ml_processor_inputs.tsx | 36 +++- .../modals/configure_prompt_modal.tsx | 184 ++++++++++++++++++ .../processor_inputs/modals/index.ts | 8 + .../{ => modals}/input_transform_modal.tsx | 12 +- .../{ => modals}/output_transform_modal.tsx | 12 +- 5 files changed, 237 insertions(+), 15 deletions(-) create mode 100644 public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/configure_prompt_modal.tsx create mode 100644 public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/index.ts rename public/pages/workflow_detail/workflow_inputs/processor_inputs/{ => modals}/input_transform_modal.tsx (98%) rename public/pages/workflow_detail/workflow_inputs/processor_inputs/{ => modals}/output_transform_modal.tsx (98%) 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.tsx index f6d3be5e..3075ae1b 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.tsx @@ -17,6 +17,7 @@ import { EuiSpacer, EuiText, EuiToolTip, + EuiSmallButton, } from '@elastic/eui'; import { IProcessorConfig, @@ -30,8 +31,11 @@ import { IndexMappings, } from '../../../../../common'; import { MapArrayField, ModelField } from '../input_fields'; -import { InputTransformModal } from './input_transform_modal'; -import { OutputTransformModal } from './output_transform_modal'; +import { + ConfigurePromptModal, + InputTransformModal, + OutputTransformModal, +} from './modals'; import { AppState, getMappings, useAppDispatch } from '../../../../store'; import { formikToPartialPipeline, @@ -108,13 +112,14 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) { } }, [props.uiConfig.search.enrichRequest.processors]); - // advanced transformations modal state + // various modal states const [isInputTransformModalOpen, setIsInputTransformModalOpen] = useState< boolean >(false); const [isOutputTransformModalOpen, setIsOutputTransformModalOpen] = useState< boolean >(false); + const [isPromptModalOpen, setIsPromptModalOpen] = useState(false); // model interface state const [modelInterface, setModelInterface] = useState< @@ -240,6 +245,14 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) { onClose={() => setIsOutputTransformModalOpen(false)} /> )} + {isPromptModalOpen && ( + setIsPromptModalOpen(false)} + /> + )} + {props.context === PROCESSOR_CONTEXT.SEARCH_RESPONSE && ( + <> + {`Configure prompt (Optional)`} + + setIsPromptModalOpen(true)} + > + Configure + + + + )} void; +} + +/** + * A modal to configure advanced JSON-to-JSON transforms from a model's expected output + */ +export function ConfigurePromptModal(props: ConfigurePromptModalProps) { + const dispatch = useAppDispatch(); + const dataSourceId = getDataSourceId(); + const { values } = useFormikContext(); + + // get some current form values + const modelConfigPath = `${props.baseConfigPath}.${props.config.id}.model_config`; + const modelConfig = getIn(values, modelConfigPath) as string; + const modelInputs = parseModelInputs(props.modelInterface); + + // prompt state + const [prompt, setPrompt] = useState(''); + + // hook to set the prompt if found in the model config + useEffect(() => { + const modelConfigString = getIn( + values, + `${props.baseConfigPath}.${props.config.id}.model_config` + ) as string; + try { + const prompt = JSON.parse(modelConfigString)?.prompt; + if (!isEmpty(prompt)) { + setPrompt(prompt); + } else { + setPrompt(''); + } + } catch {} + }, [ + getIn(values, `${props.baseConfigPath}.${props.config.id}.model_config`), + ]); + + return ( + + + +

{`Configure prompt`}

+
+
+ + + + <> + + Configure a custom prompt template. Optionally use model input + values in the prompt template with placeholders. + + + Model inputs + + {modelInputs.length > 0 ? ( + <> + + {customStringify({ + parameters: parseModelInputsObj(props.modelInterface), + })} + + + ) : ( + + )} + + {modelInputs.length > 0 && ( + <> + Model input placeholders + {modelInputs.map((modelInput) => { + return ( + {`parameters.${modelInput.label}`} + ); + })} + + + )} + Prompt + + console.log('value now: ', value)} + /> + + + + + + + Close + + +
+ ); +} diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/index.ts b/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/index.ts new file mode 100644 index 00000000..790872f3 --- /dev/null +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/index.ts @@ -0,0 +1,8 @@ +/* + * Copyright OpenSearch Contributors + * SPDX-License-Identifier: Apache-2.0 + */ + +export * from './input_transform_modal'; +export * from './output_transform_modal'; +export * from './configure_prompt_modal'; diff --git a/public/pages/workflow_detail/workflow_inputs/processor_inputs/input_transform_modal.tsx b/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/input_transform_modal.tsx similarity index 98% rename from public/pages/workflow_detail/workflow_inputs/processor_inputs/input_transform_modal.tsx rename to public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/input_transform_modal.tsx index ba3a0017..2aefddcb 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/input_transform_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/input_transform_modal.tsx @@ -43,25 +43,25 @@ import { WorkflowConfig, WorkflowFormValues, customStringify, -} from '../../../../../common'; +} from '../../../../../../common'; import { formikToPartialPipeline, generateTransform, prepareDocsForSimulate, unwrapTransformedDocs, -} from '../../../../utils'; +} from '../../../../../utils'; import { searchIndex, simulatePipeline, useAppDispatch, -} from '../../../../store'; -import { getCore } from '../../../../services'; +} from '../../../../../store'; +import { getCore } from '../../../../../services'; import { 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/output_transform_modal.tsx b/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/output_transform_modal.tsx similarity index 98% rename from public/pages/workflow_detail/workflow_inputs/processor_inputs/output_transform_modal.tsx rename to public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/output_transform_modal.tsx index bec619dd..334dc620 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/output_transform_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/output_transform_modal.tsx @@ -41,25 +41,25 @@ import { WorkflowConfig, WorkflowFormValues, customStringify, -} from '../../../../../common'; +} from '../../../../../../common'; import { formikToPartialPipeline, generateTransform, 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; From c917c80f9b3283204b739484ec40ef41a0cc43e2 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Tue, 17 Sep 2024 17:46:44 -0700 Subject: [PATCH 2/4] Display table of model inputs Signed-off-by: Tyler Ohlsen --- .../modals/configure_prompt_modal.tsx | 146 +++++++++++------- 1 file changed, 89 insertions(+), 57 deletions(-) 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/modals/configure_prompt_modal.tsx index 7a2ba0bc..a5ee7624 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/configure_prompt_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/configure_prompt_modal.tsx @@ -5,7 +5,7 @@ import React, { useState, useEffect } from 'react'; import { useFormikContext, getIn } from 'formik'; -import { cloneDeep, isEmpty, set } from 'lodash'; +import { isEmpty } from 'lodash'; import { EuiCodeEditor, EuiFlexGroup, @@ -15,8 +15,6 @@ import { EuiModalFooter, EuiModalHeader, EuiModalHeaderTitle, - EuiCompressedSelect, - EuiSelectOption, EuiSmallButton, EuiSpacer, EuiText, @@ -26,43 +24,19 @@ import { EuiCodeBlock, EuiCallOut, EuiCode, + EuiBasicTable, + EuiAccordion, } from '@elastic/eui'; import { - IConfigField, IProcessorConfig, - IngestPipelineConfig, - JSONPATH_ROOT_SELECTOR, - ML_INFERENCE_DOCS_LINK, - ML_INFERENCE_RESPONSE_DOCS_LINK, - MapArrayFormValue, + ModelInputFormField, ModelInterface, - PROCESSOR_CONTEXT, - SearchHit, - SearchPipelineConfig, - SimulateIngestPipelineResponse, - WorkflowConfig, WorkflowFormValues, customStringify, } from '../../../../../../common'; import { - formikToPartialPipeline, - generateTransform, - prepareDocsForSimulate, - unwrapTransformedDocs, -} from '../../../../../utils'; -import { - searchIndex, - simulatePipeline, - useAppDispatch, -} from '../../../../../store'; -import { getCore } from '../../../../../services'; -import { BooleanField, MapArrayField } from '../../input_fields'; -import { - getDataSourceId, parseModelInputs, parseModelInputsObj, - parseModelOutputs, - parseModelOutputsObj, } from '../../../../../utils/utils'; interface ConfigurePromptModalProps { @@ -76,8 +50,6 @@ interface ConfigurePromptModalProps { * A modal to configure advanced JSON-to-JSON transforms from a model's expected output */ export function ConfigurePromptModal(props: ConfigurePromptModalProps) { - const dispatch = useAppDispatch(); - const dataSourceId = getDataSourceId(); const { values } = useFormikContext(); // get some current form values @@ -85,6 +57,9 @@ export function ConfigurePromptModal(props: ConfigurePromptModalProps) { const modelConfig = getIn(values, modelConfigPath) as string; const modelInputs = parseModelInputs(props.modelInterface); + // popover state containing the model interface details, if applicable + const [popoverOpen, setPopoverOpen] = useState(false); + // prompt state const [prompt, setPrompt] = useState(''); @@ -117,39 +92,96 @@ export function ConfigurePromptModal(props: ConfigurePromptModalProps) { <> - - Configure a custom prompt template. Optionally use model input - values in the prompt template with placeholders. - - - Model inputs {modelInputs.length > 0 ? ( <> - - {customStringify({ - parameters: parseModelInputsObj(props.modelInterface), - })} - + + <> + + + To use any model inputs in the prompt template, copy the + relevant placeholder string directly. + + + ( + + {modelInput.type === 'array' + ? `\$\{parameters.${label}.toString()\}` + : `\$\\{parameters.${label}\\}`} + + ), + }, + ]} + /> + + setPopoverOpen(false)} + button={ + setPopoverOpen(!popoverOpen)} + > + View full input schema + + } + > + + The JSON Schema defining the model's expected input + + + {customStringify({ + parameters: parseModelInputsObj( + props.modelInterface + ), + })} + + + + + ) : ( )} - {modelInputs.length > 0 && ( - <> - Model input placeholders - {modelInputs.map((modelInput) => { - return ( - {`parameters.${modelInput.label}`} - ); - })} - - - )} Prompt Date: Wed, 18 Sep 2024 11:01:11 -0700 Subject: [PATCH 3/4] Add presets dropdown; more cleanup Signed-off-by: Tyler Ohlsen --- common/constants.ts | 40 ++- common/interfaces.ts | 5 + .../processor_inputs/ml_processor_inputs.tsx | 3 +- .../modals/configure_prompt_modal.tsx | 235 ++++++++++++------ .../search_inputs/edit_query_modal.tsx | 6 +- .../new_workflow/quick_configure_modal.tsx | 4 + 6 files changed, 209 insertions(+), 84 deletions(-) diff --git a/common/constants.ts b/common/constants.ts index 319b720a..e28d84ca 100644 --- a/common/constants.ts +++ b/common/constants.ts @@ -3,7 +3,12 @@ * SPDX-License-Identifier: Apache-2.0 */ -import { MapEntry, QueryPreset, WORKFLOW_STATE } from './interfaces'; +import { + MapEntry, + PromptPreset, + QueryPreset, + WORKFLOW_STATE, +} from './interfaces'; import { customStringify } from './utils'; export const PLUGIN_ID = 'search-studio'; @@ -409,6 +414,39 @@ export const QUERY_PRESETS = [ }, ] as QueryPreset[]; +/** + * PROMPT PRESETS + */ +export const SUMMARIZE_DOCS_PROMPT = + "Human: You are a professional data analyist. \ +You are given a list of document results. You will \ +analyze the data and generate a human-readable summary of the results. If you don't \ +know the answer, just say I don't know.\ +\n\n Results: \ +\n\n Human: Please summarize the results.\ +\n\n Assistant:"; + +export const QA_WITH_DOCUMENTS_PROMPT = + "Human: You are a professional data analyist. \ +You are given a list of document results, along with a question. You will \ +analyze the results and generate a human-readable response to the question, \ +based on the results. If you don't know the answer, just say I don't know.\ +\n\n Results: \ +\n\n Question: \ +\n\n Human: Please answer the question using the provided results.\ +\n\n Assistant:"; + +export const PROMPT_PRESETS = [ + { + name: 'Summarize documents', + prompt: SUMMARIZE_DOCS_PROMPT, + }, + { + name: 'QA with documents', + prompt: QA_WITH_DOCUMENTS_PROMPT, + }, +] as PromptPreset[]; + /** * MISCELLANEOUS */ diff --git a/common/interfaces.ts b/common/interfaces.ts index 9cd0d1b2..674a21a6 100644 --- a/common/interfaces.ts +++ b/common/interfaces.ts @@ -482,6 +482,11 @@ export type QueryPreset = { query: string; }; +export type PromptPreset = { + name: string; + prompt: string; +}; + export type QuickConfigureFields = { modelId?: string; vectorField?: string; 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.tsx index 3075ae1b..faf53875 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.tsx @@ -276,7 +276,7 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) { > Configure - + )} @@ -311,7 +311,6 @@ export function MLProcessorInputs(props: MLProcessorInputsProps) { - (); + const { values, setFieldValue, setFieldTouched } = useFormikContext< + WorkflowFormValues + >(); // get some current form values const modelConfigPath = `${props.baseConfigPath}.${props.config.id}.model_config`; const modelConfig = getIn(values, modelConfigPath) as string; const modelInputs = parseModelInputs(props.modelInterface); - // popover state containing the model interface details, if applicable - const [popoverOpen, setPopoverOpen] = useState(false); + // popover states + const [schemaPopoverOpen, setSchemaPopoverOpen] = useState(false); + const [presetsPopoverOpen, setPresetsPopoverOpen] = useState(false); - // prompt state - const [prompt, setPrompt] = useState(''); + // prompt str state. manipulated as users manually update, or + // from selecting a preset + const [promptStr, setPromptStr] = useState(''); // hook to set the prompt if found in the model config useEffect(() => { @@ -72,9 +81,9 @@ export function ConfigurePromptModal(props: ConfigurePromptModalProps) { try { const prompt = JSON.parse(modelConfigString)?.prompt; if (!isEmpty(prompt)) { - setPrompt(prompt); + setPromptStr(prompt); } else { - setPrompt(''); + setPromptStr(''); } } catch {} }, [ @@ -93,10 +102,83 @@ export function ConfigurePromptModal(props: ConfigurePromptModalProps) { <> - {modelInputs.length > 0 ? ( + setPresetsPopoverOpen(!presetsPopoverOpen)} + > + Choose from a preset + + } + isOpen={presetsPopoverOpen} + closePopover={() => setPresetsPopoverOpen(false)} + anchorPosition="downLeft" + > + ({ + name: preset.name, + onClick: () => { + setFieldValue( + modelConfigPath, + customStringify({ + ...JSON.parse(modelConfig), + prompt: preset.prompt, + }) + ); + setFieldTouched(modelConfigPath, true); + setPresetsPopoverOpen(false); + }, + })), + }, + ]} + /> + + + Prompt + + setPromptStr(value)} + onBlur={(e) => { + let updatedModelConfig = JSON.parse(modelConfig); + if (isEmpty(promptStr)) { + // if the input is blank, it is assumed the user + // does not want any prompt. hence, remove the "prompt" field + // from the config altogether. + delete updatedModelConfig.prompt; + } else { + updatedModelConfig.prompt = promptStr; + } + setFieldValue( + modelConfigPath, + customStringify(updatedModelConfig) + ); + setFieldTouched(modelConfigPath); + }} + /> + {modelInputs.length > 0 && ( <> + @@ -108,52 +190,19 @@ export function ConfigurePromptModal(props: ConfigurePromptModalProps) { color="subdued" > To use any model inputs in the prompt template, copy the - relevant placeholder string directly. + placeholder string directly.
- ( - - {modelInput.type === 'array' - ? `\$\{parameters.${label}.toString()\}` - : `\$\\{parameters.${label}\\}`} - - ), - }, - ]} - /> + setPopoverOpen(false)} + isOpen={schemaPopoverOpen} + closePopover={() => setSchemaPopoverOpen(false)} button={ setPopoverOpen(!popoverOpen)} + onClick={() => + setSchemaPopoverOpen(!schemaPopoverOpen) + } > View full input schema @@ -167,41 +216,16 @@ export function ConfigurePromptModal(props: ConfigurePromptModalProps) { fontSize="m" isCopyable={false} > - {customStringify({ - parameters: parseModelInputsObj( - props.modelInterface - ), - })} + {customStringify( + parseModelInputsObj(props.modelInterface) + )} - ) : ( - )} - - Prompt - - console.log('value now: ', value)} - />
@@ -214,3 +238,56 @@ export function ConfigurePromptModal(props: ConfigurePromptModalProps) { ); } + +const columns = [ + { + name: 'Name', + field: 'label', + width: '30%', + }, + { + name: 'Type', + field: 'type', + width: '15%', + }, + { + name: 'Placeholder string', + field: 'label', + width: '55%', + render: (label: string, modelInput: ModelInputFormField) => ( + + {modelInput.type === 'array' + ? `\$\{parameters.${label}.toString()\}` + : `\$\\{parameters.${label}\\}`} + + ), + }, + { + name: 'Actions', + field: 'label', + width: '10%', + render: (label: string, modelInput: ModelInputFormField) => ( + + {(copy) => ( + + )} + + ), + }, +]; diff --git a/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx b/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx index 693556f5..256b38cf 100644 --- a/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/search_inputs/edit_query_modal.tsx @@ -34,7 +34,9 @@ interface EditQueryModalProps { */ export function EditQueryModal(props: EditQueryModalProps) { // Form state - const { setFieldValue } = useFormikContext(); + const { setFieldValue, setFieldTouched } = useFormikContext< + WorkflowFormValues + >(); // popover state const [popoverOpen, setPopoverOpen] = useState(false); @@ -69,9 +71,9 @@ export function EditQueryModal(props: EditQueryModalProps) { name: preset.name, onClick: () => { setFieldValue(props.queryFieldPath, preset.query); + setFieldTouched(props.queryFieldPath, true); setPopoverOpen(false); }, - size: 'full', })), }, ]} diff --git a/public/pages/workflows/new_workflow/quick_configure_modal.tsx b/public/pages/workflows/new_workflow/quick_configure_modal.tsx index 316a13b4..53d868a0 100644 --- a/public/pages/workflows/new_workflow/quick_configure_modal.tsx +++ b/public/pages/workflows/new_workflow/quick_configure_modal.tsx @@ -214,6 +214,10 @@ function injectQuickConfigureFields( } case WORKFLOW_TYPE.RAG: { if (!isEmpty(quickConfigureFields) && workflow.ui_metadata?.config) { + workflow.ui_metadata.config = updateIndexConfig( + workflow.ui_metadata.config, + quickConfigureFields + ); workflow.ui_metadata.config = updateSearchResponseProcessors( workflow.ui_metadata.config, quickConfigureFields, From aa69457a9cecdc53a2952e687cfce3797c8c70f7 Mon Sep 17 00:00:00 2001 From: Tyler Ohlsen Date: Wed, 18 Sep 2024 11:13:02 -0700 Subject: [PATCH 4/4] column width updates; util fn refactoring Signed-off-by: Tyler Ohlsen --- .../modals/configure_prompt_modal.tsx | 27 ++++++++++--------- 1 file changed, 15 insertions(+), 12 deletions(-) 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/modals/configure_prompt_modal.tsx index d8dbdd0f..4850a6c8 100644 --- a/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/configure_prompt_modal.tsx +++ b/public/pages/workflow_detail/workflow_inputs/processor_inputs/modals/configure_prompt_modal.tsx @@ -243,7 +243,7 @@ const columns = [ { name: 'Name', field: 'label', - width: '30%', + width: '25%', }, { name: 'Type', @@ -253,7 +253,7 @@ const columns = [ { name: 'Placeholder string', field: 'label', - width: '55%', + width: '50%', render: (label: string, modelInput: ModelInputFormField) => ( - {modelInput.type === 'array' - ? `\$\{parameters.${label}.toString()\}` - : `\$\\{parameters.${label}\\}`} + {getPlaceholderString(modelInput.type, label)} ), }, @@ -273,13 +271,7 @@ const columns = [ field: 'label', width: '10%', render: (label: string, modelInput: ModelInputFormField) => ( - + {(copy) => (