Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add basic preset queries; onboard hybrid/multimodal search use cases #302

Merged
merged 3 commits into from
Aug 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
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
Loading