LambdaでLangChainを動かす【AWS SAM】

# はじめに

AWS SAM初心者がAWS SAMを使ってLangChainを動かした際のメモです。
LangChainではOpenAI APIではなく、Azure OpenAI ServiceのAPIを使っています。

Zennに記録した自分のスクラップを元に書いてます。

# AWS SAM CLIをインストールする

```
pip install --upgrade aws-sam-cli
sam --version
```

# プロジェクトを作る
`lambda-langchain-sample`というプロジェクト名にしました

```
❯ sam init

You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.

Which template source would you like to use?
	1 - AWS Quick Start Templates
	2 - Custom Template Location
Choice: 2

Template location (git, mercurial, http(s), zip, path): ^C^[[AAborted!
❯ sam init

You can preselect a particular runtime or package type when using the `sam init` experience.
Call `sam init --help` to learn more.

Which template source would you like to use?
	1 - AWS Quick Start Templates
	2 - Custom Template Location
Choice: 1

Choose an AWS Quick Start application template
	1 - Hello World Example
	2 - Data processing
	3 - Hello World Example With Powertools
	4 - Multi-step workflow
	5 - Scheduled task
	6 - Standalone function
	7 - Serverless API
	8 - Infrastructure event management
	9 - Lambda Response Streaming
	10 - Serverless Connector Hello World Example
	11 - Multi-step workflow with Connectors
	12 - Lambda EFS example
	13 - DynamoDB Example
	14 - Machine Learning
Template: 1

Use the most popular runtime and package type? (Python and zip) [y/N]:

Which runtime would you like to use?
	1 - aot.dotnet7 (provided.al2)
	2 - dotnet6
	3 - go1.x
	4 - go (provided.al2)
	5 - graalvm.java11 (provided.al2)
	6 - graalvm.java17 (provided.al2)
	7 - java17
	8 - java11
	9 - java8.al2
	10 - java8
	11 - nodejs18.x
	12 - nodejs16.x
	13 - nodejs14.x
	14 - nodejs12.x
	15 - python3.9
	16 - python3.8
	17 - python3.7
	18 - python3.10
	19 - ruby2.7
	20 - rust (provided.al2)
Runtime: 18

What package type would you like to use?
	1 - Zip
	2 - Image
Package type: 2

Based on your selections, the only dependency manager available is pip.
We will proceed copying the template using pip.

Would you like to enable X-Ray tracing on the function(s) in your application?  [y/N]:

Would you like to enable monitoring using CloudWatch Application Insights?
For more info, please view https://docs.aws.amazon.com/AmazonCloudWatch/latest/monitoring/cloudwatch-application-insights.html [y/N]:

Project name [sam-app]: lambda-langchain-sample

Cloning from https://github.com/aws/aws-sam-cli-app-templates (process may take a moment)

    -----------------------
    Generating application:
    -----------------------
    Name: lambda-langchain-sample
    Base Image: amazon/python3.10-base
    Architectures: x86_64
    Dependency Manager: pip
    Output Directory: .
    Configuration file: lambda-langchain-sample/samconfig.toml

    Next steps can be found in the README file at lambda-langchain-sample/README.md


Commands you can use next
=========================
[*] Create pipeline: cd lambda-langchain-sample && sam pipeline init --bootstrap
[*] Validate SAM template: cd lambda-langchain-sample && sam validate
[*] Test Function in the Cloud: cd lambda-langchain-sample && sam sync --stack-name {stack-name} --watch

```

# ビルドしてローカルで動かしてみる

```
sam build
```
```
sam local start-api
```

ローカルでhello worldが動きました。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/268801/2bd21861-d095-7399-78ca-3ae4f12b390f.png)

# 一旦AWSにデプロイしてみます。

```
 sam deploy --guided
```

途中認証ないけどほんまにデプロイしていいん？みたいに聞かれるけどOKします。
発行されるURLはユニークなIDがついてるので、誰かに見つかるまでは不正アクセスされません。

発行されたURLが表示されるのアクセスして動作を確認します。

安全のため、疎通を確認できたら`sam delete`で消しましょう

# LangChainのコードを書く

疎通するだけのサンプルコードです。

