Knowledge base for Amazon Bedrockは冷たい回答をする？(LangChainで解決？)

Knowledge base for Amazon Bedrockがマネージドで手軽そうなので試してみました。

- [S3バケットを作成](#s3バケットを作成)
- [Knowledge baseを作成](#knowledge-baseを作成)
- [データを投入](#データを投入)
- [Pythonから実行](#pythonから実行)
- [LangChainで実装](#langchainで実装)

# S3バケットを作成

Knowledge baseで使用するS3バケットをus-east-1で作成します。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3117889/477cf62e-9556-4ad5-d299-b60c44dc7d24.png)

:::note warn
knowledge baseと異なるリージョンで作成すると以下のようなエラーが発生します。

Encountered error: The authorization header is malformed; the region 'us-east-1' is wrong; expecting 'ap-northeast-1' (Service: S3, Status Code: 400, Request ID: FN180R0R8AMGPMG4, Extended Request ID: EAHFoSfHXsAoZQLBXsJyuYU5te9OGwWUxJNbHMyVRiQBR3z2q6AFt8Qkv72U0PjKtg+EWenD4EY=). Call to Amazon S3 Source did not succeed.
:::

# Knowledge baseを作成

1. 「Amazon Bedrock」サービスから、Knowledge baseを選択
1. 名前を入力します。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3117889/a6552dbe-f3cf-bc46-b58d-3b5dad472941.png)

1. 必要に応じてタグを設定し、「Next」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3117889/dd2a9c9f-3519-b12d-1c00-2aa53f8f341b.png)

1. データソース名と最初に作ったバケットを指定し、「Next」をクリックします。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3117889/6edc9899-563e-1f27-1e20-8f4f86a5d1ba.png)

1. ベクトルデータベースに利用する基盤モデルとデータベースを選択します。デフォルトで「Next」を選択しました。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3117889/5894bcd8-3fd4-f1b4-0e4e-ce493b1ea086.png)

1. 確認画面が表示されます。「Create Knowledge base」をクリックします。

すると、「Preparing vector database in Amazon Opensearch Serverless. This process may take several minutes to complete.」と表示され、しばらく待ちます。

完了すると以下のメッセージが表示されます。
![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3117889/04504880-dc73-c695-eb73-40325b11ad06.png)

# データを投入

今回は、以下のサンプルをknowledge baseに突っ込んでみます。

https://github.com/aws-samples/amazon-bedrock-samples

Cloud9で以下のコマンドを実行します。

```bash
$ git clone https://github.com/aws-samples/amazon-bedrock-samples
$ aws s3 cp --recursive amazon-bedrock-samples s3://amazon-bedrock-samples-20231223
```

「Sync」ボタンでSyncを開始します。しばらく時間がかかります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3117889/8de365d8-d8ff-70a1-14e6-bae961289855.png)

完了しましたが、以下のエラーが出ました。

```
Error warning
Ignored 1 files as they were larger than service limit of MaximumFileSizeSupported: 52428800 bytes
Encountered error: Ignored 2 files as their file format was not supported.. Call to Amazon S3 Source did not succeed.
```

Knowledge baseでサポートするファイルは以下の通りのため、
画像ファイルなどでエラーが発生していると思います。気にせず進めます。

- Plain text (.txt)
- Markdown (.md)
- HyperText Markup Language (.html)
- Microsoft Word document (.doc/.docx)
- Comma-separated values (.csv)
- Microsoft Excel spreadsheet (.xls/.xlsx)
- Portable Document Format (.pdf)

:::note warn
The maximum file size for a single file is 50 MB.
:::

https://docs.aws.amazon.com/bedrock/latest/userguide/knowledge-base-setup.html

とりあえず試してみます。
ipynbはKnowledge baseの対象になかったですが、認識しているようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3117889/57c05783-bcbc-d987-9ccb-98662e019954.png)

Knowledge baseに登録されていることは教えてくれますが、

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3117889/31a6524f-0aa6-5437-1e91-5edb7d8a9ca7.png)

タスクの依頼は、「Sorry, I am unable to assist you with this request.」の一点張り。。。
機嫌を損ねたのかと思い、おだててみましたが同じでした。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3117889/caf6fb79-52cd-cc90-11e5-54b1f02b7aaa.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3117889/7880a2ba-60a9-c681-16f8-92a9fcd33588.png)

# Pythonから実行

```python
import boto3
import json

bedrock_agent = boto3.client(
    service_name='bedrock-agent-runtime',
    region_name='us-east-1'
)

prompt = """\n\nHuman:
あなたはAWSのエキスパートです。
agents/agentsforbedrock-retailagent/open-api-spec/customerservicebot.jsonを参考にAWS SAMのtemplate.yamlを作成してください。
\n\nAssistant:
"""

response = bedrock_agent.retrieve_and_generate(
    input={
        'text': prompt,
    },
    retrieveAndGenerateConfiguration={
        'type': 'KNOWLEDGE_BASE',
        'knowledgeBaseConfiguration': {
            'knowledgeBaseId': '作成したIDを入力',
            'modelArn': 'arn:aws:bedrock:us-east-1::foundation-model/anthropic.claude-v2:1',
        }
    }
)

print(response['output'])
```

