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

ユーザーからの入力内容をKendraとLLMの両方のInputに使うのは良くないケースがある事が分かりました。

前回の結果

https://qiita.com/cyberBOSE/items/5cdec3580eb22b567d39?1

LangChainのAgent機能を使えば、ツールの実行前のタイミングでLLMを介在させる事が可能な為、ユーザーからの入力内容をKendraとLLMで分けるようにします。

Agent機能の例はこちら

https://qiita.com/cyberBOSE/items/a331ed830ed1859a198d?1

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

# プログラム

以下のプログラムを実行します。
使用するKendraIndexは前回の「Bedrock(英語)とKendra(日本語)について、日本語の文書としてインデックスしたもの」を使います。
（Kendraの言語指定って何なんでしょうね）

```Python:LangChainXmlAgentRagSample.py
import sys
from langchain.llms import Bedrock
from langchain.chains import LLMChain
from langchain.agents import XMLAgent
from langchain.agents.agent import AgentExecutor
from langchain.agents import Tool

# Amazon Kendraから情報を取得する
# 日本語で"登録されている"ドキュメントを20件(top_k=20)検索して取得結果を全て結合する
from langchain.retrievers import AmazonKendraRetriever
kendra_index_id="xxxxxxxx-xxxx-xxxx-xxxx-xxxxxxxxxxxx" #各自のKendra Index IDに書き換えてください
docs=[]
def get_retrieval_result(query) -> str:
    attribute_filter = {"EqualsTo": {"Key": "_language_code","Value": {"StringValue": "ja"}}}
    retriever = AmazonKendraRetriever(index_id=kendra_index_id,attribute_filter=attribute_filter,top_k=20)
    global docs
    docs = retriever.get_relevant_documents(query=query)
    context=""
    for doc in docs:
        context += doc.page_content
    return context

# 使用可能なツールと説明
tools = [
    Tool(
        name = "KendraSearch",
        func=get_retrieval_result,
        description=""""
            このツールは最新のWeb情報を検索するツールです。引数は検索キーワードです。検索キーワードの例は<examples>の通りです。
            <examples>
                <question>Bedrockについて説明してください</question><検索キーワード>Bedrock</検索キーワード>
                <question>Kendraについて英語で教えてください</question><検索キーワード>Kendra</検索キーワード>
                <question>LangChainはBedrockで使用する事が出来ますか？</question><検索キーワード>LangChain Bedrock</検索キーワード>                
                <question>Amplifyは無料で使用する事が出来ますか？</question><検索キーワード>Amplify</検索キーワード>
                <question>アマゾンとは何か</question><検索キーワード>アマゾン</検索キーワード>
            </examples>"""
    )
]

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

# Agentの定義
chain = LLMChain(
    llm=llm,
    prompt=XMLAgent.get_default_prompt(),
    output_parser=XMLAgent.get_default_output_parser()
)
agent = XMLAgent(tools=tools, llm_chain=chain)
agent_executor = AgentExecutor(agent=agent, tools=tools, verbose=False, handle_parsing_errors=True)

# Agentの実行
answer = agent_executor.invoke({"input": "特に言語の指定が無い場合はあなたは質問に対して日本語で回答します。<question>" + sys.argv[1] + "</question>\
    もし<question>の内容が<observation>に含まれない場合は、「検索結果にありません」と答えてください\n\nAssistant:"})
print(answer['output'])

# Kendraで検索したドキュメントのURLを見たい場合は以下
#print("参考文書：")
#for doc in docs:
#    print(doc.metadata["source"])
```
プロンプトの定義は2カ所です。

まず以下はツール（実態はKendra）への検索キーワードからLLMへの指示を取り除くように例を示しています。こんな適当なので良いのかと思いつつ…
また、これはKendraのセマンティック検索を潰してしまうケースもあると思いますが、今回の例は端的な言葉でKendra検索を行って後はLLMに判断させるという思想です。

```Python:ツールの説明
        description=""""
            このツールは最新のWeb情報を検索するツールです。引数は検索キーワードです。検索キーワードの例は<examples>の通りです。
            <examples>
                <question>Bedrockについて説明してください</question><検索キーワード>Bedrock</検索キーワード>
                <question>Kendraについて英語で教えてください</question><検索キーワード>Kendra</検索キーワード>
                <question>LangChainはBedrockで使用する事が出来ますか？</question><検索キーワード>LangChain Bedrock</検索キーワード>                
                <question>Amplifyは無料で使用する事が出来ますか？</question><検索キーワード>Amplify</検索キーワード>
                <question>アマゾンとは何か</question><検索キーワード>アマゾン</検索キーワード>
            </examples>"""
```
以下は最終的にLLMに指示しているプロンプトです。
```Python:LLMへの最終的な指示
# Agentの実行
answer = agent_executor.invoke({"input": "特に言語の指定が無い場合はあなたは質問に対して日本語で回答します。<question>" + sys.argv[1] + "</question>\
    もし<question>の内容が<observation>に含まれない場合は、「検索結果にありません」と答えてください\n\nAssistant:"})
```

