[Rust] Amazon KendraでGetSnapshots APIを使う

[Rust] Amazon KendraでGetSnapshots APIを使う

Clock Icon2024.09.17

Introduction

Amazon Kendraの GetSnapshots APIは、Kendraの検索分析データを取得するためのAPIです。
このAPIを使うことで、Kendraに対して行われた検索クエリの統計情報を取得できます。

例えば、特定の期間にどれだけの検索が行われたか、どのようなクエリで検索されたか、
検索結果の有用性などの情報を取得できます。
これらの分析結果から、Kendraの改善に役立てることができます。

Environment

  • MacBook Pro (14-inch, M3, 2023)
  • OS : MacOS 14.5
  • Rust : 1.81.0

AWSアカウントはすでにあり、
アクセス可能なKendraも存在するとします。
KendraのIndexはこのあたりをみて作成しましょう。

Getsnapshots API

では実際に試してみましょう。
期間とMetricTypeを指定してDKで用意してあるAPIを実行するだけです。
ここの内容にそって進めていきます。

APIで指定するパラメータは下記です。
重要なパラメータについていくつか解説します。

{
   "IndexId": "<Kendra Index ID>",
   "Interval": "期間",
   "MaxResults": <データ取得数>,
   "MetricType": "メトリクスタイプ",
   "NextToken": "<任意>"
}

期間(Interval)

指定する期間は、いつのデータを統計対象にするかを指定します。
以下は、現在の日付を「2024年9月17日(火曜日)」とした場合の例です。

期間 説明 データ取得期間(2024年9月17日基準)
THIS_WEEK 日曜日から始まり、現在の日付の前日に終わる現在の週。 2024年9月15日(日)~2024年9月16日(月)
ONE_WEEK_AGO 日曜日に始まり、次の土曜日に終わる前の週。 2024年9月8日(日)~2024年9月14日(土)
TWO_WEEKS_AGO 前の週の前の週。日曜日に始まり、次の土曜日に終わる。 2024年9月1日(日)~2024年9月7日(土)
THIS_MONTH 現在の月。その月の1日から始まり、現在の日付の前日に終わる。 2024年9月1日(日)~2024年9月16日(月)
ONE_MONTH_AGO 前月。月の最初の日から始まり、月の最後の日に終わる。 2024年8月1日(木)~2024年8月31日(土)
TWO_MONTHS_AGO 前月の前月。その月の1日から始まり、その月の末日まで続く。 2024年7月1日(月)~2024年7月31日(水)

このように、GetSnapshot APIを使用する際に、指定した期間に基づいてデータを取得することができます。
期間の指定は、↓にあるようにAPI実行タイミングによって結果に違いが出るので注意しましょう。

タイミング 説明
週の変わり目(日曜日)に実行 - THIS_WEEKのデータが空になる可能性あり。
- ONE_WEEK_AGOTWO_WEEKS_AGOのデータが1週間ずつシフトする。
月の変わり目に実行 - THIS_MONTHのデータが空になる可能性あり。
- ONE_MONTH_AGOTWO_MONTHS_AGOのデータが1ヶ月ずつシフトする。
日付が変わる直前や直後に実行 - THIS_WEEKTHIS_MONTHのデータ量が、実行時刻によって大きく異なる可能性あり
月末の日数が異なる月をまたぐ場合 - ONE_MONTH_AGOTWO_MONTHS_AGOのデータ量が月によって異なる可能性あり

MetricType

もう1つのパラメータが、取得するデータのタイプです。
タイプは↓のように6つあり、「検索されたクエリ」だったり
「検索後にユーザーがクリックしなかったクエリ」などです。

メトリクスタイプ 説明
QUERIES_BY_COUNT 検索結果の有無に関わらず、実行されたすべての検索クエリの数
QUERIES_BY_ZERO_CLICK_RATE 検索結果が表示されたが、ユーザーがクリックしなかったクエリの割合
QUERIES_BY_ZERO_RESULT_RATE 検索結果が0件だったクエリの割合
DOCS_BY_CLICK_COUNT 検索結果として表示され、ユーザーにクリックされたドキュメントの数
AGG_QUERY_DOC_METRICS 検索クエリと関連するドキュメントに関する集計情報
TREND_QUERY_DOC_METRICS 検索クエリと関連するドキュメントに関するトレンド情報

