Skip to content

[MLflow Demo] Add mlflow demo cli command#20048

Merged
B-Step62 merged 6 commits intomlflow:masterfrom
BenWilson2:stack/demo/cli
Feb 3, 2026
Merged

[MLflow Demo] Add mlflow demo cli command#20048
B-Step62 merged 6 commits intomlflow:masterfrom
BenWilson2:stack/demo/cli

Conversation

@BenWilson2
Copy link
Member

@BenWilson2 BenWilson2 commented Jan 16, 2026

🥞 Stacked PR

Use this link to review incremental changes.


Related Issues/PRs

#xxx

What changes are proposed in this pull request?

Adds a new mlflow demo cli command that:

  1. Starts up a sqlite DB in a temp directory with a local artifact path in another temp directory
  2. Starts the tracking server pointing to the ephemeral tracking DB, finding a random safe port (or allowing override)
  3. Generates the Demo experiment
  4. Populates all demo data to the Demo experiment
  5. Opens the MLflow UI in the system default browser

There is log suppression activated for the ephemeral demo server to reduce the noise in stdout. Logging can be turned on by setting the --debug flag when starting the demo server.

There is an option to start up the demo to point to an existing tracking server. A prompt is included when running the command (default "no") about whether an existing running tracking server is available. If 'y' is selected, an additional prompt for the tracking server URL is shown that will permit generating the demos and providing a link to see the demo experiment directly.

Screenshot 2026-01-16 at 11 28 54 AM

How is this PR tested?

  • Existing unit/integration tests
  • New unit/integration tests
  • Manual tests

Does this PR require documentation update?

  • No. You can skip the rest of this section.
  • Yes. I've updated:
    • Examples
    • API references
    • Instructions

Release Notes

Is this a user-facing change?

  • No. You can skip the rest of this section.
  • Yes. Give a description of this change to be included in the release notes for MLflow users.

What component(s), interfaces, languages, and integrations does this PR affect?

Components

  • area/tracking: Tracking Service, tracking client APIs, autologging
  • area/models: MLmodel format, model serialization/deserialization, flavors
  • area/model-registry: Model Registry service, APIs, and the fluent client calls for Model Registry
  • area/scoring: MLflow Model server, model deployment tools, Spark UDFs
  • area/evaluation: MLflow model evaluation features, evaluation metrics, and evaluation workflows
  • area/gateway: MLflow AI Gateway client APIs, server, and third-party integrations
  • area/prompts: MLflow prompt engineering features, prompt templates, and prompt management
  • area/tracing: MLflow Tracing features, tracing APIs, and LLM tracing functionality
  • area/projects: MLproject format, project running backends
  • area/uiux: Front-end, user experience, plotting, JavaScript, JavaScript dev server
  • area/build: Build and test infrastructure for MLflow
  • area/docs: MLflow documentation pages

How should the PR be classified in the release notes? Choose one:

  • rn/none - No description will be included. The PR will be mentioned only by the PR number in the "Small Bugfixes and Documentation Updates" section
  • rn/breaking-change - The PR will be mentioned in the "Breaking Changes" section
  • rn/feature - A new user-facing feature worth mentioning in the release notes
  • rn/bug-fix - A user-facing bug fix worth mentioning in the release notes
  • rn/documentation - A user-facing documentation change worth mentioning in the release notes

Should this PR be included in the next patch release?

Yes should be selected for bug fixes, documentation updates, and other small changes. No should be selected for new features and larger changes. If you're unsure about the release classification of this PR, leave this unchecked to let the maintainers decide.

What is a minor/patch release?
  • Minor release: a release that increments the second part of the version number (e.g., 1.2.0 -> 1.3.0).
    Bug fixes, doc updates and new features usually go into minor releases.
  • Patch release: a release that increments the third part of the version number (e.g., 1.2.0 -> 1.2.1).
    Bug fixes and doc updates usually go into patch releases.
  • Yes (this PR will be cherry-picked and included in the next patch release)
  • No (this PR will be included in the next minor release)

@BenWilson2 BenWilson2 changed the title add cli for demo [MLflow Demo] Add mlflow demo cli command Jan 16, 2026
@BenWilson2 BenWilson2 marked this pull request as ready for review January 16, 2026 01:47
Copilot AI review requested due to automatic review settings January 16, 2026 01:47
@github-actions
Copy link
Contributor