# 実行結果

幾つか質問の仕方を変えながら、Kendraの検索キーワードとして何が渡されているかを確認します。Agentは`verbose=True`で実行過程を見る事が出来ます。
`<tool_input>`がKendraに渡されている検索キーワードになります。

```:ターミナル
> python LangChainXmlAgentRagSample.py "Bedrock"

<tool>KendraSearch</tool>
<tool_input>Bedrock

（実行結果）
Bedrockは、Amazonやサードパーティのモデルプロバイダーからの基本モデルをAPIを通じてアクセスできるようにする、完全マネージド型のサービスです。
Bedrockの主な機能は以下の通りです。
- テキストプレイグラウンド - AWS管理コンソール上の手軽なテキスト生成アプリケーション
- API - 基本モデルへの低レイテンシーでスケーラブルなアクセス
- カスタマイズ - 事前トレーニング済みモデルのニーズに合わせた微調整
- 統合 - 既存のアプリケーションとのシームレスな統合
Bedrockは、自然言語処理やコンピュータビジョンなどの様々なタスクにおいて、高品質な基本モデルを簡単に利用できるように設計されています。
```
```:ターミナル
> python LangChainXmlAgentRagSample.py "Bedrockとは何ですか"

<tool>KendraSearch</tool>
<tool_input>Bedrockとは

（実行結果）
BedrockはAmazonが提供する汎用AI基盤モデルをAPI経由で利用できるマネージドサービスです。
主な特徴は以下の通りです。
- 文字列生成や画像生成など、汎用的なAIタスクを実行できる複数の基盤モデルが用意されています。
- 基盤モデルをカスタマイズして、ユーザー固有のデータセットに特化したモデルを作成できます。
- モデルの呼び出し回数に応じた従量課金制です。
- セキュリティ面では、IAMポリシーによるアクセス制御が可能です。
- APIやコンソールから簡単に利用できるマネージドサービスです。
Bedrockにより、自社で大規模なAIモデルを構築・運用するコストを抑えつつ、汎用AI機能をアプリケーションに組み込 むことができます。
```
```:ターミナル
> python LangChainXmlAgentRagSample.py "Bedrockについて一言で説明してください"

<tool>KendraSearch</tool>
<tool_input>Bedrock

（実行結果）
Bedrockは、Amazonが提供する機械学習の基礎モデルをAPIで利用できるフルマネージドのクラウドサービスです。
```
```:ターミナル
> python LangChainXmlAgentRagSample.py "Bedrockについて英語で説明してください"

<tool>KendraSearch</tool>
<tool_input>Bedrock

（実行結果）
Based on the Kendra search results, here is a summary about Bedrock in English:

Bedrock is a fully managed service from Amazon Web Services (AWS) that provides access to foundational models for natural language processing through an API. Key features and capabilities of Bedrock include:
- Access to pre-trained models like Claude and Anthropic's language model without needing to train or host models yourself. Models can generate text, answer questions, summarize text, etc.
- Ability to fine-tune models on your own data to customize them for specific use cases. This is done through model customization jobs.
- Provisioned throughput pricing model so you only pay for the inferencing capacity you need.
- Access control through AWS Identity and Access Management (IAM) to manage permissions.
- Integration with other AWS services like Amazon S3 for data sources and AWS KMS for encryption.
In summary, Bedrock provides easy access to cutting-edge AI models for text generation, question answering,and more while handling the infrastructure and management overhead. Companies can leverage Bedrock to quickly prototype and deploy natural language AI into applications.

（日本語訳）
Kendra の検索結果に基づいて、Bedrock について英語で要約すると次のようになります。

Bedrock は、アマゾン ウェブ サービス (AWS) のフルマネージド サービスであり、API を介して自然言語処理の基本モデルへのアクセスを提供します。 Bedrock の主な特徴と機能は次のとおりです。
- モデルを自分でトレーニングしたりホストしたりする必要がなく、Claude や Anthropic の言語モデルなどの事前トレーニングされたモデルにアクセスできます。モデルはテキストの生成、質問への回答、テキストの要約などを行うことができます。
- 独自のデータに基づいてモデルを微調整して、特定のユースケースに合わせてカスタマイズする機能。これは、モデルのカスタマイズ ジョブを通じて行われます。
- プロビジョニングされたスループット価格モデルにより、必要な推論容量に対してのみ料金を支払います。
- AWS Identity and Access Management (IAM) によるアクセス制御によるアクセス許可の管理。
- データソース用の Amazon S3 や暗号化用の AWS KMS などの他の AWS サービスとの統合。
要約すると、Bedrock は、インフラストラクチャと管理のオーバーヘッドを処理しながら、テキスト生成、質問応答などのための最先端の AI モデルへの簡単なアクセスを提供します。企業は Bedrock を活用して、自然言語 AI のプロトタイプを迅速に作成し、アプリケーションに導入できます。
```
```:ターミナル
> python LangChainXmlAgentRagSample.py "BedrockはClaude v2を使う事は出来ますか" 

<tool>KendraSearch</tool>
<tool_input>Bedrock Claude v2

（実行結果）
はい、Anthropic Claude v2モデルはAmazon Bedrockで使用できます。
```
```
> python LangChainXmlAgentRagSample.py "BedrockはGPT-3.5を使う事は出来ますか" 

<tool>KendraSearch</tool>
<tool_input>Bedrock GPT-3.5

（実行結果）
Bedrockでは直接GPT-3.5を使うことはできない
```
完全に思い通りでも無いですが、概ね入力内容からLLMへの指示内容を取り除いてKendraを検索してくれています。
（上記は比較的いい回答をしてくれたものをピックアップしているので、実際にはまだときどき少し的外れな回答をしてきたり、Kendraの検索結果が悪さをしているのかLangChain側でパースエラーが起きるときもあります）

