Amazon BedrockのEmbeddingsで作成したベクトルDBをLambda単体で使用する手順

[前回の投稿](d68ee613776e2e18b81b)で、Amazon BedrockのEmbeddingsを試しました。
LLMと独自ナレッジを組み合わせたFAQに活用できそうです。

ベクトルデータベースのQdrantでは、単体のサーバーとして構築する方法だけでなく、メモリー上で動作させたりローカルファイルシステムで動作させることができます。

ローカルファイルとして動作するということは、 **Lambda化できるのではないか！？** と思い、挑戦しました。

## 手順

1. SAMプロジェクトを作成
    `sam init`コマンドを実行し、SAMプロジェクトを生成します。言語はPython 3.10、Zipでデプロイの方式を選択しました。

1. `hello_world/requirements.txt`に必要なライブラリーを追加します。バージョンは2023/9/30時点のものです。GA直後のため、今後バージョンアップするものと思われます。

    ```txt
    boto3==1.28.57
    langchain==0.0.303
    qdrant-client
    ```

1. Qdrantのデータベースファイルを`hello_world`ディレクトリに格納します。
    hello_worldディレクトリ内の配置はこのようになります。
    
    ```txt
    hello_world/
    ├── app.py
    ├── __init__.py
    ├── local_qdrant
    │   ├── collection
    │   │   └── ec2
    │   │       └── storage.sqlite
    │   └── meta.json
    └── requirements.txt
    ```

1. app.pyにロジックを記述します。

    1点ひねった部分があります。
    Lambda上での動作時、ファイルシステムが/tmpを除いてリードオンリーになります。Qdrantに接続した際に`.lock`ファイルを作成しようとするのですが、作成できないのでエラーとなります。
    エラーを回避するため、Qdrantのファイルを/tmpにコピーしています。

    ```python
    shutil.copytree('./local_qdrant', '/tmp/local_qdrant')
    ```

    <details><summary>app.py</summary>
    
    ```python
    import json
    import boto3
    import shutil
    
    from langchain.chains import RetrievalQA
    from langchain.embeddings import BedrockEmbeddings
    from langchain.llms import Bedrock
    from langchain.prompts import PromptTemplate
    from langchain.vectorstores import Qdrant
    from qdrant_client import QdrantClient
    
    shutil.copytree('./local_qdrant', '/tmp/local_qdrant')    # データベースファイルを/tmpにコピー
    
    embeddings = BedrockEmbeddings(
      client=boto3.client('bedrock-runtime'),
      model_id='amazon.titan-embed-text-v1'
    )
    
    qdrant_client=QdrantClient(path='/tmp/local_qdrant')    # /tmpのデータベースファイルを使用する
    
    db = Qdrant(
      client=qdrant_client,
      collection_name='ec2',
      embeddings=embeddings,
      )
    
    prompt_template = '''Human: 
    Text: {context}
    
    Question: {question}
    
    Answer the question based on the text provided. If the text doesn't contain the answer, reply that the answer is not available.
    
    Assistant:
    '''
    
    PROMPT = PromptTemplate(
        template=prompt_template, input_variables=['context', 'question']
    )
    
    llm = Bedrock(
        client=boto3.client('bedrock-runtime'),
        model_id="anthropic.claude-instant-v1",
        verbose=True
    )
    
    chain_type_kwargs = {"prompt": PROMPT}
    
    qa = RetrievalQA.from_chain_type(llm=llm, 
                                     chain_type="stuff", 
                                     retriever=db.as_retriever(), 
                                     chain_type_kwargs=chain_type_kwargs, 
                                     return_source_documents=True)
    
    
    def lambda_handler(event, context):
    
        answer = qa(event["input"])
        print(answer)
    
        return {
            "statusCode": 200,
            "body": answer["result"],
        }
    ```
    
    </details>

1. template.yamlを編集
    1. メモリーを512MB以上にする
    1. タイムアウト時間を1分程度にする
    1. Bedrockへのアクセス権限を付与する

1. `sam build`と`sam deploy`を実行

以上です。これでベクトルDBを内部に保持したLambdaができました。

## 呼び出し

* テストイベント

```json
{
  "input": "EC2とは？"
}
```

* レスポンス

```json
{
  "statusCode": 200,
  "body": " EC2 (Amazon Elastic Compute Cloud) は、Amazon が提供するクラウドコンピューティングサービスです。EC2はクラウド内で「コンピューティング」を可能にするサービスで、ユーザーはEC2のウェブサービスインターフェイスを使用して必要な機能やリソースを簡単に取得および設定できるとのことです。つまりEC2はAmazonのクラウド上で仮想サーバー(インスタンス)を作成、管理、使用できるサービスです。"
}
```

やったね!!