個人的ベスト LLMのObservabilityツール

# はじめに
LLMのObservabilityツールでよく聞くものとしてLangSmith、Langfuse、MLflowなどがあります。

しかしこれらはSelf-hostが環境によっては面倒です。（私はメモリかつかつなのでDockerはできるだけ使いたくない状況です。。。）

また、業務で使用する際などは外部に保存はしたくありませんし、ほんとにちょっとだけ確認したいことがある際にいちいちDocker立てるのも面倒でした。

何かないかなぁと思い探しているとAlizeのphoenixというObservabilityツールがあり、非常に使いやすく私の状況にピッタリだったので備忘録としてまとめました。


https://phoenix.arize.com/


また、本記事はご紹介としてさらっとだけ記述します。
より詳しい内容は別の方がまとめていらっしゃったのでそちらをご覧ください！（人任せ...)


https://qiita.com/moritalous/items/578e1825325a21e0f013


# 試してみる
今回使用したのはphoenixのNotebook-selfHostです。

Notebook内でコードを実行すると、localhostにダッシュボードが作成されます。

Runtimeを削除すると当然記録は消えますが簡単にLLMの動きをトレースしたい際などとても便利です。
（私は試したことありませんが、記録を残しておきたい場合はDockerコンテナとして立ち上げ、別途DBなどに保存すればいいそうです。）

## インストール
pip で簡単にインストールできます。

```python
pip install -qU arize-phoenix
```


## ダッシュボードの立ち上げ
下記コードでダッシュボードを立ち上げます。

ほんの数行で立ち上げることができるので非常に簡単です。

```python
import phoenix as px
px.launch_app()
```

デフォルトでは`localhost:6006`に立ち上がります。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3491855/d5974351-e4bf-4bb5-bd30-3753e8cab4e3.png)


## トレースの設定をする
ダッシュボードが立ち上がったのでLLMの出力をトレースする準備をしていきます。

トレースの方法はいくつかあるのですが、まずは最も簡単な`register`を使用していきます。

`register`を使用するとphoenixが対応している一部のサードパーティーライブラリ（OpenAI、langchain、llamaindexなど）を自動的にトレースしてくれます。

後ほど実際にトレースをしますが、`register`を使用すると非常に簡単にトレースをしてくれます。

下記コードで`register`を登録します。

```python
from phoenix.otel import register

tracer_provider = register(
  project_name="test", # Default is 'default'
  auto_instrument=True # Auto-instrument your app based on installed OI dependencies
)
```


## langchainをトレースしてみる
`register`を作成したので実際にトレースしてみます。

この部分も非常に簡単です。というかほぼ何もしません。
まずは`openinference-instrumentation-langchain`をインストールして、先ほど作成した`register`と紐づけます。

```python
!pip install -qU openinference-instrumentation-langchain


from openinference.instrumentation.langchain import LangChainInstrumentor

LangChainInstrumentor().instrument(tracer_provider=tracer_provider)
```

あとは、同じRuntime上でいつもどおりlangchainにinvokeするだけです！

試しに下記コードを実行してみます。（APIキーはご自身でご用意ください。）

```python
!pip install -qU langchain-openai


from langchain_openai import ChatOpenAI

llm = ChatOpenAI(model="gpt-4o-mini")
llm.invoke("こんにちは")
```

すると先ほどのダッシュボード上に`test`というプロジェクトが追加され、更にlangchainの出力がトレースされているのが確認できるはずです！

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3491855/e6f38d19-61b1-4795-b0ad-e5968c4abaf2.png)

中身はこんな感じ

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3491855/9cbd49cd-81ee-4fc2-a242-da47dc52a02d.png)


![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3491855/db2db1f6-8c99-4a41-8ba7-e2f7a3b75f18.png)


Latencyや使用したToken料などをトレースできます！
今回はシンプルにChatModelにinvoke()しただけなのであっさりしていますが、langgraphなどで複雑なワークフローを組んでいるときなどは各ノードで何が行われたのかなどを細かく確認することができます。

そして何より簡単です！

もし既存のアプリがlangchainなどで書かれてあり、一時的にテストとしてトレースしたいときなどはコードの上部に数行付け足すだけでトレースが出来てしまいます！


## より柔軟なトレース（補足） 
`openinference`の`TracerProvider`を使用することにより、トレースをより細かく（スパン単位で）制御可能です。

例えば先ほど使用した`register`が対応していない独自の関数などを使用した際などに有効です。

こちらの手法を私はあまり使ったことが無いため触りの部分だけまとめます。

まずは下記コード例で`tracer`を作成します。

```python
from opentelemetry.exporter.otlp.proto.http.trace_exporter import OTLPSpanExporter
from opentelemetry.sdk.resources import Resource
from opentelemetry.sdk.trace.export import ConsoleSpanExporter, SimpleSpanProcessor

from openinference.instrumentation import TracerProvider
from openinference.semconv.resource import ResourceAttributes

endpoint = "http://localhost:6006/v1/traces"
resource = Resource(attributes={ResourceAttributes.PROJECT_NAME: "tracer-test"})
tracer_provider = TracerProvider(resource=resource)
tracer_provider.add_span_processor(SimpleSpanProcessor(OTLPSpanExporter(endpoint)))
tracer = tracer_provider.get_tracer(__name__)
```

この`tracer`を使用してトレースを行っていきますが、主に`デコレーター`と`with句`の2つの使い方があります。

`デコレーター`の場合の基本的使い方はこちら

```python
@tracer.chain
def my_func(input: str) -> str:
    return "output"

# トレース結果見たいのでとりあえず実行
my_func("あいうえお")
```

`with句`の場合の基本的使い方はこちら

```python
from opentelemetry.trace import Status, StatusCode

with tracer.start_as_current_span(
    "my-span-name",
    openinference_span_kind="chain",
) as span:
    span.set_input("input")
    span.set_output("output")
    span.set_status(Status(StatusCode.OK))
```

結果はこんな感じです。

![image.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/3491855/b9bba151-e38a-4aba-817f-b4ab792be7b9.png)


ちなみにスパンとはこのレコード1行ずつのことを指していると私は理解しています。

またスパンの種類にもいくつかあり、今回は`chain`のみを使用していますがほかにも`llm`、`agent`、`tool`などがあります。


# まとめ
Arize phoenix について簡単にまとめました！

phoenixは非常に簡単に使い始めることのできるObservabilityツールとして今後も使っていこうかと思います。

また、今回はトレースのみ行いましたが他にもRAGなどの評価を行うことができる様なので勉強したいと思います!