🛠 DevTools 🛠

Install mlflow from this PR

# mlflow
pip install git+https://github.com/mlflow/mlflow.git@refs/pull/20048/merge
# mlflow-skinny
pip install git+https://github.com/mlflow/mlflow.git@refs/pull/20048/merge#subdirectory=libs/skinny

For Databricks, use the following command:

%sh curl -LsSf https://raw.githubusercontent.com/mlflow/mlflow/HEAD/dev/install-skinny.sh | sh -s pull/20048/merge

@github-actions github-actions bot added area/tracking Tracking service, tracking client APIs, autologging rn/feature Mention under Features in Changelogs. labels Jan 16, 2026
Copy link
Contributor

Copilot AI left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Pull request overview

This PR adds a new mlflow demo CLI command that provides a frictionless way to explore MLflow's GenAI features. The command creates a temporary environment with pre-populated demo data including traces, evaluations, and prompts, then starts a local MLflow server and opens it in a browser.

Changes:

  • Adds new mlflow demo CLI command with port selection and browser control options
  • Implements demo data generation framework with versioning support for traces, evaluations, and prompts
  • Adds utility functions for port availability checking
  • Creates comprehensive test suite and integration tests
  • Adds HTTP API endpoints for demo data generation and deletion

Reviewed changes

Copilot reviewed 25 out of 26 changed files in this pull request and generated 8 comments.

Show a summary per file
File Description
mlflow/cli/demo.py New CLI command implementation that sets up ephemeral environment and launches server
mlflow/cli/init.py Registers the new demo command
mlflow/utils/init.py Adds is_port_available() helper function
mlflow/demo/ Complete demo framework with generators, registry, and base classes
mlflow/server/handlers.py API endpoints for programmatic demo generation/deletion
tests/demo/ Comprehensive test suite including unit, integration, and CLI tests
.github/workflows/master.yml CI configuration for running demo tests

💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.

max_results=100,
)

assert len(traces) == 70 # 35 v1 + 35 v2
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The expected trace count appears incorrect based on the test data. According to test_traces_generator.py line 98, the total should be 82 traces (35 v1 + 47 v2), not 70. The v2 set includes 12 additional prompt-linked traces that are not present in v1.

Suggested change
assert len(traces) == 70 # 35 v1 + 35 v2
assert len(traces) == 82 # 35 v1 + 47 v2

Copilot uses AI. Check for mistakes.
Comment on lines +86 to +90
def open_browser():
time.sleep(1.5)
# Open just the base URL - the React app will handle routing
# Opening with fragment directly causes encoding issues
webbrowser.open(f"http://127.0.0.1:{port}/")
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The hardcoded 1.5 second delay before opening the browser may not be sufficient on slower systems or may be unnecessarily long on fast systems. Consider using a more robust approach such as polling the server's health endpoint until it responds successfully, or making the delay configurable.

Copilot uses AI. Check for mistakes.

from urllib.parse import quote

encoded_experiment = quote(DEMO_EXPERIMENT_NAME, safe="")
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The variable experiment_url is constructed but only used for display in a console message. Given the comment on line 88-89 about fragment encoding issues, consider clarifying why this URL is displayed but not used for the actual browser navigation.

Suggested change
encoded_experiment = quote(DEMO_EXPERIMENT_NAME, safe="")
encoded_experiment = quote(DEMO_EXPERIMENT_NAME, safe="")
# Construct a direct link to the demo experiment traces for display only.
# Note: We intentionally do not use this URL with `webbrowser.open()` because
# opening with a fragment causes encoding issues (see comment below).

Copilot uses AI. Check for mistakes.

port = find_free_port()
assert isinstance(port, int)
assert 1 <= port <= 65535
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The port range validation is incorrect. Valid port numbers range from 1 to 65535, but line 210 checks 1 <= port <= 65535 which would allow port 0. Additionally, privileged ports (1-1023) typically cannot be bound without elevated permissions. The assertion should be 1024 <= port <= 65535 or the test should handle potential permission errors when testing with low port numbers.

Suggested change
assert 1 <= port <= 65535
assert 1024 <= port <= 65535

