CCCMKホールディングス TECH Labの Tech Blog

TECH Labスタッフによる格闘記録やマーケティング界隈についての記事など

AI SearchでSharepoint上のデータをベクトル化する

こんにちは、CCCMKホールディングスTECH LABの井上です。

前回の記事の続きになります。

AI SearchのSharePointからのインデックス作成をやってみました - CCCMKホールディングス TECH Labの Tech Blog

前回、Sharepointをデータソースにしてベクトル化したときにメタデータが取得できないというところで終了したのですが、できました。
使ったAPIのリクエスト内容は以下のものになります。

  1. データソース作成
POST https://{{search_service}}.search.windows.net/datasources?api-version={{api-version}}
Content-Type: application/json
api-key: [admin key]
{
    "name": "{{index_name}}-datasource",
    "type": "sharepoint",
    "credentials": {
        "connectionString": "{{env_sharepoint_connection_string}}"
    },
    "container": {
        "name": "defaultSiteLibrary",
        "query": null
    }
}


2.インデックス作成

POST https://{{search_service}}.search.windows.net/indexes?api-version={{api-version}}
Content-Type: application/json
api-key: [admin key]
{
    "name": "{{index_name}}",
    "defaultScoringProfile": null,
    "fields": [
        {
            "name": "chunk_id",
            "type": "Edm.String",
            "searchable": true,
            "filterable": true,
            "retrievable": true,
            "sortable": true,
            "facetable": true,
            "key": true,
            "indexAnalyzer": null,
            "searchAnalyzer": null,
            "analyzer": "keyword",
            "normalizer": null,
            "dimensions": null,
            "vectorSearchProfile": null,
            "synonymMaps": []
        },
        {
            "name": "parent_id",
            "type": "Edm.String",
            "searchable": true,
            "filterable": true,
            "retrievable": true,
            "sortable": true,
            "facetable": true,
            "key": false,
            "indexAnalyzer": null,
            "searchAnalyzer": null,
            "analyzer": null,
            "normalizer": null,
            "dimensions": null,
            "vectorSearchProfile": null,
            "synonymMaps": []
        },
        {
            "name": "chunk",
            "type": "Edm.String",
            "searchable": true,
            "filterable": false,
            "retrievable": true,
            "sortable": false,
            "facetable": false,
            "key": false,
            "indexAnalyzer": null,
            "searchAnalyzer": null,
            "analyzer": "ja.lucene",
            "normalizer": null,
            "dimensions": null,
            "vectorSearchProfile": null,
            "synonymMaps": []
        },
        {
            "name": "title",
            "type": "Edm.String",
            "searchable": true,
            "filterable": true,
            "retrievable": true,
            "sortable": false,
            "facetable": false,
            "key": false,
            "indexAnalyzer": null,
            "searchAnalyzer": null,
            "analyzer": "ja.lucene",
            "normalizer": null,
            "dimensions": null,
            "vectorSearchProfile": null,
            "synonymMaps": []
        },
        {
            "name": "vector",
            "type": "Collection(Edm.Single)",
            "searchable": true,
            "filterable": false,
            "retrievable": true,
            "sortable": false,
            "facetable": false,
            "key": false,
            "indexAnalyzer": null,
            "searchAnalyzer": null,
            "analyzer": null,
            "normalizer": null,
            "dimensions": 1536,
            "vectorSearchProfile": "{{index_name}}-profile",
            "synonymMaps": []
        },
        {
            "name": "url_path",
            "type": "Edm.String",
            "searchable": true,
            "filterable": true,
            "retrievable": true,
            "sortable": false,
            "facetable": false,
            "key": false,
            "indexAnalyzer": null,
            "searchAnalyzer": null,
            "analyzer": "ja.lucene",
            "normalizer": null,
            "dimensions": null,
            "vectorSearchProfile": null,
            "synonymMaps": []
        }
    ],
    "scoringProfiles": [],
    "corsOptions": {
        "allowedOrigins": [
            "*"
        ],
        "maxAgeInSeconds": 300
    },
    "suggesters": [],
    "analyzers": [],
    "normalizers": [],
    "tokenizers": [],
    "tokenFilters": [],
    "charFilters": [],
    "encryptionKey": null,
    "similarity": {
        "@odata.type": "#Microsoft.Azure.Search.BM25Similarity",
        "k1": null,
        "b": null
    },
    "semantic": {
        "defaultConfiguration": "{{index_name}}-semantic-configuration",
        "configurations": [
            {
                "name": "{{index_name}}-semantic-configuration",
                "prioritizedFields": {
                    "titleField": {
                        "fieldName": "title"
                    },
                    "prioritizedContentFields": [
                        {
                            "fieldName": "chunk"
                        }
                    ],
                    "prioritizedKeywordsFields": []
                }
            }
        ]
    },
    "vectorSearch": {
        "algorithms": [
            {
                "name": "{{index_name}}-algorithm",
                "kind": "hnsw",
                "hnswParameters": {
                    "metric": "cosine",
                    "m": 4,
                    "efConstruction": 400,
                    "efSearch": 500
                },
                "exhaustiveKnnParameters": null
            }
        ],
        "profiles": [
            {
                "name": "{{index_name}}-profile",
                "algorithm": "{{index_name}}-algorithm",
                "vectorizer": "{{index_name}}-vectorizer"
            }
        ],
        "vectorizers": [
            {
                "name": "{{index_name}}-vectorizer",
                "kind": "azureOpenAI",
                "azureOpenAIParameters": {
                    "resourceUri": "{{resourceUri}}",
                    "deploymentId": "{{deploymentId}}",
                    "apiKey": "{{openAIapiKey}}",
                    "authIdentity": null
                },
                "customWebApiParameters": null
            }
        ]
    }
}


