AWS 入門ブログリレー 2024 〜Amazon Kendra編〜

Amazon Kendra について2024年時点の情報をさらぁっとまとめてみました。AWSサービス入門記事として是非ご活用下さい。
2024.04.08

こんにちは! AWS 事業本部コンサルティング部のたかくに(@takakuni_)です。

当エントリは弊社 AWS 事業本部による『 AWS 入門ブログリレー 2024』の 15 日目のエントリです。

このブログリレーの企画は、普段 AWS サービスについて最新のネタ・深い/細かいテーマを主に書き連ねてきたメンバーの手によって、 今一度初心に返って、基本的な部分を見つめ直してみよう、解説してみようというコンセプトが含まれています。

AWS をこれから学ぼう!という方にとっては文字通りの入門記事として、またすでに AWS を活用されている方にとっても AWS サービスの再発見や 2024 年のサービスアップデートのキャッチアップの場となればと考えておりますので、ぜひ最後までお付合い頂ければ幸いです。

では、さっそくいってみましょう。今回のテーマは『Amazon Kendra』です。

Amazon Kendra とは

Amazon Kendra とは、機械学習を利用したエンタープライズ向けマネージド検索サービスです。自然言語で企業内の様々な場所に散らばっているドキュメントや情報を、効率的に検索できるようにするサービスです。

文字を読むことは意外と大変

このブログを含め、文字を読むのは人それぞれ、得意不得意あると思います。(私は不得意です。)

例えば以下のような、ドキュメントがあったとします。

株式会社サンプル企業 就業規則

第1章 総則

第1条 (目的)
この就業規則は、株式会社サンプル企業(以下「会社」という)の労働条件及び服務規律を定めることを目的とする。

第2条 (適用範囲)  
この就業規則は、会社に雇用される全ての従業員に適用される。ただし、別に定める場合はこの限りではない。

第2章 人事

第3条 (採用)
会社は、従業員の採用に際して、性別、年齢、国籍、信条等による不当な差別をしない。

第4条 (期間の定めのない労働契約と有期労働契約)
従業員の労働契約期間は、期間の定めのないものと有期のものとがある。有期労働契約の更新については別に定める。

第5条 (内定取消し)
次の各号の一つに該当する場合には、内定を取り消すことがある。
(1) 重大な虚偽の申告があった場合
(2) 会社の指示に従わない場合
(3) その他入社が不適当と認められる場合

第3章 勤務時間、休憩、休日

第6条 (所定労働時間)
1. 所定労働時間は、1週間につき40時間とし、始業・終業の時刻および休憩時間は、次のとおりとする。
   始業時刻 9:00
   終業時刻 17:30
   休憩時間 12:00 - 13:00

第7条 (フレックスタイム制)  
1. 業務の性質上必要がある場合には、フレックスタイム制を採用することがある。
2. フレックスタイム制における運用細則は別に定める。

第8条 (時間外労働)
1. 業務の都合上必要がある場合には、所定労働時間を超えて時間外労働を命ずることがある。
2. 時間外労働を行わせる場合には、法定の手続きに基づき割増賃金を支払う。

第9条 (休日)  
1. 休日は次のとおりとする。
   (1) 日曜日
   (2) 土曜日
   (3) 国民の祝日
   (4) 年末年始(12月29日から1月3日まで)
   (5) その他、臨時に指定する日
2. 業務の都合により、前項の休日を変更することがある。

第10条 (年次有給休暇)
1. 年次有給休暇の付与日数は、次のとおりとする。
   (1) 6ヶ月継続勤務した場合 10日
   (2) 1年6ヶ月継続勤務した場合 11日
   (3) 2年6ヶ月継続勤務した場合 12日
   (4) 以降、継続勤務1年ごとに1日ずつ加算し、最高20日を上限とする。
2. 年次有給休暇の取得については、原則として事前に所属長の承認を得なければならない。

さて、内定取消しとなる条件は何だったでしょうか? 僕が初見だったら、おそらく見返しているかと思います。

Amazon Kendra では、ドキュメント(今回の場合だと就業規則等)を読みこませ、 「内定取消しとなる条件は何?」 といった自然言語で検索できるようにするサービスです。とても便利ですよね。それでは、構成要素を深掘りしていきましょう。

ドキュメント

ドキュメントは文字通り、 Amazon Kendra に読み込ませるデータの総称を指します。

Amazon Kendra では CSV, JSON, XML のような構造化されたデータに加え、 PDF, PPT, TXT のような構造化されてないデータもサポートしています。

