LLM監視の定番OSS、LangfuseをAWS App Runnerにデプロイしてみよう！

# Langfuseとは？

生成AIアプリケーションのトレース監視やプロンプト管理など、開発・運用に便利な機能を多数備えたGUIソフトウェアです。

https://speakerdeck.com/minorun365/llmapuriworagasdeping-jia-site-langfusedeke-shi-hua-siyou

この分野ではLangChain社のLangSmithが有名ですが、SaaSのため本格利用するにはサブスク契約して課金する必要があります。

これに対して、Langfuseはこれと似たような機能を持つOSSとして公開されているため、自分でクラウドなどへデプロイすればライセンス費用なしにLLMアプリ監視サーバーを利用することができます。

https://langfuse.com/docs/deployment/self-host

AWSの場合、コンテナイメージさえあればアプリをマネージドにデプロイ・運用できるApp Runnerという便利サービスがあるので、これを使ってLangfuseを構築してみようというのが今回の記事です。

![スクリーンショット 2024-11-04 13.23.09.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/f13eb164-94ba-2c8b-f35c-8e169aa27a43.png)


# AWS App Runnerとは？

AWSにアプリケーションのコンテナをデプロイする際、代表的なサービスとしてAmazon ECSがあります。

ECSはAWSのビルディングブロックの一つであるため、実際に利用する際には基盤となるFargateや、ネットワークをルーティングしてくれるALBなど、自分で各サービスと組み合わせて構築する必要があります。

それに対して、App Runnerは **「ELB + ECS + Fargate + その他諸々」をいい感じにマネージドで構築＆運用してくれる便利サービス** です。2021年にGAされた比較的新しいサービスで、設定が簡単なぶん自由度はある程度制限されますが、VPCへの接続やWAF対応など、かゆいところに手が届くアップデートがどんどん追加されています。

https://qiita.com/eisuke000000/items/b829312ca357db7592d8

いわば令和最新版のElastic Beanstalkとでも言うべきサービスですね。
ちなみに正式名称は「AWS App Runner」で、単語間にスペースが入るため注意しましょう（笑）


# 構築手順

Amazon Bedrockを活用してLLMアプリ開発で利用されることが多い、バージニア北部リージョンを例に紹介しますが、東京など任意のリージョンに読み替えていただいても問題ありません。

AWSアカウントを作成し、マネジメントコンソールにサインインしましょう。

https://aws.amazon.com/jp/register-flow/


### VPC作成

マネコン上部の検索バーで「VPC」を検索してVPCコンソールへ移動し、「VPCを作成」からウィザードに沿って今回用の新規VPCを作成しましょう。

- 名前タグの自動生成：`langfuse`
- パブリックサブネットの数：0

他の設定はデフォルトのままでOKです。

![スクリーンショット 2024-11-04 13.25.33.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/bfe178c1-d8b6-0758-ae8c-7be5d465637a.png)


:::note info
App RunnerはAWSが管理するVPC上に存在するため、自分のVPCにはNAT Gatewayなどの作成は不要です。
:::


### RDS作成

Langfuseが利用するデータベースを用意します。AWSならAurora Serverlessなど、PostgreSQLに対応していればOKなのですが、今回は一番安く利用できるRDSを使ってみます。

RDSのコンソールに移動し、「データベースの作成」からウィザードを起動します。

- エンジンのタイプ：PostgreSQL（※Auroraを選ばないように注意）
- テンプレート：無料利用枠
- DBインスタンス識別子：好きな名前（ `langfuse-db` など）
- マスターパスワード：任意のパスワードを設定（この後使います）
- VPC：`langfuse-vpc`

上記以外はデフォルトのままで問題ありません。

![スクリーンショット 2024-11-04 12.02.40.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/51435e92-db93-7447-a739-97a5fa740fb5.png)

:::note info
無料利用枠でないデータベースを利用する場合は、EventBridgeスケジューラーを使って夜間自動停止などを実施すればコスト削減ができます。
:::


### ECR作成

App Runnerにデプロイするための、Langfuseのコンテナイメージを格納する場所を作成します。

ECRのコンソールにアクセスし、「作成」ボタンを押すとプライベートレジストリにリポジトリを作成できます。

- リポジトリ名：`langfuse`

上記以外はデフォルトのままで問題ありません。

![スクリーンショット 2024-11-04 12.01.32.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/79779161-457d-f21c-b85d-0099538a90a5.png)

リポジトリを作成したら、インターネット上のLangfuseコンテナイメージを取得してきて、ここにプッシュします。作業端末としてAWS CloudShellを利用しましょう。

マネジメントコンソール右上のCloudShellアイコン（一番左寄りのもの）をクリックすると、画面内にターミナルが開きます。便利ですね！

