Skip to content

Commit

Permalink
Add basic preset queries; onboard hybrid/multimodal search use cases (o…
Browse files Browse the repository at this point in the history
…pensearch-project#302)

Signed-off-by: Tyler Ohlsen <[email protected]>
  • Loading branch information
ohltyler authored Aug 20, 2024
1 parent fb3dbba commit cc54976
Show file tree
Hide file tree
Showing 12 changed files with 303 additions and 55 deletions.
96 changes: 89 additions & 7 deletions common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,8 @@
* SPDX-License-Identifier: Apache-2.0
*/

import { WORKFLOW_STATE } from './interfaces';
import { QueryPreset, WORKFLOW_STATE } from './interfaces';
import { customStringify } from './utils';

export const PLUGIN_ID = 'flow-framework';
export const SEARCH_STUDIO = 'Search Studio';
Expand Down Expand Up @@ -59,6 +60,8 @@ export const SEARCH_MODELS_NODE_API_PATH = `${BASE_MODEL_NODE_API_PATH}/search`;
// frontend-specific workflow types, derived from the available preset templates
export enum WORKFLOW_TYPE {
SEMANTIC_SEARCH = 'Semantic search',
MULTIMODAL_SEARCH = 'Multimodal search',
HYBRID_SEARCH = 'Hybrid search',
CUSTOM = 'Custom',
UNKNOWN = 'Unknown',
}
Expand Down Expand Up @@ -142,6 +145,91 @@ export const FIXED_TOKEN_LENGTH_OPTIONAL_FIELDS = [
export const DELIMITER_OPTIONAL_FIELDS = ['delimiter'];
export const SHARED_OPTIONAL_FIELDS = ['max_chunk_limit', 'description', 'tag'];

/**
* QUERIES
*/
export const FETCH_ALL_QUERY = {
query: {
match_all: {},
},
size: 1000,
};
export const SEMANTIC_SEARCH_QUERY = {
_source: {
excludes: [`{{vector_field}}`],
},
query: {
neural: {
[`{{vector_field}}`]: {
query_text: `{{query_text}}`,
model_id: `{{model_id}}`,
k: 100,
},
},
},
};
export const MULTIMODAL_SEARCH_QUERY = {
_source: {
excludes: [`{{vector_field}}`],
},
query: {
neural: {
[`{{vector_field}}`]: {
query_text: `{{query_text}}`,
query_image: `{{query_image}}`,
model_id: `{{model_id}}`,
k: 100,
},
},
},
};
export const HYBRID_SEARCH_QUERY = {
_source: {
excludes: [`{{vector_field}}`],
},
query: {
hybrid: {
queries: [
{
match: {
[`{{text_field}}`]: {
query: `{{query_text}}`,
},
},
},
{
neural: {
[`{{vector_field}}`]: {
query_text: `{{query_text}}`,
model_id: `{{model_id}}`,
k: 5,
},
},
},
],
},
},
};

export const QUERY_PRESETS = [
{
name: 'Fetch all',
query: customStringify(FETCH_ALL_QUERY),
},
{
name: WORKFLOW_TYPE.SEMANTIC_SEARCH,
query: customStringify(SEMANTIC_SEARCH_QUERY),
},
{
name: WORKFLOW_TYPE.MULTIMODAL_SEARCH,
query: customStringify(MULTIMODAL_SEARCH_QUERY),
},
{
name: WORKFLOW_TYPE.HYBRID_SEARCH,
query: customStringify(HYBRID_SEARCH_QUERY),
},
] as QueryPreset[];

/**
* MISCELLANEOUS
*/
Expand All @@ -152,12 +240,6 @@ export const DEFAULT_NEW_WORKFLOW_STATE = WORKFLOW_STATE.NOT_STARTED;
export const DEFAULT_NEW_WORKFLOW_STATE_TYPE = ('NOT_STARTED' as any) as typeof WORKFLOW_STATE;
export const DATE_FORMAT_PATTERN = 'MM/DD/YY hh:mm A';
export const EMPTY_FIELD_STRING = '--';
export const FETCH_ALL_QUERY_BODY = {
query: {
match_all: {},
},
size: 1000,
};
export const INDEX_NOT_FOUND_EXCEPTION = 'index_not_found_exception';
export const ERROR_GETTING_WORKFLOW_MSG = 'Failed to retrieve template';
export const NO_MODIFICATIONS_FOUND_TEXT =
Expand Down
5 changes: 5 additions & 0 deletions common/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -456,6 +456,11 @@ export type WorkflowDict = {
[workflowId: string]: Workflow;
};

export type QueryPreset = {
name: string;
query: string;
};

/**
********** OPENSEARCH TYPES/INTERFACES ************
*/
Expand Down
6 changes: 3 additions & 3 deletions public/pages/workflow_detail/workflow_detail.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ import {
EuiPage,
EuiPageBody,
} from '@elastic/eui';
import { APP_PATH, BREADCRUMBS, SHOW_ACTIONS_IN_HEADER} from '../../utils';
import { APP_PATH, BREADCRUMBS, SHOW_ACTIONS_IN_HEADER } from '../../utils';
import { getCore } from '../../services';
import { WorkflowDetailHeader } from './components';
import {
Expand All @@ -28,7 +28,7 @@ import {
import { ResizableWorkspace } from './resizable_workspace';
import {
ERROR_GETTING_WORKFLOW_MSG,
FETCH_ALL_QUERY_BODY,
FETCH_ALL_QUERY,
MAX_WORKFLOW_NAME_TO_DISPLAY,
getCharacterLimitedString,
} from '../../../common';
Expand Down Expand Up @@ -102,7 +102,7 @@ export function WorkflowDetail(props: WorkflowDetailProps) {
// - fetch available models as their IDs may be used when building flows
useEffect(() => {
dispatch(getWorkflow({ workflowId, dataSourceId }));
dispatch(searchModels({ apiBody: FETCH_ALL_QUERY_BODY, dataSourceId }));
dispatch(searchModels({ apiBody: FETCH_ALL_QUERY, dataSourceId }));
}, []);

return errorMessage.includes(ERROR_GETTING_WORKFLOW_MSG) ? (
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,11 +12,6 @@ import {
EuiFlexGroup,
EuiFlexItem,
EuiFormRow,
EuiModal,
EuiModalBody,
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
EuiSuperSelect,
EuiSuperSelectOption,
EuiText,
Expand All @@ -31,6 +26,7 @@ import {
useAppDispatch,
} from '../../../../store';
import { getDataSourceId } from '../../../../utils/utils';
import { EditQueryModal } from './edit_query_modal';

interface ConfigureSearchRequestProps {
setQuery: (query: string) => void;
Expand Down Expand Up @@ -88,33 +84,10 @@ export function ConfigureSearchRequest(props: ConfigureSearchRequestProps) {
return (
<>
{isEditModalOpen && (
<EuiModal
onClose={() => setIsEditModalOpen(false)}
style={{ width: '70vw' }}
>
<EuiModalHeader>
<EuiModalHeaderTitle>
<p>{`Edit query`}</p>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<JsonField
label="Query"
fieldPath={'search.request'}
editorHeight="25vh"
readOnly={false}
/>
</EuiModalBody>
<EuiModalFooter>
<EuiButton
onClick={() => setIsEditModalOpen(false)}
fill={false}
color="primary"
>
Close
</EuiButton>
</EuiModalFooter>
</EuiModal>
<EditQueryModal
setModalOpen={setIsEditModalOpen}
queryFieldPath="search.request"
/>
)}
<EuiFlexGroup direction="column">
<EuiFlexItem grow={false}>
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,112 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React, { useState, useEffect } from 'react';
import { getIn, useFormikContext } from 'formik';
import {
EuiButton,
EuiModal,
EuiModalBody,
EuiModalFooter,
EuiModalHeader,
EuiModalHeaderTitle,
EuiSpacer,
EuiSuperSelect,
EuiSuperSelectOption,
EuiText,
} from '@elastic/eui';
import { JsonField } from '../input_fields';
import {
QUERY_PRESETS,
QueryPreset,
WorkflowFormValues,
} from '../../../../../common';

interface EditQueryModalProps {
queryFieldPath: string;
setModalOpen(isOpen: boolean): void;
}

/**
* Basic modal for configuring a query. Provides a dropdown to select from
* a set of pre-defined queries targeted for different use cases.
*/
export function EditQueryModal(props: EditQueryModalProps) {
// Form state
const { values, setFieldValue } = useFormikContext<WorkflowFormValues>();

// selected preset state
const [queryPreset, setQueryPreset] = useState<string | undefined>(undefined);

// if the current query matches some preset, display the preset name as the selected
// option in the dropdown. only execute when first rendering so it isn't triggered
// when users are updating the underlying value in the JSON editor.
useEffect(() => {
setQueryPreset(
QUERY_PRESETS.find(
(preset) => preset.query === getIn(values, props.queryFieldPath)
)?.name
);
}, []);

return (
<EuiModal
onClose={() => props.setModalOpen(false)}
style={{ width: '70vw' }}
>
<EuiModalHeader>
<EuiModalHeaderTitle>
<p>{`Edit query`}</p>
</EuiModalHeaderTitle>
</EuiModalHeader>
<EuiModalBody>
<EuiText color="subdued">
Start with a preset or enter manually.
</EuiText>{' '}
<EuiSpacer size="s" />
<EuiSuperSelect
options={QUERY_PRESETS.map(
(preset: QueryPreset) =>
({
value: preset.name,
inputDisplay: (
<>
<EuiText size="s">{preset.name}</EuiText>
</>
),
dropdownDisplay: <EuiText size="s">{preset.name}</EuiText>,
disabled: false,
} as EuiSuperSelectOption<string>)
)}
valueOfSelected={queryPreset || ''}
onChange={(option: string) => {
setQueryPreset(option);
setFieldValue(
props.queryFieldPath,
QUERY_PRESETS.find((preset) => preset.name === option)?.query
);
}}
isInvalid={false}
/>
<EuiSpacer size="s" />
<JsonField
label="Query"
fieldPath={props.queryFieldPath}
editorHeight="25vh"
readOnly={false}
/>
</EuiModalBody>
<EuiModalFooter>
<EuiButton
onClick={() => props.setModalOpen(false)}
fill={false}
color="primary"
>
Close
</EuiButton>
</EuiModalFooter>
</EuiModal>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@
*/

import React, { useCallback, useEffect, useState } from 'react';
import { useSelector } from 'react-redux';
import { getIn, useFormikContext } from 'formik';
import { debounce, isEmpty, isEqual } from 'lodash';
import {
Expand Down Expand Up @@ -814,7 +813,9 @@ export function WorkflowInputs(props: WorkflowInputsProps) {
<EuiFlexItem grow={false}>
<EuiButton
disabled={
isRunningSearch || isProposingNoSearchResources
isRunningSearch ||
(isProposingNoSearchResources &&
hasProvisionedSearchResources(props.workflow))
}
isLoading={isRunningSearch}
fill={false}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -30,7 +30,7 @@ import {
searchWorkflows,
useAppDispatch,
} from '../../../store';
import { FETCH_ALL_QUERY_BODY, Workflow } from '../../../../common';
import { FETCH_ALL_QUERY, Workflow } from '../../../../common';
import { WORKFLOWS_TAB } from '../workflows';
import { getDataSourceId } from '../../../utils/utils';

Expand Down Expand Up @@ -151,7 +151,7 @@ export function ImportWorkflowModal(props: ImportWorkflowModalProps) {
const { workflow } = result;
dispatch(
searchWorkflows({
apiBody: FETCH_ALL_QUERY_BODY,
apiBody: FETCH_ALL_QUERY,
dataSourceId,
})
);
Expand Down
Loading

0 comments on commit cc54976

Please sign in to comment.