Datadog LLM Observabilityとは
DatadogはObservabilityツールで、そのプロダクトにDatadog LLM Observabilityがあります。
LLMアプリを分析するためのプロダクトで、トレースの結果やメトリクスを可視化することができます。
また、LLM Observabilityそのものはこちらで紹介した書籍がすごく分かりやすいです。
LLM Observabilityは新しい領域のようで、様々なツールが誕生しています。
私もLLMを使ったRAGを構築しています。開発中にはアプリの性能をあげるために、ドキュメントを改善するべきか、チャンク戦略を見直すべきか、モデルを変えるべきか、通常のアプリ開発アプローチとは別の視点の評価や判断が必要だなと感じていました。
LLMは進化が早く、より優秀なモデルが誕生し性能やコスト変動も激しいです。
モデルを変更した際に、その判断が良かったのか。アプリの挙動を改善したいとき、どのような指標をもとに何を改善するのか。悩ましいです。
今回はDatadogのLLM Observabilityを試す機会があったので、公式サンプルをベースに体験してみました。
公式サンプル
DatadogのリポジトリにはJupyter Notebookから実行してLLM Observabilityを体験できるサンプルが公開されています。
それ利用してLLM Observabilityを体験してみます。
セットアップ
リポジトリをダウンロードし、python環境をセットアップ、環境変数を設定するだけで動作します。
環境変数の設定
.envファイルを作成し環境変数を設定します。
OPENAI_API_KEY=Open AIのAPI Key
DD_API_KEY=DatadogのAPI Key
DD_SITE=ap1.datadoghq.com
DD_LLMOBS_AGENTLESS_ENABLED=1
DD_LLMOBS_ML_APP=onboarding-quickstart
アプリはOpen AIを呼び出すのでAPI Keyが必要です。
DD_LLMOBS_AGENTLESS_ENABLEDを指定することでDatadog Agentなしで利用できるようです。
リポジトリの構成
リポジトリは4つのNoteBookで構成されています。
- 1-llm-span.ipynb
- 2-workflow-span.ipynb
- 3-agent-span.ipynb
- 4-custom-evaluations.ipynb
それぞれためしてみます。
1-llm-span.ipynb
このNoteBookでは基本的なLLMスパンのトレースを体験できます。
コード前半で環境変数の読み取りとddtraceのllmobsを有効にしています。
from dotenv import load_dotenv
load_dotenv()
from ddtrace.llmobs import LLMObs
LLMObs.enable()
このサンプルではOpen AIのchat.completions.createでLLMを呼び出しています。
oai_client.chat.completions.create(
messages=messages,
model="gpt-3.5-turbo",
response_format={"type": "json_object"},
)
Open AIを利用している場合は、特に追加の作業なしにトレースすることができます。
実行時間や呼び出したモデル、トークン数やコストが確認できます。
モデルへのインプット、モデルからのレスポンスも記録されています。
プロンプトも見やすく整理されています。タグ付けもされていますね。
対応した言語やモデルプロバイダの場合は、追加設定なしにトレースできるのはすばらしいですね。
ドキュメントに対応しているプロバイダやモデルの情報があります。
2-workflow-span.ipynb
このサンプルではfunction callingを使ったLLMの関数呼び出しのトレースを体験できます。
DatadogのLLM Observabilityではスパンに種類が定義されており、一連の呼び出しや関数の実行をまとめ可視化することができます。
スパンの設定にはpythonのデコレータを利用することができます。
from ddtrace.llmobs.decorators import *
function callingで利用する関数にToolデコレータが指定されおり、関数中にLLMOs.annotateを利用してInputやOutputの内容を指定しています。
@tool()
def fetch_met_urls(query_parameters):
# We annotate the tool call with input_data here
LLMObs.annotate(
input_data=query_parameters,
)
response = requests.get(SEARCH_ENDPOINT, params=query_parameters)
response.raise_for_status()
object_ids = response.json().get("objectIDs")
objects_to_return = object_ids[:MAX_RESULTS] if object_ids else []
urls = [
f"https://www.metmuseum.org/art/collection/search/{objectId}"
for objectId in objects_to_return
]
# We annotate the tool call with output_data here
LLMObs.annotate(
output_data=urls,
)
return urls
一連の処理をまとめた関数定義にworkflowデコレータが指定されています。
@workflow()
def find_artworks(question):
# We annotate the workflow span with input_data here
LLMObs.annotate(
input_data=question,
)
query = parse_query(question)
print("Parsed query parameters", query)
urls = fetch_met_urls(query)
# We annotate the workflow span with output_data here
LLMObs.annotate(
output_data=urls,
)
return urls
トレース全体がworkflowスパンにまとまっていることが確認できます。
LLM呼び出しも記録されています。
関数を実行する際のInput情報やレスポンス、実行時間などが記録されています。
デコレーターだけで簡単にスパンの種類をつけられるのはよいですね。またスパンの名前も関数名で自動的に記録されています。
簡単に処理をまとめて可視化できます。
スパンの種類は他にもありますが、親スパンにできるものが限られています。詳しくはドキュメントに記載があります。
3-agent-span.ipynb
ReActを使ったAgentの実行をトレースを体験できます。コードを見るだけでも勉強になるサンプルです。
ReAct Agentのロジック全体の記載がある関数をworkflowデコレータを指定しています。
@workflow()
def execute_loop_step(messages, calls_left=MAX_CALLS):
全体をまとめた関数にagentデコレータを指定しています。
@agent()
def call_weather_assistant(question_prompt):
LLMObs.annotate(
input_data=question_prompt,
)
messages = get_initial_messages(question_prompt)
answer = execute_loop_step(messages)
LLMObs.annotate(
output_data=answer,
)
return answer
複雑なReAct Agentの実行がトレースできています。表示が崩れているように見えなくもないですが、新しいプロダクトなのでまだバグがあるのかもしれません。
Toolが3回実行さています。関数名がわかりやすいですね。
Agentのロジックは複雑になりがちなので、スパンで区切って可視化されているとLLMが実行を選択した関数の妥当性や、レスポンスのボトルネックになっている関数などが特定できそうです。
4-custom-evaluations.ipynb
最後はRAGのトレースとカスタム評価を体験できます。最後のサンプルはボリューム満点です。
このサンプルではllama-indexでRAGを構築しています。
RAGを呼び出す関数にretrivalデコレータを使用しています。
@retrieval
def retrieve_nodes(query, retriever=base_retriever):
nodes = retriever.retrieve(query)
LLMObs.annotate(
tags={"retriever": retriever.__class__.__name__},
metadata={"top_k": TOP_K},
input_data=query,
output_data=[
{"text": node.text, "id": node.node_id, "score": node.score}
for node in nodes
],
)
return nodes
呼び出し全体にworkflowデコレータを指定しています。お約束ですね。
@workflow
def ask_docs(
query, retriever=base_retriever, response_synthesizer=response_synthesizer_compact
):
LLMObs.annotate(input_data=query)
nodes = retrieve_nodes(query, retriever=retriever)
response = response_synthesizer.synthesize(query, nodes=nodes)
LLMObs.annotate(output_data=str(response))
return response, LLMObs.export_span()
RAGの呼び出しがまとまって見やすく可視化されています。
検索されたドキュメントの内容やスコアなどがまとめて可視化されて見やすいです。
Embeddingのトークンやコストも可視化されています。
また、複数のRAG設定での呼び出し結果の違いを評価するためにRAGASというツールを使いRAGのカスタム評価をDatadogに送信しています。
RAGアプリを作っていると、質問に対して何でこんな回答なんだっけ?と思うことがあるので、参照したドキュメントやレスポンスが見える化できるのは良いです。
カスタム評価指標も取り込めるので、柔軟なメトリクスの取り込みができそうです。
イチオシの機能 Trace Cluster Map
Trace Cluster Mapはトレースしたデータをトピックごとにグループ化しクラスタリングできます。
ユーザーのインプット、LLMアウトプットをそれぞれ可視化することができ、表示を切り替えることができます。
RAGアプリの開発ではユーザーのインプットの傾向を調べやすく、ユーザーが求めている回答ができておらず、RAGに足りないドキュメントがあることに気がつく方ができました。大変重宝した機能です。
まとめ
よかった点
Datadog LLM Observabilityを利用すると対応している言語やプロバイダであれば最低限のコードで可視化することができ、ツールの恩恵を受けるまでの時間が短縮できました。
普段使い慣れているツールで操作できるのも良く、Dataodgのおしゃれで親しみのあるUIで分析できたのは良かったです。
検証中にもアプリ改善に繋がるインサイトを見つけることができ、今後も必須のツールになるなと感じています。
改善を感じた点
実際に開発していたアプリはJavascriptで構築しており、バックエンドにPythonやLangChainを使っておらず、トレースのための実装が多くなり、導入に苦労しました。
最新のプロダクトなのであまり情報がなかったです。
Pythonが優遇されがちなので、対応言語が増えていくと便利になると思います。
Open AIが前提になっている機能などもあるので、柔軟にプロバイダを選択できることや機能をオミットするなどできると良いと感じます。
最後に
他にもたくさんの機能があるのですが、もっとじっくり使ってから記事化できたらと思います。