![スクリーンショット 2024-11-04 12.04.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/7e8b84ff-b608-0779-f99e-2931f10b47cd.png)

以下のコマンドを順に実行しましょう。

:::note info
これらのコマンドは、ECRのリポジトリ内で「プッシュコマンドを表示」ボタンを押すと確認することができます。
:::

```bash: CloudShell
# ECRにログイン
aws ecr get-login-password --region <リージョン名> | docker login --username AWS --password-stdin <AWSアカウントID>.dkr.ecr.us-east-1.amazonaws.com

# Langfuseのイメージを取得
docker pull langfuse/langfuse:latest

# 取得したイメージにタグを付ける
docker tag langfuse:latest <AWSアカウントID>.dkr.ecr.us-east-1.amazonaws.com/langfuse:latest

# ECRにイメージをプッシュ
docker push <AWSアカウントID>.dkr.ecr.us-east-1.amazonaws.com/langfuse:latest
```


### パラメーターストア作成

これからApp Runner上にデプロイするLangfuseアプリには、環境変数として特定の値を設定してあげる必要があります。その値をSSMパラメーターストアに安全に保存しておきます。

https://langfuse.com/docs/deployment/self-host#configuring-environment-variables

似た機能にAWS Secrets Managerもありますが、主な違いは「Secrets Managerの方が若干高いがパスワードの自動ローテーションが可能」という点です。今回は安いパラメーターストアにしておきます。

https://dev.classmethod.jp/articles/secretsmanager-or-parameterstore/

AWS Systems Managerのコンソールにアクセスし、パラメーターストアを開いて以下4つのパラメータを作成します。

| 名前 | タイプ | 値 |
|------|--------|-----|
| `langfuse-DATABASE_URL` | 安全な文字列 | `postgresql://postgres:<RDSのマスターパスワード>@<RDSのエンドポイント>:5432/langfuse` |
| `langfuse-NEXTAUTH_SECRET` | 安全な文字列 | CloudShellで `openssl rand -base64 32` を実行した結果を貼り付け |
| `langfuse-SALT` | 安全な文字列 | CloudShellで `openssl rand -base64 32` を実行した結果を貼り付け |
| `langfuse-ENCRYPTION_KEY` | 安全な文字列 | CloudShellで `openssl rand -hex 32` を実行した結果を貼り付け |

上記以外はデフォルト設定のままでOKです。

![スクリーンショット 2024-11-04 12.17.25.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/955ef145-69e3-6f2c-9ad5-766d9a44f620.png)


### WAF作成

Langfuse用のApp Runnerは、AWSが管理するVPCに構築され、初期状態だとURLが分かればインターネット上から誰でもアクセスできてしまいます。

Langfuse自体にパスワード認証が掛かるものの、セキュリティのため可能であれば自分がアクセスする送信元IPアドレスを制限できるようにしておきましょう。

最初にIPセットを作成します。

- IP set name：`my-IP`
- IP addresses：以下を列挙して記載する
    - Langfuseにアクセスする際の送信元IPアドレス
    - 自分のLLMアプリの送信元IPアドレス（例：NAT GatewayのEIPなど）

自分のグローバルIPは以下のようなサイトでも確認できます。プロキシサーバーなどを経由して自社PCからアクセスする場合は、そのプロキシのグローバルIPを指定しましょう。

https://www.cman.jp/network/support/go_access.cgi

上記以外はデフォルトでOKです。

![スクリーンショット 2024-11-04 12.55.21.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/bc25c587-7c51-88b0-0a50-47e2a766b773.png)


このIPセットを使って、「このIPだけ許可する」というウェブACLを作成します。
WAFの「Web ACLs」へ移動し、「Create web ACL」を押してウィザードで以下を設定します。

- Step 1
    - Name: `langfuse-IP`
- Step 2
    - Rules > Add my own rules and rule groups
        - Rule type：IP set
        - Name: `allow-my-IP`
        - IP set: `my-IP`
        - Action: Allow
    - Default action: Block（＝自分のソースIP以外は弾く）

上記以外はデフォルトでOKです。

![スクリーンショット 2024-11-04 12.59.22.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/0bb5a9da-db18-d685-27ed-5909586f0042.png)


### IAMロール作成

App Runnerがパラメーターストアにアクセスできるよう、IAMポリシーとロールを作成しておきます。

IAMのコンソールに移動して、IAMポリシーを新規作成します。

- アクセス許可を指定：JSONエディターで下記内容を入力
- ポリシー名：AppRunner-ParameterStore

