<?xml version="1.0" encoding="UTF-8"?>
<rss xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#" xmlns:taxo="http://purl.org/rss/1.0/modules/taxonomy/" version="2.0">
  <channel>
    <title>article Evaluate Multi‑Turn Chatbots on Databricks with MLflow: A Step‑by‑Step Guide in Technical Blog</title>
    <link>https://community.databricks.com/t5/technical-blog/evaluate-multi-turn-chatbots-on-databricks-with-mlflow-a-step-by/ba-p/114741</link>
    <description>&lt;P&gt;&lt;SPAN&gt;Hi, I’m Debu. I spend a lot of my day building and stress‑testing LLM‑powered systems, and one lesson keeps coming back: &lt;/SPAN&gt;&lt;STRONG&gt;if you don’t measure your agent’s behavior over an entire conversation, you’re flying blind&lt;/STRONG&gt;&lt;SPAN&gt;. Below is the exact notebook pattern I use on Databricks to score a chatbot’s performance turn‑by‑turn and track those numbers over time.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;STRONG&gt;Why Multi‑Turn Evaluation?&lt;/STRONG&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;Real users don’t stop after one message. They change topics, ask follow‑ups, and expect the bot to remember context. A single‑turn test can’t surface issues like:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;losing track of earlier instructions,&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;contradicting itself three turns later, or&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;drifting into unsafe territory when the dialog gets longer.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;That’s why every example you’ll see here treats the conversation as a list of messages—not isolated prompts.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;STRONG&gt;What You’ll Build&lt;/STRONG&gt;&lt;/H2&gt;
&lt;OL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Set up the notebook environment&lt;/STRONG&gt;&lt;SPAN&gt; with the &lt;/SPAN&gt;&lt;SPAN&gt;databricks-sdk&lt;/SPAN&gt;&lt;SPAN&gt; and &lt;/SPAN&gt;&lt;SPAN&gt;databricks-agents&lt;/SPAN&gt;&lt;SPAN&gt; libraries.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Create a small multi‑turn eval set&lt;/STRONG&gt;&lt;SPAN&gt;—two dialogs, each with its own rubric.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Write a tiny rule‑based agent&lt;/STRONG&gt;&lt;SPAN&gt; (swap it for your real model later) and wrap it in &lt;/SPAN&gt;&lt;SPAN&gt;mlflow.trace&lt;/SPAN&gt;&lt;SPAN&gt;.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Define global guidelines&lt;/STRONG&gt;&lt;SPAN&gt; for helpfulness, clarity, and safety.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Run &lt;/STRONG&gt;&lt;STRONG&gt;mlflow.evaluate&lt;/STRONG&gt;&lt;SPAN&gt; with the built‑in Databricks agent grader.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Store the scores in Delta&lt;/STRONG&gt;&lt;SPAN&gt; so you can watch trends and catch regressions.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/OL&gt;
&lt;H2&gt;&lt;STRONG&gt;1  — Prerequisites &amp;amp; Environment Setup&lt;/STRONG&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;I’m using DBR 14.3 LTS and MLflow ≥ 2.12. Install the extras and restart Python so Databricks picks them up:&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;%pip install databricks-sdk databricks-agents
dbutils.library.restartPython()
&lt;/LI-CODE&gt;
&lt;P&gt;&lt;SPAN&gt;Then pull in the usual suspects:&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;import mlflow
from mlflow.deployments import get_deploy_client  # optional for prod deploys
import pandas as pd&lt;/LI-CODE&gt;
&lt;H2&gt;&lt;STRONG&gt;2  — Crafting a Multi‑Turn Evaluation Dataset&lt;/STRONG&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;Each row holds the &lt;/SPAN&gt;&lt;STRONG&gt;full conversation so far&lt;/STRONG&gt;&lt;SPAN&gt; plus the grading guidelines for the next response:&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;eval_set = [
    {
        "request": {
            "messages": [
                {"role": "user", "content": "Hi"},
                {"role": "assistant", "content": "Hello! How can I help you today?"},
                {"role": "user", "content": "Tell me a joke"}
            ]
        },
        "guidelines": [
            "The response should be humorous but appropriate",
            "The response should be concise"
        ]
    },
    {
        "request": {
            "messages": [
                {"role": "user", "content": "What's the weather like?"},
                {"role": "assistant", "content": "I don't have real‑time weather data. You'd need to check a weather service for that information."},
                {"role": "user", "content": "Can you explain how LLMs work?"}
            ]
        },
        "guidelines": [
            "The response should be technical but accessible",
            "The response should include a brief explanation of attention mechanisms"
        ]
    }
]

# Convert to a DataFrame so mlflow.evaluate can treat it as a table‑like object
eval_df = pd.DataFrame(eval_set)&lt;/LI-CODE&gt;
&lt;P&gt;&lt;SPAN&gt;I keep it in Pandas because it’s easy to version and quick to inspect.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;STRONG&gt;3  — Implementing a Simple Agent (Demo Only)&lt;/STRONG&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;Here’s a throw‑away rule‑based agent. The only thing that matters is the &lt;/SPAN&gt;&lt;STRONG&gt;function signature&lt;/STRONG&gt;&lt;SPAN&gt;—&lt;/SPAN&gt;&lt;SPAN&gt;messages&lt;/SPAN&gt;&lt;SPAN&gt; comes in as the full dialog.&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;@mlflow.trace(span_type="AGENT")
def my_agent(messages):
    """A trivial rule‑based agent for illustration."""
    last_user_message = next((m["content"] for m in reversed(messages) if m["role"] == "user"), "")

    if "joke" in last_user_message.lower():
        return "Why did the AI go to art school? To learn how to draw conclusions!"
    elif "weather" in last_user_message.lower():
        return "I don't have access to real‑time weather data, but I can help you understand weather patterns in general."
    elif any(term in last_user_message.lower() for term in ["llm", "language model"]):
        return (
            "Large Language Models (LLMs) are AI systems trained on vast amounts of text data. "
            "They use transformer architectures with attention mechanisms to model relationships between tokens."
        )
    else:
        return f\"I understand you asked about: '{last_user_message}'. How can I help with that?\"&lt;/LI-CODE&gt;
&lt;P&gt;&lt;STRONG&gt;mlflow.trace&lt;/STRONG&gt;&lt;SPAN&gt; gives me latency and nested‑call traces for free—handy once I replace this with a real chain‑of‑thought or RAG pipeline.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;STRONG&gt;4  — Global Guidelines&lt;/STRONG&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;I add a second layer of checks that apply to &lt;STRONG&gt;every&lt;/STRONG&gt; row:&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;global_guidelines = {
    "helpfulness": ["The response must be helpful and directly address the user's question"],
    "clarity": ["The response must be clear and well‑structured"],
    "safety": ["The response must be safe and appropriate"]
}
&lt;/LI-CODE&gt;
&lt;H2&gt;&lt;STRONG&gt;5  — Running the Evaluation&lt;/STRONG&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;Now let’s grade the agent. Everything lives inside an MLflow run so I can track it later:&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;with mlflow.start_run(run_name="agent_evaluation_v1") as run:
    evaluation_results = mlflow.evaluate(
        data=eval_df,
        model=lambda request: my_agent(**request),
        model_type="databricks-agent",
        evaluator_config={
            "databricks-agent": {
                "global_guidelines": global_guidelines
            }
        }
    )&lt;/LI-CODE&gt;
&lt;P&gt;&lt;SPAN&gt;Under the hood, Databricks calls proprietary expert LLM judges and returns scores like &lt;/SPAN&gt;&lt;SPAN&gt;helpfulness_score&lt;/SPAN&gt;&lt;SPAN&gt;, &lt;/SPAN&gt;&lt;SPAN&gt;clarity_score&lt;/SPAN&gt;&lt;SPAN&gt;, and &lt;/SPAN&gt;&lt;SPAN&gt;safety_score&lt;/SPAN&gt;&lt;SPAN&gt;.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;STRONG&gt;6  — Inspecting the Results&lt;/STRONG&gt;&lt;/H2&gt;
&lt;LI-CODE lang="python"&gt;print("Aggregated metrics:\n", evaluation_results.metrics)
per_request_results = evaluation_results.tables["eval_results"]
print("\nPer‑request results:\n", per_request_results)&lt;/LI-CODE&gt;
&lt;P&gt;&lt;SPAN&gt;Need a quick visual? In a notebook just run:&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;display(per_request_results)&lt;/LI-CODE&gt;
&lt;P&gt;&lt;SPAN&gt;Aggregates catch regressions; per‑request rows tell me exactly &lt;/SPAN&gt;&lt;STRONG&gt;which turn&lt;/STRONG&gt;&lt;SPAN&gt; broke the guideline.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;STRONG&gt;7  — Persisting Metrics to Delta&lt;/STRONG&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;I push every run into Delta so I can chart trends and set alerts:&lt;/SPAN&gt;&lt;/P&gt;
&lt;LI-CODE lang="python"&gt;def append_metrics_to_table(run_name, mlflow_metrics, delta_table_name):
    data = {k: v for k, v in mlflow_metrics.items() if "error_count" not in k}
    data.update({"run_name": run_name, "timestamp": pd.Timestamp.now()})

    (spark.createDataFrame([data])
        .write.mode("append")
        .saveAsTable(delta_table_name))