Copilot uses AI. Check for mistakes.
Comment on lines +358 to +371
# Also create the EntityAssociation record for proper filtering support
if trace_id and trace_def.linked_prompt:
try:
client = mlflow.MlflowClient()
prompt_version = client.get_prompt_version(
name=trace_def.linked_prompt.prompt_name,
version=str(trace_def.linked_prompt.version),
)
client.link_prompt_versions_to_trace(
prompt_versions=[prompt_version],
trace_id=trace_id,
)
except Exception:
_logger.debug("Failed to link prompt to trace", exc_info=True)
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The broad except Exception clause silently swallows all errors. While this is intentional for demo data generation resilience, consider catching more specific exceptions (e.g., MlflowException) to avoid masking unexpected errors like AttributeError or KeyError that might indicate code bugs.

Copilot uses AI. Check for mistakes.
def pass_fail_scorer(inputs, outputs) -> Feedback:
content = str(inputs) + str(outputs)
hash_input = f"{content}:{name}"
hash_val = int(hashlib.md5(hash_input.encode(), usedforsecurity=False).hexdigest()[:8], 16)
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usedforsecurity=False parameter is correct for this demo use case (deterministic scoring based on content hashes). However, this parameter was added in Python 3.9. While MLflow likely requires Python 3.9+, consider verifying the minimum supported Python version to ensure compatibility.

Copilot uses AI. Check for mistakes.
deleted_features = []
for name in demo_registry.list_generators():
generator = demo_registry.get(name)()
if generator._data_exists():
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The API endpoint is calling a private method _data_exists() on the generator. This breaks encapsulation. Consider exposing a public method or property for this check, or using the existing is_generated() method if appropriate.

Suggested change
if generator._data_exists():
if generator.is_generated():

Copilot uses AI. Check for mistakes.
--quiet --requires-ssh --ignore-flavors \
--ignore=tests/examples \
--ignore=tests/evaluate \
--ignore=tests/genai \
Copy link

Copilot AI Jan 16, 2026

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The demo tests are excluded from the main test job but run in a separate job. Consider adding a comment explaining why demo tests are separated (e.g., for parallelization, different dependencies, or to manage test execution time).

Suggested change
--ignore=tests/genai \
--ignore=tests/genai \
# Demo tests are excluded from the main test job and executed in a separate job to keep this
# job's runtime manageable and to isolate their additional dependencies.

Copilot uses AI. Check for mistakes.
@github-actions
Copy link
Contributor

github-actions bot commented Jan 16, 2026

Documentation preview for f424136 is available at:

More info
  • Ignore this comment if this PR does not change the documentation.
  • The preview is updated when a new commit is pushed to this PR.
  • This comment was created by this workflow run.
  • The documentation was built by this workflow run.

@BenWilson2 BenWilson2 force-pushed the stack/demo/cli branch 3 times, most recently from 6f6e5cd to a6f6926 Compare January 17, 2026 04:24
@BenWilson2 BenWilson2 added the team-review Trigger a team review request label Jan 18, 2026
pytest tests/genai --ignore tests/genai/test_genai_import_without_agent_sdk.py \
--ignore tests/genai/optimize --ignore tests/genai/prompts

demo:
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a reason we need to separate this job?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good call - there isn't a reason. I've updated the CI def to include it in the Python tests. Thanks for the call out!

@BenWilson2 BenWilson2 force-pushed the stack/demo/cli branch 2 times, most recently from 6178743 to 5b91a6f Compare January 26, 2026 15:00
@BenWilson2 BenWilson2 force-pushed the stack/demo/cli branch 3 times, most recently from 04d0776 to 6858f52 Compare January 28, 2026 19:16
if value is None:
return None
if not value.startswith(("http://", "https://")):
raise click.BadParameter("Tracking URI must start with 'http://' or 'https://'")
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why does this need to be http? Does demo generation logic break with sql tracking URI?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I actually wasn't even thinking of a local tracking server haha. Great point!

if tracking_uri is None:
tracking_uri = _get_tracking_uri_interactive(port)

if tracking_uri == _NEW_SERVER_SENTINEL:
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need _NEW_SERVER_SENTINEL? Does None work?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nah None will work. Updated!