```app.py
import json
import os
from langchain.llms import AzureOpenAI
from langchain.prompts import PromptTemplate
from langchain.chains import LLMChain

def lambda_handler(event, context):
    print("#### DEBUG #####")
    print (os.environ["OPENAI_API_TYPE"]) # 接続に必要な情報は環境変数で渡します
    print (os.environ["OPENAI_API_VERSION"])
    print (os.environ["OPENAI_API_BASE"])
    print (os.environ["OPENAI_API_KEY"])

    # LLMの準備
    llm = AzureOpenAI(
        deployment_name="test-gpt-35-turbo", # AzuleOpenAIの設定を確認してください
        model_name="test-gpt-35-turbo", # AzuleOpenAIの設定を確認してください
        temperature=0.9,
    )

    prompt = PromptTemplate(
        input_variables=["product"],
        template="What is a good name for a company that makes {product}?",
    )

    chain = LLMChain(llm=llm, prompt=prompt)

    llm_result = chain.run("colorful socks")
    print(llm_result)

    return {
        "statusCode": 200,
        "body": json.dumps(
            {
                "message": llm_result
            }
        ),
    }

```

requirement.txtにlangchainとopenaiを追加します。

```txt:requirement.txt
requests
langchain
openai
```

AzuleOpenAIのAPIキーなんかの環境変数はtemplate.yamlにいれます。

```yaml:template.yaml
AWSTemplateFormatVersion: '2010-09-09'
Transform: AWS::Serverless-2016-10-31
Description: >
  python3.10

  Sample SAM Template for lambda-langchain-sample

# More info about Globals: https://github.com/awslabs/serverless-application-model/blob/master/docs/globals.rst
Globals:
  Function:
    Timeout: 3 #

Resources:
  HelloWorldFunction:
    Type: AWS::Serverless::Function # More info about Function Resource: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#awsserverlessfunction
    Properties:
      PackageType: Image
      Architectures:
        - arm64
      Events:
        HelloWorld:
          Type: Api # More info about API Event Source: https://github.com/awslabs/serverless-application-model/blob/master/versions/2016-10-31.md#api
          Properties:
            Path: /hello
            Method: get
      Timeout: 90 # すぐタイムアウトになってしまうので伸ばしたけどもっと伸ばしたほうがいいかも
      Environment:
        Variables:
          OPENAI_API_TYPE: "azure"
          OPENAI_API_VERSION: "2023-03-15-preview"
          OPENAI_API_BASE: !Sub '{{resolve:ssm:/OPENAI_API_BASE}}' # AWS System Manager Parameter Storeから取得 
          OPENAI_API_KEY: !Sub '{{resolve:ssm:/OPENAI_API_KEY}}' # AWS System Manager Parameter Storeから取得
    Metadata:
      Dockerfile: Dockerfile
      DockerContext: ./hello_world
      DockerTag: python3.10-v1
### 略
```

API_KEYとAPI_BASEは秘匿なので、AWSのparameter storeというサービスに入れます。
parameter storeへの追加方法は割愛します。説明不要なくらい簡単でした。

# ローカル実行用に環境変数を用意する

ローカルだと `!Sub`が動かなくてParameterStoreから値が取れなかったのでenv.jsonというファイルを作りここに環境変数を設定します。

```env.json
{
    "Parameters": {
        "OPENAI_API_TYPE": "YOUR_VALUE",
        "OPENAI_API_VERSION": "YOUR_VALUE",
        "OPENAI_API_BASE": "YOUR_VALUE",
        "OPENAI_API_KEY": "YOUR_VALUE"
    }
}
```
.gitignoreにenv.jsonを忘れずに追加しましょう。

```.gitignore
env.json
```

以下のコマンドでローカル実行します
jsonから値を渡すために`--env-vers`が必要です。

```
sam local start-api  --env-vars env.json
```

# デプロイする

先ほどと同じコマンドでデプロイします。
対話型じゃなくデプロイする方法、適切な認証をつける方法は調べてないので、本番運用する場合はその辺の調査が必要です。
```
 sam deploy --guided
```


# おわりに

以上、参考になりましたら幸いです。

ちなみに、AWS SAMで秘匿な環境変数を埋め込む方法を調べるのに時間の８割を持ってかれています。
ベストプラクティスを教えてください。


# 参考資料

https://zenn.dev/tsuzaki92/scraps/ff786716a18d8d

https://dev.classmethod.jp/articles/lambda-container-pkgfmt-with-sam-cli/

https://docs.aws.amazon.com/ja_jp/lambda/latest/dg/urls-auth.html