OCI Search with OpenSearchでセマンティック検索をOCI Generative AI Embeddingを使ってやってみた。(2024/04/24)

# はじめに

OCI Search with OpenSearchでは、OpenSearchバージョン2.11以降のセマンティク検索がサポートされています。
こちらの[記事](https://qiita.com/kenwatan/items/19f252ecb034d07ff527)ではOpenSearchに組み込まれている事前トレーニング済モデルを使用したセマンティック検索を実施してみました。今回はOCI Generative AIを使用して行ってみました。

# 前提条件

- OpenSearchバージョンが2.11以降のOpenSearchクラスタのプロビジョニング
    - [OCI Search Service for OpenSearch を使って検索アプリケーションを作成しよう](https://oracle-japan.github.io/ocitutorials/opensearch/search-application-for-beginners/)などを参考
- OpenSearch ダッシュボードへのアクセス
    - 作業は OpenSearch ダッシュボードの Dev Toolsを使って実行



# 作業ステップ

- 事前準備
- ステップ1 モデル・グループの登録
- ステップ2 コネクタの作成
- ステップ3 モデルのモデル・グループへの登録
- ステップ4 モデルのデプロイ
- ステップ5 Ingestionパイプラインの作成
- ステップ6 インデックスの作成
- ステップ7 ドキュメントの取り込み
- ステップ8 Embeddingが正しく生成されていることを確認
- セマンティック検索の実行

# 実行例

### OpenSearchからOGI Generative AIサービスへアクセスするためのリソース・プリンシパルの設定

opensearchクラスタがGenerative AI リソースへのアクセスを許可するようにIAMポリシーを構成します。(リソース・プリンシパルの設定)

```
ALLOW ANY-USER to manage generative-ai-family in tenancy WHERE ALL {request.principal.type='opensearchcluster',request.resource.compartment.id='< cluster_compartment_id >'}
```

< cluster_compartment_id >にOCI Search with OpenSearchクラスタが存在するコンパートメントIDを指定します。

### セマンティック検索を実行できるようにクラスタ設定を更新

```
PUT _cluster/settings
{
  "persistent": {
    "plugins": {
      "ml_commons": {
        "only_run_on_ml_node": "false",
        "model_access_control_enabled": "true",
        "native_memory_threshold": "99",
        "rag_pipeline_feature_enabled": "true",
        "memory_feature_enabled": "true",
        "allow_registering_model_via_local_file": "true",
        "allow_registering_model_via_url": "true",
        "model_auto_redeploy.enable":"true",
        "model_auto_redeploy.lifetime_retry_times": 10
        }
      }
    }
}
```

### ステップ1 モデル・グループの登録

特定のモデルへのアクセスを管理するモデル・グループを作成します。
モデル・グループの登録APIの実行

```
POST /_plugins/_ml/model_groups/_register
{
  "name": "general pretrained models",
  "description": "A model group for models of OCI Gen AI"
}
```

レスポンスで返されたmodel_group_idを記録します。

```
{
  "model_group_id": "SGkEvI4BapwKtdhKEyeT",
  "status": "CREATED"
}
```

### ステップ2 コネクタの作成

OCI Generative AIを利用するためのコネクタを作成します。
認証方式、利用するモデル、OpenSearchクラスタの存在するコンパートメントのOCIDをパラメータ入力します。

```
POST _plugins/_ml/connectors/_create
{
  "name": "OCI GenAI Connector cohere-embed",
  "description": "The connector to public Cohere model service for embed",
  "version": "2",
  "protocol": "oci_sigv1",
  "parameters": {
  "endpoint": "inference.generativeai.us-chicago-1.oci.oraclecloud.com",
  "auth_type": "resource_principal",
  "model": "cohere.embed-multilingual-v3.0",
  "input_type":"search_document",
  "truncate": "END"
  },
  "credential": {    
  },
  "actions": [
    {
      "action_type": "predict",
      "method":"POST",
      "url": "https://${parameters.endpoint}/20231130/actions/embedText",
      "request_body": "{ \"inputs\": [\"${passage_text}\"],
      \"truncate\":\"${parameters.truncate}\" , \"compartmentId\":\"< cluster_compartment_id >\",
      \"servingMode\": { \"modelId\": \"${parameters.model}\",  \"servingType\":\"ON_DEMAND\" } }",
      "pre_process_function": "connector.pre_process.cohere.embedding",
      "post_process_function": "connector.post_process.cohere.embedding"
    }
  ]
}
```

この例では
- 認証方式にリソースプリンシパル ( resource_principal )
- 利用するモデルに cohere.embed-multilingual-v3.0
- < cluster_compartment_id > に OpenSearchクラスタの存在するコンパートメントのOCID 
を指定します。 
 


レスポンスで返されたconnector_idを記録します。

```
{
  "connector_id": "hGmF244BapwKtdhKTyca"
}
```

### ステップ3 モデルのモデル・グループへの登録

コネクタを使用するモデルを登録します。以下の情報を入力します。
- model_group_id: 登録するモデル・グループのmodel_group_id (SGkEvI4BapwKtdhKEyeT)
- connector_id: 作成したコネクタのconnector_id (hGmF244BapwKtdhKTyca)
- 名前: 使用する事前トレーニング済モデルのモデル名
- Function_name: リモート
- description: モデルの説明

モデルの登録例

```
POST /_plugins/_ml/models/_register
{
  "name": "oci-genai-embedding",
  "function_name": "remote",
  "model_group_id": "SGkEvI4BapwKtdhKEyeT",
  "description": "cohere multilingual embedding model",
  "connector_id": "hGmF244BapwKtdhKTyca"
}
```



レスポンスで返されたtask_idを記録

```
{
  "task_id": "heyif40BJxmKsWKUbBae",
  "status": "CREATED",
  "model_id": "hmmG244BapwKtdhKjScY"
}
```

task_id (heyif40BJxmKsWKUbBae)を使用してモデル登録のステータスを確認

```
GET /_plugins/_ml/tasks/heyif40BJxmKsWKUbBae
```

レスポンスのステータスがCOMPLETEDになっていることを確認し、レスポンスで返されたmodel_idを記録します。

```
{
  "model_id": "hmmG244BapwKtdhKjScY",
  "task_type": "REGISTER_MODEL",
  "function_name": "TEXT_EMBEDDING",
  "state": "COMPLETED",
  "worker_node": [
    "0Hic_MLcR1WQhLq3zCgMuw"
  ],
  "create_time": 1712552074058,
  "last_update_time": 1712552118631,
  "is_async": true
}
```

### ステップ4 モデルのデプロイ

パイプラインからモデルを利用できるようにmodel_id (hmmG244BapwKtdhKjScY)を指定してモデルをクラスタにデプロイします。

```
POST /_plugins/_ml/models/hmmG244BapwKtdhKjScY/_deploy
```

レスポンスでステータスが"COMPLETED"になっていることを確認します。

```
{
  "task_id": "h2mH244BapwKtdhKGicD",
  "task_type": "DEPLOY_MODEL",
  "status": "COMPLETED"
}
```


### ステップ5 Ingestionパイプラインの作成

デプロイされたモデルを使用してIngestionパイプラインを作成します。
- Ingestionパイプラインは、デプロイされたモデルを使用して、データ取込み時に各ドキュメントのEmbedベクトルを自動的に生成します。
    - ベクトル化するテキストフィールドをマッピングするだけします。
- パイプラインとデプロイされたモデルのmodel_idを指定します。
    
Ingestionパイプラインの作成例

```
PUT _ingest/pipeline/pipeline_name01
{
  "description": "An example neural search pipeline",
  "processors" : [
    {
      "text_embedding": {
        "model_id": "hmmG244BapwKtdhKjScY",
        "field_map": {
          "field_name01": "embedding_field_name01"
        }
      }
    }
  ]
}
```

```
{
  "acknowledged": true
}
```

### ステップ6 インデックスの作成

作成したIngestionパイプラインを使用し、セマンティック検索で使用するANNエンジンを指定し、インデックスを作成します。

Lucene Engineを使用したインデックス作成の例
- パイプライン名、ANNエンジンを指定します。
- Embeddingモデルのディメンションを適切に指定します。
- IngestionパイプラインとEmbeddingフィールドを指定することで、パイプラインはEmbeddingの作成方法を認識し、取込み時にドキュメントにEmbeddingし結果を格納します。

```
PUT /lucene-index
{
  "settings": {
    "index.knn": true,
    "default_pipeline": "pipeline_name01"
  },
  "mappings": {
    "properties": {
      "embedding_field_name01": {
        "type": "knn_vector",
        "dimension": 1024,
        "method": {
          "name":"hnsw",
          "engine":"lucene",
          "space_type": "l2",
          "parameters":{
            "m":512,
            "ef_construction": 245
          }
        }
      },
      "field_name01": {
        "type": "text"
      }
    }
  }
}
```

索引が正常に作成された場合のレスポンス

```
{
  "acknowledged": true,
  "shards_acknowledged": true,
  "index": "lucene-index"
}
```

### ステップ7 ドキュメントの取り込み

データをインデックスに取り込みます。(4件)

```
POST /lucene-index/_doc/1
{
  "field_name01": "there are many sharks in the ocean"
}
```

レスポンス

```
{
  "_index": "lucene-index",
  "_id": "1",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 0,
  "_primary_term": 1
}
```

```
POST /lucene-index/_doc/2
{
  "field_name01": "fishes must love swimming"
}
```

レスポンス

```
{
  "_index": "lucene-index",
  "_id": "2",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 1,
  "_primary_term": 1
}
```

```
POST /lucene-index/_doc/3
{
  "field_name01": "summers are usually very hot"
}
```

レスポンス

```
{
  "_index": "lucene-index",
  "_id": "3",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 2,
  "_primary_term": 1
}
```

```
POST /lucene-index/_doc/4
{
  "field_name01": "florida has a nice weather all year round"
}
```

レスポンス

```
{
  "_index": "lucene-index",
  "_id": "4",
  "_version": 1,
  "result": "created",
  "_shards": {
    "total": 2,
    "successful": 1,
    "failed": 0
  },
  "_seq_no": 3,
  "_primary_term": 1
}
```

（ そのほかの データも格納しました。）

### ステップ8 Embeddingが正しく生成されていることを確認

Embeddingが正しくされていることを確認します

```
GET /lucene-index/_doc/3
```

レスポンス

```
{
  "_index": "lucene-index",
  "_id": "3",
  "_version": 1,
  "_seq_no": 2,
  "_primary_term": 1,
  "found": true,
  "_source": {
    "embedding_field_name01": [
      -0.12549585,
      -0.31517762,
      0.03526806,
      0.39322084,
      -0.04755569,
      -0.12378363,
      -0.032554734,
以下略
```

### セマンティック検索の実行

- 登録しデプロイしたモデルIDを使用して、
セマンティック検索を実行

```
GET /lucene-index/_search
{
  "_source": {
    "excludes": [
      "embedding_field_name01"
    ]
  },
  "query": {
    "neural": {
      "embedding_field_name01": {
        "query_text": "Cars",
        "model_id": "hmmG244BapwKtdhKjScY",
        "k": 5
      }
    }
  }
}


```

「Cars」のキーワードで検索した結果
- Porsches
- Toyota
- Ford
- Nissan
- Volvo
の文字が含まれた文が返されました。

```
{
  "hits": {
    "total": {
      "value": 5,
      "relation": "eq"
    },
    "max_score": 0.01621401,
    "hits": [
      {
        "_index": "lucene-index","_id": "40","_score": 0.01621401,
        "_source": {
          "field_name01": "Porsches are fast and reliable."
        }
      },
      {
        "_index": "lucene-index","_id": "38","_score": 0.014877659,
        "_source": {
          "field_name01": "Toyotas are reliable."
        }
      },
      {
        "_index": "lucene-index","_id": "35","_score": 0.014761,
        "_source": {
          "field_name01": "Ford 150 are popular."
        }
      },
      {
        "_index": "lucene-index","_id": "41","_score": 0.014484754,
        "_source": {
          "field_name01": "Nissan GTRs are great."
        }
      },
      {
        "_index": "lucene-index","_id": "37","_score": 0.014443698,
        "_source": {
          "field_name01": "Volvos are safe."
        }
      }
    ]
}
```

# おわりに

OCI Generative AIを使ってベクトル生成し、OpenSearch 2.11 でセマンティック検索ができました。

### 参考情報

- [OCIのセマンティック検索 OpenSearch](https://docs.public.oneportal.content.oci.oraclecloud.com/ja-jp/iaas/Content/search-opensearch/Concepts/semanticsearch.htm)
- [OCI Search Service for OpenSearch を使って検索アプリケーションを作成しよう](https://oracle-japan.github.io/ocitutorials/opensearch/search-application-for-beginners/)
- [OCI Search with OpenSearchでセマンティック検索やってみた。(2024/04/08)](https://qiita.com/kenwatan/items/19f252ecb034d07ff527)