3.スキルセット作成

PUT https://{{search_service}}.search.windows.net/skillsets/{{index_name}}-skillset?api-version={{api-version}}
Content-Type: application/json
api-key: [admin key]
{
  "name": "{{index_name}}-skillset",
  "description": "Skillset to chunk documents and generate embeddings",
  "skills": [
    {
      "@odata.type": "#Microsoft.Skills.Text.AzureOpenAIEmbeddingSkill",
      "name": "#1",
      "description": null,
      "context": "/document/pages/*",
      "resourceUri": "{{resourceUri}}",
      "apiKey": "{{openAIapiKey}}",
      "deploymentId": "{{deploymentId}}",
      "inputs": [
        {
          "name": "text",
          "source": "/document/pages/*"
        }
      ],
      "outputs": [
        {
          "name": "embedding",
          "targetName": "vector"
        }
      ],
      "authIdentity": null
    },
    {
      "@odata.type": "#Microsoft.Skills.Text.SplitSkill",
      "name": "#2",
      "description": "Split skill to chunk documents",
      "context": "/document",
      "defaultLanguageCode": "ja",
      "textSplitMode": "pages",
      "maximumPageLength": 2000,
      "pageOverlapLength": 500,
      "maximumPagesToTake": 0,
      "inputs": [
        {
          "name": "text",
          "source": "/document/content"
        }
      ],
      "outputs": [
        {
          "name": "textItems",
          "targetName": "pages"
        }
      ]
    }
  ],
  "cognitiveServices": null,
  "knowledgeStore": null,
  "indexProjections": {
    "selectors": [
      {
        "targetIndexName": "{{index_name}}",
        "parentKeyFieldName": "parent_id",
        "sourceContext": "/document/pages/*",
        "mappings": [
          {
            "name": "chunk",
            "source": "/document/pages/*",
            "sourceContext": null,
            "inputs": []
          },
          {
            "name": "vector",
            "source": "/document/pages/*/vector",
            "sourceContext": null,
            "inputs": []
          },
          {
            "name": "title",
            "source": "/document/metadata_spo_item_name",
            "sourceContext": null,
            "inputs": []
          },
          {
            "name": "url_path",
            "source": "/document/metadata_spo_item_weburi",
            "sourceContext": null,
            "inputs": []
          }
        ]
      }
    ],
    "parameters": {
      "projectionMode": "skipIndexingParentDocuments"
    }
  },
  "encryptionKey": null
}


4.インデクサー作成

POST https://{{search_service}}.search.windows.net/indexers?api-version={{api-version}}
Content-Type: application/json
api-key: [admin key]
{
    "name": "{{index_name}}-indexer",
    "description": null,
    "dataSourceName": "{{index_name}}-datasource",
    "skillsetName": "{{index_name}}-skillset",
    "targetIndexName": "{{index_name}}",
    "disabled": null,
    "schedule": null,
    "parameters": {
        "batchSize": null,
        "maxFailedItems": null,
        "maxFailedItemsPerBatch": null,
        "base64EncodeKeys": null,
        "configuration": {
            "dataToExtract": "contentAndMetadata",
            "parsingMode": "default"
        }
    },
    "fieldMappings": [],
    "outputFieldMappings": [],
    "cache": null,
    "encryptionKey": null
}

※ {{}}内の変数を適宜環境に合わせて設定してください。

ちなみにこのボディ部はAzureのPortalで「データのインポートとベクター化」からインデックスを作成したときに生成されるJSONがベースになっています。 スキルセットにおいて下記の変更をすればSharepointのメタデータが反映されます。

また、一つのインデックスに対してSharepointとBlobそれぞれをデータソースとしてデータを取り込むことも試しました。 こちらは詳細は省きますが、SharepointとBlob用にそれぞれデータソース、スキルセット、インデクサーを作成します。インデックスはどちらか一方のみ作成します。 インデクサーにある項目のtargetIndexNameを同一のものを指定することで一つのインデックスにデータが格納されます。
検索した結果が以下のようになっており、SharepointとBlobのデータが格納されてることが確認できます。

このリンクをクリックするとSharepoint上のデータはブラウザで表示されますがBlobのデータは表示されません。Blobのデータを表示(取得)するにはURLの後ろにパラメータとしてSASトークンを付与します。(Edgeでは表示できるがChromeではダウンロードになる)SASトークンの発行は詳細は省きますが、Azure Portal上でストレージアカウント画面にあるShared Access Signatureで行います。
SASトークンをURLに表示したくないのでPOSTでページを開こうとも試みましたがBlobがPOSTを許可してないようで開けませんでした。

前回と今回で、以下のことが確認できました。
・Sharepoint上のデータのベクトル化
・Sharepoint上のメタデータの取得
・異なるデータソースから1つのインデックスの作成