📊

Browser Use のプロンプトや出力をLangfuse で見たい

に公開

概要

自分のPC のブラウザを自然言語で操作指示できるエージェントとして、Browser Use が流行っている。
使ってみると確かに動作して驚きなのだが、LangGraph 等でエージェントを構築したことがあるなら、LLM とどんなやり取りをしているのか?気になると思う。

LLM のトレース監視ツールとして、LangChain 対応のものはLangSmithLangfuse が有名である。
両者の比較は既に色々なサイトにあるので省略するが、LangSmith はLangChain 公式で便利(環境変数を設定するだけで利用できる)な一方、Self-hosting するにはenterprise plan 加入が必須なのがネック。

LangSmith のマネージドSaaS サービスはある程度無料で使えるので、個人で遊ぶ分にはそれで問題無い。
だが商用利用まで見据えるなら、LLM への入出力結果は手元で管理しておきたい...と個人的には思う。

というわけで本記事では、Browser Use によるLLM への入力・出力をLangfuse で可視化することを目指す。

ツール簡易解説

Browser Use

その名の通り、ブラウザを生成AI に操作させるツール。詳細や導入はGitHub のREADME 等を参考のこと。特にハマった部分は無かったので省略。

特徴として、LLM 部分はLangChain ライブラリを利用している。Agent 機能は独自実装だが、LangChain(LangGraph)のAgent と連携も可能らしい。
参考)https://qiita.com/rairaii/items/a8c4dc3d2a1623b43e45

Langfuse

生成AI 開発プラットフォーム。LangChain 非公式なのでどうしても既存のプログラムに手を加える必要があるが、LangSmith と違ってSelf-hosting が無料なのが嬉しい。

Self-hosting の手順は公式ドキュメント参照。単にdocker compose up すればOK。

試行錯誤

結果だけ書くと拍子抜けなので、備忘録として失敗した過程も折り畳みで残しておく。

@observe を使う

@observe デコレータで、(生成AI やLangChain 関係無く)Python 関数の入出力の内容をLangfuse サーバに送れる。詳細は公式ドキュメント 参照のこと。

# 環境変数LANGFUSE_SECRET_KEY, LANGFUSE_PUBLIC_KEY, LANGFUSE_HOST は設定済の前提
from langfuse.decorators import observe

@observe
def add(num1, num2):
    return num1 + num2

print(add(1, 2))  #3

ところが、Browser Use でユーザが実行するのはagent.run() のみで、これを@observe しても入出力はnull で意味が無い。
やるならライブラリのこのあたりに手を加えることになるが、最終手段なのでまずは別の方法を検討。

with_config を使う

Langfuse はLCEL もサポートしている。こちらも公式ドキュメント参照。

from langchain_openai import ChatOpenAI
from langchain_core.output_parsers import StrOutputParser

llm = ChatOpenAI(model="gpt-4o")
chain = llm | StrOutputParser()

print(chain.invoke("こんにちは", config={"callbacks": [langfuse_handler]}))
#こんにちは、どうも!お困りごとや相談があればお気軽にお知らせくださいね。どういたしまして。

config は上記のようにinvoke する際の引数としても渡せるが、with_config を使えばチェーンのデフォルト値として設定できる。

chain = (llm | StrOutputParser()).with_config(callbacks=[langfuse_handler])

print(chain.invoke("こんにちは"))



ところが、Browser Use のAgent を定義する際、以下のように設定するとエラーになる。

from browser_use import Agent

agent = Agent(
    task=task,
    llm=llm.with_config(callbacks=[langfuse_handler]),
    browser=browser
)

ChatOpenAIBaseChatOpenAIBaseChatModelBaseLanguageModelRunnableSerializable という継承関係であり、with_config() で返されるのはRunnableBinding クラスのオブジェクトであること に起因したエラーである。

Browser Use をRunnableBinding クラスのオブジェクトに対応するように改修するのは大変そうなので、これも断念。

結論

BaseLanguageModel クラスのコンストラクタの引数にcallbacks が渡せるので、llm オブジェクトを作成する際にcallbacks を入力すれば良い。

from browser_use import Agent
from langchain_openai import ChatOpenAI
from langfuse.callback import CallbackHandler


langfuse_handler = CallbackHandler()

llm = ChatOpenAI(model="gpt-4o",callbacks=[langfuse_handler])
task = 'Nintendo Switch 2 のスペックについて教えて。公式サイトの情報を必ずチェックして'

agent = Agent(
    task=task,
    llm=llm
)

async def main():
    await agent.run()

if __name__ == '__main__':
    asyncio.run(main())

というわけであっさり見れた。これを基に、Browser Use がどんなプロンプトを投げて、どんな方針で操作しているのか...をこれから調査予定。

余談

OSS の"自律型エージェント"として、OpenManus も流行っていて、Browser Use コミュニティも開発に協力しているらしい。
OpenManus の裏側もトレースしたかったのだが、ライブラリの中身を除いてみると、LLM 部分はLangChain を使わない独自実装。Browser Use はツールとしての利用に留まるようである。

というわけで、OpenManus をLangfuse で可視化するにはライブラリに手を加えるしかないと思う。そのうち試してみるかも

Discussion