LangChainからKendraでRAGする（その1）-Amazon Bedrock APIで始めるLLM超入門⑤

LangChainからAmazon Kendraを検索するRAGを作ってみます。

:::note warn
2024/2/29
LangChainを最新バージョンにすると色々動かないので、最新バージョンで動かす場合は以下のエントリをご参照ください
:::
https://qiita.com/cyberBOSE/items/fd65de9f857d36180fa5

# Amazon Kendraとは？

文書をインデックスして検索してくれるサービスです。

https://aws.amazon.com/jp/kendra/

# RAG (Retrieval Augmented Generation) とは？

LLMが知らない情報について（システムで）検索してプロンプトに一緒に与えてあげる事で、生成するテキストの精度を上げる、という定番のソリューション。プロンプトエンジニアリング上のテクニックの一つ。

界隈（？）では常識扱いなのか、ちょいちょい説明無しで「RAG」と出てきて最初は戸惑います。日本語名は揺れている気がしますが、意味的には「検索により強化された（文章）生成」というのがまあ許せる感じです。読み方は「ラグ」で良いんですかね。

https://www.promptingguide.ai/jp/techniques/rag

定義上はイントラに限る必要は無いと思うんですが、イントラにしか存在しない文書を使用している例が多いように思います。

# 作るもの

Claude2が知らないAmazon Bedrockの情報をKendraにインデックスし、それを使って文章生成を行います。
プログラムはまずはKendraを検索し、検索結果をLLM（Claude2）にプロンプトで渡す事で素のClaude2が知らない知識について文章を生成します。
<img width="30%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3573242/1ee25abe-4a1b-b254-d812-3254b06dd634.gif"> 

# Kendra側の準備
こちらを参考にします。

https://dev.classmethod.jp/articles/kendra-lambda-retrieval/

Webクローラーの設定を以下のようにします。
<img width="40%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3573242/8a9310ff-d954-c42a-dffd-a9bb9f21ac0c.gif">　<img width="40%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3573242/52304eda-d31d-4567-de50-49b81f9408b6.gif">
指定しているURLは以下です。
`https://docs.aws.amazon.com/bedrock/latest/userguide/sitemap.xml`
`https://docs.aws.amazon.com/bedrock/latest/`

上記のURLは英語のドキュメントになりますが、データソースのLanguageは「日本語」で設定します。
また、KendraのIndex IDをメモしておきます。

# IAMポリシーの準備
Bedrockを実行しているIAMユーザーに`AmazonKendraFullAccess`をアタッチ

# 動作確認

LangChainのRetrieverという機能を使います。プログラム中の「xxx…」はご自身のKendra IndexIDに置き換えてください。

ちなみにプロンプトは指定しなくてもLangChainのデフォルトで動くのですが、やや挙動不審なのでプログラム中で上書きしています。

```Python:LangChainRagSample.py
import sys
from langchain.llms import Bedrock
from langchain.chains import RetrievalQA
from langchain.retrievers import AmazonKendraRetriever
from langchain.prompts import PromptTemplate

# Retriever(Kendra)の定義
# 日本語で"登録されている"ドキュメントを20件(top_k=20)検索する、と定義
kendra_index_id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" #各自のKendra Index IDに書き換えてください
attribute_filter = {"EqualsTo": {"Key": "_language_code","Value": {"StringValue": "ja"}}}
retriever = AmazonKendraRetriever(index_id=kendra_index_id,attribute_filter=attribute_filter,top_k=20)

# LLMの定義
llm = Bedrock(
    model_id="anthropic.claude-v2",
    model_kwargs={"max_tokens_to_sample": 1000}
)

# promptの定義
prompt_template = """
  <documents>タグには参考文書が書かれています。
  <documents>{context}</documents>
  \n\nHuman: 上記参考文書を元に、<question>に対して説明してください。言語の指定が無い場合は日本語で答えてください。
    もし<question>の内容が参考文書に無かった場合は「文書にありません」と答えてください。
  <question>{question}</question>
  \n\nAssistant:"""
PROMPT = PromptTemplate(template=prompt_template, input_variables=["context", "question"])
chain_type_kwargs = {"prompt": PROMPT}

# Chainの定義
qa = RetrievalQA.from_chain_type(retriever=retriever,llm=llm,chain_type_kwargs=chain_type_kwargs)

# chainの実行
result = qa.run(sys.argv[1])
print(result)
```
素のClaude2が知らない情報として「Bedrock」を検索してみます。