ドキュメント内のコンテンツを Kendra に読み込ませることを 抽出 と呼びます。

前処理

各ドキュメントのファイル形式によって、抽出前にドキュメントに前処理がかかるケースがあります。例えば、 PDF 形式のドキュメントは抽出前に HTML に変換してから、コンテンツを抽出します。

以下のドキュメントに、サポートされるデータの形式とデータの前処理の流れが記載されていますので、気になる方は併せてご覧ください。

形式は何がいいのか

「 RAG の構成において、ドキュメントの形式は何がいいのか?」とよく聞かれます。個人的な意見としては可能であれば、マークダウンやテキスト形式が望ましいと思います。

例えばですが、次のスライドがあったとします。

【IaC導入を成功させよう】IaCジャーニーマップを作ってみた | DevelopersIO

このスライドにおいて、以下のような人間と同じような読み方で抽出したい(検索結果として返したい)ケースで Amazon Kendra は、異なる順序で抽出する可能性があります。

そのため、 Amazon Kendra の検索結果を人間らしくするには、コンテンツの抽出に一工夫必要になります。詳しくは以下をご覧ください。

Claude3を使って人間が読むようにパワポ資料を読み込んでみる | DevelopersIO

マークダウンや、テキスト形式の場合、基本的には上から下へ読んでいくものなので、人間の読み方に合わせやすい性質を持っています。

また、 FAQs (よくある質問) を利用する場合は、 CSV または JSON と形式が指定されています。

よくある質問 (FAQ) のインデックスへの追加 - Amazon Kendra

データソース

データソースは、 Amazon S3 や Box など、ドキュメントを格納する格納先の総称です。

Amazon Kendra からデータソースへの接続機能は、 データソースコネクター と呼ばれ、執筆現在 38 項目サポートされています。

データソースへの接続は、IAM ロールに加え、 ID/Password などの認証があるデータソースの場合、認証情報を Secrets Manager に保管し、 Secrets Manager 経由で接続を試みます。各コネクターの仕様に従って、接続を行なってください。

Data sources - Amazon Kendra

VPC 経由での接続

コネクターによっては、 VPC 経由でデータソースへ接続できるものもあります。データソースへの接続に固定 IP の通信要件がある場合は、コネクターが対応しているか確認してみましょう。

ファセット定義

カテゴリ別ドキュメントの作成時期 など、さまざまな面(ファセット)でソートして検索する方法をファセット検索といいます。

Amazon Kendra では、登録するデータに対してメタデータを追加することで、ファセット検索をサポートします。 Amazon S3 をデータソースとする場合、メタデータの付与に以下の 2 つの方法で行えます。

  • メタデータファイルの利用
  • CDE (Contents Data Enrichment) の利用

メタデータファイルの利用

Amazon S3 をデータソースとする場合に利用可能な定義方法です。データセット内にメタデータファイルを格納することで、ファイルにメタデータを付与できます。

メタデータフォルダの利用有無でさらに 2 つに分割されます。

# メタデータフォルダを使わないパターン
tree .
.
└── data # 同一フォルダ内にプレフィックスを合わせて配置
    ├── 就業規則.pdf.metadata.json
    └── 就業規則.pdf

# メタデータフォルダを使うパターン
tree .
.
├── data
│   └── 就業規則.pdf
└── metadata # メタデータフォルダ(名前は何でも OK)
    └── data # data 配下なら メタデータファイルも data フォルダ配下に格納
        └── 就業規則.pdf.metadata.json

なお、メタデータファイルは次のような形式で指定します。

{
  "Attributes": {
    "_category": "General",
    "number_of_pages": 26
  },
  "Title": "就業規則",
  "ContentType": "PDF"
}

この形式の場合、カテゴリ別(_category)や、ページ数別(number_of_pages)に、フィルターが行えます。

その他メタデータのスキーマが気になる方は、 Amazon S3 ドキュメントメタデータ - Amazon Kendra も合わせてご覧ください。

CDE (Contents Data Enrichment) の利用

Contents Data Enrichment (CDE) は、 データソースに関わらず、コンテンツのエンリッチメントを行う機能です。データの操作には Basic operations, Advanced operations両方の組み合わせ の 3 パターンがあります。

まず、 Basic operations ではフォルダ名,ファイル名や、既存で割り当てられたメタデータに従って、メタデータの更新及び削除を行うことができます。

例えば、以下の形式でデータ格納されていたとします。