こういったパラメータを指定することで、下記のようにCLIで簡単にデータを取得できます。

% aws kendra get-snapshots \
--index-id <Knedra Index ID> \
--interval "ONE_WEEK_AGO" \
--metric-type "QUERIES_BY_COUNT"

//response

{ "SnapShotTimeFilter": { "StartTime": "2024-xx-xxT00:00:00+09:00", 
"EndTime": "2024-xx-xxT23:00:00+09:00" }, 
"SnapshotsDataHeader": [ "query_content", 
"count", "ctr", "zero_click_rate", "click_depth", 
"instant_answer", "confidence" ], 
"SnapshotsData": [ 
    [ "hoge", "23", "0.0", "1.0", "0.0", "0.0", "LOW" ], 
    ・・・
], 
   "NextToken": "xxxxx" }

SnapshotsDataHeaderは、各列のヘッダーを表しており、
内容は下記です。

- query_content: 検索クエリ内容
- count: クエリ実行回数
- ctr: スルーしたクリックの割合
- zero_click_rate: 結果を見たが何もクリックしなかった割合
- click_depth: 何番目の結果をクリックしたか
- instant_answer: 即時回答の割合
- confidence: 信頼度

SnapshotsDataの各行は検索クエリに関するデータです。
例えば、最初の行の意味は下記のようになります。

- "hoge" が23回検索された
- クリックスルー率は0%
- ゼロクリック率は100%(すべての検索で結果をクリックしていない)
- クリック深度は0(クリックがないため)
- インスタントアンサーの割合は0%
- 信頼度は低い(LOW)

Try

では、Getsnapshot APIをRust用SDKで試してみます。
ここではQUERIES_BY_COUNTとQUERIES_BY_ZERO_RESULT_RATEをメトリクスを取得してみます。

Cargoでプロジェクトを作成して必要なcrateをaddします。

% cargo new kendra-edample
% cd kendra-edample
% cargo add anyhow aws-config aws-sdk-kendra tokio thiserror

src/main.rsを下記のように記述します。

use aws_sdk_kendra::{
    types::{Interval, MetricType},
    config::BehaviorVersion,
    Client,
};
use anyhow::{Context, Result};

const INDEX_ID: &str = "Kendta Index ID";
const MAX_RESULTS: i32 = 100;

#[tokio::main]
async fn main() -> Result<()> {
    let config = aws_config::load_defaults(BehaviorVersion::v2024_03_28()).await;
    let client = Client::new(&config);

    let interval = Interval::OneWeekAgo;

    // QUERIES_BY_COUNT データの取得
    let count_data = get_snapshot_data(&client, interval.clone(), MetricType::QueriesByCount)
        .await
        .context("Failed to get QUERIES_BY_COUNT data")?;
    let sorted_count_queries = sort_by_count(&count_data);

    // QUERIES_BY_ZERO_RESULT_RATE データの取得
    let zero_rate_data = get_snapshot_data(&client, interval, MetricType::QueriesByZeroResultRate)
        .await
        .context("Failed to get QUERIES_BY_ZERO_RESULT_RATE data")?;
    let sorted_zero_rate_queries = sort_by_zero_result_rate(&zero_rate_data);

    // 人気のあるクエリを出力
    println!("\n人気のあるクエリ (QUERIES_BY_COUNT):");
    for (query, count) in &sorted_count_queries {
        println!("クエリ: {}, 検索回数: {}", query, count);
    }
    println!("Total: {}", sorted_count_queries.len());

    // ゼロ結果率の高いクエリを出力
    println!("\nゼロ結果率の高いクエリ (QUERIES_BY_ZERO_RESULT_RATE):");
    for (query, zero_rate) in &sorted_zero_rate_queries {
        println!("クエリ: {}, ゼロ結果率: {}", query, zero_rate);
    }
    println!("Total: {}", sorted_zero_rate_queries.len());

    Ok(())
}