他も幾つか試してみます。

```:ターミナル
> python LangChainXmlAgentRagSample.py "Amplifyについて教えてください"

<tool>KendraSearch</tool>
<tool_input>Amplify

（実行結果）
申し訳ありませんが、Amplifyに関する情報は検索結果にありませんでした。
```
```:ターミナル
> python LangChainXmlAgentRagSample.py "Kendraについて詳しく教えてください"

<tool>KendraSearch</tool>
<tool_input>Kendra

（実行結果）
Kendraは、Amazon Web Services(AWS)が提供するクラウド上の検索サービスです。特徴は以下の通りです。
- テキスト、PDF、Microsoft Officeドキュメント等の非構造化データからメタデータを自動抽出し、検索インデックスを構築できる
- 自然言語での質問応答が可能な認知検索エンジンを搭載
- 検索結果のランキングやフィルタリングをカスタマイズできる
- 複数のデータソースを1つの検索インデックスに統合できる
- Kendra ConsoleやAPI、AWS SDKを使って管理・操作が可能
Kendraは大量の企業データを高速に検索できるソリューションとして利用されています。
```
インデックスしたBedrockとKendraについて上手く検索してLLMが指示通りに生成してくれるようになりました。

```:ターミナル
> python LangChainXmlAgentRagSample.py "Kendraは何の言語に対応していますか"

<tool>KendraSearch</tool>
<tool_input>Kendra 言語

（実行結果）
Kendraは英語の他、アラビア語、アルメニア語、バスク語、ベンガル語、ブルガリア語、カタロニア語、
中国語(簡体字と繁体字)、チェコ語、デンマーク語、オランダ語、フィンランド語、フランス語、ガリシア語、
ドイツ語、ギリシャ語 、ヒンディー語、ハンガリー語、インドネシア語、イタリア語、日本語、韓国語、
マラーティー語、ノルウェー語、ポーランド語、ポルトガル語、ルーマニア語、ロシア語、スロバキア語、
スペイン語、スウェーデン語、タミル語、テルグ語、タイ語、トルコ語、ウクライナ語、ベトナム語など多数の言語をサポートしています。
```
これはなんかちょっと嘘をつかれてる気がします。


# まとめ

もう少しプロンプトの調整の余地はありそうですが、LangChainのAgent機能を使えばユーザーからの入力内容をKendraとLLMで分ける事が出来ました。AWSのRAGのサンプルコーディングを見ると同じ入力をKendraとLLMに渡している例が多いですが、実際のところ実務ではどうしているケースが多いんでしょうね。
今回の例では何を聞かれても必ずKendraに検索を行っていますが、ツールのdescriptionを調整して、「何を調べたい場合に限って、このKendra検索を行う」というのをプロンプトに追加する事も出来そうです。或いは、Agent機能を使うと少しブラックボックスになるところもあるので、Agent機能を使わずにKendraの検索キーワードを整える専用のLLM処理を実行する方法も考えられます。
ただ、そこまでするならKendra用の検索欄とLLMへの指示を別の項目に分けるのが素直かもしれません。

Bedrockにおいては、LangChainのClaude2対応がまだちょっと微妙な気がするのと、Bedrockのエージェント機能が控えているので、本命はBedrockのエージェント機能を使ったRAGかもしれません。

https://aws.amazon.com/jp/blogs/news/preview-connect-foundation-models-to-your-company-data-sources-with-agents-for-amazon-bedrock/

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