はじめに
MLflow 3.10.0(2025年2月20日リリース)で、マルチターン会話の評価機能が大幅に強化されました。ConversationSimulatorのパブリックAPI化やセッションレベルスコアラーの導入により、チャットボットやAIエージェントの会話品質を体系的に評価できるようになっています。
この記事では、Databricks上でMLflow 3.10.0のマルチターン評価機能を実際に動かしながら、以下の2つのアプローチを紹介します。
- 事前に生成された会話の評価 - 既存のトレースをセッション単位で評価
-
会話シミュレーションによる評価 -
ConversationSimulatorでエージェントを自動テスト
本記事のノートブックはDatabricksのノートブックエクスペリメントを使用します。
参考ドキュメント:
セットアップ
パッケージのインストール
%pip install --upgrade 'mlflow[databricks]>=3.10'
dbutils.library.restartPython()
エクスペリメントとエンドポイントの設定
ノートブックエクスペリメントを使用するため、mlflow.set_experiment()は呼びません。ノートブックに紐づくエクスペリメントが自動的に使われます。
import mlflow
print(f"MLflow version: {mlflow.__version__}")
notebook_path = dbutils.notebook.entry_point.getDbutils().notebook().getContext().notebookPath().get()
experiment = mlflow.get_experiment_by_name(notebook_path)
EXPERIMENT_ID = experiment.experiment_id
print(f"Notebook Experiment: {experiment.name}")
print(f"Experiment ID: {EXPERIMENT_ID}")
# Databricks Foundation Model APIs のエンドポイント名
# 環境に応じて変更してください
MODEL_ENDPOINT = "<YOUR_MODEL_ENDPOINT>"
JUDGE_MODEL = f"databricks:/{MODEL_ENDPOINT}"
Databricksノートブック内からFMAPIを呼ぶ場合、mlflow.deploymentsクライアントを使えばPATやホスト名の明示的な設定は不要です。
from mlflow.deployments import get_deploy_client
deploy_client = get_deploy_client("databricks")
# 疎通確認
test_resp = deploy_client.predict(
endpoint=MODEL_ENDPOINT,
inputs={"messages": [{"role": "user", "content": "Hello"}], "max_tokens": 16},
)
print(f"エンドポイント疎通OK: {test_resp.choices[0]['message']['content']}")
Part 1: 事前に生成された会話の評価
まず簡易チャットボットで会話トレースを生成し、その後セッション単位で評価します。
マルチターン評価の前提条件:
- トレースのmetadataに
mlflow.trace.sessionを設定する(tagsではない) - MLflowがセッションIDでトレースを自動グループ化して、会話全体を評価する
- 評価結果は各セッションの最初のトレース(時系列順)に保存される
mlflow.trace.sessionはtagsではなくmetadataとして設定する必要があります。tagsで設定するとUIのセッション列に表示されず、セッション単位の評価が正しく機能しません。
1-1. 簡易チャットボットの定義
@mlflow.trace
def simple_chatbot(question: str, history: list[dict], session_id: str) -> str:
"""シンプルなチャットボット。会話履歴を保持してマルチターン対話を行う。"""
# セッションIDをトレースのメタデータに設定(マルチターン評価に必須)
# ※ metadata であって tags ではない点に注意
mlflow.update_current_trace(
metadata={"mlflow.trace.session": session_id}
)
messages = [
{
"role": "system",
"content": (
"あなたはMLflowの技術サポートアシスタントです。"
"ユーザーの質問に丁寧かつ正確に回答してください。"
"日本語で回答してください。"
),
}
]
messages.extend(history)
messages.append({"role": "user", "content": question})
response = deploy_client.predict(
endpoint=MODEL_ENDPOINT,
inputs={"messages": messages, "max_tokens": 512},
)
return response.choices[0]["message"]["content"]
ポイントはmlflow.update_current_trace(metadata={"mlflow.trace.session": session_id})の部分です。@mlflow.traceデコレータで自動生成されたトレースに対して、セッションIDをメタデータとして付与しています。
1-2. サンプル会話の生成
3つのセッションでそれぞれ異なるパターンの会話トレースを生成します。
import time
def run_conversation(session_id: str, questions: list[str]) -> None:
"""指定されたセッションIDと質問リストで会話を実行する。"""
history = []
for question in questions:
answer = simple_chatbot(question, history, session_id)
history.append({"role": "user", "content": question})
history.append({"role": "assistant", "content": answer})
print(f" Q: {question}")
print(f" A: {answer[:120]}...")
print()
# セッション1: MLflowのトレーシングについてのスムーズな問い合わせ
print("=== セッション1: トレーシングに関する質問 ===")
run_conversation(
session_id="session-tracing-101",
questions=[
"MLflowのトレーシング機能って何ができるんですか?",
"自動トレーシングはどのフレームワークに対応していますか?",
"LangChainと組み合わせて使う場合の具体的なコード例を教えてください。",
],
)
# セッション2: 不満を示すユーザーとの会話
print("=== セッション2: 不満を持つユーザー ===")
run_conversation(
session_id="session-eval-frustrated",
questions=[
"MLflowでLLMの評価をしたいのですが、どうすればいいですか?",
"それだとよく分からないです。もっと具体的にコードで説明してもらえますか?",
"エラーが出ます。mlflow.genai.evaluateの引数が合っていないみたいです。正しい使い方を教えて。",
],
)
# セッション3: プロンプトレジストリについての円満な会話
print("=== セッション3: プロンプトレジストリ ===")
run_conversation(
session_id="session-prompt-registry",
questions=[
"MLflow 3のプロンプトレジストリについて教えてください。",
"プロンプトのバージョン管理はどのように行うのですか?",
"ありがとうございます。よく理解できました!",
],
)
# トレースがサーバーに反映されるまで少し待つ
time.sleep(5)
セッションIDが正しくmetadataに設定されると、エクスペリメントUIのトレース一覧で「セッション」列にIDが表示されます。
セッションタブではより詳細にセッションの中身を確認できます。
1-3. 既存トレースのマルチターン評価
mlflow.search_sessions()でセッション単位のトレースを取得し、平坦化してevaluate()に渡します。
from mlflow.genai.scorers import (
ConversationCompleteness(model=JUDGE_MODEL),
UserFrustration(model=JUDGE_MODEL),
)
# search_sessions でセッション単位のトレースを取得
sessions = mlflow.search_sessions(
locations=[EXPERIMENT_ID],
max_results=50,
)
print(f"取得したセッション数: {len(sessions)}")
for i, session in enumerate(sessions):
print(f" Session {i+1}: {len(session)} traces")
# セッションを平坦化して全トレースのリストにする
all_traces = [trace for session in sessions for trace in session]
print(f"\n全トレース数: {len(all_traces)}")
# マルチターン評価の実行
results = mlflow.genai.evaluate(
data=all_traces,
scorers=[
ConversationCompleteness(), # 会話の完全性: ユーザーの質問にすべて回答したか
UserFrustration(), # ユーザーの不満: ユーザーが不満を感じたか
],
)
print("ビルトインスコアラーによる評価完了!")
display(results.tables["eval_results"])
ビルトインのマルチターンスコアラーには以下の2つがあります。
| スコアラー | 評価内容 |
|---|---|
ConversationCompleteness |
ユーザーの質問や要求にすべて回答できたか |
UserFrustration |
ユーザーが不満を感じたか(繰り返しの質問、トーンの変化など) |
1-4. カスタムマルチターンジャッジ
make_judge()で独自の評価基準を定義できます。マルチターン評価で使う際のポイントは以下の通りです。
- プロンプトのパラメータ名は
instructions(judge_promptではない) -
{{ conversation }}テンプレート変数で会話履歴全体にアクセス -
{{ conversation }}は{{ expectations }}とのみ併用可能({{ inputs }},{{ outputs }},{{ trace }}とは併用不可)
from mlflow.genai.judges import make_judge
from typing import Literal
# カスタムジャッジ: 技術的正確性
technical_accuracy_judge = make_judge(
name="technical_accuracy",
instructions=(
"以下の会話において、アシスタントの技術的な回答が正確かどうかを評価してください。\n\n"
"{{ conversation }}\n\n"
"評価基準:\n"
"- accurate: 技術的に正確で、誤解を招く情報がない\n"
"- mostly_accurate: 概ね正確だが、細かい点で不正確な部分がある\n"
"- inaccurate: 明らかに誤った情報が含まれている\n"
),
feedback_value_type=Literal["accurate", "mostly_accurate", "inaccurate"],
)
# カスタムジャッジ: 日本語の自然さ
japanese_naturalness_judge = make_judge(
name="japanese_naturalness",
instructions=(
"以下の会話において、アシスタントの日本語が自然で読みやすいかを評価してください。\n\n"
"{{ conversation }}\n\n"
"評価基準:\n"
"- natural: 自然な日本語で、読みやすい\n"
"- acceptable: 理解はできるが、やや不自然な表現がある\n"
"- unnatural: 不自然な日本語で、理解しづらい\n"
),
feedback_value_type=Literal["natural", "acceptable", "unnatural"],
)
# カスタムジャッジで評価実行
custom_results = mlflow.genai.evaluate(
data=all_traces,
scorers=[
technical_accuracy_judge,
japanese_naturalness_judge,
],
)
print("カスタムジャッジ評価完了!")
display(custom_results.tables["eval_results"])
Part 2: 会話シミュレーションによる評価
ConversationSimulatorを使い、定義したゴールとペルソナに基づいて自動的に会話を生成しながらエージェントを評価します。Part 1が「既存の会話を評価する」アプローチだったのに対し、こちらは「会話を自動生成してテストする」アプローチです。
2-1. predict関数の定義
ConversationSimulatorに渡すpredict_fnは、第1引数inputに会話履歴(list[dict])を受け取り、アシスタントの応答文字列を返します。
def predict_fn(input: list[dict], **kwargs) -> str:
"""
ConversationSimulator用のpredict関数。
inputには会話履歴(list[dict])が渡される。
"""
messages = [
{
"role": "system",
"content": (
"あなたはMLflowの技術サポートアシスタントです。"
"ユーザーの質問に丁寧かつ正確に回答してください。"
"日本語で回答してください。"
"分からないことは正直に分からないと伝えてください。"
),
}
]
messages.extend(input)
response = deploy_client.predict(
endpoint=MODEL_ENDPOINT,
inputs={"messages": messages, "max_tokens": 512},
)
# deploy_client.predict()の返り値はdict形式
return response.choices[0]["message"]["content"]
2-2. テストケースの定義とシミュレーション実行
テストケースにはgoal(必須)とpersona(任意)を指定します。シミュレータはこれらに基づいてユーザー役のメッセージを自動生成し、predict_fnとのマルチターン会話を実行します。
from mlflow.genai.simulators import ConversationSimulator
from mlflow.genai.scorers import ConversationCompleteness, Safety
simulator = ConversationSimulator(
test_cases=[
# シナリオ1: 初心者ユーザー
{
"goal": "MLflowのExperiment Trackingの基本的な使い方を理解する",
"persona": (
"あなたはMLflowを初めて使うデータサイエンティストです。"
"基本的なことから丁寧に教えてほしいです。日本語で質問します。"
),
},
# シナリオ2: 具体的な問題を抱えた経験者
{
"goal": "MLflowのモデルサービングでエラーが発生している問題を解決する",
"persona": (
"あなたは経験豊富なMLエンジニアです。"
"簡潔で技術的な回答を求めています。"
"すでにドキュメントは読みましたが解決できていません。日本語で質問します。"
),
},
# シナリオ3: 比較検討しているテックリード
{
"goal": "MLflow 3のGenAI評価機能が自分のプロジェクトに適しているか判断する",
"persona": (
"あなたはテックリードで、チームに導入するツールを比較検討しています。"
"メリットとデメリットの両方を知りたいです。日本語で質問します。"
),
},
# シナリオ4: エッジケース - 話題がそれるユーザー
{
"goal": "アシスタントが無関係な話題に対して適切に対応するか確認する",
"persona": (
"あなたは話題がそれやすいユーザーです。"
"MLflowの質問から始めますが、途中で全く関係ない話題(料理や映画)に変えます。"
"日本語で質問します。"
),
},
],
max_turns=4,
# シミュレーションのユーザー役にもDatabricksエンドポイントを使用
user_model=f"databricks:/{MODEL_ENDPOINT}",
)
注意
user_modelを指定しないと、デフォルトでOpenAI APIを使おうとします。Databricks環境にOpenAI APIキーがない場合、全会話が失敗してSimulation produced no tracesエラーになります。databricks:/<エンドポイント名>の形式でDatabricks FMAPIのエンドポイントを指定しましょう。
# シミュレーション + 評価の実行
sim_results = mlflow.genai.evaluate(
data=simulator,
predict_fn=predict_fn,
scorers=[
ConversationCompleteness(model=JUDGE_MODEL),
Safety(model=JUDGE_MODEL),
],
)
print("シミュレーション評価完了!")
display(sim_results.tables["eval_results"])
2-3. 本番会話からテストケースを生成(generate_test_cases)
本番環境の会話セッションからgoal/personaペアを自動抽出し、テストケースとして再利用できます。
generate_test_cases()はMLflow 3.10.0時点で実験的機能です。
try:
from mlflow.genai.simulators import generate_test_cases
# search_sessions でセッション単位で取得
sessions = mlflow.search_sessions(
locations=[EXPERIMENT_ID],
max_results=50,
)
test_cases = generate_test_cases(sessions, model=f"databricks:/{MODEL_ENDPOINT}")
print("生成されたテストケース:")
for i, tc in enumerate(test_cases):
print(f"\n--- テストケース {i+1} ---")
print(f" Goal: {tc.get('goal', 'N/A')}")
persona = tc.get("persona", "N/A")
print(f" Persona: {persona[:120]}{'...' if len(str(persona)) > 120 else ''}")
except Exception as e:
print(f"generate_test_casesの実行時エラー: {e}")
print("この機能はMLflow 3.10.0の実験的機能のため、環境によっては利用できない場合があります。")
生成されたテストケース:
--- テストケース 1 ---
Goal: Evaluate whether MLflow 3's GenAI evaluation features are worth adopting by understanding their capabilities, limitations, and how they compare to alternatives, in order to make an informed adoption decision.
Persona: You are a technically-informed Japanese-speaking practitioner (likely an ML engineer or data scientist) who is evaluatin...
--- テストケース 2 ---
Goal: Learn how to use MLflow for machine learning experiment tracking, starting from installation and basic usage, then progressively understanding deeper concepts like `mlflow.start_run()`, UI-based experiment comparison, and the visual features of the comparison screen.
Persona: You are a Japanese-speaking beginner or intermediate developer who is curious about MLflow and learns by asking follow-u...
--- テストケース 3 ---
Goal: Learn how to save models in MLflow, starting with a general question and then expressing interest in autolog, while also testing the assistant's boundaries by asking an off-topic question about pasta recipes.
Persona: You are a developer or data scientist who is relatively new to MLflow and communicates in a casual, conversational Japan...
--- テストケース 4 ---
Goal: Get help diagnosing and resolving an "Address already in use" OSError that occurs when running `mlflow models serve`
Persona: You are a developer or data scientist with some MLflow experience who communicates concisely in Japanese, shares error m...
--- テストケース 5 ---
Goal: Learn about MLflow 3's Prompt Registry feature, including its overview and how version management works for prompts.
Persona: You are a Japanese-speaking developer or ML practitioner who is curious about MLflow 3's new features. You communicate i...
--- テストケース 6 ---
Goal: Learn how to correctly use MLflow's LLM evaluation API (specifically mlflow.genai.evaluate) with working code examples, after encountering errors with the arguments.
Persona: You are a Japanese-speaking developer with some familiarity with MLflow and LLMs, but you are frustrated and struggling ...
--- テストケース 7 ---
Goal: Learn how to use MLflow's tracing feature with LLM frameworks, starting from a general overview and progressively drilling down into specific details like supported frameworks and concrete code examples with LangChain.
Persona: You are a Japanese-speaking developer with intermediate knowledge of AI/ML frameworks who asks focused, concise question...
Part 3: 特定セッションの評価
mlflow.search_traces()でmetadataフィルタを使い、特定のセッションだけを取り出して評価することもできます。
target_session_id = "session-eval-frustrated"
session_traces = mlflow.search_traces(
filter_string=f"metadata.`mlflow.trace.session` = '{target_session_id}'",
return_type="list",
)
print(f"セッション '{target_session_id}' のトレース数: {len(session_traces)}")
if session_traces:
session_results = mlflow.genai.evaluate(
data=session_traces,
scorers=[
ConversationCompleteness(),
UserFrustration(),
technical_accuracy_judge,
],
)
display(session_results.tables["eval_results"])
ハマりどころと対処法
実際に動かす中で遭遇したハマりどころをまとめます。
セッション列が空になる
mlflow.update_current_trace()でセッションIDをtagsとして設定すると、UIの「セッション」列に表示されません。metadataとして設定する必要があります。
# NG: tagsで設定するとセッショングループ化が機能しない
mlflow.update_current_trace(
tags={"mlflow.trace.session": session_id}
)
# OK: metadataで設定する
mlflow.update_current_trace(
metadata={"mlflow.trace.session": session_id}
)
同様にsearch_traces()のフィルタもmetadata.プレフィックスを使います。
# NG
filter_string="tags.`mlflow.trace.session` = 'xxx'"
# OK
filter_string="metadata.`mlflow.trace.session` = 'xxx'"
make_judge()でTypeError
make_judge()のプロンプトパラメータ名はinstructionsです。judge_promptは存在しません。
# NG: judge_promptは存在しない
make_judge(name="xxx", judge_prompt="...")
# OK: instructionsを使う
make_judge(name="xxx", instructions="...")
また、{{ conversation }}テンプレート変数は{{ expectations }}とのみ併用可能です。{{ inputs }}、{{ outputs }}、{{ trace }}とは同時に使えません。
ConversationSimulatorで"Simulation produced no traces"
ConversationSimulatorはユーザー役のメッセージ生成にもLLMを使います。user_modelを指定しないとデフォルトでOpenAI APIを使おうとするため、APIキーがないDatabricks環境では全会話が失敗します。
# NG: user_model未指定 → OpenAIを使おうとして失敗
simulator = ConversationSimulator(test_cases=..., max_turns=4)
# OK: Databricksエンドポイントを指定
simulator = ConversationSimulator(
test_cases=...,
max_turns=4,
user_model=f"databricks:/{MODEL_ENDPOINT}",
)
まとめ
MLflow 3.10.0のマルチターン評価機能を2つのアプローチで試しました。
| アプローチ | ユースケース | 主要API |
|---|---|---|
| 事前生成された会話の評価 | 本番データ・QAデータの品質分析 |
mlflow.search_sessions() + mlflow.genai.evaluate()
|
| 会話シミュレーション | 新バージョンの体系的テスト |
ConversationSimulator + mlflow.genai.evaluate()
|
| カスタムジャッジ | ドメイン固有の評価基準 |
make_judge(instructions=...) + {{ conversation }}
|
| テストケース生成 | 本番会話からのテストケース再利用 | generate_test_cases() |
実装上の重要なポイントをおさらいしておきます。
- セッションIDはmetadata(tagsではない)として
mlflow.trace.sessionに設定する -
mlflow.search_sessions()でセッション単位のトレースを取得し、平坦化してevaluate()に渡す -
ConversationSimulatorではDatabricks環境ならuser_model="databricks:/<エンドポイント名>"を必ず指定する -
make_judge()のプロンプトパラメータはinstructionsで、{{ conversation }}で会話全体にアクセスできる
次の記事では、ConversationSimulatorのより高度な使い方として、contextフィールドによるパラメータ引き渡し、ステートフルエージェントの実装パターン、MLflow評価データセットへのテストケース保存などを紹介予定です。
参考リンク
- Databricks: 会話を評価する
- Databricks: 会話シミュレーション
- Databricks: トレースにコンテキストを追加する
- MLflow: Conversation Simulation
- MLflow: Custom Judges (make_judge)
- MLflow: Predefined Scorers (Multi-turn)
- MLflow: Track Users & Sessions





