🪢

Langfuse による LLM アプリのログの取得

この記事では、Langfuse を用いてログをとる方法についてまとめます。

Langfuse とは

Langfuse は LLM や関連する処理の入出力を記録できるツールです。
例えば、検索拡張生成(RAG)の場合は

  • ベクトルDB へのクエリと得られたドキュメント
  • LLM へのプロンプトと出力

など、各処理の入出力を確認することができます。最終的な出力が変な場合に、どの処理がうまくいっていないのかを簡単に特定できるようになります。

Langfuse の起動

Langfuseの魅力の一つは、Self-Hostで運用できる点です。ここではDocker compose を用いて立ち上げる方法について書きます。
この方法は非常に簡単に立ち上げられるという利点がある一方で、時間がたつと自動的に記録が削除されるなどの欠点があり、本番環境での使用は推奨されていません。代わりの方法としてはDockerを用いるものなどがあります。

  1. 以下を実行する。

    git clone https://github.com/langfuse/langfuse.git
    cd langfuse
    docker compose up
    

    http://localhost:3000/ に接続して以下のような画面が見えたら成功です。


    Langfuse. Langfuse の Sign in 画面のスクショ

  2. 「Sign up」をクリックしてサインアップ画面に移り、必要な情報を入力してアカウントを作成する。
    無事にアカウントを作ることができた場合、以下のような画面になります。

    Langfuse. Langfuse の プロジェクト作成画面のスクショ

  3. 「+ New project」をクリックしてプロジェクト名を入力し、プロジェクトを作成する。

Trace の記録

動作確認

  1. プロジェクト画面左にある「Settings」をクリックして設定画面を開く。「Create new API keys」からAPIキーを発行して Secret Key、Public Key をメモする。

  2. pip install langfuse を実行する。

  3. .envに以下を記入する。
    LANGFUSE_SECRET_KEYLANGFUSE_PUBLIC_KEY は 1 で取得したものです。

    .env
    LANGFUSE_SECRET_KEY="sk-lf-..."
    LANGFUSE_PUBLIC_KEY="pk-lf-..."
    LANGFUSE_HOST=http://localhost:3000
    
  4. 以下を実行する。

    from langfuse.decorators import observe
    
    @observe()
    def fact(n: int) -> int:
        if n == 0:
            return 1
        return n * fact(n - 1)
    
    fact(2)
    

    先ほど作成したプロジェクトの「Traces」を開き、ログが追加されていれば成功です。

    各関数の入出力、実行時間などが記録されます。LLMの実行を含む場合は使用したトークン数やコストなども確認できます。


    Langfuse. Langfuse の Trace 確認画面のスクショ

使用方法

Langfuse を用いてログをとるためにはどのようにコードを書けばよいのかを説明します。
Langfuse で Trace を記録する方法は大きく 2 つあります。

  1. Python SDK の利用
    以下のように @observe デコレータをつけることで関数の入出力を記録できます。

    from langfuse.decorators import observe
    
    @observe()
    def main():
    

    デフォルトでは

    {
        "input": 関数の引数,
        "output": 関数の戻り値
    }
    

    のような形式で記録されます。これら以外の値も記録したいときは、以下のように変更できます。

    from langfuse.decorators import langfuse_context, observe
    
    @observe()
    def main(n):
        langfuse_context.update_current_observation(
            input=json.dumps(
                {
                    "n": n,
                    "foo": "bar",
                }
            ),
        )
    
  2. インテグレーションの利用
    LangfuseにはLangchainやLlamaIndexなど、様々なライブラリとのインテグレーションが用意されています。ここでは Langchain とのインテグレーションについて書きたいと思います。
    基本的には以下のコードのように CallbackHandler() でハンドラを作成し、chain の実行時の引数に config={"callbacks": [langfuse_handler]} を加えるだけでログをとることができます。

    from langfuse.callback import CallbackHandler
    
    langfuse_handler = CallbackHandler()
    
    chain.invoke(config={"callbacks": [langfuse_handler]})
    

    自作関数の内部で chain を実行する場合には少し注意が必要です。chain のログを個別の Trace として記録する場合は CallbackHandler() でハンドラを作成すればよいですが、関数内の処理などと同じ Trace に記録したい場合は以下のようにする必要があります。

    from langfuse.decorators import langfuse_context, observe
    
    @observe()
    def main():
        langfuse_hander = langfuse_context.get_current_langchain_handler()
    
        chain.invoke(config={"callbacks": [langfuse_handler]})
    

その他 Trace に関する機能

  • Session ID
    以下のようにすることで Trace に対し Session ID や User ID、Tag などを付与することができます。

    1. @observe を使用する場合
      from langfuse.decorators import langfuse_context, observe
      
      @observe()
      def main():
          langfuse_context.update_current_trace(
              session_id="session_id",
              user_id="user_id",
              tags=["tag1", "tag2"],
          )
      
    2. ハンドラを使用する場合
      from langfuse.callback import CallbackHandler
      
      langfuse_handler = CallbackHandler(
          session_id="session_id", user_id="user_id", tags=["tag1", "tag2"]
      )
      

    これらの値でフィルタリングして Trace を表示することなどが可能です。


    Langfuse. Langfuse の Trace 一覧画面のスクショ

  • Evaluation
    RAGASなどのフレームワークや、ユーザーからのフィードバックによって得られた評価を Trace と紐づけることができます。

    1. @observe を使用する場合
      from langfuse.decorators import langfuse_context, observe
      
      @observe()
      def main():
          output = chain.invoke(input)
      
          # 評価を行う。
          evaluation = 0.8
          comment = "すごく良い出力!"
      
          langfuse_context.score_current_trace(name="feedback", value=evaluation, comment=comment)
      
    2. ハンドラを使用する場合
      from langfuse import Langfuse
      from langfuse.callback import CallbackHandler
      
      langfuse_handler = CallbackHandler()
      output = chain.invoke(input, config={"callbacks": [langfuse_handler]})
      
      # 評価を行う。
      evaluation = 0.8
      comment = "すごく良い出力!"
      
      trace_id = langfuse_handler.get_trace_id()
      langfuse = Langfuse()
      trace = langfuse.score(trace_id=trace_id, name="feedback", value=evaluation, comment=comment)
      

    以下のように Langfuse の UI で確認できます。Value でフィルタリングすることも可能です。


    Langfuse. Langfuse の Score 一覧画面のスクショ

    ※上記の方法ではNUMERIC 型の評価しか送れないようです。Langfuse の UI で評価を行う場合はCATEGORICALBOOLEAN 型の評価も行うことができます。

おわりに

本記事では Langfuse を用いてログをとる方法についてまとめました。

Langfuse は数コマンドで立ち上げることができ、ログをとるために加えなければならないコード量もごくわずかであるため、簡単に導入できる点が魅力の一つだと感じました。

LLM を用いたアプリを作成する際には Langfuse の活用を検討するのが良いと思います。

参考文献

Discussion