tree .
.
└── data
    ├── financial
    │   └── 決算報告.pdf
    └── general
        └── 就業規則.pdf

このデータに対し、次のような定義が可能です。

この定義を行うことで、メタデータファイルを用意せずとも、自動的にメタデータの付与を行えます。とても便利ですよね。

Advanced operations では、 Lambda 関数を利用して、データのエンリッチメントを行います。エンリッチメントは pre-extraction (コンテンツの抽出前) と post-extraction (コンテンツ抽出後かつインデックス登録前) の 2 つのタイミングで定義可能です。

Advanced operations はメタデータ操作以外にも、次のようなユースケースで有効です。

  • PPTのスライドに添付されている画像ファイルのコンテンツ抽出
    • Kendra 単体では抽出できないので、 Lambda (OCR) を使いコンテンツの抽出を行う
  • MP3 や MP4 に格納された音声データの文字起こし

Amazon Textract で OCR できれば良いのですが、執筆時点では東京や日本語対応しておらず、日本語対応している Claude からテキスト化してもらうのも良い手だと思います。

再掲となりますが、参考までに以下をご覧ください。

Claude3を使って人間が読むようにパワポ資料を読み込んでみる | DevelopersIO

最後に組み合わせパターンになります。組み合わせパターンは次の順序で実行されます。

  1. Basic Operation が実行される
  2. pre-extraction の Lambda 関数が実行される
  3. Kendra によってテキストの抽出が行われる
  4. post-extraction の Lambda 関数が実行される
  5. インデックスにテキスト及びメタデータが登録される

Basic から Advanced の順番は変更できない点に注意です。

Enrich your content and metadata to enhance your search experience with custom document enrichment in Amazon Kendra | AWS Machine Learning Blog

アクセス制御

最後にアクセス制御についてです。 RAG の構成を組んでいると部署ごとに閲覧できるデータを制限したいケースがよく出てきます。制御パターンは 2 パターンあります。

メタデータからフィルターする

先ほどまで話していたメタデータを利用して、アプリ側でフィルターを行うパターンです。この場合だと、 group_id (所属) でフィルターするパターンになります。シンプルなフィルターであればこちらで賄えそうです。

import boto3
import os

kendra = boto3.client("kendra")
INDEX_ID = os.environ.get("INDEX_ID")


def lambda_handler(event, context):
    query_text = event.get("query_text")
    response = kendra.retrieve(
        QueryText=query_text,
        IndexId=INDEX_ID,
        AttributeFilter={
            "EqualsTo": {
                "Key": "_language_code",
                "Value": {"StringValue": "en"},
            },
            "EqualsTo": {
                "Key": "group_id",
                "Value": {"StringValue": event.get("group_id")},  # 所属でフィルターする
            },
        },
        PageSize=5,
    )
    return response

ACL を利用する

Amazon S3 の場合は各ドキュメントやフォルダレベルで ACL (Access Control List) を紐づけることができます。

ACL に紐付けする方法はメタデータファイルに加える or ACL 用のファイルを定義することができます。

メタデータに含める場合は以下のような形式のファイルになります。

/data/就業規則.pdf.metadata.json

{
  "Attributes": {
    "_category": "General",
    "number_of_pages": 26
  },
  "Title": "就業規則",
  "ContentType": "PDF",
  "AccessControlList": [
    {
      "Name": "user1",
      "Type": "USER",
      "Access": "ALLOW"
    }
  ]
}

この場合 /data/就業規則.pdf は、 user1 がアクセス可能になります。

続いて、ACL 設定ファイルに関してです。データソース側でどのファイルが ACL 設定ファイルなのか指定します。

メタデータファイルの場合はファイルごとに設定していましたが、 ACL 設定ファイルはフォルダ単位やファイル単位で設定可能になります。

/acl/acl.json

[
    {
        "keyPrefix": "s3://BUCKETNAME/prefix1/",
        "aclEntries": [
            {
                "Name": "user1",
                "Type": "USER",
                "Access": "ALLOW"
            },
            {
                "Name": "group1",
                "Type": "GROUP",
                "Access": "DENY"
            }
        ]
    },
    {
        "keyPrefix": "s3://prefix2",
        "aclEntries": [
            {
                "Name": "user2",
                "Type": "USER",
                "Access": "ALLOW"
            },
            {
                "Name": "user1",
                "Type": "USER",
                "Access": "DENY"
            },
            {
                "Name": "group1",
                "Type": "GROUP",
                "Access": "DENY"
            }
        ]
    }
]