# append_metrics_to_table("agent_evaluation_v1", evaluation_results.metrics, "catalog.schema.agent_eval_results")
&lt;/LI-CODE&gt;
&lt;P&gt;&lt;SPAN&gt;Hook this into DLT or a Databricks SQL dashboard and you’ve got continuous monitoring.&lt;/SPAN&gt;&lt;/P&gt;
&lt;H2&gt;&lt;STRONG&gt;Wrap‑Up &amp;amp; Next Steps&lt;/STRONG&gt;&lt;/H2&gt;
&lt;P&gt;&lt;SPAN&gt;You now have a &lt;/SPAN&gt;&lt;STRONG&gt;repeatable, multi‑turn evaluation loop&lt;/STRONG&gt;&lt;SPAN&gt; that is:&lt;/SPAN&gt;&lt;/P&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Reproducible&lt;/STRONG&gt;&lt;SPAN&gt; – every run is logged in MLflow.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Swappable&lt;/STRONG&gt;&lt;SPAN&gt; – replace the toy agent with your production model; the harness stays the same.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;STRONG&gt;Observable&lt;/STRONG&gt;&lt;SPAN&gt; – metrics and traces live in Delta and MLflow for long‑term visibility.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;H3&gt;&lt;STRONG&gt;Where I go from here&lt;/STRONG&gt;&lt;/H3&gt;
&lt;UL&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Expand the eval set with adversarial prompts and longer chats.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Track latency, token usage, and cost alongside quality scores.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Wire this into CI/CD—block model promotion if helpfulness drops.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;LI style="font-weight: 400;" aria-level="1"&gt;&lt;SPAN&gt;Store everything in Unity Catalog for governance.&lt;/SPAN&gt;&lt;SPAN&gt;&lt;BR /&gt;&lt;BR /&gt;&lt;/SPAN&gt;&lt;/LI&gt;
&lt;/UL&gt;
&lt;P&gt;&lt;SPAN&gt;Embed these checks early and your agents will improve with every release—no surprises when they hit real users. If you have questions or tweaks, ping me anytime. Happy shipping!&lt;/SPAN&gt;&lt;/P&gt;
&lt;P&gt;&amp;nbsp;&lt;/P&gt;</description>
    <pubDate>Tue, 08 Apr 2025 14:16:45 GMT</pubDate>
    <dc:creator>Debu-Sinha</dc:creator>
    <dc:date>2025-04-08T14:16:45Z</dc:date>
    <item>
      <title>Evaluate Multi‑Turn Chatbots on Databricks with MLflow: A Step‑by‑Step Guide</title>
      <link>https://community.databricks.com/t5/technical-blog/evaluate-multi-turn-chatbots-on-databricks-with-mlflow-a-step-by/ba-p/114741</link>
      <description>&lt;P&gt;Think your chatbot is ready for prime time? In this hands‑on walkthrough I show you—step by step—how I grade multi‑turn conversations on Databricks, surface clarity‑ and safety‑scores with MLflow, and log everything to Delta so regressions never slip through. Five minutes of setup, lifetime of confidence.&lt;/P&gt;</description>
      <pubDate>Tue, 08 Apr 2025 14:16:45 GMT</pubDate>
      <guid>https://community.databricks.com/t5/technical-blog/evaluate-multi-turn-chatbots-on-databricks-with-mlflow-a-step-by/ba-p/114741</guid>
      <dc:creator>Debu-Sinha</dc:creator>
      <dc:date>2025-04-08T14:16:45Z</dc:date>
    </item>
  </channel>
</rss>

