AIDXナレッジ - INSIGHT LAB

【AWS Kendra, Bedrock】AWSでRAGアプリケーションを作成してみた - ①

作成者: Budo Ogimoto|2024年6月04日

はじめに

この記事では、AWSを利用してRAGを搭載したチャットシステムを構築する方法を紹介します。
また、この記事で紹介している内容は5/21, 22, 23にグランドニッコー東京 台場で開催された「ガートナー データ&アナリティクス サミット 2024」に出展しました。

ガートナー データ&アナリティクス サミット2024
東京で開催されるガートナー データ&アナリティクス サミット2024でCDAOとデータ&アナリティクスのリーダーのためのインサイト、戦略、フレームワークを探求しましょう。ご登録はこちら
https://www.gartner.com/jp/conferences/apac/data-analytics-japan

RAGについて

RAG(Retrieval-Augmented Generation)とは、生成モデルとベクトルDBを組み合わせたアプローチ方法を指します。

基本的な構成は下記の画像の様になります。

事前にベクトルDBに生成モデルに参照させたいドキュメントをベクトル化しておきます。
ユーザーの入力文より類似した文を検索することで生成モデルが回答文を生成する為の参考文を検索します。
付随情報を参照するようなプロンプトを入力文と共に入力することで回答文を生成します。

このような構成にすることで、プロンプトの調整次第ですが付随情報の内容のみを参照させて回答させることができます。

RAGの利点としては以下のような点が挙げられます。

  • ハルシネーションの防止

    生成モデルを利用する上での注意点であるハルシネーション(生成モデルの嘘)を参考文の情報のみを参照させることで防止ができます。
    また、定期的にベクトルDBを更新することで最新情報のキャッチアップが可能です。

  • 生成モデルの調整コストカット

    生成モデルに専門性を持たせる際にファインチューニングや強化学習を行う必要がありますがRAGに於いては参照先のベクトルDBを切り替えることで対応可能です。

システムの構成

今回構築していくRAGの構成を解説します。
構成としては、下記のようになっています。

構成自体は単純で、ユーザーの入出力を制御するEC2内のアプリケーションをPythonで構築して、ベクトルDBをAmazon Kendraとし、生成AIをAmazon Bedrockを介してClaude v3 Haikuとする形になっています。

肝心の参考文に関しては、Notion内にあるISL Knowledgeの記事全件を読み込んでいます。
よって、今回のRAGはISLの技術力の集合知を表すものになるかと思います。

ドキュメントの収集

今回はNotionからAPIを利用して全件の記事を収集します。

まずは、全記事の概要データを収集します。
下記のPythonコードで実行します。

import requests
import json
import numpy as np
import pandas as pd
from tqdm import tqdm

# トークン情報
NOTION_ACCESS_TOKEN = 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
NOTION_DATABASE_ID = 'XXXXXXXXXXXXXXXXXXXX'


# 各変数
loop_cnt = 1
has_more = True
data = []
all_titles = []
authors = []
create_times = []
article_ids = []
urls = []
statuses = []
tag_sets = []
author_emails = []
next_cursor = None


# APIでデータベース内のデータを全件取得
while has_more:
    url = f"https://api.notion.com/v1/databases/{NOTION_DATABASE_ID}/query"
    headers = {
        'Authorization': 'Bearer ' + NOTION_ACCESS_TOKEN,
        'Notion-Version': '2021-05-13',
        'Content-Type': 'application/json',
    }
    payload = {'page_size': 100} if loop_cnt == 1 else {'page_size': 100, 'start_cursor': next_cursor}
    result_json = requests.post(url, headers=headers, data=json.dumps(payload))
    data += result_json.json().get('results')
    has_more = result_json.json().get('has_more')
    next_cursor = result_json.json().get('next_cursor')
    print('loop: {}'.format(loop_cnt))
    loop_cnt += 1

# 取得したデータを保存
with open('notion_knowledge_data.json', 'w')as f:
    json.dump(data, f, indent=1)

# 各記事の情報を収集
for record in tqdm(data):
    status = record['properties']['Status']['status']['name']
    url = record['url']
    article_id = record['id']
    try:
        title = record['properties']['Knowledge']['title'][0]['plain_text']
    except IndexError:
        title = ''
    try:
        author = record['properties']['Author']['created_by']['name']
    except KeyError:
        author = 'unknown'
    try:
        author_email = record['properties']['Author']['created_by']['person']['email']
    except KeyError:
        author_email = 'unknown'
    create_time = record['properties']['CreateTime']['created_time']
    tags = '&&'.join([x['name'] for x in record['properties']['Tag']['multi_select']])
    article_ids.append(article_id)
    all_titles.append(title)
    authors.append(author)
    create_times.append(create_time)
    urls.append(url)
    statuses.append(status)
    tag_sets.append(tags)
    author_emails.append(author_email)


# データフレーム化して保存
df = pd.DataFrame(
    np.array([[i for i in range(len(all_titles))], all_titles, authors, author_emails, create_times, urls, article_ids, statuses, tag_sets]).T,
    columns=[
		    'index', 'title', 'author',
		    'author_email', 'create_time', 'url',
		    'article_id', 'status', 'tags'
    ]
)
df.to_csv('notion_knowledges.csv', index=False, encoding='utf_8_sig')

今回は継続的にドキュメント内容を更新させるわけではないので、上記のスクリプトのみとなっています。
実際に運用する場合は、差分を取りに行くlambda等を作成して更新するスクリプトを仕込む必要があります。

おわりに

本記事では、構築するシステムの概要とAmazon Kendraに格納するドキュメントの収集をNotionから行いました。
次回の記事からは、Amazon KendraのベクトルDBに当たるIndexを作成していきます。
次回の記事もご一読頂けますと幸いです。