Skip to content

Commit dad80a3

Browse files
committed
Added workflow preset for Semantic Search using Sparse Encoders
Signed-off-by: saimedhi <[email protected]>
1 parent deb1afa commit dad80a3

File tree

17 files changed

+708
-41
lines changed

17 files changed

+708
-41
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ Inspired from [Keep a Changelog](https://keepachangelog.com/en/1.1.0/)
66
## [Unreleased 3.0](https://github.com/opensearch-project/anomaly-detection/compare/2.x...HEAD)
77
### Features
88
### Enhancements
9+
#### Added workflow preset for Semantic Search using Sparse Encoders (https://github.com/opensearch-project/dashboards-flow-framework/pull/742)
910
### Bug Fixes
1011
### Infrastructure
1112
### Documentation

common/constants.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -155,6 +155,38 @@ export const OPENAI_CONFIGS = {
155155
} as RemoteEmbeddingModelConfig,
156156
};
157157

158+
// Neural Sparse
159+
export const NEURAL_SPARSE_CONFIGS = {
160+
[`opensearch-neural-sparse-encoding-v2-distill`]: {
161+
dimension: 30522,
162+
fieldName: 'passage_embedding',
163+
} as RemoteEmbeddingModelConfig,
164+
[`opensearch-neural-sparse-encoding-v1`]: {
165+
dimension: 30522,
166+
fieldName: 'passage_embedding',
167+
} as RemoteEmbeddingModelConfig,
168+
[`opensearch-neural-sparse-encoding-multilingual-v1`]: {
169+
dimension: 105879,
170+
fieldName: 'passage_embedding',
171+
} as RemoteEmbeddingModelConfig,
172+
[`opensearch-neural-sparse-encoding-doc-v2-mini`]: {
173+
dimension: 30522,
174+
fieldName: 'passage_embedding',
175+
} as RemoteEmbeddingModelConfig,
176+
[`opensearch-neural-sparse-encoding-doc-v3-distill`]: {
177+
dimension: 30522,
178+
fieldName: 'passage_embedding',
179+
} as RemoteEmbeddingModelConfig,
180+
[`opensearch-neural-sparse-encoding-doc-v1`]: {
181+
dimension: 30522,
182+
fieldName: 'passage_embedding',
183+
} as RemoteEmbeddingModelConfig,
184+
[`opensearch-neural-sparse-encoding-doc-v2-distill`]: {
185+
dimension: 30522,
186+
fieldName: 'passage_embedding',
187+
} as RemoteEmbeddingModelConfig,
188+
};
189+
158190
/**
159191
* Various constants pertaining to Workflow configs
160192
*/
@@ -173,6 +205,7 @@ export enum WORKFLOW_TYPE {
173205
HYBRID_SEARCH = 'Hybrid Search',
174206
VECTOR_SEARCH_WITH_RAG = 'RAG with Vector Retrieval',
175207
HYBRID_SEARCH_WITH_RAG = 'RAG with Hybrid Search',
208+
SEMANTIC_SEARCH_USING_SPARSE_ENCODERS = 'Semantic Search using Sparse Encoders',
176209
CUSTOM = 'Custom Search',
177210
UNKNOWN = 'Unknown',
178211
}
@@ -211,6 +244,7 @@ export enum MODEL_TYPE {
211244
export enum MODEL_CATEGORY {
212245
EMBEDDING = 'EMBEDDING',
213246
LLM = 'LLM',
247+
SPARSE_ENCODER = 'SPARSE_ENCODER',
214248
}
215249

216250
/**
@@ -293,6 +327,13 @@ export const COHERE_EMBEDDING_MODEL_DOCS_LINK =
293327
export const BEDROCK_TITAN_EMBEDDING_DOCS_LINK =
294328
'https://github.com/opensearch-project/dashboards-flow-framework/blob/main/documentation/models.md#amazon-bedrock-titan-text-embedding';
295329

330+
// Sparse Encoder Models Documentation Links
331+
export const OPENSEARCH_NEURAL_SPARSE_DOCS_LINK =
332+
'https://huggingface.co/opensearch-project/opensearch-neural-sparse-encoding-v2-distill';
333+
334+
export const SAGEMAKER_SPARSE_DEPLOY_LINK =
335+
'https://github.com/zhichao-aws/opensearch-neural-sparse-sample/tree/main/examples/deploy_on_sagemaker';
336+
296337
// ML Models setup Documentation Link
297338
export const ML_MODELS_SETUP_DOCS_LINK =
298339
'https://github.com/opensearch-project/dashboards-flow-framework/blob/main/documentation/models.md';
@@ -595,6 +636,18 @@ export const HYBRID_SEARCH_QUERY_MATCH_TERM = {
595636
},
596637
},
597638
};
639+
export const NEURAL_SPARSE_SEARCH_QUERY = {
640+
_source: {
641+
excludes: [VECTOR_FIELD_PATTERN],
642+
},
643+
query: {
644+
neural_sparse: {
645+
[VECTOR_FIELD_PATTERN]: {
646+
query_tokens: VECTOR_PATTERN,
647+
},
648+
},
649+
},
650+
};
598651

599652
export const QUERY_PRESETS = [
600653
{
@@ -649,6 +702,10 @@ export const QUERY_PRESETS = [
649702
name: WORKFLOW_TYPE.MULTIMODAL_SEARCH,
650703
query: customStringify(MULTIMODAL_SEARCH_QUERY_BOOL),
651704
},
705+
{
706+
name: 'Neural Sparse Search Query',
707+
query: customStringify(NEURAL_SPARSE_SEARCH_QUERY),
708+
},
652709
{
653710
name: 'Semantic search (neural query)',
654711
query: customStringify(SEMANTIC_SEARCH_QUERY_NEURAL),

common/utils.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ export function isVectorSearchUseCase(workflowType?: WORKFLOW_TYPE): boolean {
5353
WORKFLOW_TYPE.HYBRID_SEARCH,
5454
WORKFLOW_TYPE.VECTOR_SEARCH_WITH_RAG,
5555
WORKFLOW_TYPE.HYBRID_SEARCH_WITH_RAG,
56+
WORKFLOW_TYPE.SEMANTIC_SEARCH_USING_SPARSE_ENCODERS,
5657
].includes(workflowType)
5758
);
5859
}

documentation/models.md

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -473,6 +473,123 @@ POST /_plugins/_ml/models/_register
473473
}
474474
```
475475

476+
### Neural Sparse Encoding
477+
478+
Deploy a sparse encoding model from the Hugging Face Model Hub to a SageMaker real-time inference endpoint using this [guide](https://github.com/zhichao-aws/opensearch-neural-sparse-sample/tree/main/examples/deploy_on_sagemaker).
479+
480+
Connector:
481+
482+
```
483+
POST /_plugins/_ml/connectors/_create
484+
{
485+
"name": "Neural Sparse Encoding",
486+
"description": "Test connector for Sagemaker model",
487+
"version": 1,
488+
"protocol": "aws_sigv4",
489+
"credential": {
490+
"access_key": "",
491+
"secret_key": "",
492+
"session_token": ""
493+
},
494+
"parameters": {
495+
"region": "us-east-1",
496+
"service_name": "sagemaker",
497+
"model": "opensearch-neural-sparse-encoding-v2-distill"
498+
},
499+
"actions": [
500+
{
501+
"action_type": "predict",
502+
"method": "POST",
503+
"headers": {
504+
"content-type": "application/json"
505+
},
506+
"url": "https://runtime.sagemaker.us-east-1.amazonaws.com/endpoints/xxxx/invocations",
507+
"request_body": "[\"${parameters.text_doc}\"]",
508+
"post_process_function": "String escape(def input) { if (input instanceof String) { if (input.contains('\\\\')) { input = input.replace('\\\\', '\\\\\\\\'); } if (input.contains('\"')) { input = input.replace('\"', '\\\\\"'); } if (input.contains('\r')) { input = input.replace('\r', '\\\\r'); } if (input.contains('\t')) { input = input.replace('\t', '\\\\t'); } if (input.contains('\n')) { input = input.replace('\n', '\\\\n'); } if (input.contains('\b')) { input = input.replace('\b', '\\\\b'); } if (input.contains('\f')) { input = input.replace('\f', '\\\\f'); } return input; } return input.toString(); } if (params.result == null || params.result.length == 0) { return '{\"dataAsMap\":{\"error\":\"no response error\"}}'; } String response = params.result[0].toString(); response = response.substring(1, response.length() - 1).replace('=', '\":').replace(', ', ',\"'); return '{\"dataAsMap\":{\"response\":{\"' + response + '}}}';"
509+
}
510+
]
511+
}
512+
```
513+
514+
Model:
515+
516+
```
517+
POST /_plugins/_ml/models/_register
518+
{ "name": "Neural Sparse Encoding Model",
519+
"function_name": "remote",
520+
"version": "1.0.0",
521+
"connector_id": "<connector-id>",
522+
"description": "Test connector for Sagemaker model",
523+
"interface": {
524+
"input": {
525+
"type": "object",
526+
"properties": {
527+
"parameters": {
528+
"type": "object",
529+
"properties": {
530+
"text_doc": {
531+
"type": "string"
532+
}
533+
},
534+
"additionalProperties": true,
535+
"required": [
536+
"text_doc"
537+
]
538+
}
539+
}
540+
},
541+
"output": {
542+
"type": "object",
543+
"properties": {
544+
"inference_results": {
545+
"type": "array",
546+
"items": {
547+
"type": "object",
548+
"properties": {
549+
"output": {
550+
"type": "array",
551+
"items": {
552+
"type": "object",
553+
"properties": {
554+
"dataAsMap": {
555+
"type": "object",
556+
"properties": {
557+
"response": {
558+
"type": "object",
559+
"additionalProperties": {
560+
"type": "number"
561+
}
562+
}
563+
},
564+
"required": [
565+
"response"
566+
]
567+
}
568+
},
569+
"required": [
570+
"dataAsMap"
571+
]
572+
}
573+
},
574+
"status_code": {
575+
"type": "integer"
576+
}
577+
},
578+
"required": [
579+
"output",
580+
"status_code"
581+
]
582+
}
583+
}
584+
},
585+
"required": [
586+
"inference_results"
587+
]
588+
}
589+
}
590+
}
591+
```
592+
476593
## Generative models
477594

478595
### Claude 3 Sonnet (hosted on Amazon Bedrock)

documentation/tutorial.md

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -491,3 +491,46 @@ Override the query to a knn query, including the embedding output. For example:
491491
}
492492
}
493493
```
494+
495+
---
496+
497+
## 9. Neural sparse search
498+
499+
### ML resources
500+
Create and deploy a [Neural Sparse Encoding model](https://github.com/opensearch-project/dashboards-flow-framework/blob/main/documentation/models.md#neural-sparse-encoding).
501+
502+
### Index
503+
504+
Ensure the index mappings have a `rank_features` field - something like the following:
505+
506+
```
507+
"<embedding_field_name>": {
508+
"type": "rank_features"
509+
}
510+
```
511+
512+
### Ingest pipeline
513+
514+
Single ML inference processor. Map your input text to the `text_doc` model input field. Optionally map the output `response` to a new document field. Transform the response if needed using JSONPath expression.
515+
516+
517+
### Search pipeline
518+
519+
Single ML inference **search request** processor. Map the query field containing the input text to the `text_doc` model input field. Optionally map the output `response` to a new field. Transform the response if needed using JSONPath expression. Override the query to a neural sparse query. For example:
520+
521+
```
522+
{
523+
"_source": {
524+
"excludes": [
525+
"<embedding_field>"
526+
]
527+
},
528+
"query": {
529+
"neural_sparse": {
530+
"<embedding_field>": {
531+
"query_tokens": ${response},
532+
}
533+
}
534+
}
535+
}
536+
```

package.json

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,8 @@
3131
"formik": "2.4.2",
3232
"jsonpath": "^1.1.1",
3333
"reactflow": "^11.8.3",
34-
"yup": "^1.3.2"
34+
"yup": "^1.3.2",
35+
"react-markdown": "^4.3.1"
3536
},
3637
"devDependencies": {},
3738
"resolutions": {}

public/pages/workflow_detail/component_input/component_input.tsx

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -319,7 +319,7 @@ export function ComponentInput(props: ComponentInputProps) {
319319
</EuiFlexItem>
320320
</EuiFlexGroup>
321321
) : props.selectedComponentId === COMPONENT_ID.INGEST_DATA ? (
322-
<IngestData disabled={props.readonly} />
322+
<IngestData disabled={props.readonly} workflowType={props.workflow?.ui_metadata?.type} />
323323
) : props.selectedComponentId === COMPONENT_ID.SEARCH_REQUEST ? (
324324
<ConfigureSearchRequest disabled={props.readonly} />
325325
) : props.selectedComponentId === COMPONENT_ID.RUN_QUERY ? (

public/pages/workflow_detail/component_input/ingest_inputs/advanced_settings.tsx

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,7 @@ import {
1414
} from '@elastic/eui';
1515
import { JsonField } from '../input_fields';
1616
import { getIn, useFormikContext } from 'formik';
17-
import { WorkflowFormValues } from '../../../../../common';
17+
import { WorkflowFormValues, WORKFLOW_TYPE } from '../../../../../common';
1818
import { AppState } from '../../../../store';
1919
import {
2020
getEmbeddingField,
@@ -28,6 +28,7 @@ import {
2828

2929
interface AdvancedSettingsProps {
3030
setHasInvalidDimensions: (hasInvalidDimensions: boolean) => void;
31+
workflowType: WORKFLOW_TYPE | undefined;
3132
disabled: boolean;
3233
}
3334

@@ -65,7 +66,7 @@ export function AdvancedSettings(props: AdvancedSettingsProps) {
6566

6667
// If a dimension is found, it is a known embedding model.
6768
// Ensure the index is configured to be knn-enabled.
68-
if (dimension !== undefined) {
69+
if (dimension !== undefined && props.workflowType !== WORKFLOW_TYPE.SEMANTIC_SEARCH_USING_SPARSE_ENCODERS) {
6970
if (!isKnnIndex(curSettings)) {
7071
setFieldValue(
7172
indexSettingsPath,

public/pages/workflow_detail/component_input/ingest_inputs/ingest_data.tsx

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,10 +13,11 @@ import {
1313
} from '@elastic/eui';
1414
import { TextField } from '../input_fields';
1515
import { AdvancedSettings } from './advanced_settings';
16-
import { KNN_VECTOR_DOCS_LINK } from '../../../../../common';
16+
import { KNN_VECTOR_DOCS_LINK, WORKFLOW_TYPE } from '../../../../../common';
1717

1818
interface IngestDataProps {
1919
disabled: boolean;
20+
workflowType: WORKFLOW_TYPE | undefined;
2021
}
2122

2223
/**
@@ -57,6 +58,7 @@ export function IngestData(props: IngestDataProps) {
5758
<EuiFlexItem>
5859
<AdvancedSettings
5960
setHasInvalidDimensions={setHasInvalidDimensions}
61+
workflowType={props.workflowType}
6062
disabled={props.disabled}
6163
/>
6264
</EuiFlexItem>

0 commit comments

Comments
 (0)