この場合、 s3://BUCKETNAME/prefix1/ から始まるオブジェクトは user1 に閲覧が許可されています。 group1 は拒否されていますね。

フォルダレベルで設定できるため、運用考えると ACL 設定ファイルは漏れなく設定できそうですね。

では、上記の user1 や group1 などの属性はどうやって判断するのでしょうか?

Token configuration

Amazon Kendra へのクエリリクエストにはトークンを付与して、クエリをリクエストできます。

import boto3
import os

kendra = boto3.client("kendra")
INDEX_ID = os.environ.get("INDEX_ID")


def lambda_handler(event, context):
    query_text = event.get("query_text")
    response = kendra.retrieve(
        QueryText=query_text,
        IndexId=INDEX_ID,
        AttributeFilter={
            "EqualsTo": {
                "Key": "_language_code",
                "Value": {"StringValue": "en"},
            },
        },
        PageSize=5,
        Token=event.get("token") # IdP が発行したトークンをもとにクエリ実行
    )
    return response

IdP に Cognito, OpenID を利用したトークン発行を想定します。

Amazon Kendra は受け取ったトークンをもとに、ユーザープールの 署名キー URL を利用して、トークンの検証およびユーザープールで登録されている、ユーザー名やグループなどのパラメーターを取得します。

Cognito から受け取ったパラメーターをもとに、 ACL や メタデータのフィルターが行われていく仕組みになります。

トークンタイプはそのほかにも、次の形式をサポートしています。

  • OpenID
  • 共有シークレットを持つ JWT
  • パブリックキーを持つ JWT
  • JSON

トークンによるドキュメントへのアクセスの制御  - Amazon Kendra

インデックス

インデックスは、データソースから抽出されたドキュメントの内容が格納される保管庫です。

話が前後しちゃうのですが、インデックスではアクセスコントロールの管理、抽出ログの設定、抽出したコンテンツの暗号化、エディションの設定を行います。

エディション

Amazon Kendra には Developer Edition と Enterprise Edition の 2 パターンがあります。主な違いは以下の通りです。

項目 Developer Edition Enterprise Edition 備考
1 日あたりの最大クエリ件数 4,000 8,000 Enterprise Edition は別途プロビジョニング可能
データソース数 5 50 Enterprise Edition は別途プロビジョニング可能
アベイラビリティーゾーン 1 3
1 時間あたりの料金 1.125 USD/時間 1.4 USD/時間
月額 810 USD 1,008 USD
抽出されたドキュメント利用料金 0.000001 USD/ドキュメント 0.000001 USD/ドキュメント
データソースコネクターの同期時の利用料金 0.35 USD/時間 0.35 USD/時間

アベイラビリティーゾーン の数や 1 日あたりの最大クエリ件数 に関しては、比較検討の余地があるのではないでしょうか。

Quotas も Developer Edition の場合、調整不可のケースもあるため注意しながらエディションの選定を行っていきましょう。

Quotas for Amazon Kendra - Amazon Kendra

ログ

Amazon Kendra のログは クエリ等の API を記録する CloudTrail のログとデータソースのコンテンツ抽出ログの 2 パターンが存在します。インデックスでは後者のコンテンツ抽出ログの設定を行います。

CloudTrail のログ

例えば、 Query API は次のような形式でログ記録されます。

{
    "eventVersion": "1.08",
    "userIdentity": {
        "type": "AssumedRole",
        "principalId": "MASKED",
        "arn": "arn:aws:sts::MASKED:assumed-role/MASKED/MASKED",
        "accountId": "MASKED",
        "accessKeyId": "MASKED",
        "sessionContext": {
            "sessionIssuer": {
                "type": "Role",
                "principalId": "MASKED",
                "arn": "arn:aws:iam::MASKED:role/MASKED",
                "accountId": "MASKED",
                "userName": "MASKED"
            },
            "webIdFederationData": {},
            "attributes": {
                "creationDate": "2024-04-08T01:12:12Z",
                "mfaAuthenticated": "true"
            }
        }
    },
    "eventTime": "2024-04-08T02:54:41Z",
    "eventSource": "kendra.amazonaws.com",
    "eventName": "Query",
    "awsRegion": "us-east-1",
    "sourceIPAddress": "MASKED",
    "userAgent": "MASKED",
    "requestParameters": {
        "indexId": "MASKED"
    },
    "responseElements": null,
    "requestID": "MASKED",
    "eventID": "MASKED",
    "readOnly": true,
    "eventType": "AwsApiCall",
    "managementEvent": true,
    "recipientAccountId": "MASKED",
    "eventCategory": "Management",
    "tlsDetails": {
        "tlsVersion": "TLSv1.3",
        "cipherSuite": "TLS_AES_128_GCM_SHA256",
        "clientProvidedHostHeader": "kendra.us-east-1.amazonaws.com"
    },
    "sessionCredentialFromConsole": "true"
}