{'text': '残念ながら、検索結果にtemplate.yamlファイルの作成方法が含まれていませんでした。OpenAPI仕様ファイルは参照できますが、SAMテンプレートファイルの作成手順は不明です。

Claudeは、SAMテンプレートの作り方を知っているはずなのに、かなり冷たい回答です。
JSONだから読めないのかと思ったら、内容はわかっているようです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3117889/5a9b6be5-7c7e-8e65-6a75-c8148f1e3252.png)


# Text Playgroundで通常実行

普通のClaude2に質問してみました。
今度は、SAMテンプレートを作成してくれました。

Knowledge baseを使うと劣化するのかわかりませんが、かなり冷たい回答ばかりでした。

`````


Human:
あなたはAWSのエキスパートです。以下のOpenAPI仕様ファイルをもとに、AWS SAMのtemplate.yamlを作成してください。
```json
{
"openapi": "3.0.0",
    "info": {
        "title": "Customer Service Bot API",
        "version": "1.0.0",
        "description": "Customer service APIs for a retail store selling shoes"
    },
    "paths": {
        "/customer/{CustomerName}": {
            "get": {
                "summary": "Get customer information",
                "description": "Based on provided customer name, return customer information like customer ID, preferred activity and others",
                "operationId": "getCustomerInfo",
                "parameters": [{
                    "name": "CustomerName",
                    "in": "path",
                    "description": "Customer Name",
                    "required": true,
                    "schema": {
                        "type": "string"
                    }
                }],
                "responses": {
                    "200": {
                        "description": "Get customer information",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "array",
                                    "items": {
                                        "type": "object",
                                        "properties": {
                                            "CustomerID": {
                                                "type": "int",
                                                "description": "This is the Customer ID used to place the order"
                                            },
                                            "Addr1": {
                                                "type": "string",
                                                "description": "Customer billing address line1"
                                            },
                                            "Addr2": {
                                                "type": "string",
                                                "description": "Customer billing address line2"
                                            },
                                            "City": {
                                                "type": "string",
                                                "description": "Customer billing city"
                                            },
                                            "State": {
                                                "type": "string",
                                                "description": "Customer billing state"
                                            },
                                            "Zipcode": {
                                                "type": "string",
                                                "description": "Customer billing zipcode"
                                            },
                                            "PreferredActivity": {
                                                "type": "string",
                                                "description": "Customer preferred activity"
                                            },
                                            "ShoeSize": {
                                                "type": "int",
                                                "description": "Customer shoe size"
                                            },
                                            "OtherInfo": {
                                                "type": "string",
                                                "description": "Additional information about customer like interests"
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/place_order": {
            "get": {
                "summary": "Sub task to place an order on behalf of the customer",
                "description": "Place an order for a shoe by creating an Order record and updating inventory in the database",
                "operationId": "placeshoeOrder",
                "parameters": [{
                    "name": "ShoeID",
                    "in": "query",
                    "description": "Shoe ID to place an order",
                    "required": true,
                    "schema": {
                        "type": "int"
                    }
                },
                {
                    "name": "CustomerID",
                    "in": "query",
                    "description": "Customer ID to place an order",
                    "required": true,
                    "schema": {
                        "type": "int"
                    }
                }],
                "responses": {
                    "200": {
                        "description": "Order has been placed",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "message": {
                                            "type": "string",
                                            "description": "Your order has been placed"
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "/check_inventory": {
            "get": {
                "summary": "Returns all details related to shoes, including inventory details",
                "description": "Checks inventory for shoes and returns all availale information about available shoes, including shoe ID, shoe colors, inventory, best fit activity, style description and price ",
                "operationId": "checkShoeInventory",
                "responses": {
                    "200": {
                        "description": "Returns Shoe information",
                        "content": {
                            "application/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "ShoeID": {
                                            "type": "int",
                                            "description": "This is the shoe ID for this shoe"
                                        },
                                         "BestFitActivity": {
                                            "type": "string",
                                            "description": "Best fit activity for this shoe"
                                        },
                                         "StyleDesc": {
                                            "type": "string",
                                            "description": "Detailed description of the shoe"
                                        },
                                         "ShoeColors": {
                                            "type": "string",
                                            "description": "The colors of this shoe"
                                        },
                                         "Price": {
                                            "type": "string",
                                            "description": "Price of this shoe"
                                        },
                                         "InvCount": {
                                            "type": "int",
                                            "description": "Inventory count"
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        }
    }    
}
```

Assistant:
 Here is the AWS SAM template.yaml generated from the OpenAPI spec:

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  Customer service APIs for a retail store selling shoes
  
Globals:
  Api:
    OpenApiVersion: 3.0.0
    EndpointConfiguration: REGIONAL

Resources:

  GetCustomerInfoFunction:
    Type: AWS::Serverless::Function 
    Properties:
      CodeUri: get_customer_info/
      Handler: app.lambda_handler
      Runtime: python3.8
      Events:
        GetCustomerInfo:
          Type: Api 
          Properties:
            Path: /customer/{CustomerName}
            Method: get

  PlaceShoeOrderFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: place_shoe_order/
      Handler: app.lambda_handler
      Runtime: python3.8
      Events:
        PlaceShoeOrder:
          Type: Api
          Properties:
            Path: /place_order
            Method: get

  CheckShoeInventoryFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: check_shoe_inventory/
      Handler: app.lambda_handler
      Runtime: python3.8
      Events:
        CheckShoeInventory:
          Type: Api
          Properties:
            Path: /check_inventory
            Method: get
              
Outputs:

  GetCustomerInfoApi:
    Description: "API Gateway endpoint URL for GetCustomerInfo function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Stage/customer/{CustomerName}"
  
  PlaceShoeOrderApi:
    Description: "API Gateway endpoint URL for PlaceShoeOrder function" 
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Stage/place_order"

  CheckShoeInventoryApi:
    Description: "API Gateway endpoint URL for CheckShoeInventory function"
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Stage/check_inventory"
```

The key aspects are:

- Each path in the OpenAPI spec is defined as an AWS::Serverless::Function resource
- The function CodeUri points to the implementation code for that function
- API Gateway endpoints are configured for each path/method combination using Api event
- Output section defines the endpoint URLs for each function

Let me know if you have any other questions!
`````

# LangChainで実装

LangChainでもKnowledge baseが使えるようなので試してみました。

```Python
from langchain.llms import Bedrock
from langchain.chains import RetrievalQA
from langchain.retrievers.bedrock import AmazonKnowledgeBasesRetriever

llm = Bedrock(
  region_name='us-east-1',
  model_id='anthropic.claude-v2:1',
  model_kwargs={
      "max_tokens_to_sample":4096, 
      "temperature":0,
      "top_k":250,
      "top_p":1,
      "stop_sequences": ["\n\nHuman"]
  }
)

retriever = AmazonKnowledgeBasesRetriever(
    knowledge_base_id="作成したIDを入力",
    region_name="us-east-1",
    retrieval_config={
        "vectorSearchConfiguration": {
            "numberOfResults": 4
        }
    }
)

prompt = """
\n\nHuman:
あなたはAWSのエキスパートです。
以下のOpenAPI仕様が書かれたファイルをもとに、AWS SAMのtemplate.yamlを作成してください。
agents/agentsforbedrock-retailagent/open-api-spec/customerservicebot.json
\n\nAssistant:
"""

qa_chain = RetrievalQA.from_chain_type(
    llm=llm,
    chain_type="stuff",
    retriever=retriever,
    verbose=True
)

result = qa_chain.run(prompt)
print(result)
```

以下が結果です。
かなり改善されたように思いますが、パスが{proxy+}になっているあたり手抜き感があります。。。

`````
> Entering new RetrievalQA chain...

> Finished chain.


はい、AWS SAMのtemplate.yamlファイルを作成するのを助けましょう。

まず、提供されたOpenAPI仕様ファイル(customerservicebot.json)を確認します。このファイルには、エージェントが使用できるAPIの詳細が定義されています。

次に、その情報を使用してSAMのtemplate.yamlを以下のように作成します:

```yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: An AWS SAM template for creating a customer service bot agent

Globals:
  Function:
    Timeout: 10
    MemorySize: 128
    Tracing: Active  

Resources:

  CustomerServiceBotFunction:
    Type: AWS::Serverless::Function
    Properties:
      CodeUri: customer_service_bot_code/
      Handler: app.lambdaHandler
      Runtime: python3.7
      Policies:
        - Version: "2012-10-17"
          Statement:
            - Effect: Allow
              Action:
                - logs:CreateLogGroup
                - logs:CreateLogStream
                - logs:PutLogEvents
              Resource: arn:aws:logs:*:*:*
      Events:
        CatchAll:
          Type: Api 
          Properties:
            Path: /{proxy+}
            Method: ANY

Outputs:

  CustomerServiceBotApi:
    Description: "API Gateway endpoint URL"  
    Value: !Sub "https://${ServerlessRestApi}.execute-api.${AWS::Region}.amazonaws.com/Prod/"
```

このtemplateでは、CustomerServiceBotFunctionというLambda関数を定義しています。これがOpenAPI仕様に定義されたAPIを実装する関数になります。

またAPI Gatewayも定義して、このLambda関数を公開するエンドポイントを作成しています。

必要に応じてこのtemplateをカスタマイズしていただければと思います。ご不明な点があれば遠慮なくご質問
`````