From 6a709ac397bdaa95d98dad85374f5be56c45db75 Mon Sep 17 00:00:00 2001 From: "opensearch-trigger-bot[bot]" <98922864+opensearch-trigger-bot[bot]@users.noreply.github.com> Date: Thu, 25 Jan 2024 13:57:49 -0800 Subject: [PATCH] add tutorial doc for semantic search on amazon opensearch (#1923) (#1928) * add tutorial doc for semantic search on amazon opensearch Signed-off-by: Yaliang Wu * add missing part Signed-off-by: Yaliang Wu * fix typo Signed-off-by: Yaliang Wu * add bedrock embedding model tutorial Signed-off-by: Yaliang Wu * add sagemaker embedding model tutorial Signed-off-by: Yaliang Wu * fine tune Signed-off-by: Yaliang Wu * fix comments Signed-off-by: Yaliang Wu --------- Signed-off-by: Yaliang Wu (cherry picked from commit 9d8f64f4db86ee098a0b7af8afd9ae3ef99ead42) Co-authored-by: Yaliang Wu --- .../sagemaker_connector_blueprint.md | 29 +- .../semantic_search/mapping_iam_role_arn.png | Bin 0 -> 85214 bytes ...arch_with_bedrock_titan_embedding_model.md | 348 ++++++++++++++++ ...ntic_search_with_cohere_embedding_model.md | 373 +++++++++++++++++ ...c_search_with_sagemaker_embedding_model.md | 385 ++++++++++++++++++ 5 files changed, 1133 insertions(+), 2 deletions(-) create mode 100644 docs/tutorials/aws/images/semantic_search/mapping_iam_role_arn.png create mode 100644 docs/tutorials/aws/semantic_search_with_bedrock_titan_embedding_model.md create mode 100644 docs/tutorials/aws/semantic_search_with_cohere_embedding_model.md create mode 100644 docs/tutorials/aws/semantic_search_with_sagemaker_embedding_model.md diff --git a/docs/remote_inference_blueprints/sagemaker_connector_blueprint.md b/docs/remote_inference_blueprints/sagemaker_connector_blueprint.md index d3e76d802b..4a90977deb 100644 --- a/docs/remote_inference_blueprints/sagemaker_connector_blueprint.md +++ b/docs/remote_inference_blueprints/sagemaker_connector_blueprint.md @@ -1,5 +1,28 @@ ### Sagemaker connector blueprint example for embedding: +Read more details on https://opensearch.org/docs/latest/ml-commons-plugin/remote-models/blueprints/ + +Make sure your Sagemaker model input follow such format, so the [default pre-process function](https://opensearch.org/docs/latest/ml-commons-plugin/remote-models/blueprints/#preprocessing-function) can work +``` +["hello world", "how are you"] +``` +and output follow such format, so the [default post-process function](https://opensearch.org/docs/latest/ml-commons-plugin/remote-models/blueprints/#post-processing-function) can work +``` +[ + [ + -0.048237994, + -0.07612697, + ... + ], + [ + 0.32621247, + 0.02328475, + ... + ] +] +``` + +Then, you can create Sagemaker embedding model with default pre/post process function: ```json POST /_plugins/_ml/connectors/_create { @@ -23,8 +46,10 @@ POST /_plugins/_ml/connectors/_create "headers": { "content-type": "application/json" }, - "url": "", - "request_body": "" + "url": "", + "request_body": "${parameters.inputs}", + "pre_process_function": "connector.pre_process.default.embedding", + "post_process_function": "connector.post_process.default.embedding" } ] } diff --git a/docs/tutorials/aws/images/semantic_search/mapping_iam_role_arn.png b/docs/tutorials/aws/images/semantic_search/mapping_iam_role_arn.png new file mode 100644 index 0000000000000000000000000000000000000000..047752bbc5ccce4e12cca497245c46f398809b19 GIT binary patch literal 85214 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

@qeHrLAy!dKJ*Bx&W?7>K)F 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 literal 0 HcmV?d00001 diff --git a/docs/tutorials/aws/semantic_search_with_bedrock_titan_embedding_model.md b/docs/tutorials/aws/semantic_search_with_bedrock_titan_embedding_model.md new file mode 100644 index 0000000000..5e2085141b --- /dev/null +++ b/docs/tutorials/aws/semantic_search_with_bedrock_titan_embedding_model.md @@ -0,0 +1,348 @@ +# Topic + +> The easiest way for setting up embedding model on your Amazon OpenSearch cluster is using [AWS CloudFormation](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/cfn-template.html) + +> This tutorial explains detail steps if you want to configure everything manually. + +This doc introduces how to build semantic search in Amazon managed OpenSearch with [Bedrock Titan embedding model](https://docs.aws.amazon.com/bedrock/latest/userguide/titan-embedding-models.html). +If you are not using Amazon OpenSearch, you can refer to [bedrock_connector_titan_embedding_blueprint](https://github.com/opensearch-project/ml-commons/blob/2.x/docs/remote_inference_blueprints/bedrock_connector_titan_embedding_blueprint.md). + +Note: You should replace the placeholders with prefix `your_` with your own value + +# Steps + +## 0. Create OpenSearch cluster + +Go to AWS OpenSearch console UI and create OpenSearch domain. + +Copy the domain ARN which will be used in later steps. + +## 1. Create IAM role to invoke Bedrock model +To invoke Bedrock model, we need to create an IAM role with proper permission. +This IAM role will be configured in connector. Connector will use this role to invoke Bedrock model. + +Go to IAM console, create IAM role `my_invoke_bedrock_role` with: + +- Custom trust policy: +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "es.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] +} +``` +- Permission +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "bedrock:InvokeModel" + ], + "Effect": "Allow", + "Resource": "arn:aws:bedrock:*::foundation-model/amazon.titan-embed-text-v1" + } + ] +} +``` + +Copy the role ARN which will be used in later steps. + +## 2. Configure IAM role in OpenSearch + +### 2.1 Create IAM role for Signing create connector request + +Generate a new IAM role specifically for signing your create connector request. + + +Create IAM role `my_create_bedrock_connector_role` with +- Custom trust policy. Note: `your_iam_user_arn` is the IAM user which will run `aws sts assume-role` in step 3.1 +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": "your_iam_user_arn" + }, + "Action": "sts:AssumeRole" + } + ] +} +``` +- permission +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "iam:PassRole", + "Resource": "your_iam_role_arn_created_in_step1" + }, + { + "Effect": "Allow", + "Action": "es:ESHttpPost", + "Resource": "your_opensearch_domain_arn_created_in_step0" + } + ] +} +``` + +Copy this role ARN which will be used in later steps. + +### 2.2 Map backend role + +1. Log in to your OpenSearch Dashboard and navigate to the "Security" page, which you can find in the left-hand menu. +2. Then click "Roles" on security page (you can find it on left-hand), then find "ml_full_access" role and click it. +3. On "ml_full_access" role detail page, click "Mapped users", then click "Manage mapping". Paste IAM role ARN created in step 2.1 to backend roles part. +Click "Map", then the IAM role configured successfully in your OpenSearch cluster. + +![Alt text](images/semantic_search/mapping_iam_role_arn.png) + +## 3. Create Connector + +Find more details on [connector](https://opensearch.org/docs/latest/ml-commons-plugin/remote-models/connectors/) + + +### 3.1 Get temporary credential of the role created in step 2.1: +``` +aws sts assume-role --role-arn your_iam_role_arn_created_in_step2.1 --role-session-name your_session_name +``` + +Configure the temporary credential in `~/.aws/credentials` like this + +``` +[default] +AWS_ACCESS_KEY_ID=your_access_key_of_role_created_in_step2.1 +AWS_SECRET_ACCESS_KEY=your_secret_key_of_role_created_in_step2.1 +AWS_SESSION_TOKEN=your_session_token_of_role_created_in_step2.1 +``` + +### 3.2 Create connector + +Run this python code with the temporary credential configured in `~/.aws/credentials` + +``` +import boto3 +import requests +from requests_aws4auth import AWS4Auth + +host = 'your_amazon_opensearch_domain_endpoint_created_in_step0' +region = 'your_amazon_opensearch_domain_region' +service = 'es' + +credentials = boto3.Session().get_credentials() +awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token) + +path = '/_plugins/_ml/connectors/_create' +url = host + path + +payload = { + "name": "Amazon Bedrock Connector: titan embedding v1", + "description": "The connector to bedrock Titan embedding model", + "version": 1, + "protocol": "aws_sigv4", + "parameters": { + "region": "your_bedrock_model_region", + "service_name": "bedrock" + }, + "credential": { + "roleArn": "your_iam_role_arn_created_in_step1" + }, + "actions": [ + { + "action_type": "predict", + "method": "POST", + "url": "https://bedrock-runtime.your_bedrock_model_region.amazonaws.com/model/amazon.titan-embed-text-v1/invoke", + "headers": { + "content-type": "application/json", + "x-amz-content-sha256": "required" + }, + "request_body": "{ \"inputText\": \"${parameters.inputText}\" }", + "pre_process_function": "\n StringBuilder builder = new StringBuilder();\n builder.append(\"\\\"\");\n String first = params.text_docs[0];\n builder.append(first);\n builder.append(\"\\\"\");\n def parameters = \"{\" +\"\\\"inputText\\\":\" + builder + \"}\";\n return \"{\" +\"\\\"parameters\\\":\" + parameters + \"}\";", + "post_process_function": "\n def name = \"sentence_embedding\";\n def dataType = \"FLOAT32\";\n if (params.embedding == null || params.embedding.length == 0) {\n return params.message;\n }\n def shape = [params.embedding.length];\n def json = \"{\" +\n \"\\\"name\\\":\\\"\" + name + \"\\\",\" +\n \"\\\"data_type\\\":\\\"\" + dataType + \"\\\",\" +\n \"\\\"shape\\\":\" + shape + \",\" +\n \"\\\"data\\\":\" + params.embedding +\n \"}\";\n return json;\n " + } + ] +} + +headers = {"Content-Type": "application/json"} + +r = requests.post(url, auth=awsauth, json=payload, headers=headers) +print(r.text) +``` +The script will output connector id. + +sample output +``` +{"connector_id":"N0qpQY0BOhavBOmfOCnw"} +``` + +Copy connector id which will be used in later steps. + +## 4. Create Model and test + +Login your OpenSearch Dashboard, open DevTools, then run these + +1. Create model group +``` +POST /_plugins/_ml/model_groups/_register +{ + "name": "Bedrock_embedding_model", + "description": "Test model group for bedrock embedding model" +} +``` +Sample output +``` +{ + "model_group_id": "LxWiQY0BTaDH9c7t9xeE", + "status": "CREATED" +} +``` + +2. Register model + +``` +POST /_plugins/_ml/models/_register +{ + "name": "bedrock titan embedding model v1", + "function_name": "remote", + "description": "test embedding model", + "model_group_id": "LxWiQY0BTaDH9c7t9xeE", + "connector_id": "N0qpQY0BOhavBOmfOCnw" +} +``` +Sample output +``` +{ + "task_id": "O0q3QY0BOhavBOmf1SmL", + "status": "CREATED", + "model_id": "PEq3QY0BOhavBOmf1Sml" +} +``` + +3. Deploy model +``` +POST /_plugins/_ml/models/PEq3QY0BOhavBOmf1Sml/_deploy +``` +Sample output +``` +{ + "task_id": "PUq4QY0BOhavBOmfBCkQ", + "task_type": "DEPLOY_MODEL", + "status": "COMPLETED" +} +``` +4. Predict +``` +POST /_plugins/_ml/models/PEq3QY0BOhavBOmf1Sml/_predict +{ + "parameters": { + "inputText": "hello world" + } +} +``` +Sample response +``` +{ + "inference_results": [ + { + "output": [ + { + "name": "sentence_embedding", + "data_type": "FLOAT32", + "shape": [ + 1536 + ], + "data": [ + 0.7265625, + -0.0703125, + 0.34765625, + ...] + } + ], + "status_code": 200 + } + ] +} +``` + +## 5. Semantic search + +### 5.1 create ingest pipeline +Find more details: [ingest pipeline](https://opensearch.org/docs/latest/ingest-pipelines/) + +``` +PUT /_ingest/pipeline/my_bedrock_embedding_pipeline +{ + "description": "text embedding pentest", + "processors": [ + { + "text_embedding": { + "model_id": "your_bedrock_embedding_model_id_created_in_step4", + "field_map": { + "text": "text_knn" + } + } + } + ] +} +``` +### 5.2 create k-NN index +Find more details: [k-NN index](https://opensearch.org/docs/latest/search-plugins/knn/knn-index/) + +You should customize your k-NN index for better performance. +``` +PUT my_index +{ + "settings": { + "index": { + "knn.space_type": "cosinesimil", + "default_pipeline": "my_bedrock_embedding_pipeline", + "knn": "true" + } + }, + "mappings": { + "properties": { + "text_knn": { + "type": "knn_vector", + "dimension": 1536 + } + } + } +} +``` +### 5.3 ingest test data +``` +POST /my_index/_doc/1000001 +{ + "text": "hello world." +} +``` +### 5.4 search +Find more details: [neural search](https://opensearch.org/docs/latest/search-plugins/neural-search/). +``` +POST /my_index/_search +{ + "query": { + "neural": { + "text_knn": { + "query_text": "hello", + "model_id": "your_embedding_model_id_created_in_step4", + "k": 100 + } + } + }, + "size": "1", + "_source": ["text"] +} +``` \ No newline at end of file diff --git a/docs/tutorials/aws/semantic_search_with_cohere_embedding_model.md b/docs/tutorials/aws/semantic_search_with_cohere_embedding_model.md new file mode 100644 index 0000000000..d42f433ee5 --- /dev/null +++ b/docs/tutorials/aws/semantic_search_with_cohere_embedding_model.md @@ -0,0 +1,373 @@ +# Topic + +> The easiest way for setting up embedding model on your Amazon OpenSearch cluster is using [AWS CloudFormation](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/cfn-template.html) + +> This tutorial explains detail steps if you want to configure everything manually. You can also connect to other service with similar way. + +This doc introduces how to build semantic search in Amazon managed OpenSearch with [Cohere embedding model](https://docs.cohere.com/reference/embed). +If you are not using Amazon OpenSearch, you can refer to [cohere_v3_connector_embedding_blueprint](https://github.com/opensearch-project/ml-commons/blob/2.x/docs/remote_inference_blueprints/cohere_v3_connector_embedding_blueprint.md) and [OpenSearch semantic search](https://opensearch.org/docs/latest/search-plugins/semantic-search/). + + +Note: You should replace the placeholders with prefix `your_` with your own value + +Cohere embedding model also available on Bedrock. You can use same steps of [semantic_search_with_cohere_embedding_model.md](https://github.com/opensearch-project/ml-commons/tree/2.x/docs/tutorials/aws/semantic_search_with_bedrock_titan_embedding_model.md) to use Bedrock Cohere embedding model. + +# Steps + +## 0. Create OpenSearch cluster + +Go to AWS OpenSearch console UI and create OpenSearch domain. + +Copy the domain ARN which will be used in later steps. + +## 1. Create secret +Store your Cohere API key in Secret Manager. + +Use default value if not mentioned. + +1. Choose "Other type of secret" type. +2. Create a "my_cohere_key" key pais with your Cohere API key as value. +3. On next page, input `my_test_cohere_secret` as secret name + +Copy the secret ARN which will be used in later steps. + +## 2. Create IAM role +To use the secret created in Step1, we need to create an IAM role with read secret permission. +This IAM role will be configured in connector. Connector will use this role to read secret. + +Go to IAM console, create IAM role `my_cohere_secret_role` with: + +- Custom trust policy: +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "es.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] +} +``` +- Permission +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Action": [ + "secretsmanager:GetSecretValue" + ], + "Effect": "Allow", + "Resource": "your_secret_arn_created_in_step1" + } + ] +} +``` + +Copy the role ARN which will be used in later steps. + +## 3. Configure IAM role in OpenSearch + +### 3.1 Create IAM role for Signing create connector request + +Generate a new IAM role specifically for signing your create connector request. + + +Create IAM role `my_create_connector_role` with +- Custom trust policy. Note: `your_iam_user_arn` is the IAM user which will run `aws sts assume-role` in step 4.1 +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": "your_iam_user_arn" + }, + "Action": "sts:AssumeRole" + } + ] +} +``` +- permission +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "iam:PassRole", + "Resource": "your_iam_role_arn_created_in_step2" + }, + { + "Effect": "Allow", + "Action": "es:ESHttpPost", + "Resource": "your_opensearch_domain_arn_created_in_step0" + } + ] +} +``` + +Copy this role ARN which will be used in later steps. + +### 3.2 Map backend role + +1. Log in to your OpenSearch Dashboard and navigate to the "Security" page, which you can find in the left-hand menu. +2. Then click "Roles" on security page (you can find it on left-hand), then find "ml_full_access" role and click it. +3. On "ml_full_access" role detail page, click "Mapped users", then click "Manage mapping". Paste IAM role ARN created in Step 3.1 to backend roles part. +Click "Map", then the IAM role configured successfully in your OpenSearch cluster. + +![Alt text](images/semantic_search/mapping_iam_role_arn.png) + +## 4. Create Connector + +Find more details on [connector](https://opensearch.org/docs/latest/ml-commons-plugin/remote-models/connectors/) + + +### 4.1 Get temporary credential of the role created in step 3.1: +``` +aws sts assume-role --role-arn your_iam_role_arn_created_in_step3.1 --role-session-name your_session_name +``` + +Configure the temporary credential in `~/.aws/credentials` like this + +``` +[default] +AWS_ACCESS_KEY_ID=your_access_key_of_role_created_in_step3.1 +AWS_SECRET_ACCESS_KEY=your_secret_key_of_role_created_in_step3.1 +AWS_SESSION_TOKEN=your_session_token_of_role_created_in_step3.1 +``` + +### 4.2 Create connector + +Run this python code with the temporary credential configured in `~/.aws/credentials` + +``` +import boto3 +import requests +from requests_aws4auth import AWS4Auth + +host = 'your_amazon_opensearch_domain_endpoint_created_in_step0' +region = 'your_amazon_opensearch_domain_region' +service = 'es' + +credentials = boto3.Session().get_credentials() +awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token) + +path = '/_plugins/_ml/connectors/_create' +url = host + path + +payload = { + "name": "cohere-embed-v3", + "description": "The connector to public Cohere model service for embed", + "version": "1", + "protocol": "http", + "credential": { + "secretArn": "your_secret_arn_created_in_step1", + "roleArn": "your_iam_role_arn_created_in_step2" + }, + "parameters": { + "model": "embed-english-v3.0", + "input_type":"search_document", + "truncate": "END" + }, + "actions": [ + { + "action_type": "predict", + "method": "POST", + "url": "https://api.cohere.ai/v1/embed", + "headers": { + "Authorization": "Bearer ${credential.secretArn.my_cohere_key}", + "Request-Source": "unspecified:opensearch" + }, + "request_body": "{ \"texts\": ${parameters.texts}, \"truncate\": \"${parameters.truncate}\", \"model\": \"${parameters.model}\", \"input_type\": \"${parameters.input_type}\" }", + "pre_process_function": "connector.pre_process.cohere.embedding", + "post_process_function": "connector.post_process.cohere.embedding" + } + ] +} + +headers = {"Content-Type": "application/json"} + +r = requests.post(url, auth=awsauth, json=payload, headers=headers) +print(r.text) +``` +The script will output connector id. + +sample output +``` +{"connector_id":"qp2QP40BWbTmLN9Fpo40"} +``` +Copy connector id which will be used in later steps. +## 5. Create Model and test + +Login your OpenSearch Dashboard, open DevTools, then run these + +1. Create model group +``` +POST /_plugins/_ml/model_groups/_register +{ + "name": "Cohere_embedding_model", + "description": "Test model group for cohere embedding model" +} +``` +Sample output +``` +{ + "model_group_id": "KEqTP40BOhavBOmfXikp", + "status": "CREATED" +} +``` + +2. Register model + +``` +POST /_plugins/_ml/models/_register +{ + "name": "cohere embedding model v3", + "function_name": "remote", + "description": "test embedding model", + "model_group_id": "KEqTP40BOhavBOmfXikp", + "connector_id": "qp2QP40BWbTmLN9Fpo40" +} +``` +Sample output +``` +{ + "task_id": "q52VP40BWbTmLN9F9I5S", + "status": "CREATED", + "model_id": "MErAP40BOhavBOmfQCkf" +} +``` + +3. Deploy model +``` +POST /_plugins/_ml/models/MErAP40BOhavBOmfQCkf/_deploy +``` +Sample output +``` +{ + "task_id": "KUqWP40BOhavBOmf4Clx", + "task_type": "DEPLOY_MODEL", + "status": "COMPLETED" +} +``` +4. Predict +``` +POST /_plugins/_ml/models/MErAP40BOhavBOmfQCkf/_predict +{ + "parameters": { + "texts": ["hello world", "how are you"] + } +} +``` +Sample response +``` +{ + "inference_results": [ + { + "output": [ + { + "name": "sentence_embedding", + "data_type": "FLOAT32", + "shape": [ + 1024 + ], + "data": [ + -0.029510498, + -0.023223877, + -0.059631348, + ...] + }, + { + "name": "sentence_embedding", + "data_type": "FLOAT32", + "shape": [ + 1024 + ], + "data": [ + 0.02279663, + 0.014976501, + -0.04058838,] + } + ], + "status_code": 200 + } + ] +} +``` + +## 6. Semantic search + +### 6.1 create ingest pipeline +Find more details: [ingest pipeline](https://opensearch.org/docs/latest/ingest-pipelines/) + +``` +PUT /_ingest/pipeline/my_cohere_embedding_pipeline +{ + "description": "text embedding pentest", + "processors": [ + { + "text_embedding": { + "model_id": "your_cohere_embedding_model_id_created_in_step5", + "field_map": { + "text": "text_knn" + } + } + } + ] +} +``` +### 6.2 create k-NN index +Find more details: [k-NN index](https://opensearch.org/docs/latest/search-plugins/knn/knn-index/) + +You should customize your k-NN index for better performance. +``` +PUT my_index +{ + "settings": { + "index": { + "knn.space_type": "cosinesimil", + "default_pipeline": "my_cohere_embedding_pipeline", + "knn": "true" + } + }, + "mappings": { + "properties": { + "text_knn": { + "type": "knn_vector", + "dimension": 1024 + } + } + } +} +``` +### 6.3 ingest test data +``` +POST /my_index/_doc/1000001 +{ + "text": "hello world." +} +``` +### 6.4 search +Find more details: [neural search](https://opensearch.org/docs/latest/search-plugins/neural-search/). +``` +POST /my_index/_search +{ + "query": { + "neural": { + "text_knn": { + "query_text": "hello", + "model_id": "your_embedding_model_id_created_in_step5", + "k": 100 + } + } + }, + "size": "1", + "_source": ["text"] +} +``` \ No newline at end of file diff --git a/docs/tutorials/aws/semantic_search_with_sagemaker_embedding_model.md b/docs/tutorials/aws/semantic_search_with_sagemaker_embedding_model.md new file mode 100644 index 0000000000..3f86e2b087 --- /dev/null +++ b/docs/tutorials/aws/semantic_search_with_sagemaker_embedding_model.md @@ -0,0 +1,385 @@ +# Topic + +> The easiest way for setting up embedding model on your Amazon OpenSearch cluster is using [AWS CloudFormation](https://docs.aws.amazon.com/opensearch-service/latest/developerguide/cfn-template.html) + +> This tutorial explains detail steps if you want to configure everything manually. + +This doc introduces how to build semantic search in Amazon managed OpenSearch with embedding model running on [Sagemaker](https://aws.amazon.com/sagemaker/). +If you are not using Amazon OpenSearch, you can refer to [sagemaker_connector_blueprint](https://github.com/opensearch-project/ml-commons/blob/2.x/docs/remote_inference_blueprints/sagemaker_connector_blueprint.md). + +Note: You should replace the placeholders with prefix `your_` with your own value + +This doc doesn't cover how to deploy model to Sagemaker, read [Sagemaker doc](https://docs.aws.amazon.com/sagemaker/latest/dg/realtime-endpoints.html) for more details. +Make sure your sageMaker model inputs follow the format as an array of Strings, so the [default pre-process function](https://opensearch.org/docs/latest/ml-commons-plugin/remote-models/blueprints/#preprocessing-function) can work +``` +["hello world", "how are you"] +``` +and output follow such format as an array of array, with each array corresponds to the result of an input String, so the [default post-process function](https://opensearch.org/docs/latest/ml-commons-plugin/remote-models/blueprints/#post-processing-function) can work +``` +[ + [ + -0.048237994, + -0.07612697, + ... + ], + [ + 0.32621247, + 0.02328475, + ... + ] +] +``` +If your Sagemaker model input/output is different, you need to write your own pre/post process function. +# Steps + +## 0. Create OpenSearch cluster + +Go to AWS OpenSearch console UI and create OpenSearch domain. + +Copy the domain ARN which will be used in later steps. + +## 1. Create IAM role to invoke Sagemaker model +To invoke Sagemaker model, we need to create an IAM role with proper permission. +This IAM role will be configured in connector. Connector will use this role to invoke Sagemaker model. + +Go to IAM console, create IAM role `my_invoke_sagemaker_model_role` with: + +- Custom trust policy: +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "Service": "es.amazonaws.com" + }, + "Action": "sts:AssumeRole" + } + ] +} +``` +- Permission +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "sagemaker:InvokeEndpoint" + ], + "Resource": [ + "your_sagemaker_model_inference_endpoint_arn" + ] + } + ] +} +``` + +Copy the role ARN which will be used in later steps. + +## 2. Configure IAM role in OpenSearch + +### 2.1 Create IAM role for Signing create connector request + +Generate a new IAM role specifically for signing your create connector request. + + +Create IAM role `my_create_sagemaker_connector_role` with +- Custom trust policy. Note: `your_iam_user_arn` is the IAM user which will run `aws sts assume-role` in step 3.1 +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Principal": { + "AWS": "your_iam_user_arn" + }, + "Action": "sts:AssumeRole" + } + ] +} +``` +- permission +``` +{ + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": "iam:PassRole", + "Resource": "your_iam_role_arn_created_in_step1" + }, + { + "Effect": "Allow", + "Action": "es:ESHttpPost", + "Resource": "your_opensearch_domain_arn_created_in_step0" + } + ] +} +``` + +Copy this role ARN which will be used in later steps. + +### 2.2 Map backend role + +1. Log in to your OpenSearch Dashboard and navigate to the "Security" page, which you can find in the left-hand menu. +2. Then click "Roles" on security page (you can find it on left-hand), then find "ml_full_access" role and click it. +3. On "ml_full_access" role detail page, click "Mapped users", then click "Manage mapping". Paste IAM role ARN created in step 2.1 to backend roles part. +Click "Map", then the IAM role configured successfully in your OpenSearch cluster. + +![Alt text](images/semantic_search/mapping_iam_role_arn.png) + +## 3. Create Connector + +Find more details on [connector](https://opensearch.org/docs/latest/ml-commons-plugin/remote-models/connectors/) + + +### 3.1 Get temporary credential of the role created in step 2.1: +``` +aws sts assume-role --role-arn your_iam_role_arn_created_in_step2.1 --role-session-name your_session_name +``` + +Configure the temporary credential in `~/.aws/credentials` like this + +``` +[default] +AWS_ACCESS_KEY_ID=your_access_key_of_role_created_in_step2.1 +AWS_SECRET_ACCESS_KEY=your_secret_key_of_role_created_in_step2.1 +AWS_SESSION_TOKEN=your_session_token_of_role_created_in_step2.1 +``` + +### 3.2 Create connector + +Run this python code with the temporary credential configured in `~/.aws/credentials` + +``` + +import boto3 +import requests +from requests_aws4auth import AWS4Auth + +host = 'your_amazon_opensearch_domain_endpoint_created_in_step0' +region = 'your_amazon_opensearch_domain_region' +service = 'es' + +credentials = boto3.Session().get_credentials() +awsauth = AWS4Auth(credentials.access_key, credentials.secret_key, region, service, session_token=credentials.token) + + +path = '/_plugins/_ml/connectors/_create' +url = host + path + +payload = { + "name": "Sagemaker embedding model connector", + "description": "Connector for my Sagemaker embedding model", + "version": "1.0", + "protocol": "aws_sigv4", + "credential": { + "roleArn": "your_iam_role_arn_created_in_step1" + }, + "parameters": { + "region": "your_sagemaker_model_region", + "service_name": "sagemaker" + }, + "actions": [ + { + "action_type": "predict", + "method": "POST", + "headers": { + "content-type": "application/json" + }, + "url": "your_sagemaker_model_inference_endpoint", + "request_body": "${parameters.input}", + "pre_process_function": "connector.pre_process.default.embedding", + "post_process_function": "connector.post_process.default.embedding" + } + ] +} + +headers = {"Content-Type": "application/json"} + +r = requests.post(url, auth=awsauth, json=payload, headers=headers) +print(r.status_code) +print(r.text) +``` +The script will output connector id. + +sample output +``` +{"connector_id":"tZ09Qo0BWbTmLN9FM44V"} +``` + +Copy connector id which will be used in later steps. + +## 4. Create Model and test + +Login your OpenSearch Dashboard, open DevTools, then run these + +1. Create model group +``` +POST /_plugins/_ml/model_groups/_register +{ + "name": "Sagemaker_embedding_model", + "description": "Test model group for Sagemaker embedding model" +} +``` +Sample output +``` +{ + "model_group_id": "MhU3Qo0BTaDH9c7tKBfR", + "status": "CREATED" +} +``` + +2. Register model + +``` +POST /_plugins/_ml/models/_register +{ + "name": "Sagemaker embedding model", + "function_name": "remote", + "description": "test embedding model", + "model_group_id": "MhU3Qo0BTaDH9c7tKBfR", + "connector_id": "tZ09Qo0BWbTmLN9FM44V" +} +``` +Sample output +``` +{ + "task_id": "NhU9Qo0BTaDH9c7t0xft", + "status": "CREATED", + "model_id": "NxU9Qo0BTaDH9c7t1Bca" +} +``` + +3. Deploy model +``` +POST /_plugins/_ml/models/NxU9Qo0BTaDH9c7t1Bca/_deploy +``` +Sample output +``` +{ + "task_id": "MxU4Qo0BTaDH9c7tJxde", + "task_type": "DEPLOY_MODEL", + "status": "COMPLETED" +} +``` +4. Predict +``` +POST /_plugins/_ml/models/NxU9Qo0BTaDH9c7t1Bca/_predict +{ + "parameters": { + "inputs": ["hello world", "how are you"] + } +} +``` +Sample response +``` +{ + "inference_results": [ + { + "output": [ + { + "name": "sentence_embedding", + "data_type": "FLOAT32", + "shape": [ + 384 + ], + "data": [ + -0.034477264, + 0.031023195, + 0.0067349933, + ...] + }, + { + "name": "sentence_embedding", + "data_type": "FLOAT32", + "shape": [ + 384 + ], + "data": [ + -0.031369038, + 0.037830487, + 0.07630822, + ...] + } + ], + "status_code": 200 + } + ] +} +``` + +## 5. Semantic search + +### 5.1 create ingest pipeline +Find more details: [ingest pipeline](https://opensearch.org/docs/latest/ingest-pipelines/) + +``` +PUT /_ingest/pipeline/my_sagemaker_embedding_pipeline +{ + "description": "text embedding pentest", + "processors": [ + { + "text_embedding": { + "model_id": "your_sagemaker_embedding_model_id_created_in_step4", + "field_map": { + "text": "text_knn" + } + } + } + ] +} +``` +### 5.2 create k-NN index +Find more details: [k-NN index](https://opensearch.org/docs/latest/search-plugins/knn/knn-index/) + +You should customize your k-NN index for better performance. +``` +PUT my_index +{ + "settings": { + "index": { + "knn.space_type": "cosinesimil", + "default_pipeline": "my_sagemaker_embedding_pipeline", + "knn": "true" + } + }, + "mappings": { + "properties": { + "text_knn": { + "type": "knn_vector", + "dimension": your_sagemake_model_embedding_dimension + } + } + } +} +``` +### 5.3 ingest test data +``` +POST /my_index/_doc/1000001 +{ + "text": "hello world." +} +``` +### 5.4 search +Find more details: [neural search](https://opensearch.org/docs/latest/search-plugins/neural-search/). +``` +POST /my_index/_search +{ + "query": { + "neural": { + "text_knn": { + "query_text": "hello", + "model_id": "your_embedding_model_id_created_in_step4", + "k": 100 + } + } + }, + "size": "1", + "_source": ["text"] +} +``` \ No newline at end of file