async fn get_snapshot_data(
    client: &Client,
    interval: Interval,
    metric_type: MetricType,
) -> Result<Vec<Vec<String>>> {
    let mut all_data = Vec::new();
    let mut next_token: Option<String> = None;
    let mut request_count = 0;

    loop {
        request_count += 1;
        let response = client
            .get_snapshots()
            .index_id(INDEX_ID)
            .interval(interval.clone())
            .metric_type(metric_type.clone())
            .max_results(MAX_RESULTS)
            .set_next_token(next_token.clone())
            .send()
            .await
            .context("Failed to send get_snapshots request")?;

        let items_received = response.snapshots_data().len();
        println!(
            "Request {}: Received {} items.",
            request_count, items_received
        );

        all_data.extend(response.snapshots_data().iter().cloned());

        next_token = response.next_token().map(|s| s.to_string());
        if next_token.is_none() {
            break;
        }
    }

    println!("Total requests made: {}", request_count);
    println!("Total items fetched: {}", all_data.len());
    Ok(all_data)
}

fn sort_by_count(data: &Vec<Vec<String>>) -> Vec<(String, i32)> {
    let mut queries_with_count: Vec<(String, i32)> = data
        .iter()
        .map(|item| {
            let query = item[0].clone();
            let count = item[1].parse::<i32>().unwrap_or(0); // 検索回数はitem[1]
            (query, count)
        })
        .collect();

    // 検索回数で降順にソート
    queries_with_count.sort_by(|a, b| b.1.cmp(&a.1));

    queries_with_count
}

fn sort_by_zero_result_rate(data: &Vec<Vec<String>>) -> Vec<(String, f64)> {
    let mut queries_with_zero_rate: Vec<(String, f64)> = data
        .iter()
        .map(|item| {
            let query = item[0].clone();
            let zero_rate = item[2].parse::<f64>().unwrap_or(0.0); // ゼロ結果率はitem[2]
            (query, zero_rate)
        })
        .collect();

    // ゼロ結果率で降順にソート
    queries_with_zero_rate.sort_by(|a, b| b.1.partial_cmp(&a.1).unwrap());

    queries_with_zero_rate
}

このプログラムはKendraのGetSnapshots APIを使用して検索クエリの統計情報を取得します。
QUERIES_BY_COUNTメトリクスでは、検索回数が多いクエリを取得し、
QUERIES_BY_ZERO_RESULT_RATEメトリクスでは、
検索結果が0件だったクエリの割合を取得します。

get_snapshot_data関数で、指定したintervalとmetrics typeに基づいてデータを取得します。
データは複数ページにわたる可能性があるため、next_tokenを使用してpagenationも考慮。

その後取得したデータをsort_by_count/sort_by_zero_result_rateでソートします。
私の環境で実行した結果、↓のようになりました。

人気のあるクエリ (QUERIES_BY_COUNT):
クエリ: The Shawshank Redemption, 検索回数: 1032
クエリ: I'm going to make him an offer he can't refuse., 検索回数: 202
クエリ: Pride and Prejudice, 検索回数: 124
クエリ: Inception, 検索回数: 120
クエリ: One Hundred Years of Solitude, 検索回数: 118
クエリ: Jurassic Park, 検索回数: 56
クエリ: The Godfather, 検索回数: 35
クエリ: I'll be back., 検索回数: 21
・
・
・
Total: xxxx

ゼロ結果率の高いクエリ (QUERIES_BY_ZERO_RESULT_RATE):
クエリ: hoge, ゼロ結果率: 1
クエリ: fugafuga, ゼロ結果率: 1
クエリ: test, ゼロ結果率: 1
クエリ: jjj, ゼロ結果率: 0.5
・
・
・
Total: xxxx

Summary

今回はAmazon KendraのGetSnapshots APIをRustで使用し、検索クエリの統計データを取得してみました。
各種メトリクスを指定し、検索回数やゼロ結果率の高いクエリを分析します。
こういったデータを定期的に分析することで、
Kendraのクエリパフォーマンスを評価し、改善に役立てることができます。

References

Share this article

facebook logohatena logotwitter logo

© Classmethod, Inc. All rights reserved.