Lambda+LangChain+SQLDatabaseChainでマイクロサービスを作る

# 概要

LangChain+SQLDatabaseChainを使って、自然言語でデータベースを検索するマイクロサービスを作成するメモ。

LangChainは発展中のため、どれが最新情報か少しわかりにくい状態。

（参考）
簡単にLangChainを使う
https://qiita.com/fukuebiz/items/dd99941988f6d836f930

Pose検出するマイクロサービスを作成する
https://qiita.com/fukuebiz/items/1c58dadbcdb58eb57b53

Lambda + DynamoDB + LangChainで外部情報に対応したサーバレスチャットボットを作ってみる
https://tech.nri-net.com/entry/lambda_dynamodb_create_chatbot


# 前提

- Azure OpenAI ServiceのGPT-4モデルをデプロイ済


# Dockerイメージの作成

依存ライブラリーが多いので、DockerイメージでLambda関数を作成する。

```dockerfile:Dockerfile
# AWS提供ベースイメージ
FROM public.ecr.aws/lambda/python:3.9

COPY requirements.txt .
COPY src/ .
RUN  pip3 install -r requirements.txt

CMD ["app.main"]
```

```python:src/app.py
#!/usr/bin/env python
import json
import os
from dotenv import load_dotenv
import langchain
from langchain.chat_models import ChatOpenAI
from langchain.agents import initialize_agent, load_tools, AgentType
from langchain.llms.openai import OpenAI
from langchain.prompts import PromptTemplate
from langchain.sql_database import SQLDatabase
from langchain_experimental.sql import SQLDatabaseChain


load_dotenv(verbose=True)
dotenv_path = os.path.join(os.path.dirname(__file__), ".env")
load_dotenv(dotenv_path)

# Set Database connection info
URI = os.environ.get("PG_URI")

def main(event, context):
    print('event: {}'.format(event))
    print('context: {}'.format(context))

    template = None
    input = None
    if 'queryStringParameters' in event:
        if 'template' in event['queryStringParameters']:
            template = event['queryStringParameters']['template']
        if 'input' in event['queryStringParameters']:
            input = event['queryStringParameters']['input']
    if 'body' in event:
        body = json.loads(event['body'])
        if 'template' in body:
            template = body['template']
        if 'input' in body:
            input = body['input']

    db = SQLDatabase.from_uri(URI)

    llm = ChatOpenAI(temperature=0, engine='gpt-4', model_name='gpt-4', verbose=True)

    db_chain = SQLDatabaseChain.from_llm(llm, db, verbose=True)
    result = db_chain(input)
    return {
        'statusCode': 200,
        'body': json.dumps(result)
    }

```

```text:requirements.txt
langchain==0.0.319
langchain-experimental==0.0.32
openai==0.27.8
psycopg2-binary==2.9.9
python-dotenv==1.0.0
```

```yaml:serverless.yaml
service: langchain-function
frameworkVersion: ">=3.12.0"

useDotenv: true

provider:
  name: aws
  runtime: python3.9
  region: ${env:REGION}
  timeout: 180

functions:
  pdf-reader:
    image: "${env:ACCOUNT_ID}.dkr.ecr.${env:REGION}.amazonaws.com/\
      ${env:REPOSITORY}@${env:DIGEST}"
    url:
      cors:
        allowCredentials: true
        allowedHeaders:
          - Content-Type
        allowedMethods:
          - "*"
        allowedOrigins:
          - "*"
        maxAge: 6000
    memorySize: 2048
```


```sh:.env例
PG_URI=postgresql://<user>:<password>@xxx.ap-northeast-1.rds.amazonaws.com:5432/<db>

OPENAI_API_TYPE=azure
OPENAI_API_VERSION=2023-05-15
OPENAI_API_BASE=https://???.openai.azure.com
OPENAI_API_KEY=

REGION=ap-northeast-1
ACCOUNT_ID=
REPOSITORY=
DIGEST=
```

ビルドする。

```bash:
docker build -t langchain-function .
```

テストのため実行する。

```bash:
docker run -p=8080:8080 langchain-function:latest
```

うまくビルドできたか試してみる

```bash:
curl -X POST "http://localhost:8080/2015-03-31/functions/function/invocations" -d '{"body":""}'
```

エラーが出力されるが、Dockerイメージができている確認でよしとする。


# Lambda作成の準備

Serverlessで作成、

```sh:
sls deploy
```

または以下の手順で手動で作成する。

作成したイメージをECRに登録する。最初にタグ付けする。

この例では xxxxxxxxxxxx はアカウントID、リージョンは　ap-northeast-1　を使用。

```bash:
docker tag langchain-function:latest xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/langchain-function:latest
```

ECRにログイン

```bash:
aws ecr get-login-password | docker login --username AWS --password-stdin xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com
```

プッシュする

```bash:
aws ecr create-repository --repository-name langchain-function --image-scanning-configuration scanOnPush=true --image-tag-mutability MUTABLE

docker push xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/langchain-function:latest
```


# Lambdaの作成

関数の作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555397/c395133d-09c8-2bea-1242-1796e63f20a8.png)

作成後にメモリ、タイムアウトを設定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555397/55fbd609-a8cb-1188-b029-db0093cfeac8.png)

新機能、関数URLを作成

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555397/b34d6d5f-ad31-ebd6-1b60-87670b284159.png)

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555397/f7dfcd98-297e-892b-dd4b-c150fd73aec9.png)

環境変数の設定

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/555397/c0bfb8c3-6a12-81b5-1fb9-27e2fd1e100f.png)

# テスト

クエリを実行する

```
curl -X GET "https://???.lambda-url.ap-northeast-1.on.aws/?input=東京都の件数は?"

```

結果

```json:
{"query": "東京都の件数は?", "result": "東京都の件数は1件です。"}
```