理系学生日記

おまえはいつまで学生気分なのか

TerraformでSelf-hostedなLangfuseを構築し、 Chat Completion APIのトレーシングを試す

LLM Observability Tool

どこもかしこも生成AIですが、生成AIをインテグレーションするシステム開発においてなくてはならないものがLLM Observability Toolsです。この分野ではLangSmithが有名ですね。

特に、トレーシングは重要です。 トレーシングは、システム内のリクエストやプロセスの流れを追跡し、どこで何が起きているのかを詳細に記録する仕組みです。LLMアプリケーションは非決定的であり、また、Agenticなアプリケーションに代表されるように複雑なフローを踏むことが多いため、どういう入力に対してどう言う出力がなされたのか把握できるようにしなければ開発がおぼつきません。

langsmith tracing Add observability to your LLM application | 🦜️🛠️ LangSmithより引用

Langfuse

一方でLangSmithをSaaSとして使おうと思うと、プロンプトを含めてSaaSに送信することになります。プロンプトの中には場合によっては機密情報が含まれることもあり、情報資産管理の観点で軽々に利用することができません。 LangSmithではSelf-hostedなデプロイも可能となっていますが、そのオプションはEnterpriseプランに限られ、その費用感も公開されていない。

そこで着目したのがLangfuseです。LangfuseはMITライセンスのOSSプロダクトであり、Self-hostしても無料です1。 Self-hostしていれば安心なのか、というとそうでもないし、LangfuseはSOC 2 Type IIもISO 27001も取得していて、Self-hostedだとそれを上回ったガバナンスを提供できるのかというとアレですが、この辺りはアレなのでアレです。

LangfuseをSelf-hostしよう

Langfuseのアーキテクチャ

チームでLLMアプリの開発を行なっていくために、LangfuseをAWS上でホストしたい。サクッとできるかと思いきや、先月2024/12にv.3.0.0がリリースされ、そのアーキテクチャは大きく変わってしまいました。 v.3のアーキテクチャは以下。

architecture

langfuseがフロントエンドとして動作し、各種処理は非同期で実行されます。非同期に関してはRedisがキューとして利用され、そのキューからジョブを取り出すのがlangfuse-worker。DBは、OLTPはPostgreSQL、OLAPはClickHouseを利用します。ある程度のスケーラビリティを持たせようとすると、このくらいの複雑度が必要になると言うことでしょう。

この辺りの話は、以下エントリで詳しく解説されています。

Terraformで構築する

このアーキテクチャ、AWS上でどうやって実現しようかなと思っていたところ、GitHub の Discussionで tubone24 さんがTerraformでの実装をリンクしてくれていました。

さらに調べたところ、Langfuse v.3に関する構築手順まで公開されている!すごく助かる…!実は自分でTerraformモジュールを1から作ろうと試みて、めちゃくちゃハマって心が折れていました。

うまく動かなかったところがあったので一部修正したものの、無事にAWS上でLangfuseを構築することができました。 修正内容についてはPull Requestを送った。

構築されるアーキテクチャはこちらをご参照ください。

全てECSでも構築できそうなところ、App Runnerを使っているのは、おそらくALBのコストを嫌ったものだと理解しています。grafanaはなくても良いのかな。

ECRへのイメージPUSHに使ったのはこちらのスクリプトです。

#!/bin/bash
set -euo pipefail

push_image_to_ecr() {
    local source_image=$1
    local ecr_repository=$2

    echo "Pulling image: ${source_image}"
    docker pull --platform linux/amd64 ${source_image}

    echo "Tagging image for ECR: ${ecr_repository}"
    docker tag ${source_image} ${ecr_repository}

    echo "Pushing image to ECR: ${ecr_repository}"
    docker push ${ecr_repository}

    echo "Successfully pushed image to ECR"
}

main() {
    aws ecr get-login-password --region ap-northeast-1 \
        | docker login --username AWS --password-stdin xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com

    push_image_to_ecr \
        "ghcr.io/langfuse/langfuse:sha-d8783f8@sha256:ea63c75012b5925985694caac82cbe50818b0c29104d683aee38ad3b550347c1" \
        "xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/langfuse-web:latest"

    push_image_to_ecr \
        "ghcr.io/langfuse/langfuse-worker:sha-d8783f8@sha256:c4d6509cbee342f9da7fd39474f080b29e4c3aee2cb577ad12d6b1aae0e85329" \
        "xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/langfuse-worker:latest"

    push_image_to_ecr \
        "clickhouse/clickhouse-server:24.12.2" \
        "xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/clickhouse:latest"

    push_image_to_ecr \
        "grafana/grafana:latest" \
        "xxxxxxxxxxxx.dkr.ecr.ap-northeast-1.amazonaws.com/grafana:latest"
}

main

トレーシングを試してみる

Langfuseを構築したので、早速トレーシング機能を試してみます。 Langfuseからアクセスキーを払い出し、OPENAI_API_KEYを含めて環境変数を設定しておきます。

LANGFUSE_SECRET_KEY=sk-...
LANGFUSE_PUBLIC_KEY=pk-...
LANGFUSE_HOST="https://langfuse.example.com"

OPENAI_API_KEY=sk-proj-...

その上で以下のようなスクリプトhello.pyを用意し、uv run hello.pyで実行してみます。

from langfuse.decorators import observe
from langfuse.openai import openai  # OpenAI integration

@observe()
def story():
    return openai.chat.completions.create(
        model="gpt-3.5-turbo",
        max_tokens=100,
        messages=[
            {"role": "system", "content": "You are a great storyteller."},
            {"role": "user", "content": "Once upon a time in a galaxy far, far away..."}
        ],
    ).choices[0].message.content


@observe()
def main():
    return story()


main()

そしてLangfuseを覗くと…ちゃんとトレーシング結果が表示されている!

tracing

細かなところはこれから色々修正しなければならないのですが、とりあえずまずは開発に使える状況になってよかった…。


  1. ただし、ライセンスを購入しないと使えない機能はあります。詳細はLicense Key (self-hosted) - Langfuseを参照。