```json: ポリシーエディタ
{
	"Version": "2012-10-17",
	"Statement": [
		{
			"Effect": "Allow",
			"Action": [
				"ssm:GetParameters",
				"kms:Decrypt*"
			],
			"Resource": "*"
		}
	]
}
```

パラメーターストアの値の取得と、「安全な文字列」の読み取りに必要なKMSの復号化権限を付与しています。

他はデフォルト設定でOKです。

![スクリーンショット 2024-11-04 13.02.50.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/154701b1-4f08-aeda-7049-50c4d35aa45b.png)

その後、App Runner用のIAMロールを作成して、このポリシーをアタッチします。

IAMロールを以下のとおり作成します。

- 信頼されたエンティティタイプ：カスタム信頼ポリシー
- カスタム信頼ポリシー：JSONエディターで下記内容を入力
- 許可ポリシー：`AppRunner-ParameterStore` を検索してチェック
- ロール名：`AppRunner-Langfuse`

```json: カスタム信頼ポリシー
{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Service": "tasks.apprunner.amazonaws.com"
            },
            "Action": "sts:AssumeRole"
        }
    ]
}
```

他はデフォルトのままでOKです。

![スクリーンショット 2024-11-04 13.11.01.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/189d5b00-8ce5-5546-6cfd-55dd0fd0ce5d.png)


### App Runner作成

下準備が終わったので、いよいよApp Runnerの作成です。
App Runnerのコンソールへ移動し、「サービスの作成」を実施します。

- コンテナイメージの URI：「参照」ボタンより `langfuse` を指定
- デプロイトリガー：自動
- ECRアクセスロール：新しいサービスロールの作成
- サービス名：`Langfuse`
- 環境変数：下表のとおり

| ソース | 環境変数名 | 環境変数の値 |
|--------|------------|--------------|
| SSMパラメータストア | `DATABASE_URL` | パラメーターストアの当該ARNを入力 |
| プレーンテキスト | `NEXTAUTH_URL` | `http://localhost:3000`（※URL発行後に変更します） |
| SSMパラメータストア | `NEXTAUTH_SECRET` | パラメーターストアの当該ARNを入力 |
| SSMパラメータストア | `SALT` | パラメーターストアの当該ARNを入力 |
| SSMパラメータストア | `ENCRYPTION_KEY` | パラメーターストアの当該ARNを入力 |
| プレーンテキスト | `HOSTNAME` | `0.0.0.0` |

- ポート：`3000`
- インスタンスロール：`AppRunner-Langfuse`
- 送信ネットワークトラフィック：カスタムVPC
    - VPCコネクタ：新規追加
        - VPCコネクタ名：`Langfuse`
        - VPC：今回作成したVPCのIDを探して選択
        - サブネット：プライベートサブネットをすべて選択
        - セキュリティグループ：`default` を選択

他はデフォルトのままでOKです。

![スクリーンショット 2024-11-04 13.28.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/e404a765-e378-2c4b-cadf-fad3ba1caa8b.png)

一度デプロイが完了したら、App Runnerサービスの「デフォルトドメイン」が発行されます。
このURLをコピーして、「設定」タブから環境変数 `NEXTAUTH_URL` の値を更新しましょう。すると自動で再デプロイが走ります。

デプロイが終わったら、「デフォルトドメイン」のURLにブラウザでアクセスすると、Langfuseの管理コンソールへアクセスできます。


# 実際にLLMアプリを監視してみよう！

Langfuseサーバーが無事に稼働できたら、自分のLLMアプリからトレースを送ってみましょう。

公式クイックスタートに記載のとおり、LLMアプリの環境変数にLangfuseの認証情報やURLを設定しておき、監視したい関数にPythonの `observe()` デコレーターを付けるだけでトレースが自動で送信されるようになります。

https://langfuse.com/docs/get-started

評価ツールのRagasと組み合わせて、実際にRAGアプリを監視する例を以下の記事でも紹介しています。

https://qiita.com/minorun365/items/70ad2f5a0afaac6e5cb9

![スクリーンショット 2024-10-22 1.19.10.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1633856/793b58a0-aba4-b91c-4ddf-9fac7e536d4c.png)

また、昨日池袋サンシャインシティで開催された「技術書典17」にて、Langfuseを入門解説する書籍が頒布されました。電子版含め、オンラインでも購入可能です。

私も技術レビューに参加させていただいたのですが、非常にクオリティが高く商業書籍並みの品質です。まだLangfuseなどのLLMアプリ監視ツールを詳しく解説した日本語の書籍はほぼ無いため、これから学習したい方はマストバイです！

https://techbookfest.org/product/mn0L7GEm3s8Vhmxq971HEi?productVariantID=myG2YLxFNAEVkRf2dipG8f