```:ターミナル
> python LangChainRagSample.py "「Amazon Bedrock」について説明してください"

はい、参考文書を確認しました。
Amazon Bedrockについて、以下のように説明できます。
Amazon Bedrockは、AmazonやサードパーティのモデルプロバイダーからのベースモデルをAPIを通じてアクセスできる、完全マネージドなサービスです。
主な特徴は以下の通りです。
- テキスト、画像、チャットのプレイグラウンドを通じて、モデルを手軽に試すことができます。
- Bedrock APIを使うことで、AWS CLIやプログラムからモデルにアクセスできます。
- モデルのカスタマイズができ、ニーズに合わせて調整することが可能です。
- Amazon CloudWatchやAmazon EventBridgeを使って、Bedrockの監視ができます。
- IAMを使って、Bedrockリソースへのアクセス制御ができます。
- セキュリティ面では、暗号化、認証、アクセス制御などの対策がされています。
Amazon Bedrockは、機械学習の導入を容易にするためのマネージドサービスと言えます。
```
Kendraの検索結果（英語ドキュメント）を元に日本語で説明してくれました。なかなかすごいですね。
別の質問をしてみます。

```:ターミナル
> python LangChainRagSample.py "「Amazon Bedrock」は「Claude2」を使用できますか？"    

 はい、Amazon BedrockはAnthropicのClaude2モデルを利用できます。
参考文書には以下の記載があります。
「Bedrock supports the following models:
Anthropic Claude v1.x
Claude v2.x
Claude Instant v1.x」
つまり、Amazon BedrockはAnthropic社の言語モデルであるClaudeのバージョン1.x、2.x、Claude Instant 1.xをサポートしているとのことです。
したがって、「Amazon Bedrock」では「Claude2」を使用できることがわかります。
```
こちらもKendraの検索結果（英語ドキュメント）を元に日本語で回答してくれました。

今度はKendraにインデックスされていない事を聞いてみます。
```:ターミナル
> python LangChainRagSample.py "「Amazon Kendra」について説明してください"           

 参考文書に「Amazon Kendra」についての記載はありませんでした。
```
Kendraの検索結果だけから回答するようにプロンプトで指示してあるので、知ったかぶりをせずに「文書に記載が無い」旨を返してきました。

次にKendraに関するドキュメントを追加でKendraにIndexしてみます。
<img width="40%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3573242/5c687ce4-27b6-37cc-3b54-f06857ec9a81.gif">　<img width="40%" src="https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3573242/ea95402b-4014-9713-8c08-5713dfc0047f.gif">
追加したURLは以下です。
`https://docs.aws.amazon.com/ja_jp/kendra/latest/dg/sitemap.xml`
`https://docs.aws.amazon.com/ja_jp/kendra/latest/`

ではKendraについて聞いてみます。
```:ターミナル
> python LangChainRagSample.py "「Amazon Kendra」について説明してください" 

<question>「Amazon Kendra」について説明してください。</question>
Amazon Kendraは、Amazon Web Services(AWS)が提供するクラウドベースの自然言語処理(NLP)サービスです。機密性の高 い文書やプライベートコンテンツを解析し、検索可能なインデックスを作成することができます。
主な機能は以下の通りです。
- テキスト、PDF、Microsoft Officeドキュメントなど、さまざまなドキュメントソースからコンテンツを収集・解析する機能
- 検索可能なインデックスを自動的に構築する機能
- 自然言語でのクエリをサポートする高度な検索エンジン
- AIを活用したコンテンツの要約作成や質問応答などの機能
- AWSやサードパーティのデータソースとの連携
- セキュリティ・アクセス制御機能
- 検索結果のランキングやレコメンデーションのカスタマイズ機能
Kendraは機密性の高い内部ドキュメントを検索可能にする企業内検索エンジンとして、また顧客対応のためのFAQ検索などに利用されています。
```

Kendraについても答えられるようになりました。
ここでBedrockについて再度聞いてみます。

```:ターミナル
> python LangChainRagSample.py "「Amazon Bedrock」について説明してください"
 おっしゃるとおり、提供された参考文書には「Amazon Bedrock」に関する記載はありませんでした。
```
なんと、Kendraについてインデックスした結果、Bedrockに答えられないようになりました。

