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 f6d3be5e..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 @@ -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 a prompt template. Can manually configure, include placeholder values + * using other model inputs, and/or select from a presets library. + */ +export function ConfigurePromptModal(props: ConfigurePromptModalProps) { + 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 states + const [schemaPopoverOpen, setSchemaPopoverOpen] = useState(false); + const [presetsPopoverOpen, setPresetsPopoverOpen] = useState(false); + + // 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(() => { + const modelConfigString = getIn( + values, + `${props.baseConfigPath}.${props.config.id}.model_config` + ) as string; + try { + const prompt = JSON.parse(modelConfigString)?.prompt; + if (!isEmpty(prompt)) { + setPromptStr(prompt); + } else { + setPromptStr(''); + } + } catch {} + }, [ + getIn(values, `${props.baseConfigPath}.${props.config.id}.model_config`), + ]); + + return ( + + + +

{`Configure prompt`}

+
+
+ + + + <> + + 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 && ( + <> + + + <> + + + To use any model inputs in the prompt template, copy the + placeholder string directly. + + + + + setSchemaPopoverOpen(false)} + button={ + + setSchemaPopoverOpen(!schemaPopoverOpen) + } + > + View full input schema + + } + > + + The JSON Schema defining the model's expected input + + + {customStringify( + parseModelInputsObj(props.modelInterface) + )} + + + + + + + )} + + + + + + + Close + + +
+ ); +} + +const columns = [ + { + name: 'Name', + field: 'label', + width: '25%', + }, + { + name: 'Type', + field: 'type', + width: '15%', + }, + { + name: 'Placeholder string', + field: 'label', + width: '50%', + render: (label: string, modelInput: ModelInputFormField) => ( + + {getPlaceholderString(modelInput.type, label)} + + ), + }, + { + name: 'Actions', + field: 'label', + width: '10%', + render: (label: string, modelInput: ModelInputFormField) => ( + + {(copy) => ( + + )} + + ), + }, +]; + +// small util fn to get the full placeholder string to be +// inserted into the template. String conversion is required +// if the input is an array, for example. Also, all values +// should be prepended with "parameters.", as all inputs +// will be nested under a base parameters obj. +function getPlaceholderString(type: string, label: string) { + return type === 'array' + ? `\$\{parameters.${label}.toString()\}` + : `\$\\{parameters.${label}\\}`; +} 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; 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,