すごいぞLangfuse！トークン数計算機能と評価機能を検証

LangChainを使って色々LLMアプリを作って遊んでいます。

体感速度が遅いけど、どこが遅いかわからない
サンプルソースをコピペして作ったので、実は中身のことをわかってない
入力と出力だけじゃなくて、中間の状態も知りたい
みたいなことってありませんか？そんなときに使えるツールを見つけましたのでご紹介します。

---

環境構築の方法などは前回の内容を参照ください。

https://qiita.com/moritalous/items/76ba9f2ad200df335d07

---

## トークン数計算

Langfuseにはトークン数と価格を計算する機能があります。Bedrockのモデルの場合は少し設定が必要です。

Modelsメニューを開きます。
モデルごとの価格が設定されています。LangfuseのデフォルトではOpenAIのGPT-4やAnthropicのClaudeなどが登録されています。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/1a3f7f9a-6aa2-9d92-b212-d6854a1d8037.png)

モデル名の正規表現で対応するモデルがある場合にトークン計算が行われますが、Bedrockで使用できるClaudeの場合はマッチしないので、`Add model definition`をクリックし、価格情報を登録します。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/ac97bfe4-3e82-224d-daa1-5d66e25d1e18.png)

トレース情報がLangfuseに届いたタイミングでモデル情報があれば、トークン数と価格が表示されます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/4da88660-21ba-8183-913b-264440235aeb.png)

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/78315c0c-0ffc-6445-a2d7-83703a5cb9b5.png)



:::note
適当に構築したRAGは、一度のやり取りで6700トークンのやり取りがあるようです。以外に多いと感じませんか？？
:::

上から3件がClaude 3 Sonnet、4件目がClaude 3 Haikuです。
処理時間が半分以下で値段も10分の1ぐらいです。こういった情報が可視化されるのもいいですね。


![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/981b6f22-0eaf-b0ac-19d0-15c79b40bcfe.png)


ダッシュボードでも確認できます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/79453af4-ab66-6161-8d42-ad52945469b6.png)


## 生成AIの評価をする

トレース情報をもとに、生成AIの評価を行います。流れは以下のようなものとなります。

1. Langfuseからトレース情報を取得する
1. 取得したトレース情報から評価を行う
1. 評価結果をLangfuseに登録する

Langfuse自体には評価機能はないようで、他のツールで評価した結果を可視化できるという仕組みのようです。

参考ドキュメント

https://langfuse.com/guides/cookbook/evaluation_with_langchain

ドキュメントを読んで知ったのですが、なんと、**LangChainに評価機能がある** ようです。知らなかった。

https://python.langchain.com/docs/guides/productionization/evaluation/

LangChainの評価機能で評価を行い、Langfuseと連携してみます。

:::note
RAGの評価ツールとしてはRagasが有名です。

https://qiita.com/s3kzk/items/44b8780c656b4f747403

:::

1. 準備

    ```python
    import os
    
    os.environ["LANGFUSE_SECRET_KEY"] = "sk-lf-8031e607-0777-4abd-8d7d-ba4ed79db1fd"
    os.environ["LANGFUSE_PUBLIC_KEY"] = "pk-lf-f43905de-138c-43de-a81f-66ada70cd1fa"
    os.environ["LANGFUSE_HOST"] = "http://localhost:3000"
    
    from langfuse import Langfuse
    
    langfuse = Langfuse()
    ```

1. LangfuseからGenerations（生成AIの部分）のデータを取得

    ```python
    generations = langfuse.get_generations(limit=100)
    ```

1. 評価器を生成する

    ```python
    from langchain.evaluation import Criteria, EvaluatorType, load_evaluator
    from langchain_community.chat_models import BedrockChat
    
    evaluator = load_evaluator(
        EvaluatorType.CRITERIA,
        criteria=Criteria.CONCISENESS.value,
        llm=BedrockChat(
            model_id="anthropic.claude-3-haiku-20240307-v1:0",
            model_kwargs={
                "temperature": 1,
            },
        ),
    )
    ```

1. 評価を実行する

    ```python
    eval_result = evaluator.evaluate_strings(
        input=generations.data[0].input, prediction=generations.data[0].output
    )
    ```
    
    :::note
    簡単！
   :::

1. 評価結果をLangfuseに登録する

    ```python
    langfuse.score(
        name=Criteria.CONCISENESS.value,
        trace_id=generations.data[0].trace_id,
        observation_id=generations.data[0].id,
        value=eval_result["score"],
        comment=eval_result["reasoning"],
    )
    ```

以上です。
複数の評価指標で、複数データの処理を行う場合はこんな感じです。

```python
from langchain.evaluation import Criteria, EvaluatorType, load_evaluator
from langchain_community.chat_models import BedrockChat

CRITERIA = [
    Criteria.CONCISENESS,
    Criteria.RELEVANCE,
    Criteria.COHERENCE,
    Criteria.HELPFULNESS,
]

for c in CRITERIA:
    evaluator = load_evaluator(
        EvaluatorType.CRITERIA,
        criteria=c.value,
        llm=BedrockChat(
            model_id="anthropic.claude-3-haiku-20240307-v1:0",
            model_kwargs={
                "temperature": 1,
            },
        ),
    )

    for generation in generations.data:
        eval_result = evaluator.evaluate_strings(
            input=generation.input, prediction=generation.output
        )

        langfuse.score(
            name=c.value,
            trace_id=generation.trace_id,
            observation_id=generation.id,
            value=eval_result["score"],
            comment=eval_result["reasoning"],
        )
```

これを定期的に実行すればOKです。

Langfuseの画面で見てみましょう。

Generations画面

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/644e7625-1162-c640-09a3-41ab11e0fb66.png)

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/3030367a-07c3-91bd-6692-80b177f078da.png)

3行目に値が2個登録されているのは2回実行したからです。。一度登録したデータの消し方がわかりませんでした。。


フィルタリングできるので、評価の悪いものだけをピックアップしたりできます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/3c262d0b-3918-21d8-cf9a-831a3da85b6e.png)

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/1ff633a8-dc01-463c-0ba3-05992b562e39.png)


Traceの画面では、評価の値と、評価の理由が表示されます。

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/f83d0ff5-be4a-bbc5-5112-ebecf13a7a48.png)

![](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/41574/12dbd220-cf20-5201-a3a7-9c26c1002109.png)


素晴らしい。

## まとめ

Langfuseのトークン数算機能と評価機能について紹介しました。

まだ続くかもなので、いいねとストックをしてお待ち下さい（笑）