対象読者
- RAG(Retrieval-Augmented Generation)チャットボットの開発・PoCを進めている方
- 精度改善やチューニングの際に、原因(チャンキング/検索/質問の質/回答生成など)の切り分けに悩んでいる方
- LLMOpsやオブザーバビリティという概念に、まだ馴染みがない方
- Databricks・MLflowを使ってLLMアプリを運用しようとしている方
この記事を読んでわかること
- RAGのチューニングがなぜ「八方塞がり」に感じやすいのか、その構造
- LLMOpsという考え方に出会い、それをどう実践に落とし込んでいったかの過程
- MLflowのトレース機能をどのような手順・考え方で導入したか
- トレース可視化によって、問題の切り分けがどう変わったか(得られた効果)
本記事は本番化に向けて突破口を見つけた段階の内容であり、本番運用の完成形を示すものではありません。
エグゼクティブサマリ
RAGチューニングの原因切り分けに悩んでいたが、LLMOpsとの出会いで可観測性を導入し、問題特定にかかる精神的負担と作業量を大きく減らせた。
背景
現在、PoCでRAGチャットボットをDatabricks上で開発しており、精度面での手応えも出てきて「これは本番化までいけそうだ」という感触を持ち始めていました。
一方で、本番化に向けたチューニングの段階で、大きな壁にぶつかりました。回答の精度が悪いとき、その原因が
- チャンキング(ドキュメントの分割方法)
- 検索(どのチャンクを取得してくるか)
- 質問の質(ユーザーの入力そのもの)
- 回答生成(LLMがどう答えを組み立てるか)
のどこにあるのかを切り分けることが非常に難しかったのです。結果として、「全部にアプローチしないといけないのでは」という感覚になり、何から手をつければいいのか分からず考え込んでしまう状態が続いていました。
この時点で、私は「LLMOps」という言葉自体を知りませんでした。
課題認識:LLMOpsとの出会い
そんな状況の中、社内カレンダーで偶然案内されていたイベントに参加したことが転機になりました。
このイベント・書籍を通じて、LLMOpsとは何かを初めて理解しました。私なりの理解でまとめると、
LLMを使ったアプリケーション(RAGなど)を評価しながら、計画・実行・検証・改善のPDCAサイクルを回し続ける取り組み
というものです。
そして同時に、「なぜこのPDCAが難しいと感じていたのか」も言語化できました。理由は大きく2つあります。
-
出力が自然言語であるため、定量的に良し悪しを測りづらい
従来のソフトウェアのように「期待値と一致するか」で機械的に判定できない -
ロジックを実装しても、結果が悪いときにどこが原因か分からない
チャンキング・検索・質問・回答生成のどこに問題があるのかが見えない
一般的にLLMOpsでは、この「見えない」状態を解消するために 可観測性(オブザーバビリティ) の確保が重要だとされています。私の場合、今回の課題感とまさに一致していました。
実践:トレース導入のサイクル
理解したことを、実際のプロジェクトに落とし込んでいきました。
手動でのトレース実装
もともとRAGチャットボットのバックエンドにはLangChainを使用していました(Databricksの生成AIエンジニアリング研修をきっかけに採用した経緯があります)。
まずは、イベントで解説されていたMLflowの @mlflow.trace デコレーターを、既存のLangChainのコードに導入できないかを検討しました。コーディングエージェントと相談しながら、関数単位でデコレーターを付与していく形で実装を進めました。
イメージとしては、以下のような形です(実際のコードを一般化した例です)。
import mlflow
@mlflow.trace(name="retrieve_documents")
def retrieve_documents(query: str, top_k: int = 10):
# ベクトル検索でチャンクを取得する処理
chunks = vector_store.search(query, top_k=top_k)
return chunks
@mlflow.trace(name="generate_answer")
def generate_answer(query: str, chunks: list):
# 取得したチャンクをもとに回答を生成する処理
context = build_context(chunks)
answer = llm.invoke(query, context)
return answer
このようにデコレーターを付けた関数をアプリ経由で実行すると、MLflowのExperiments上でトレースとして可視化できるようになります。
可視化して気づいたこと
実際にトレースを可視化してみたところ、「本来見えているべきものが見えていない」状態になっていることに気づきました。この詳細は以前の記事にまとめています。
ブラックボックスの解消と自動トレーシングの検証
上記の気づきをもとに、ブラックボックス化していた箇所を1つずつ可視化できるように調整していきました。あわせて、MLflowが提供する 自動トレーシング(Autologging) 機能も試しました。
このとき意識していたのは、「使えそうな機能ならまず実装してみて、駄目だったら戻す」というスタンスです。最初から最適な形を設計しようとせず、小さく試してサイクルを回すことを優先しました。
得られた示唆
約1ヶ月ほどこのサイクルを回してみて、大きく2つの示唆を得ました。
示唆1:完璧を目指さず、まず実装してから削る
トレースが何も可視化できていない状態のままでは、可視化して初めて見えてくる課題そのものに気づくことができません。そのため、
- まずロジックの中に手当たり次第トレースを実装する
- 実際にアプリを動かし、AIと壁打ちしながら「なぜ同じようなトレースが繰り返し現れるのか」を検討する
- 不要だと判断したトレースは除外していく
という順番で進める方が、結果的に早く本質的な課題にたどり着けると感じました。
示唆2:RAGはドキュメント・チャンク単位でトレースを見ることが重要
RAG特有の観点として、「どのドキュメントが取得されたか」「どのチャンクが検索結果として返ってきたか」をトレースで確認することが、非常に有効でした。
具体的には、チャンクをtop-10件などに絞って検索していたところ、同じドキュメント内のチャンクばかりが検索結果に現れるという現象に遭遇しました。これに気づいたことで、
- 同じドキュメントしか検索できておらず、本来参照すべき別のドキュメントが検索対象から漏れているのではないか
- 本来ユーザーが求めている回答が返ってきていないのではないか
という視点を持てるようになり、検索ロジックの見直しにつなげることができました。
最も大きかった変化
そして、これら一連の取り組みを通じて最も大きかったのは、問題箇所を要素ごとに切り分けられるようになったことです。
これまでは「チャンキング・検索・質問・回答生成のどこが悪いのか分からない」という状態で、全方位にアプローチしなければならない感覚がありました。トレースによって切り分けができるようになったことで、精神的な負担と実際の作業量の両方が大きく減りました。
まだ本番化が完了したわけではありませんが、行き詰まっていた状況から抜け出す、確かな突破口を見つけられたと感じています。
今後の対応
今回得られた知見は、まずチームに展開していきたいと考えています。また、今回のような「LLMOpsという概念を知らないまま開発を続けていた」という経験は、同じような状況にいるエンジニアにも起こりうるはずです。この記事が、LLMOps・オブザーバビリティという領域が少しでも広がっていくきっかけになればと思っています。
まとめ
- RAGのチューニングは、原因の切り分けが難しく行き詰まりやすい
- LLMOps・可観測性という考え方を知ったことで、この課題に向き合う手段を得た
- MLflowの
@mlflow.traceデコレーターから始め、可視化→気づき→修正のサイクルを回した - 完璧を目指さずまず実装すること、RAGではドキュメント・チャンク単位でトレースを見ることが重要だと分かった
- トレースによる切り分けができるようになったことで、精神的負担・作業量が大きく減った
