1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

MLflow 3.10.0のマルチターン会話評価をDatabricksで試す

1
Last updated at Posted at 2026-02-24

はじめに

MLflow 3.10.0(2025年2月20日リリース)で、マルチターン会話の評価機能が大幅に強化されました。ConversationSimulatorのパブリックAPI化やセッションレベルスコアラーの導入により、チャットボットやAIエージェントの会話品質を体系的に評価できるようになっています。

この記事では、Databricks上でMLflow 3.10.0のマルチターン評価機能を実際に動かしながら、以下の2つのアプローチを紹介します。

  1. 事前に生成された会話の評価 - 既存のトレースをセッション単位で評価
  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: 事前に生成された会話の評価

まず簡易チャットボットで会話トレースを生成し、その後セッション単位で評価します。

マルチターン評価の前提条件:

  • トレースのmetadatamlflow.trace.sessionを設定する(tagsではない)
  • MLflowがセッションIDでトレースを自動グループ化して、会話全体を評価する
  • 評価結果は各セッションの最初のトレース(時系列順)に保存される

mlflow.trace.sessiontagsではなく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が表示されます。

Screenshot 2026-02-25 at 7.55.23.png

セッションタブではより詳細にセッションの中身を確認できます。

Screenshot 2026-02-25 at 7.56.37.png

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 ユーザーが不満を感じたか(繰り返しの質問、トーンの変化など)

Screenshot 2026-02-25 at 7.57.46.png

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"])

Screenshot 2026-02-25 at 7.58.52.png

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"])

Screenshot 2026-02-25 at 8.01.44.png

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"])

Screenshot 2026-02-25 at 8.08.43.png

ハマりどころと対処法

実際に動かす中で遭遇したハマりどころをまとめます。

セッション列が空になる

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無料トライアル

Databricks無料トライアル

1
0
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?