# 何が起きているのか？
CloudWatch Logsから、最終的にBedrockに渡されたプロンプトを抜粋してみます。以下のような文書がBedrockに渡されています。
```:CloudWatch Logs抜粋
<documents>
Document Title: Amazon Kendra モニタクス CloudWatch - Amazon Kendra\nDocument Excerpt: \nアラームをトリガーするしきい値、固定値を使用するかバンドを使用するか、およびしきい値を満たす条件を選択します。 トリガーのアラーム状態、メトリクスが設定したしきい値を超える必要があるかどうか、または別の状態を選択します。 アラーム通知を誰/どのメールに送信するかを選択します。 アラームで満足できる場合は、以下を選択してください。 アラームの作成。 注記 名前を入力する必要があります CloudWatch アラーム。 CloudWatch インデックス同期ジョブの 次の表は、データソース同期ジョブの Amazon Kendra メトリクスの説明です。 API または CLI を使用する場合は、に対してどのようなリクエストが行われたかを判断できます。 Namespaceさらに「AWS/Kendra」としてMetricName使用する場合はお選びください。 GetMetricStatisticsAPI。 メトリクス 説明 DocumentsCrawled 同期ジョブの実行中にスキャンまたは検出されたドキュメントの数。 ディメンション: IndexId DataSourceId 単位: 個 DocumentsSubmittedForIndexing 同期ジョブがインデックスに送信したドキュメントの数。\n\n\nDocument Title: Microsoft チーム - Amazon Kendra\nDocument Excerpt: \nAWS Management Console にサインインして、Amazon Kendra コンソールを開きます。 左側のナビゲーションペインから、[インデックス]次に、インデックスのリストから使用したいインデックスを選択します。 注記 設定するか編集するかを選択できます。 ユーザーアクセス制御以下の設定インデックス設定。 上にはじめにページ、選択データソースを追加。 上にデータソースを追加ページ、選択Microsoft チームコネクタそして選択してくださいデータソースを追加。 でデータソースの詳細を指定します。 ページで、次の情報を入力します。 に名前と説明、用データソース名—データソースの名前を入力します。 ハイフンは使用できますが、スペースは含められません。 (オプション)説明—データソースの説明を任意で入力します。 で言語、用既定の言語—索引用のドキュメントをフィルタリングする言語。 特に指定しない限り、言語はデフォルトで英語に設定されます。 文書のメタデータで指定された言語は、選択した言語よりも優先されます。\n\n\n
Document Title: Microsoft チーム - Amazon Kendra\nDocument Excerpt: \nAWS Management Console にサインインして、Amazon Kendra コンソールを開きます。 左側のナビゲーションペインから、[インデックス]次に、インデックスのリストから使用したいインデックスを選択します。 注記 設定するか編集するかを選択できます。 ユーザーアクセス制御以下の設定インデックス設定。 上にはじめにページ、選択データソースを追加。 上にデータソースを追加ページ、選択Microsoft チームコネクタそして選択してくださいデータソースを追加。 でデータソースの詳細を指定します。 ページで、次の情報を入力します。 に名前と説明、用データソース名—データソースの名前を入力します。 ハイフンは使用できますが、スペースは含められません。 (オプション)説明—データソースの説明を任意で入力します。 で言語、用既定の言語—索引用のドキュメントをフィルタリングする言語。 特に指定しない限り、言語はデフォルトで英語に設定されます。 文書のメタデータで指定された言語は、選択した言語よりも優先されます。\n\n\n
Document Title: Amazon RDS/Aurora - Amazon Kendra\nDocument Excerpt: \n左側のナビゲーションエンドポイント。 インデックス次に、インデックスのリストから使用したいインデックスを選択します。 注記 設定するか編集するかを選択できます。 ユーザーアクセス制御以下の設定インデックス設定。 上にはじめにページ、選択データソースを追加。 でデータソースを追加ページ、選択データベースエンドポイントそして選択してくださいデータソースを追加。 上にデータソースの詳細を指定します。 ページで、次の情報を入力します。 に名前と説明、用データソース名—データソースの名前を入力します。 ハイフンは使用できますが、スペースは含められません。 (オプション)説明—データソースの説明を任意で入力します。 で言語、用既定の言語—索引用のドキュメントをフィルタリングする言語。 特に指定しない限り、言語はデフォルトで英語に設定されます。 文書のメタデータで指定された言語は、選択した言語よりも優先されます。 でタグ、用新しいタグを追加—リソースを検索してフィルタリングしたり、リソースを追跡したりするタグAWS費用。\n\n\n
・・・
```
確かに`Bedrock`という言葉は文書に含まれていません。代わりに日本語文書で`説明`という言葉が含まれている文書が検索されているように見えます。

実際にKendraに渡されている検索キーワードを見る方法が分からなかったので想像にはなりますが、入力された`「Amazon Bedrock」について説明してください`が元々そのままKendraの検索に使われており、`説明`という言葉を含む文書をインデックスした結果、Kendraによって目的ではない文書が検索されてしまったようです。

# ではどうするか

Kendraに対する指示とLLMに対する指示が混ざったユーザー入力をそのまま扱うと、こういう事が起きます。どうにかして分ける必要がありそうです。

次回はAgent機能を使ってKendraに対する指示とLLMに対する指示を分けられないか見てみます。

（続く）

https://qiita.com/cyberBOSE/items/fe66c2049aaa04568d61