Logging Amazon Kendra API calls with AWS CloudTrail logs - Amazon Kendra Logging Amazon Kendra Intelligent Ranking API calls with AWS CloudTrail logs - Amazon Kendra

コンテンツ抽出ログ

コンテンツ抽出ログにはデータソースのログストリームと、ドキュメントのログストリームがあります。

データソースのログストリームでは、インデックスとの同期ジョブに関するエントリを発行します。次の形式でログストリームが作成されます。

data source id/YYYY-MM-DD-HH/data source sync job ID

ログレベルは errorinfo の2つに分かれ、次のようなログを出力します。

error.json

{
    "DocumentId": "document ID",
    "S3Path": "s3://bucket/prefix/object",
    "Message": "Failed to ingest document via BatchPutDocument.",
    "ErrorCode": "InvalidRequest",
    "ErrorMessage": "No document metadata configuration found for document attribute key  city."
}

info.json

{
    "LogLevel": "Info",
    "AwsAccountId": "XXXXXXXXXX",
    "IndexId": "XXXXXXXXXX",
    "SourceId": "XXXXXXXXXX",
    "Message": "Received connector configure request for connector."
}

つづいて、ドキュメントのログストリームに関してです。ドキュメントのログストリームでは、ドキュメントの処理に関する情報を記録します。例えば以下のようなログが出力されます。

info.json

{
    "LogLevel": "Info",
    "AwsAccountId": "XXXXXXXXXX",
    "IndexId": "XXXXXXXXXX",
    "SourceId": "XXXXXXXXXX",
    "DocumentId": "s3://XXXXXXXXXX/data/就業規則.pdf",
    "DocumentTitle": "就業規則",
    "Message": "Indexing document to index."
}

Monitoring Amazon Kendra with Amazon CloudWatch Logs - Amazon Kendra

監視

Amazon Kendra では CloudWatch Metrics もサポートしています。

Monitoring Amazon Kendra with Amazon CloudWatch - Amazon Kendra

IndexQueryCountIndexDocumentCount, IndexDocumentStorageSize などは、メトリクスとしてアラームをあげておくといいかもしれません。

普段のモニタリングは Amazon Kendra コンソールから、 IndexDocumentCountIndexQueryCount が閲覧できます。

Monitoring your index (console) - Amazon Kendra

データ暗号化

最後に RAG 構成においてデータの暗号化はかなり多くの質問をいただくので、ご紹介します。

転送時の暗号化

クエリを実行するクライアントと Amazon Kendra 間の通信は HTTPS (TLS 1.2以上) で疎通されます。また、 VPC Endpoint を利用して、プライベートな通信経路で API を実行できます。

Data protection in Amazon Kendra - Amazon Kendra

Encryption in transit - Amazon Kendra

保管時の暗号化

コンテンツの保管はデフォルトでは AWS KMS キーを利用します。

インデックスの設定で aws/kendra から始まる AWS マネージドキーや、カスタマーマネージドキーで暗号化も設定可能です。

インデックスの作成後、暗号化キーの変更は不可のため要件を固めてからインデックスの作成を始めましょう。

Encryption at rest - Amazon Kendra

ML 駆動の検索エンジンで企業の情報管理を革新 ! Amazon Kendra をグラレコで解説 - builders.flash☆ - 変化を求めるデベロッパーを応援するウェブマガジン | AWS

おわりに

以上、「AWS 入門ブログリレー 2024 〜Amazon Kendra編〜」でした。

入門リレーなので触りの雰囲気くらいで押さえてみました。この観点知りたいとかあれば、ぜひ たかくに(@takakuni_) までご連絡ください。

続きが気になる方は、 ワークショップ や Blackbelt 等をみてみてください。かなり解像度変わると思います。

次回、 04/09 は弊社 emi さん による「 Amazon QuickSight 編」の予定です!

このエントリが誰かの助けになれば幸いです。

AWS 事業本部コンサルティング部のたかくに(@takakuni_) でした!