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

Onboard normalization processor #311

Merged
merged 1 commit into from
Aug 26, 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
3 changes: 3 additions & 0 deletions common/constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ export enum PROCESSOR_TYPE {
SPLIT = 'split',
SORT = 'sort',
TEXT_CHUNKING = 'text_chunking',
NORMALIZATION = 'normalization-processor',
}

export enum MODEL_TYPE {
Expand Down Expand Up @@ -129,6 +130,8 @@ export const TEXT_CHUNKING_PROCESSOR_LINK =
'https://opensearch.org/docs/latest/ingest-pipelines/processors/text-chunking/';
export const CREATE_WORKFLOW_LINK =
'https://opensearch.org/docs/latest/automating-configurations/api/create-workflow/';
export const NORMALIZATION_PROCESSOR_LINK =
'https://opensearch.org/docs/latest/search-plugins/search-pipelines/normalization-processor/';

/**
* Text chunking algorithm constants
Expand Down
1 change: 1 addition & 0 deletions public/configs/search_response_processors/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,3 +6,4 @@
export * from './ml_search_response_processor';
export * from './split_search_response_processor';
export * from './sort_search_response_processor';
export * from './normalization_processor';
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import { PROCESSOR_TYPE } from '../../../common';
import { Processor } from '../processor';

/**
* The normalization processor config. Used in search flows.
*/
export class NormalizationProcessor extends Processor {
constructor() {
super();
this.type = PROCESSOR_TYPE.NORMALIZATION;
this.name = 'Normalization Processor';
this.fields = [];
this.optionalFields = [
{
id: 'weights',
type: 'string',
},
{
id: 'normalization_technique',
type: 'select',
selectOptions: ['min_max', 'l2'],
},
{
id: 'combination_technique',
type: 'select',
selectOptions: ['arithmetic_mean', 'geometric_mean', 'harmonic_mean'],
},
{
id: 'description',
type: 'string',
},
{
id: 'tag',
type: 'string',
},
];
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
/*
* Copyright OpenSearch Contributors
* SPDX-License-Identifier: Apache-2.0
*/

import React from 'react';
import { EuiAccordion, EuiFlexItem, EuiSpacer } from '@elastic/eui';
import {
IProcessorConfig,
PROCESSOR_CONTEXT,
WorkflowConfig,
NORMALIZATION_PROCESSOR_LINK,
} from '../../../../../common';
import { TextField } from '../input_fields';
import { ConfigFieldList } from '../config_field_list';

interface NormalizationProcessorInputsProps {
uiConfig: WorkflowConfig;
config: IProcessorConfig;
baseConfigPath: string; // the base path of the nested config, if applicable. e.g., 'ingest.enrich'
context: PROCESSOR_CONTEXT;
}

/**
* Specialized component to render the normalization processor. Adds some helper text around weights field.
* In the future, may have a more customizable / guided way for specifying the array of weights.
* For example, could have some visual way of linking it to the underlying sub-queries in the query field,
* enforce its length = the number of queries, etc.
*/
export function NormalizationProcessorInputs(
props: NormalizationProcessorInputsProps
) {
// extracting field info from the config
const optionalFields = props.config.optionalFields || [];
const weightsFieldPath = `${props.baseConfigPath}.${props.config.id}.weights`;
const optionalFieldsWithoutWeights = optionalFields.filter(
(field) => field.id !== 'weights'
);

return (
// We only have optional fields for this processor, so everything is nested under the accordion
<EuiAccordion
id={`advancedSettings${props.config.id}`}
buttonContent="Advanced settings"
paddingSize="none"
>
<EuiSpacer size="s" />
<EuiFlexItem>
<TextField
label={'Weights'}
helpText={`A comma-separated array of floating-point values specifying the weight for each query. For example: '0.8, 0.2'`}
helpLink={NORMALIZATION_PROCESSOR_LINK}
fieldPath={weightsFieldPath}
showError={true}
/>
</EuiFlexItem>
<EuiSpacer size="s" />
<ConfigFieldList
configId={props.config.id}
configFields={optionalFieldsWithoutWeights}
baseConfigPath={props.baseConfigPath}
/>
</EuiAccordion>
);
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ import {
import { MLProcessorInputs } from './ml_processor_inputs';
import { ConfigFieldList } from '../config_field_list';
import { TextChunkingProcessorInputs } from './text_chunking_processor_inputs';
import { NormalizationProcessorInputs } from './normalization_processor_inputs';

/**
* Base component for rendering processor form inputs based on the processor type
Expand Down Expand Up @@ -69,6 +70,20 @@ export function ProcessorInputs(props: ProcessorInputsProps) {
);
break;
}
case PROCESSOR_TYPE.NORMALIZATION: {
el = (
<EuiFlexItem>
<NormalizationProcessorInputs
uiConfig={props.uiConfig}
config={props.config}
baseConfigPath={props.baseConfigPath}
context={props.context}
/>
<EuiSpacer size={PROCESSOR_INPUTS_SPACER_SIZE} />
</EuiFlexItem>
);
break;
}
default: {
el = (
<EuiFlexItem>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,6 +28,7 @@ import {
MLIngestProcessor,
MLSearchRequestProcessor,
MLSearchResponseProcessor,
NormalizationProcessor,
SortIngestProcessor,
SortSearchResponseProcessor,
SplitIngestProcessor,
Expand Down Expand Up @@ -272,6 +273,15 @@ export function ProcessorsList(props: ProcessorsListProps) {
);
},
},
{
name: 'Normalization Processor',
onClick: () => {
closePopover();
addProcessor(
new NormalizationProcessor().toObj()
);
},
},
],
},
]}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,7 @@ export function QuickConfigureInputs(props: QuickConfigureInputsProps) {
<EuiAccordion
id="optionalConfiguration"
buttonContent="Optional configuration"
initialIsOpen={true}
initialIsOpen={false}
>
<EuiSpacer size="m" />
<EuiCompressedFormRow
Expand Down
63 changes: 61 additions & 2 deletions public/utils/config_to_template_utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -116,11 +116,26 @@ function searchConfigToTemplateNodes(
const searchRequestProcessors = processorConfigsToTemplateProcessors(
searchConfig.enrichRequest.processors
);
// For the configured response processors, we don't maintain separate UI / config
// between response processors and phase results processors. So, we parse
// out those different processor types here when configuring the final search pipeline.
// Currently, the only special phase results processor supported is the normalization processor,
// so we filter & partition on that type.
const normalizationProcessor = searchConfig.enrichResponse.processors.find(
(processor) => processor.type === PROCESSOR_TYPE.NORMALIZATION
);
const phaseResultsProcessors = processorConfigsToTemplateProcessors(
normalizationProcessor ? [normalizationProcessor] : []
);
const searchResponseProcessors = processorConfigsToTemplateProcessors(
searchConfig.enrichResponse.processors
searchConfig.enrichResponse.processors.filter(
(processor) => processor.type !== PROCESSOR_TYPE.NORMALIZATION
)
);
const hasProcessors =
searchRequestProcessors.length > 0 || searchResponseProcessors.length > 0;
searchRequestProcessors.length > 0 ||
searchResponseProcessors.length > 0 ||
phaseResultsProcessors.length > 0;

return hasProcessors
? [
Expand All @@ -133,6 +148,7 @@ function searchConfigToTemplateNodes(
configurations: JSON.stringify({
request_processors: searchRequestProcessors,
response_processors: searchResponseProcessors,
phase_results_processors: phaseResultsProcessors,
} as SearchPipelineConfig),
},
} as CreateSearchPipelineNode,
Expand Down Expand Up @@ -254,6 +270,49 @@ export function processorConfigsToTemplateProcessors(
});
break;
}
// optionally add any parameters specified, in the expected nested format
// for the normalization processor
case PROCESSOR_TYPE.NORMALIZATION: {
const {
normalization_technique,
combination_technique,
weights,
} = processorConfigToFormik(processorConfig);

let finalConfig = {} as any;
if (!isEmpty(normalization_technique)) {
finalConfig = {
...finalConfig,
normalization: {
technique: normalization_technique,
},
};
}
if (!isEmpty(combination_technique)) {
finalConfig = {
...finalConfig,
combination: {
...(finalConfig?.combination || {}),
technique: combination_technique,
},
};
}
if (!isEmpty(weights) && weights.split(',').length > 0) {
finalConfig = {
...finalConfig,
combination: {
...(finalConfig?.combination || {}),
parameters: {
weights: weights.split(',').map(Number),
},
},
};
}
processorsList.push({
[processorConfig.type]: finalConfig,
});
break;
}
case PROCESSOR_TYPE.SPLIT:
case PROCESSOR_TYPE.SORT:
default: {
Expand Down
Loading