Migrating from AzureML SDK v1 to azure-ai-projects in Microsoft Foundry
AzureML SDK v1 reaches end-of-support on June 30, 2026. CLI v1 already hit end-of-support in September 2025. If you have production pipelines built on azureml-sdk, azureml-pipeline, or azureml-train, this isn't optional cleanup — after the deadline, security patches stop, and Microsoft Foundry platform updates may introduce breaking behavior against unsupported code with no fix forthcoming. This post is the migration itself: a script, a test harness, and a rollout plan.
v1 to v2 conceptual mapping
Rollout timeline against the deadline
| Migration phase | Duration | Key activity |
|---|---|---|
| Inventory + v2 rewrite | ~4 weeks | Catalog v1 usage, rewrite pipelines as v2 YAML jobs |
| Drift testing | ~3 weeks | Compare v1 vs v2 outputs on identical inputs |
| Parallel run (shadow mode) | ~2 weeks | Run both in production, compare without acting on v2 |
| Cutover | ~1 week | Route traffic to v2, keep v1 as rollback |
Step 1: Inventory what's actually on v1
grep -rln "import azureml" --include="*.py" . | tee v1_files.txt
grep -rn "azureml.core.Workspace\|azureml.pipeline\|azureml.train" --include="*.py" .For each hit, classify: (a) workspace/resource management calls, (b) pipeline definitions, (c) training job submission, (d) model registration/deployment. Each category migrates differently.
Step 2: Workspace and connection migration
# v1
from azureml.core import Workspace
ws = Workspace.get(
name="my-workspace",
subscription_id="...",
resource_group="...",
)
# v2 / azure-ai-projects
from azure.ai.projects import AIProjectClient
from azure.identity import DefaultAzureCredential
client = AIProjectClient(
endpoint="https://your-foundry-resource.services.ai.azure.com/api/projects/your-project",
credential=DefaultAzureCredential(),
)The conceptual shift: v1's Workspace object was a generic container; v2's AIProjectClient is scoped to a Foundry project specifically, with the Hub handling what used to be workspace-level shared config. If your code assumed one workspace = one team's everything, you'll likely split that into a Hub (shared) + multiple Projects (per-workload) structure.
Step 3: Pipeline definition migration
# v1 — pipeline steps defined imperatively
from azureml.pipeline.core import Pipeline
from azureml.pipeline.steps import PythonScriptStep
step1 = PythonScriptStep(
script_name="preprocess.py",
compute_target=compute,
source_directory="./src",
)
pipeline = Pipeline(workspace=ws, steps=[step1])
# v2 — declarative YAML + SDK job submission
from azure.ai.ml import MLClient, command
from azure.ai.ml.entities import Job
ml_client = MLClient.from_config(credential=DefaultAzureCredential())
job = command(
code="./src",
command="python preprocess.py",
environment="azureml:my-env:1",
compute="my-compute-cluster",
)
ml_client.jobs.create_or_update(job)This is the part teams underestimate: v1 pipelines were built imperatively in Python; v2 favors YAML job specs with the SDK as a thin submission layer. A straight code-to-code port doesn't work well — plan to actually rewrite pipeline definitions as YAML, not just swap import statements.
Step 4: A test harness for behavioral drift
The dangerous failure mode isn't an import error — it's a pipeline that runs successfully on v2 but produces subtly different output (different default hyperparameters, different data splitting behavior, different environment resolution). Build a comparison harness before cutting over:
def compare_pipeline_outputs(v1_output_path, v2_output_path, tolerance=1e-4):
import pandas as pd
v1_df = pd.read_csv(v1_output_path)
v2_df = pd.read_csv(v2_output_path)
if v1_df.shape != v2_df.shape:
raise AssertionError(f"Shape mismatch: v1={v1_df.shape}, v2={v2_df.shape}")
numeric_cols = v1_df.select_dtypes(include="number").columns
diffs = (v1_df[numeric_cols] - v2_df[numeric_cols]).abs()
max_diff = diffs.max().max()
if max_diff > tolerance:
raise AssertionError(f"Output drift exceeds tolerance: max_diff={max_diff}")
return TrueRun both pipelines against identical input data and diff the outputs before decommissioning v1. Pay particular attention to anything involving random seeds, data shuffling, or environment-pinned package versions — these are where "equivalent" pipelines quietly diverge.
The environment definition trap
A migration detail teams consistently underestimate: v1's Environment objects and v2's environment YAML specs don't resolve dependencies identically, even when pointed at what looks like the same conda file.
# v2 environment YAML — looks equivalent to a v1 Environment.from_conda_specification call,
# but pip dependency resolution order and base image defaults can differ
name: my-training-env
channels:
- conda-forge
dependencies:
- python=3.10
- pip
- pip:
- scikit-learn==1.3.0
- pandas==2.0.0The failure mode: a v1 environment that pinned scikit-learn==1.3.0 might have silently resolved a different numpy transitive dependency version than the equivalent v2 spec does, because the base curated image each SDK version pulls from has drifted. This doesn't show up as an error — it shows up as a model that trains successfully but produces subtly different results than the v1 pipeline did on the same data. This is exactly why the drift-testing harness from Step 4 needs to run full end-to-end training, not just a syntax check that the pipeline submits successfully.
If you have compliance requirements around reproducibility (common in regulated industries), pin your base image explicitly by digest rather than tag, and freeze the full dependency tree with pip freeze or a lockfile rather than relying on loose version ranges — this closes the gap between "the pipeline runs" and "the pipeline produces the same result it did under v1."
Step 5: Rollout plan given the deadline
With June 30, 2026 as a hard date:
- Now – 6 weeks out: inventory complete, v2 equivalents written, drift-tested against v1 in a staging environment.
- 6–3 weeks out: run v1 and v2 in parallel in production (shadow mode), comparing outputs without acting on v2's results yet.
- 3–0 weeks out: cut traffic to v2, keep v1 code available (but not relied upon) as rollback for the first week.
- Post-deadline: remove v1 dependencies entirely; don't leave dead
azureml-sdkimports around as "just in case" — they'll bit-rot and confuse the next engineer.
What's next
With the SDK migration behind you, Part 5 moves to a capability v1 pipelines never really needed to worry about: designing function-calling schemas that hold up under real production traffic, including malformed calls and retry logic.
References
- Azure Machine Learning SDK v2 migration guide: https://learn.microsoft.com/en-us/azure/machine-learning/migrate-to-v2
- AzureML SDK v1 end-of-support notice: https://learn.microsoft.com/en-us/azure/machine-learning/reference-migrate-sdk-v1-mlflow-tracking
- azure-ai-ml Python SDK reference: https://learn.microsoft.com/en-us/python/api/overview/azure/ai-ml-readme
- Azure ML pipeline YAML schema: https://learn.microsoft.com/en-us/azure/machine-learning/reference-yaml-job-pipeline