Comment on lines +144 to +146
import mlflow
from mlflow.demo import generate_all_demos
from mlflow.demo.base import DEMO_EXPERIMENT_NAME
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Do we need lazy import?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah otherwise the eager load chain creates a huge import dependency that really messes up CI.

Comment on lines +198 to +202
with tempfile.TemporaryDirectory() as tmpdir:
tmpdir_path = Path(tmpdir)
db_path = tmpdir_path / "mlflow.db"
artifact_path = tmpdir_path / "artifacts"
artifact_path.mkdir()
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is the intention of using temporary directory is not to persist the demo data? I wonder if it is useful to keep the demo data around, otherwise user need to regenerate it again if they stop the demo command once.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There wasn't a strong reason to do it other than to not pollute a user's machine if they just wanted to check it out. I wasn't strongly opinionated either way. Updated to use a reserved directory name for persistence.


@catch_mlflow_exception
@_disable_if_artifacts_only
def _generate_demo():
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shall we add a parameter for list of features to generate demo, with default to all?

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

good idea! Will add

@BenWilson2 BenWilson2 force-pushed the stack/demo/cli branch 5 times, most recently from 30b2c83 to 095ef9a Compare January 30, 2026 03:26
@BenWilson2 BenWilson2 requested a review from B-Step62 January 30, 2026 03:29
Comment on lines +186 to +187
demo_dir = Path.home() / ".mlflow" / "demo"
demo_dir.mkdir(parents=True, exist_ok=True)
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@BenWilson2 Is there any reason for not using the current directory? I think it is confusing the mlflow server and mlflow demo behaves differently in terms of where to create database.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Excellent point - there's really no need to have a different path here. Updated to use the standard path

BenWilson2 and others added 6 commits February 2, 2026 10:13
…plates

- Add timestamp distribution across 7 days (v1 in days 0-3.5, v2 in days 3.5-7)
- Add token count estimation as span attributes (SpanAttributeKey.CHAT_USAGE)
- Add prompt-based traces with template rendering and variables
- Restructure sessions to have 2-4 turns each across 3 sessions
- Fix trace metadata by using InMemoryTraceManager directly
- Update test expectations for new trace counts (34 total: 4 RAG, 4 agent, 12 prompt, 14 session)

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Ben Wilson <benjamin.wilson@databricks.com>
…plates

- Add timestamp distribution across 7 days (v1 in days 0-3.5, v2 in days 3.5-7)
- Add token count estimation as span attributes (SpanAttributeKey.CHAT_USAGE)
- Add prompt-based traces with template rendering and variables
- Restructure sessions to have 2-4 turns each across 3 sessions
- Fix trace metadata by using InMemoryTraceManager directly
- Update test expectations for new trace counts (34 total: 4 RAG, 4 agent, 12 prompt, 14 session)

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Ben Wilson <benjamin.wilson@databricks.com>
Signed-off-by: Ben Wilson <benjamin.wilson@databricks.com>
Signed-off-by: Ben Wilson <benjamin.wilson@databricks.com>
Signed-off-by: Ben Wilson <benjamin.wilson@databricks.com>
Signed-off-by: Ben Wilson <benjamin.wilson@databricks.com>
Copy link
Collaborator

@B-Step62 B-Step62 left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

LGTM!

@B-Step62 B-Step62 added this pull request to the merge queue Feb 3, 2026
Merged via the queue into mlflow:master with commit ef73690 Feb 3, 2026
51 of 53 checks passed
BenWilson2 added a commit to BenWilson2/mlflow that referenced this pull request Feb 5, 2026
Signed-off-by: Ben Wilson <benjamin.wilson@databricks.com>
Co-authored-by: Claude <noreply@anthropic.com>
BenWilson2 added a commit to BenWilson2/mlflow that referenced this pull request Feb 11, 2026
Signed-off-by: Ben Wilson <benjamin.wilson@databricks.com>
Co-authored-by: Claude <noreply@anthropic.com>
BenWilson2 added a commit to BenWilson2/mlflow that referenced this pull request Feb 18, 2026
Signed-off-by: Ben Wilson <benjamin.wilson@databricks.com>
Co-authored-by: Claude <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

area/tracking Tracking service, tracking client APIs, autologging rn/feature Mention under Features in Changelogs. team-review Trigger a team review request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants