From d7ab1256f0de7232fae7f8ea0d200465bf62dbcb Mon Sep 17 00:00:00 2001 From: Jayesh Tanna Date: Wed, 4 Feb 2026 17:25:25 +0530 Subject: [PATCH 1/9] Parametrizing the finetuning test cases --- sdk/ai/azure-ai-projects/assets.json | 2 +- .../tests/finetuning/test_finetuning.py | 342 ++++++----------- .../tests/finetuning/test_finetuning_async.py | 359 ++++++------------ sdk/ai/azure-ai-projects/tests/test_base.py | 9 + 4 files changed, 246 insertions(+), 466 deletions(-) diff --git a/sdk/ai/azure-ai-projects/assets.json b/sdk/ai/azure-ai-projects/assets.json index cf8a8fe386d2..6b439df99b6a 100644 --- a/sdk/ai/azure-ai-projects/assets.json +++ b/sdk/ai/azure-ai-projects/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "python", "TagPrefix": "python/ai/azure-ai-projects", - "Tag": "python/ai/azure-ai-projects_7cddb7d06f" + "Tag": "python/ai/azure-ai-projects_28ff91480c" } diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py index 88dfd1c265b4..b9e4451d2d7f 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py @@ -5,7 +5,6 @@ # ------------------------------------ import pytest -import os import time from pathlib import Path from test_base import ( @@ -17,12 +16,43 @@ STANDARD_TRAINING_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, DEVELOPER_TIER_TRAINING_TYPE, + SUPERVISED_METHOD_TYPE, + DPO_METHOD_TYPE, + REINFORCEMENT_METHOD_TYPE, + OPENAI_MODEL_TYPE, + OSS_MODEL_TYPE, ) from devtools_testutils import recorded_by_proxy, RecordedTransport, is_live from azure.mgmt.cognitiveservices import CognitiveServicesManagementClient from azure.mgmt.cognitiveservices.models import Deployment, DeploymentProperties, DeploymentModel, Sku +# Intermediate decorators required for pytest.mark.parametrize with recorded_by_proxy +# See: https://github.com/Azure/azure-sdk-for-python/blob/main/doc/dev/test_proxy_troubleshooting.md#special-case-using-pytestmarkparametrize-with-recorded-tests +def _pass_cancel_args(fn): + def _wrapper(test_class, job_type, model_type, training_type, expected_method_type, **kwargs): + fn(test_class, job_type, model_type, training_type, expected_method_type, **kwargs) + return _wrapper + + +def _pass_create_args(fn): + def _wrapper(test_class, job_type, model_type, training_type, **kwargs): + fn(test_class, job_type, model_type, training_type, **kwargs) + return _wrapper + + +def _pass_deploy_args(fn): + def _wrapper(test_class, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs): + fn(test_class, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs) + return _wrapper + + +def _pass_retrieve_args(fn): + def _wrapper(test_class, job_type, expected_method_type, **kwargs): + fn(test_class, job_type, expected_method_type, **kwargs) + return _wrapper + + class TestFineTuning(TestBase): def _create_sft_finetuning_job(self, openai_client, train_file_id, validation_file_id, training_type, model_type): @@ -31,7 +61,7 @@ def _create_sft_finetuning_job(self, openai_client, train_file_id, validation_fi validation_file=validation_file_id, model=self.test_finetuning_params["sft"][model_type]["model_name"], method={ - "type": "supervised", + "type": SUPERVISED_METHOD_TYPE, "supervised": { "hyperparameters": { "n_epochs": self.test_finetuning_params["n_epochs"], @@ -49,7 +79,7 @@ def _create_dpo_finetuning_job(self, openai_client, train_file_id, validation_fi validation_file=validation_file_id, model=self.test_finetuning_params["dpo"][model_type]["model_name"], method={ - "type": "dpo", + "type": DPO_METHOD_TYPE, "dpo": { "hyperparameters": { "n_epochs": self.test_finetuning_params["n_epochs"], @@ -80,7 +110,7 @@ def _create_rft_finetuning_job(self, openai_client, train_file_id, validation_fi validation_file=validation_file_id, model=self.test_finetuning_params["rft"][model_type]["model_name"], method={ - "type": "reinforcement", + "type": REINFORCEMENT_METHOD_TYPE, "reinforcement": { "grader": grader, "hyperparameters": { @@ -367,148 +397,76 @@ def _test_deploy_and_infer_helper( f"[{test_prefix}] Successfully completed deployment and inference test for job: {completed_job_id}" ) - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sft_finetuning_create_job_openai_standard(self, **kwargs): - self._test_sft_create_job_helper("openai", STANDARD_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sft_finetuning_create_job_openai_developer(self, **kwargs): - self._test_sft_create_job_helper("openai", DEVELOPER_TIER_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sft_finetuning_create_job_openai_globalstandard(self, **kwargs): - self._test_sft_create_job_helper("openai", GLOBAL_STANDARD_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sft_finetuning_create_job_oss_globalstandard(self, **kwargs): - self._test_sft_create_job_helper("oss", GLOBAL_STANDARD_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_dpo_finetuning_create_job_openai_standard(self, **kwargs): - self._test_dpo_create_job_helper("openai", STANDARD_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_dpo_finetuning_create_job_openai__developer(self, **kwargs): - self._test_dpo_create_job_helper("openai", DEVELOPER_TIER_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_dpo_finetuning_create_job_openai_globalstandard(self, **kwargs): - self._test_dpo_create_job_helper("openai", GLOBAL_STANDARD_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_rft_finetuning_create_job_openai_standard(self, **kwargs): - self._test_rft_create_job_helper("openai", STANDARD_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_rft_finetuning_create_job_openai_globalstandard(self, **kwargs): - self._test_rft_create_job_helper("openai", GLOBAL_STANDARD_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_rft_finetuning_create_job_openai_developer(self, **kwargs): - self._test_rft_create_job_helper("openai", DEVELOPER_TIER_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_finetuning_retrieve_sft_job(self, **kwargs): + @pytest.mark.parametrize("job_type,model_type,training_type", [ + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), + (SFT_JOB_TYPE, OSS_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), + ]) + @servicePreparer() + @_pass_create_args + @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) + def test_create_job(self, job_type, model_type, training_type, **kwargs): + if job_type == SFT_JOB_TYPE: + self._test_sft_create_job_helper(model_type, training_type, **kwargs) + elif job_type == DPO_JOB_TYPE: + self._test_dpo_create_job_helper(model_type, training_type, **kwargs) + elif job_type == RFT_JOB_TYPE: + self._test_rft_create_job_helper(model_type, training_type, **kwargs) + else: + raise ValueError(f"Unsupported job type: {job_type}") + + @servicePreparer() + @pytest.mark.parametrize("job_type,expected_method_type", [ + (SFT_JOB_TYPE, SUPERVISED_METHOD_TYPE), + (DPO_JOB_TYPE, DPO_METHOD_TYPE), + (RFT_JOB_TYPE, REINFORCEMENT_METHOD_TYPE), + ]) + @servicePreparer() + @_pass_retrieve_args + @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) + def test_retrieve_job(self, job_type, expected_method_type, **kwargs): with self.create_client(**kwargs) as project_client: with project_client.get_openai_client() as openai_client: - train_file, validation_file = self._upload_test_files(openai_client, SFT_JOB_TYPE) - - fine_tuning_job = self._create_sft_finetuning_job( - openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, "openai" - ) - print(f"[test_finetuning_retrieve_sft] Created job: {fine_tuning_job.id}") - - retrieved_job = openai_client.fine_tuning.jobs.retrieve(fine_tuning_job.id) - print(f"[test_finetuning_retrieve_sft] Retrieved job: {retrieved_job.id}") - - TestBase.validate_fine_tuning_job(retrieved_job, expected_job_id=fine_tuning_job.id) - TestBase.assert_equal_or_not_none(retrieved_job.training_file, train_file.id) - TestBase.assert_equal_or_not_none(retrieved_job.validation_file, validation_file.id) - TestBase.assert_equal_or_not_none(retrieved_job.trainingType.lower(), STANDARD_TRAINING_TYPE.lower()) - assert retrieved_job.method is not None, "Method should not be None for SFT job" - TestBase.assert_equal_or_not_none(retrieved_job.method.type, "supervised") - assert ( - self.test_finetuning_params["sft"]["openai"]["model_name"] in retrieved_job.model - ), f"Expected model name {self.test_finetuning_params['sft']['openai']['model_name']} not found in {retrieved_job.model}" - - openai_client.fine_tuning.jobs.cancel(fine_tuning_job.id) - print(f"[test_finetuning_retrieve_sft] Cancelled job: {fine_tuning_job.id}") - - self._cleanup_test_file(openai_client, train_file.id) - self._cleanup_test_file(openai_client, validation_file.id) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_finetuning_retrieve_dpo_job(self, **kwargs): - with self.create_client(**kwargs) as project_client: - with project_client.get_openai_client() as openai_client: - - train_file, validation_file = self._upload_test_files(openai_client, DPO_JOB_TYPE) - - fine_tuning_job = self._create_dpo_finetuning_job( - openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, "openai" - ) - print(f"[test_finetuning_retrieve_dpo] Created job: {fine_tuning_job.id}") - - retrieved_job = openai_client.fine_tuning.jobs.retrieve(fine_tuning_job.id) - print(f"[test_finetuning_retrieve_dpo] Retrieved job: {retrieved_job.id}") - - TestBase.validate_fine_tuning_job(retrieved_job, expected_job_id=fine_tuning_job.id) - TestBase.assert_equal_or_not_none(retrieved_job.training_file, train_file.id) - TestBase.assert_equal_or_not_none(retrieved_job.validation_file, validation_file.id) - TestBase.assert_equal_or_not_none(retrieved_job.trainingType.lower(), STANDARD_TRAINING_TYPE.lower()) - assert retrieved_job.method is not None, "Method should not be None for DPO job" - TestBase.assert_equal_or_not_none(retrieved_job.method.type, "dpo") - assert ( - self.test_finetuning_params["dpo"]["openai"]["model_name"] in retrieved_job.model - ), f"Expected model name {self.test_finetuning_params['dpo']['openai']['model_name']} not found in {retrieved_job.model}" - - openai_client.fine_tuning.jobs.cancel(fine_tuning_job.id) - print(f"[test_finetuning_retrieve_dpo] Cancelled job: {fine_tuning_job.id}") - - self._cleanup_test_file(openai_client, train_file.id) - self._cleanup_test_file(openai_client, validation_file.id) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_finetuning_retrieve_rft_job(self, **kwargs): - with self.create_client(**kwargs) as project_client: - with project_client.get_openai_client() as openai_client: + train_file, validation_file = self._upload_test_files(openai_client, job_type) - train_file, validation_file = self._upload_test_files(openai_client, RFT_JOB_TYPE) + if job_type == SFT_JOB_TYPE: + fine_tuning_job = self._create_sft_finetuning_job( + openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, OPENAI_MODEL_TYPE + ) + elif job_type == DPO_JOB_TYPE: + fine_tuning_job = self._create_dpo_finetuning_job( + openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, OPENAI_MODEL_TYPE + ) + elif job_type == RFT_JOB_TYPE: + fine_tuning_job = self._create_rft_finetuning_job( + openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, OPENAI_MODEL_TYPE + ) - fine_tuning_job = self._create_rft_finetuning_job( - openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, "openai" - ) - print(f"[test_finetuning_retrieve_rft] Created job: {fine_tuning_job.id}") + print(f"[test_finetuning_retrieve_{job_type}] Created job: {fine_tuning_job.id}") retrieved_job = openai_client.fine_tuning.jobs.retrieve(fine_tuning_job.id) - print(f"[test_finetuning_retrieve_rft] Retrieved job: {retrieved_job.id}") + print(f"[test_finetuning_retrieve_{job_type}] Retrieved job: {retrieved_job.id}") TestBase.validate_fine_tuning_job(retrieved_job, expected_job_id=fine_tuning_job.id) TestBase.assert_equal_or_not_none(retrieved_job.training_file, train_file.id) TestBase.assert_equal_or_not_none(retrieved_job.validation_file, validation_file.id) TestBase.assert_equal_or_not_none(retrieved_job.trainingType.lower(), STANDARD_TRAINING_TYPE.lower()) - assert retrieved_job.method is not None, "Method should not be None for RFT job" - TestBase.assert_equal_or_not_none(retrieved_job.method.type, "reinforcement") + assert retrieved_job.method is not None, f"Method should not be None for {job_type} job" + TestBase.assert_equal_or_not_none(retrieved_job.method.type, expected_method_type) assert ( - self.test_finetuning_params["rft"]["openai"]["model_name"] in retrieved_job.model - ), f"Expected model name {self.test_finetuning_params['rft']['openai']['model_name']} not found in {retrieved_job.model}" + self.test_finetuning_params[job_type][OPENAI_MODEL_TYPE]["model_name"] in retrieved_job.model + ), f"Expected model name {self.test_finetuning_params[job_type][OPENAI_MODEL_TYPE]['model_name']} not found in {retrieved_job.model}" openai_client.fine_tuning.jobs.cancel(fine_tuning_job.id) - print(f"[test_finetuning_retrieve_rft] Cancelled job: {fine_tuning_job.id}") + print(f"[test_finetuning_retrieve_{job_type}] Cancelled job: {fine_tuning_job.id}") self._cleanup_test_file(openai_client, train_file.id) self._cleanup_test_file(openai_client, validation_file.id) @@ -531,55 +489,23 @@ def test_finetuning_list_jobs(self, **kwargs): print(f"[test_finetuning_list] Validated job {job.id} with status {job.status}") print(f"[test_finetuning_list] Successfully validated list functionality with {len(jobs_list)} jobs") + @pytest.mark.parametrize("job_type,model_type,training_type,expected_method_type", [ + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), + (SFT_JOB_TYPE, OSS_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, DPO_METHOD_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, DPO_METHOD_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, DPO_METHOD_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, REINFORCEMENT_METHOD_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, REINFORCEMENT_METHOD_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, REINFORCEMENT_METHOD_TYPE), + ]) @servicePreparer() + @_pass_cancel_args @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sft_cancel_job_openai_standard(self, **kwargs): - self._test_cancel_job_helper(SFT_JOB_TYPE, "openai", STANDARD_TRAINING_TYPE, "supervised", **kwargs) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sft_cancel_job_openai_globalstandard(self, **kwargs): - self._test_cancel_job_helper(SFT_JOB_TYPE, "openai", GLOBAL_STANDARD_TRAINING_TYPE, "supervised", **kwargs) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sft_cancel_job_openai_developer(self, **kwargs): - self._test_cancel_job_helper(SFT_JOB_TYPE, "openai", DEVELOPER_TIER_TRAINING_TYPE, "supervised", **kwargs) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_sft_cancel_job_oss_globalstandard(self, **kwargs): - self._test_cancel_job_helper(SFT_JOB_TYPE, "oss", GLOBAL_STANDARD_TRAINING_TYPE, "supervised", **kwargs) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_dpo_cancel_job_openai_standard(self, **kwargs): - self._test_cancel_job_helper(DPO_JOB_TYPE, "openai", STANDARD_TRAINING_TYPE, "dpo", **kwargs) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_dpo_cancel_job_openai_globalstandard(self, **kwargs): - self._test_cancel_job_helper(DPO_JOB_TYPE, "openai", GLOBAL_STANDARD_TRAINING_TYPE, "dpo", **kwargs) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_dpo_cancel_job_openai_developer(self, **kwargs): - self._test_cancel_job_helper(DPO_JOB_TYPE, "openai", DEVELOPER_TIER_TRAINING_TYPE, "dpo", **kwargs) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_rft_cancel_job_openai_standard(self, **kwargs): - self._test_cancel_job_helper(RFT_JOB_TYPE, "openai", STANDARD_TRAINING_TYPE, "reinforcement", **kwargs) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_rft_cancel_job_openai_globalstandard(self, **kwargs): - self._test_cancel_job_helper(RFT_JOB_TYPE, "openai", GLOBAL_STANDARD_TRAINING_TYPE, "reinforcement", **kwargs) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_rft_cancel_job_openai_developer(self, **kwargs): - self._test_cancel_job_helper(RFT_JOB_TYPE, "openai", DEVELOPER_TIER_TRAINING_TYPE, "reinforcement", **kwargs) + def test_cancel_job(self, job_type, model_type, training_type, expected_method_type, **kwargs): + self._test_cancel_job_helper(job_type, model_type, training_type, expected_method_type, **kwargs) @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) @@ -592,7 +518,7 @@ def test_finetuning_list_events(self, **kwargs): train_file, validation_file = self._upload_test_files(openai_client, SFT_JOB_TYPE) fine_tuning_job = self._create_sft_finetuning_job( - openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, "openai" + openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, OPENAI_MODEL_TYPE ) print(f"[test_finetuning_list_events] Created job: {fine_tuning_job.id}") @@ -719,54 +645,22 @@ def test_finetuning_list_checkpoints(self, **kwargs): f"[test_finetuning_list_checkpoints] Successfully validated {len(checkpoints_list)} checkpoints for job: {completed_job_id}" ) + @pytest.mark.parametrize("job_id_env_var,deployment_format,deployment_capacity,test_prefix,inference_content", [ + ("completed_oai_model_sft_fine_tuning_job_id", "OpenAI", 50, "test_deploy_infer_oai_sft", "Who invented the telephone?"), + ("completed_oai_model_rft_fine_tuning_job_id", "OpenAI", 50, "test_deploy_infer_oai_rft", "Target: 85 Numbers: [20, 4, 15, 10]. Find a mathematical expression using all numbers exactly once to reach the target."), + ("completed_oai_model_dpo_fine_tuning_job_id", "OpenAI", 50, "test_deploy_infer_oai_dpo", "Explain machine learning in simple terms."), + ("completed_oss_model_sft_fine_tuning_job_id", "Mistral AI", 50, "test_deploy_infer_oss_sft", "Who invented the telephone?"), + ]) @servicePreparer() + @_pass_deploy_args @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_finetuning_deploy_and_infer_oai_model_sft_job(self, **kwargs): - completed_job_id = kwargs.get("completed_oai_model_sft_fine_tuning_job_id") - self._test_deploy_and_infer_helper( - completed_job_id, - "OpenAI", - 50, - "test_finetuning_deploy_and_infer_oai_model_sft_job", - "Who invented the telephone?", - **kwargs, - ) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_finetuning_deploy_and_infer_oai_model_rft_job(self, **kwargs): - completed_job_id = kwargs.get("completed_oai_model_rft_fine_tuning_job_id") - self._test_deploy_and_infer_helper( - completed_job_id, - "OpenAI", - 50, - "test_finetuning_deploy_and_infer_oai_model_rft_job", - "Target: 85 Numbers: [20, 4, 15, 10]. Find a mathematical expression using all numbers exactly once to reach the target.", - **kwargs, - ) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_finetuning_deploy_and_infer_oai_model_dpo_job(self, **kwargs): - completed_job_id = kwargs.get("completed_oai_model_dpo_fine_tuning_job_id") - self._test_deploy_and_infer_helper( - completed_job_id, - "OpenAI", - 50, - "test_finetuning_deploy_and_infer_oai_model_dpo_job", - "What is the largest desert in the world?", - **kwargs, - ) - - @servicePreparer() - @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_finetuning_deploy_and_infer_oss_model_sft_job(self, **kwargs): - completed_job_id = kwargs.get("completed_oss_model_sft_fine_tuning_job_id") + def test_deploy_and_infer_job(self, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs): + completed_job_id = kwargs.get(job_id_env_var) self._test_deploy_and_infer_helper( completed_job_id, - "Mistral AI", - 50, - "test_finetuning_deploy_and_infer_oss_model_sft_job", - "Who invented the telephone?", + deployment_format, + deployment_capacity, + test_prefix, + inference_content, **kwargs, ) diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py index b609c15eaf2a..15c786b15343 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py @@ -5,7 +5,6 @@ # ------------------------------------ import pytest -import os import asyncio from pathlib import Path from test_base import ( @@ -17,6 +16,11 @@ STANDARD_TRAINING_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, DEVELOPER_TIER_TRAINING_TYPE, + SUPERVISED_METHOD_TYPE, + DPO_METHOD_TYPE, + REINFORCEMENT_METHOD_TYPE, + OPENAI_MODEL_TYPE, + OSS_MODEL_TYPE, ) from devtools_testutils.aio import recorded_by_proxy_async from devtools_testutils import is_live, RecordedTransport @@ -24,6 +28,32 @@ from azure.mgmt.cognitiveservices.models import Deployment, DeploymentProperties, DeploymentModel, Sku +# Intermediate decorator required for pytest.mark.parametrize with recorded_by_proxy_async +# See: https://github.com/Azure/azure-sdk-for-python/blob/main/doc/dev/test_proxy_troubleshooting.md#special-case-using-pytestmarkparametrize-with-recorded-tests +def _pass_create_args_async(fn): + async def _wrapper(test_class, job_type, model_type, training_type, **kwargs): + await fn(test_class, job_type, model_type, training_type, **kwargs) + return _wrapper + + +def _pass_cancel_args_async(fn): + async def _wrapper(test_class, job_type, model_type, training_type, expected_method_type, **kwargs): + await fn(test_class, job_type, model_type, training_type, expected_method_type, **kwargs) + return _wrapper + + +def _pass_deploy_args_async(fn): + async def _wrapper(test_class, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs): + await fn(test_class, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs) + return _wrapper + + +def _pass_retrieve_args_async(fn): + async def _wrapper(test_class, job_type, expected_method_type, **kwargs): + await fn(test_class, job_type, expected_method_type, **kwargs) + return _wrapper + + class TestFineTuningAsync(TestBase): async def _create_sft_finetuning_job_async( @@ -34,7 +64,7 @@ async def _create_sft_finetuning_job_async( validation_file=validation_file_id, model=self.test_finetuning_params["sft"][model_type]["model_name"], method={ - "type": "supervised", + "type": SUPERVISED_METHOD_TYPE, "supervised": { "hyperparameters": { "n_epochs": self.test_finetuning_params["n_epochs"], @@ -54,7 +84,7 @@ async def _create_dpo_finetuning_job_async( validation_file=validation_file_id, model=self.test_finetuning_params["dpo"][model_type]["model_name"], method={ - "type": "dpo", + "type": DPO_METHOD_TYPE, "dpo": { "hyperparameters": { "n_epochs": self.test_finetuning_params["n_epochs"], @@ -87,7 +117,7 @@ async def _create_rft_finetuning_job_async( validation_file=validation_file_id, model=self.test_finetuning_params["rft"][model_type]["model_name"], method={ - "type": "reinforcement", + "type": REINFORCEMENT_METHOD_TYPE, "reinforcement": { "grader": grader, "hyperparameters": { @@ -209,7 +239,7 @@ async def _test_sft_create_job_helper_async(self, model_type, training_type, **k ) # For OSS models, validate the specific model name - if model_type == "oss": + if model_type == OSS_MODEL_TYPE: TestBase.validate_fine_tuning_job( fine_tuning_job, expected_model=self.test_finetuning_params["sft"]["oss"]["model_name"] ) @@ -376,154 +406,77 @@ async def _test_deploy_and_infer_helper_async( f"[{test_prefix}] Successfully completed deployment and inference test for job: {completed_job_id}" ) - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_sft_finetuning_create_job_openai_standard_async(self, **kwargs): - await self._test_sft_create_job_helper_async("openai", STANDARD_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_sft_finetuning_create_job_openai_globalstandard_async(self, **kwargs): - await self._test_sft_create_job_helper_async("openai", GLOBAL_STANDARD_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_sft_finetuning_create_job_openai_developer_async(self, **kwargs): - await self._test_sft_create_job_helper_async("openai", DEVELOPER_TIER_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_sft_finetuning_create_job_oss_globalstandard_async(self, **kwargs): - await self._test_sft_create_job_helper_async("oss", GLOBAL_STANDARD_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_dpo_finetuning_create_job_openai_standard_async(self, **kwargs): - await self._test_dpo_create_job_helper_async("openai", STANDARD_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_dpo_finetuning_create_job_openai_globalstandard_async(self, **kwargs): - await self._test_dpo_create_job_helper_async("openai", GLOBAL_STANDARD_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_dpo_finetuning_create_job_openai_developer_async(self, **kwargs): - await self._test_dpo_create_job_helper_async("openai", DEVELOPER_TIER_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_rft_finetuning_create_job_openai_standard_async(self, **kwargs): - await self._test_rft_create_job_helper_async("openai", STANDARD_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_rft_finetuning_create_job_openai_globalstandard_async(self, **kwargs): - await self._test_rft_create_job_helper_async("openai", GLOBAL_STANDARD_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_rft_finetuning_create_job_openai_developer_async(self, **kwargs): - await self._test_rft_create_job_helper_async("openai", DEVELOPER_TIER_TRAINING_TYPE, **kwargs) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_finetuning_retrieve_sft_job_async(self, **kwargs): + @pytest.mark.parametrize("job_type,model_type,training_type", [ + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), + (SFT_JOB_TYPE, OSS_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), + ]) + @servicePreparer() + @_pass_create_args_async + @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) + async def test_create_job_async(self, job_type, model_type, training_type, **kwargs): + if job_type == SFT_JOB_TYPE: + await self._test_sft_create_job_helper_async(model_type, training_type, **kwargs) + elif job_type == DPO_JOB_TYPE: + await self._test_dpo_create_job_helper_async(model_type, training_type, **kwargs) + elif job_type == RFT_JOB_TYPE: + await self._test_rft_create_job_helper_async(model_type, training_type, **kwargs) + else: + raise ValueError(f"Unsupported job type: {job_type}") + + @pytest.mark.parametrize("job_type,expected_method_type", [ + (SFT_JOB_TYPE, SUPERVISED_METHOD_TYPE), + (DPO_JOB_TYPE, DPO_METHOD_TYPE), + (RFT_JOB_TYPE, REINFORCEMENT_METHOD_TYPE), + ]) + @servicePreparer() + @_pass_retrieve_args_async + @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) + async def test_retrieve_job_async(self, job_type, expected_method_type, **kwargs): project_client = self.create_async_client(**kwargs) openai_client = project_client.get_openai_client() async with project_client: - train_file, validation_file = await self._upload_test_files_async(openai_client, SFT_JOB_TYPE) - - fine_tuning_job = await self._create_sft_finetuning_job_async( - openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, "openai" - ) - print(f"[test_finetuning_retrieve_sft] Created job: {fine_tuning_job.id}") - - retrieved_job = await openai_client.fine_tuning.jobs.retrieve(fine_tuning_job.id) - print(f"[test_finetuning_retrieve_sft] Retrieved job: {retrieved_job.id}") - - TestBase.validate_fine_tuning_job(retrieved_job, expected_job_id=fine_tuning_job.id) - TestBase.assert_equal_or_not_none(retrieved_job.training_file, train_file.id) - TestBase.assert_equal_or_not_none(retrieved_job.validation_file, validation_file.id) - TestBase.assert_equal_or_not_none(retrieved_job.trainingType.lower(), STANDARD_TRAINING_TYPE.lower()) - assert retrieved_job.method is not None, "Method should not be None for SFT job" - TestBase.assert_equal_or_not_none(retrieved_job.method.type, "supervised") - assert ( - self.test_finetuning_params["sft"]["openai"]["model_name"] in retrieved_job.model - ), f"Expected model name {self.test_finetuning_params['sft']['openai']['model_name']} not found in {retrieved_job.model}" - - await openai_client.fine_tuning.jobs.cancel(fine_tuning_job.id) - print(f"[test_finetuning_retrieve_sft] Cancelled job: {fine_tuning_job.id}") - - await self._cleanup_test_file_async(openai_client, train_file.id) - await self._cleanup_test_file_async(openai_client, validation_file.id) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_finetuning_retrieve_dpo_job_async(self, **kwargs): - project_client = self.create_async_client(**kwargs) - openai_client = project_client.get_openai_client() - - async with project_client: - - train_file, validation_file = await self._upload_test_files_async(openai_client, DPO_JOB_TYPE) - - fine_tuning_job = await self._create_dpo_finetuning_job_async( - openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, "openai" - ) - print(f"[test_finetuning_retrieve_dpo] Created job: {fine_tuning_job.id}") - - retrieved_job = await openai_client.fine_tuning.jobs.retrieve(fine_tuning_job.id) - print(f"[test_finetuning_retrieve_dpo] Retrieved job: {retrieved_job.id}") - - TestBase.validate_fine_tuning_job(retrieved_job, expected_job_id=fine_tuning_job.id) - TestBase.assert_equal_or_not_none(retrieved_job.training_file, train_file.id) - TestBase.assert_equal_or_not_none(retrieved_job.validation_file, validation_file.id) - TestBase.assert_equal_or_not_none(retrieved_job.trainingType.lower(), STANDARD_TRAINING_TYPE.lower()) - assert retrieved_job.method is not None, "Method should not be None for DPO job" - TestBase.assert_equal_or_not_none(retrieved_job.method.type, "dpo") - assert ( - self.test_finetuning_params["dpo"]["openai"]["model_name"] in retrieved_job.model - ), f"Expected model name {self.test_finetuning_params['dpo']['openai']['model_name']} not found in {retrieved_job.model}" - - await openai_client.fine_tuning.jobs.cancel(fine_tuning_job.id) - print(f"[test_finetuning_retrieve_dpo] Cancelled job: {fine_tuning_job.id}") - - await self._cleanup_test_file_async(openai_client, train_file.id) - await self._cleanup_test_file_async(openai_client, validation_file.id) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_finetuning_retrieve_rft_job_async(self, **kwargs): - project_client = self.create_async_client(**kwargs) - openai_client = project_client.get_openai_client() - - async with project_client: + train_file, validation_file = await self._upload_test_files_async(openai_client, job_type) - train_file, validation_file = await self._upload_test_files_async(openai_client, RFT_JOB_TYPE) + if job_type == SFT_JOB_TYPE: + fine_tuning_job = await self._create_sft_finetuning_job_async( + openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, OPENAI_MODEL_TYPE + ) + elif job_type == DPO_JOB_TYPE: + fine_tuning_job = await self._create_dpo_finetuning_job_async( + openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, OPENAI_MODEL_TYPE + ) + elif job_type == RFT_JOB_TYPE: + fine_tuning_job = await self._create_rft_finetuning_job_async( + openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, OPENAI_MODEL_TYPE + ) - fine_tuning_job = await self._create_rft_finetuning_job_async( - openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, "openai" - ) - print(f"[test_finetuning_retrieve_rft] Created job: {fine_tuning_job.id}") + print(f"[test_finetuning_retrieve_{job_type}] Created job: {fine_tuning_job.id}") retrieved_job = await openai_client.fine_tuning.jobs.retrieve(fine_tuning_job.id) - print(f"[test_finetuning_retrieve_rft] Retrieved job: {retrieved_job.id}") + print(f"[test_finetuning_retrieve_{job_type}] Retrieved job: {retrieved_job.id}") TestBase.validate_fine_tuning_job(retrieved_job, expected_job_id=fine_tuning_job.id) TestBase.assert_equal_or_not_none(retrieved_job.training_file, train_file.id) TestBase.assert_equal_or_not_none(retrieved_job.validation_file, validation_file.id) TestBase.assert_equal_or_not_none(retrieved_job.trainingType.lower(), STANDARD_TRAINING_TYPE.lower()) - assert retrieved_job.method is not None, "Method should not be None for RFT job" - TestBase.assert_equal_or_not_none(retrieved_job.method.type, "reinforcement") + assert retrieved_job.method is not None, f"Method should not be None for {job_type} job" + TestBase.assert_equal_or_not_none(retrieved_job.method.type, expected_method_type) assert ( - self.test_finetuning_params["rft"]["openai"]["model_name"] in retrieved_job.model - ), f"Expected model name {self.test_finetuning_params['rft']['openai']['model_name']} not found in {retrieved_job.model}" + self.test_finetuning_params[job_type][OPENAI_MODEL_TYPE]["model_name"] in retrieved_job.model + ), f"Expected model name {self.test_finetuning_params[job_type][OPENAI_MODEL_TYPE]['model_name']} not found in {retrieved_job.model}" await openai_client.fine_tuning.jobs.cancel(fine_tuning_job.id) - print(f"[test_finetuning_retrieve_rft] Cancelled job: {fine_tuning_job.id}") + print(f"[test_finetuning_retrieve_{job_type}] Cancelled job: {fine_tuning_job.id}") await self._cleanup_test_file_async(openai_client, train_file.id) await self._cleanup_test_file_async(openai_client, validation_file.id) @@ -551,67 +504,23 @@ async def test_finetuning_list_jobs_async(self, **kwargs): print(f"[test_finetuning_list] Validated job {job.id} with status {job.status}") print(f"[test_finetuning_list] Successfully validated list functionality with {len(jobs_list)} jobs") + @pytest.mark.parametrize("job_type,model_type,training_type,expected_method_type", [ + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), + (SFT_JOB_TYPE, OSS_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, DPO_METHOD_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, DPO_METHOD_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, DPO_METHOD_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, REINFORCEMENT_METHOD_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, REINFORCEMENT_METHOD_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, REINFORCEMENT_METHOD_TYPE), + ]) @servicePreparer() + @_pass_cancel_args_async @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_sft_cancel_job_openai_standard_async(self, **kwargs): - await self._test_cancel_job_helper_async(SFT_JOB_TYPE, "openai", STANDARD_TRAINING_TYPE, "supervised", **kwargs) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_sft_cancel_job_openai_developer_async(self, **kwargs): - await self._test_cancel_job_helper_async( - SFT_JOB_TYPE, "openai", DEVELOPER_TIER_TRAINING_TYPE, "supervised", **kwargs - ) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_sft_cancel_job_openai_globalstandard_async(self, **kwargs): - await self._test_cancel_job_helper_async( - SFT_JOB_TYPE, "openai", GLOBAL_STANDARD_TRAINING_TYPE, "supervised", **kwargs - ) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_sft_cancel_job_oss_globalstandard_async(self, **kwargs): - await self._test_cancel_job_helper_async( - SFT_JOB_TYPE, "oss", GLOBAL_STANDARD_TRAINING_TYPE, "supervised", **kwargs - ) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_dpo_cancel_job_openai_standard_async(self, **kwargs): - await self._test_cancel_job_helper_async(DPO_JOB_TYPE, "openai", STANDARD_TRAINING_TYPE, "dpo", **kwargs) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_dpo_cancel_job_openai_developer_async(self, **kwargs): - await self._test_cancel_job_helper_async(DPO_JOB_TYPE, "openai", DEVELOPER_TIER_TRAINING_TYPE, "dpo", **kwargs) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_dpo_cancel_job_openai_globalstandard_async(self, **kwargs): - await self._test_cancel_job_helper_async(DPO_JOB_TYPE, "openai", GLOBAL_STANDARD_TRAINING_TYPE, "dpo", **kwargs) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_rft_cancel_job_openai_standard_async(self, **kwargs): - await self._test_cancel_job_helper_async( - RFT_JOB_TYPE, "openai", STANDARD_TRAINING_TYPE, "reinforcement", **kwargs - ) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_rft_cancel_job_openai_developer_async(self, **kwargs): - await self._test_cancel_job_helper_async( - RFT_JOB_TYPE, "openai", DEVELOPER_TIER_TRAINING_TYPE, "reinforcement", **kwargs - ) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_rft_cancel_job_openai_globalstandard_async(self, **kwargs): - await self._test_cancel_job_helper_async( - RFT_JOB_TYPE, "openai", GLOBAL_STANDARD_TRAINING_TYPE, "reinforcement", **kwargs - ) + async def test_cancel_job_async(self, job_type, model_type, training_type, expected_method_type, **kwargs): + await self._test_cancel_job_helper_async(job_type, model_type, training_type, expected_method_type, **kwargs) @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) @@ -625,7 +534,7 @@ async def test_finetuning_list_events_async(self, **kwargs): train_file, validation_file = await self._upload_test_files_async(openai_client, SFT_JOB_TYPE) fine_tuning_job = await self._create_sft_finetuning_job_async( - openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, "openai" + openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, OPENAI_MODEL_TYPE ) print(f"[test_finetuning_list_events] Created job: {fine_tuning_job.id}") @@ -761,54 +670,22 @@ async def test_finetuning_list_checkpoints_async(self, **kwargs): f"[test_finetuning_list_checkpoints] Successfully validated {len(checkpoints_list)} checkpoints for job: {completed_job_id}" ) + @pytest.mark.parametrize("job_id_env_var,deployment_format,deployment_capacity,test_prefix,inference_content", [ + ("completed_oai_model_sft_fine_tuning_job_id", "OpenAI", 50, "test_deploy_infer_oai_sft", "Who invented the telephone?"), + ("completed_oai_model_rft_fine_tuning_job_id", "OpenAI", 50, "test_deploy_infer_oai_rft", "Target: 85 Numbers: [20, 4, 15, 10]. Find a mathematical expression using all numbers exactly once to reach the target."), + ("completed_oai_model_dpo_fine_tuning_job_id", "OpenAI", 50, "test_deploy_infer_oai_dpo", "Explain machine learning in simple terms."), + ("completed_oss_model_sft_fine_tuning_job_id", "Mistral AI", 50, "test_deploy_infer_oss_sft", "Who invented the telephone?"), + ]) @servicePreparer() + @_pass_deploy_args_async @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_finetuning_deploy_and_infer_oai_model_sft_job_async(self, **kwargs): - completed_job_id = kwargs.get("completed_oai_model_sft_fine_tuning_job_id") - await self._test_deploy_and_infer_helper_async( - completed_job_id, - "OpenAI", - 50, - "test_finetuning_deploy_and_infer_oai_model_sft_job", - "Who invented the telephone?", - **kwargs, - ) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_finetuning_deploy_and_infer_oai_model_rft_job_async(self, **kwargs): - completed_job_id = kwargs.get("completed_oai_model_rft_fine_tuning_job_id") - await self._test_deploy_and_infer_helper_async( - completed_job_id, - "OpenAI", - 50, - "test_finetuning_deploy_and_infer_oai_model_rft_job", - "Target: 85 Numbers: [20, 4, 15, 10]. Find a mathematical expression using all numbers exactly once to reach the target.", - **kwargs, - ) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_finetuning_deploy_and_infer_oai_model_dpo_job_async(self, **kwargs): - completed_job_id = kwargs.get("completed_oai_model_dpo_fine_tuning_job_id") - await self._test_deploy_and_infer_helper_async( - completed_job_id, - "OpenAI", - 50, - "test_finetuning_deploy_and_infer_oai_model_dpo_job", - "What is the largest desert in the world?", - **kwargs, - ) - - @servicePreparer() - @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_finetuning_deploy_and_infer_oss_model_sft_job_async(self, **kwargs): - completed_job_id = kwargs.get("completed_oss_model_sft_fine_tuning_job_id") + async def test_deploy_and_infer_job_async(self, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs): + completed_job_id = kwargs.get(job_id_env_var) await self._test_deploy_and_infer_helper_async( completed_job_id, - "Mistral AI", - 50, - "test_finetuning_deploy_and_infer_oss_model_sft_job", - "Who invented the telephone?", + deployment_format, + deployment_capacity, + test_prefix, + inference_content, **kwargs, ) diff --git a/sdk/ai/azure-ai-projects/tests/test_base.py b/sdk/ai/azure-ai-projects/tests/test_base.py index 15cc8f131aae..b1b7e0e1d668 100644 --- a/sdk/ai/azure-ai-projects/tests/test_base.py +++ b/sdk/ai/azure-ai-projects/tests/test_base.py @@ -82,6 +82,15 @@ GLOBAL_STANDARD_TRAINING_TYPE: Final[str] = "GlobalStandard" DEVELOPER_TIER_TRAINING_TYPE: Final[str] = "developerTier" +# Method type constants +SUPERVISED_METHOD_TYPE: Final[str] = "supervised" +DPO_METHOD_TYPE: Final[str] = "dpo" +REINFORCEMENT_METHOD_TYPE: Final[str] = "reinforcement" + +# Model type constants +OPENAI_MODEL_TYPE: Final[str] = "openai" +OSS_MODEL_TYPE: Final[str] = "oss" + def patched_open_crlf_to_lf(*args, **kwargs): """ From 10d78640b1681b00fd8fb904a2d56987b99a99a7 Mon Sep 17 00:00:00 2001 From: Jayesh Tanna Date: Wed, 4 Feb 2026 18:03:42 +0530 Subject: [PATCH 2/9] removed extra decorator --- sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py | 1 - 1 file changed, 1 deletion(-) diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py index b9e4451d2d7f..f943d41f9a52 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py @@ -428,7 +428,6 @@ def test_create_job(self, job_type, model_type, training_type, **kwargs): (DPO_JOB_TYPE, DPO_METHOD_TYPE), (RFT_JOB_TYPE, REINFORCEMENT_METHOD_TYPE), ]) - @servicePreparer() @_pass_retrieve_args @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) def test_retrieve_job(self, job_type, expected_method_type, **kwargs): From df34ace3e06f6309b12ff51de724db540d06c8f4 Mon Sep 17 00:00:00 2001 From: Jayesh Tanna Date: Wed, 4 Feb 2026 18:07:59 +0530 Subject: [PATCH 3/9] applying black --- .../tests/finetuning/test_finetuning.py | 118 +++++++++++------ .../tests/finetuning/test_finetuning_async.py | 122 ++++++++++++------ 2 files changed, 165 insertions(+), 75 deletions(-) diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py index f943d41f9a52..b1ec2ffa62a2 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py @@ -32,24 +32,30 @@ def _pass_cancel_args(fn): def _wrapper(test_class, job_type, model_type, training_type, expected_method_type, **kwargs): fn(test_class, job_type, model_type, training_type, expected_method_type, **kwargs) + return _wrapper def _pass_create_args(fn): def _wrapper(test_class, job_type, model_type, training_type, **kwargs): fn(test_class, job_type, model_type, training_type, **kwargs) + return _wrapper def _pass_deploy_args(fn): - def _wrapper(test_class, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs): + def _wrapper( + test_class, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs + ): fn(test_class, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs) + return _wrapper def _pass_retrieve_args(fn): def _wrapper(test_class, job_type, expected_method_type, **kwargs): fn(test_class, job_type, expected_method_type, **kwargs) + return _wrapper @@ -397,18 +403,21 @@ def _test_deploy_and_infer_helper( f"[{test_prefix}] Successfully completed deployment and inference test for job: {completed_job_id}" ) - @pytest.mark.parametrize("job_type,model_type,training_type", [ - (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), - (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), - (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), - (SFT_JOB_TYPE, OSS_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), - (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), - (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), - (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), - (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), - (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), - (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), - ]) + @pytest.mark.parametrize( + "job_type,model_type,training_type", + [ + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), + (SFT_JOB_TYPE, OSS_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), + ], + ) @servicePreparer() @_pass_create_args @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) @@ -423,11 +432,14 @@ def test_create_job(self, job_type, model_type, training_type, **kwargs): raise ValueError(f"Unsupported job type: {job_type}") @servicePreparer() - @pytest.mark.parametrize("job_type,expected_method_type", [ - (SFT_JOB_TYPE, SUPERVISED_METHOD_TYPE), - (DPO_JOB_TYPE, DPO_METHOD_TYPE), - (RFT_JOB_TYPE, REINFORCEMENT_METHOD_TYPE), - ]) + @pytest.mark.parametrize( + "job_type,expected_method_type", + [ + (SFT_JOB_TYPE, SUPERVISED_METHOD_TYPE), + (DPO_JOB_TYPE, DPO_METHOD_TYPE), + (RFT_JOB_TYPE, REINFORCEMENT_METHOD_TYPE), + ], + ) @_pass_retrieve_args @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) def test_retrieve_job(self, job_type, expected_method_type, **kwargs): @@ -488,18 +500,21 @@ def test_finetuning_list_jobs(self, **kwargs): print(f"[test_finetuning_list] Validated job {job.id} with status {job.status}") print(f"[test_finetuning_list] Successfully validated list functionality with {len(jobs_list)} jobs") - @pytest.mark.parametrize("job_type,model_type,training_type,expected_method_type", [ - (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), - (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), - (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), - (SFT_JOB_TYPE, OSS_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), - (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, DPO_METHOD_TYPE), - (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, DPO_METHOD_TYPE), - (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, DPO_METHOD_TYPE), - (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, REINFORCEMENT_METHOD_TYPE), - (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, REINFORCEMENT_METHOD_TYPE), - (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, REINFORCEMENT_METHOD_TYPE), - ]) + @pytest.mark.parametrize( + "job_type,model_type,training_type,expected_method_type", + [ + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), + (SFT_JOB_TYPE, OSS_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, DPO_METHOD_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, DPO_METHOD_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, DPO_METHOD_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, REINFORCEMENT_METHOD_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, REINFORCEMENT_METHOD_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, REINFORCEMENT_METHOD_TYPE), + ], + ) @servicePreparer() @_pass_cancel_args @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) @@ -644,16 +659,45 @@ def test_finetuning_list_checkpoints(self, **kwargs): f"[test_finetuning_list_checkpoints] Successfully validated {len(checkpoints_list)} checkpoints for job: {completed_job_id}" ) - @pytest.mark.parametrize("job_id_env_var,deployment_format,deployment_capacity,test_prefix,inference_content", [ - ("completed_oai_model_sft_fine_tuning_job_id", "OpenAI", 50, "test_deploy_infer_oai_sft", "Who invented the telephone?"), - ("completed_oai_model_rft_fine_tuning_job_id", "OpenAI", 50, "test_deploy_infer_oai_rft", "Target: 85 Numbers: [20, 4, 15, 10]. Find a mathematical expression using all numbers exactly once to reach the target."), - ("completed_oai_model_dpo_fine_tuning_job_id", "OpenAI", 50, "test_deploy_infer_oai_dpo", "Explain machine learning in simple terms."), - ("completed_oss_model_sft_fine_tuning_job_id", "Mistral AI", 50, "test_deploy_infer_oss_sft", "Who invented the telephone?"), - ]) + @pytest.mark.parametrize( + "job_id_env_var,deployment_format,deployment_capacity,test_prefix,inference_content", + [ + ( + "completed_oai_model_sft_fine_tuning_job_id", + "OpenAI", + 50, + "test_deploy_infer_oai_sft", + "Who invented the telephone?", + ), + ( + "completed_oai_model_rft_fine_tuning_job_id", + "OpenAI", + 50, + "test_deploy_infer_oai_rft", + "Target: 85 Numbers: [20, 4, 15, 10]. Find a mathematical expression using all numbers exactly once to reach the target.", + ), + ( + "completed_oai_model_dpo_fine_tuning_job_id", + "OpenAI", + 50, + "test_deploy_infer_oai_dpo", + "Explain machine learning in simple terms.", + ), + ( + "completed_oss_model_sft_fine_tuning_job_id", + "Mistral AI", + 50, + "test_deploy_infer_oss_sft", + "Who invented the telephone?", + ), + ], + ) @servicePreparer() @_pass_deploy_args @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_deploy_and_infer_job(self, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs): + def test_deploy_and_infer_job( + self, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs + ): completed_job_id = kwargs.get(job_id_env_var) self._test_deploy_and_infer_helper( completed_job_id, diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py index 15c786b15343..7bb0c20c526b 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py @@ -33,24 +33,32 @@ def _pass_create_args_async(fn): async def _wrapper(test_class, job_type, model_type, training_type, **kwargs): await fn(test_class, job_type, model_type, training_type, **kwargs) + return _wrapper def _pass_cancel_args_async(fn): async def _wrapper(test_class, job_type, model_type, training_type, expected_method_type, **kwargs): await fn(test_class, job_type, model_type, training_type, expected_method_type, **kwargs) + return _wrapper def _pass_deploy_args_async(fn): - async def _wrapper(test_class, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs): - await fn(test_class, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs) + async def _wrapper( + test_class, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs + ): + await fn( + test_class, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs + ) + return _wrapper def _pass_retrieve_args_async(fn): async def _wrapper(test_class, job_type, expected_method_type, **kwargs): await fn(test_class, job_type, expected_method_type, **kwargs) + return _wrapper @@ -406,18 +414,21 @@ async def _test_deploy_and_infer_helper_async( f"[{test_prefix}] Successfully completed deployment and inference test for job: {completed_job_id}" ) - @pytest.mark.parametrize("job_type,model_type,training_type", [ - (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), - (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), - (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), - (SFT_JOB_TYPE, OSS_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), - (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), - (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), - (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), - (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), - (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), - (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), - ]) + @pytest.mark.parametrize( + "job_type,model_type,training_type", + [ + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), + (SFT_JOB_TYPE, OSS_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), + ], + ) @servicePreparer() @_pass_create_args_async @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) @@ -431,11 +442,14 @@ async def test_create_job_async(self, job_type, model_type, training_type, **kwa else: raise ValueError(f"Unsupported job type: {job_type}") - @pytest.mark.parametrize("job_type,expected_method_type", [ - (SFT_JOB_TYPE, SUPERVISED_METHOD_TYPE), - (DPO_JOB_TYPE, DPO_METHOD_TYPE), - (RFT_JOB_TYPE, REINFORCEMENT_METHOD_TYPE), - ]) + @pytest.mark.parametrize( + "job_type,expected_method_type", + [ + (SFT_JOB_TYPE, SUPERVISED_METHOD_TYPE), + (DPO_JOB_TYPE, DPO_METHOD_TYPE), + (RFT_JOB_TYPE, REINFORCEMENT_METHOD_TYPE), + ], + ) @servicePreparer() @_pass_retrieve_args_async @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) @@ -504,18 +518,21 @@ async def test_finetuning_list_jobs_async(self, **kwargs): print(f"[test_finetuning_list] Validated job {job.id} with status {job.status}") print(f"[test_finetuning_list] Successfully validated list functionality with {len(jobs_list)} jobs") - @pytest.mark.parametrize("job_type,model_type,training_type,expected_method_type", [ - (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), - (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), - (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), - (SFT_JOB_TYPE, OSS_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), - (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, DPO_METHOD_TYPE), - (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, DPO_METHOD_TYPE), - (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, DPO_METHOD_TYPE), - (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, REINFORCEMENT_METHOD_TYPE), - (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, REINFORCEMENT_METHOD_TYPE), - (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, REINFORCEMENT_METHOD_TYPE), - ]) + @pytest.mark.parametrize( + "job_type,model_type,training_type,expected_method_type", + [ + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), + (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), + (SFT_JOB_TYPE, OSS_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, DPO_METHOD_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, DPO_METHOD_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, DPO_METHOD_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, REINFORCEMENT_METHOD_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, REINFORCEMENT_METHOD_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, REINFORCEMENT_METHOD_TYPE), + ], + ) @servicePreparer() @_pass_cancel_args_async @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) @@ -670,16 +687,45 @@ async def test_finetuning_list_checkpoints_async(self, **kwargs): f"[test_finetuning_list_checkpoints] Successfully validated {len(checkpoints_list)} checkpoints for job: {completed_job_id}" ) - @pytest.mark.parametrize("job_id_env_var,deployment_format,deployment_capacity,test_prefix,inference_content", [ - ("completed_oai_model_sft_fine_tuning_job_id", "OpenAI", 50, "test_deploy_infer_oai_sft", "Who invented the telephone?"), - ("completed_oai_model_rft_fine_tuning_job_id", "OpenAI", 50, "test_deploy_infer_oai_rft", "Target: 85 Numbers: [20, 4, 15, 10]. Find a mathematical expression using all numbers exactly once to reach the target."), - ("completed_oai_model_dpo_fine_tuning_job_id", "OpenAI", 50, "test_deploy_infer_oai_dpo", "Explain machine learning in simple terms."), - ("completed_oss_model_sft_fine_tuning_job_id", "Mistral AI", 50, "test_deploy_infer_oss_sft", "Who invented the telephone?"), - ]) + @pytest.mark.parametrize( + "job_id_env_var,deployment_format,deployment_capacity,test_prefix,inference_content", + [ + ( + "completed_oai_model_sft_fine_tuning_job_id", + "OpenAI", + 50, + "test_deploy_infer_oai_sft", + "Who invented the telephone?", + ), + ( + "completed_oai_model_rft_fine_tuning_job_id", + "OpenAI", + 50, + "test_deploy_infer_oai_rft", + "Target: 85 Numbers: [20, 4, 15, 10]. Find a mathematical expression using all numbers exactly once to reach the target.", + ), + ( + "completed_oai_model_dpo_fine_tuning_job_id", + "OpenAI", + 50, + "test_deploy_infer_oai_dpo", + "Explain machine learning in simple terms.", + ), + ( + "completed_oss_model_sft_fine_tuning_job_id", + "Mistral AI", + 50, + "test_deploy_infer_oss_sft", + "Who invented the telephone?", + ), + ], + ) @servicePreparer() @_pass_deploy_args_async @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_deploy_and_infer_job_async(self, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs): + async def test_deploy_and_infer_job_async( + self, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs + ): completed_job_id = kwargs.get(job_id_env_var) await self._test_deploy_and_infer_helper_async( completed_job_id, From 05b50506e557fcc7d0671621ebde3d29a3571faa Mon Sep 17 00:00:00 2001 From: Jayesh Tanna Date: Thu, 5 Feb 2026 10:53:59 +0530 Subject: [PATCH 4/9] updating assetjson file --- sdk/ai/azure-ai-projects/assets.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/ai/azure-ai-projects/assets.json b/sdk/ai/azure-ai-projects/assets.json index 6b439df99b6a..3a07d3003d6a 100644 --- a/sdk/ai/azure-ai-projects/assets.json +++ b/sdk/ai/azure-ai-projects/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "python", "TagPrefix": "python/ai/azure-ai-projects", - "Tag": "python/ai/azure-ai-projects_28ff91480c" + "Tag": "python/ai/azure-ai-projects_0af6df4ff9" } From ad2ec88f53016a11863243264817084cfbd1cc03 Mon Sep 17 00:00:00 2001 From: Jayesh Tanna Date: Thu, 5 Feb 2026 18:14:45 +0530 Subject: [PATCH 5/9] updating test cases --- sdk/ai/azure-ai-projects/assets.json | 2 +- .../tests/finetuning/test_finetuning.py | 116 ++++++++++++++++- .../tests/finetuning/test_finetuning_async.py | 120 +++++++++++++++++- 3 files changed, 223 insertions(+), 15 deletions(-) diff --git a/sdk/ai/azure-ai-projects/assets.json b/sdk/ai/azure-ai-projects/assets.json index 3a07d3003d6a..8b65bb82abee 100644 --- a/sdk/ai/azure-ai-projects/assets.json +++ b/sdk/ai/azure-ai-projects/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "python", "TagPrefix": "python/ai/azure-ai-projects", - "Tag": "python/ai/azure-ai-projects_0af6df4ff9" + "Tag": "python/ai/azure-ai-projects_d283620c2f" } diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py index b1ec2ffa62a2..9b23658500aa 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py @@ -407,13 +407,32 @@ def _test_deploy_and_infer_helper( "job_type,model_type,training_type", [ (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), + ], + ) + @servicePreparer() + @_pass_create_args + @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) + def test_create_job(self, job_type, model_type, training_type, **kwargs): + if job_type == SFT_JOB_TYPE: + self._test_sft_create_job_helper(model_type, training_type, **kwargs) + elif job_type == DPO_JOB_TYPE: + self._test_dpo_create_job_helper(model_type, training_type, **kwargs) + elif job_type == RFT_JOB_TYPE: + self._test_rft_create_job_helper(model_type, training_type, **kwargs) + else: + raise ValueError(f"Unsupported job type: {job_type}") + + @pytest.mark.skipif(not is_live(), reason="live only test") + @pytest.mark.parametrize( + "job_type,model_type,training_type", + [ (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), (SFT_JOB_TYPE, OSS_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), - (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), - (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), ], @@ -421,7 +440,7 @@ def _test_deploy_and_infer_helper( @servicePreparer() @_pass_create_args @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_create_job(self, job_type, model_type, training_type, **kwargs): + def test_create_job_live_extended(self, job_type, model_type, training_type, **kwargs): if job_type == SFT_JOB_TYPE: self._test_sft_create_job_helper(model_type, training_type, **kwargs) elif job_type == DPO_JOB_TYPE: @@ -436,8 +455,6 @@ def test_create_job(self, job_type, model_type, training_type, **kwargs): "job_type,expected_method_type", [ (SFT_JOB_TYPE, SUPERVISED_METHOD_TYPE), - (DPO_JOB_TYPE, DPO_METHOD_TYPE), - (RFT_JOB_TYPE, REINFORCEMENT_METHOD_TYPE), ], ) @_pass_retrieve_args @@ -482,6 +499,57 @@ def test_retrieve_job(self, job_type, expected_method_type, **kwargs): self._cleanup_test_file(openai_client, train_file.id) self._cleanup_test_file(openai_client, validation_file.id) + @pytest.mark.skipif(not is_live(), reason="live only test") + @servicePreparer() + @pytest.mark.parametrize( + "job_type,expected_method_type", + [ + (DPO_JOB_TYPE, DPO_METHOD_TYPE), + (RFT_JOB_TYPE, REINFORCEMENT_METHOD_TYPE), + ], + ) + @_pass_retrieve_args + @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) + def test_retrieve_job_live_extended(self, job_type, expected_method_type, **kwargs): + with self.create_client(**kwargs) as project_client: + with project_client.get_openai_client() as openai_client: + + train_file, validation_file = self._upload_test_files(openai_client, job_type) + + if job_type == SFT_JOB_TYPE: + fine_tuning_job = self._create_sft_finetuning_job( + openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, OPENAI_MODEL_TYPE + ) + elif job_type == DPO_JOB_TYPE: + fine_tuning_job = self._create_dpo_finetuning_job( + openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, OPENAI_MODEL_TYPE + ) + elif job_type == RFT_JOB_TYPE: + fine_tuning_job = self._create_rft_finetuning_job( + openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, OPENAI_MODEL_TYPE + ) + + print(f"[test_finetuning_retrieve_{job_type}_live] Created job: {fine_tuning_job.id}") + + retrieved_job = openai_client.fine_tuning.jobs.retrieve(fine_tuning_job.id) + print(f"[test_finetuning_retrieve_{job_type}_live] Retrieved job: {retrieved_job.id}") + + TestBase.validate_fine_tuning_job(retrieved_job, expected_job_id=fine_tuning_job.id) + TestBase.assert_equal_or_not_none(retrieved_job.training_file, train_file.id) + TestBase.assert_equal_or_not_none(retrieved_job.validation_file, validation_file.id) + TestBase.assert_equal_or_not_none(retrieved_job.trainingType.lower(), STANDARD_TRAINING_TYPE.lower()) + assert retrieved_job.method is not None, f"Method should not be None for {job_type} job" + TestBase.assert_equal_or_not_none(retrieved_job.method.type, expected_method_type) + assert ( + self.test_finetuning_params[job_type][OPENAI_MODEL_TYPE]["model_name"] in retrieved_job.model + ), f"Expected model name {self.test_finetuning_params[job_type][OPENAI_MODEL_TYPE]['model_name']} not found in {retrieved_job.model}" + + openai_client.fine_tuning.jobs.cancel(fine_tuning_job.id) + print(f"[test_finetuning_retrieve_{job_type}_live] Cancelled job: {fine_tuning_job.id}") + + self._cleanup_test_file(openai_client, train_file.id) + self._cleanup_test_file(openai_client, validation_file.id) + @servicePreparer() @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) def test_finetuning_list_jobs(self, **kwargs): @@ -504,6 +572,18 @@ def test_finetuning_list_jobs(self, **kwargs): "job_type,model_type,training_type,expected_method_type", [ (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), + ], + ) + @servicePreparer() + @_pass_cancel_args + @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) + def test_cancel_job(self, job_type, model_type, training_type, expected_method_type, **kwargs): + self._test_cancel_job_helper(job_type, model_type, training_type, expected_method_type, **kwargs) + + @pytest.mark.skipif(not is_live(), reason="live only test") + @pytest.mark.parametrize( + "job_type,model_type,training_type,expected_method_type", + [ (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), (SFT_JOB_TYPE, OSS_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), @@ -518,7 +598,7 @@ def test_finetuning_list_jobs(self, **kwargs): @servicePreparer() @_pass_cancel_args @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_cancel_job(self, job_type, model_type, training_type, expected_method_type, **kwargs): + def test_cancel_job_live_extended(self, job_type, model_type, training_type, expected_method_type, **kwargs): self._test_cancel_job_helper(job_type, model_type, training_type, expected_method_type, **kwargs) @servicePreparer() @@ -669,6 +749,28 @@ def test_finetuning_list_checkpoints(self, **kwargs): "test_deploy_infer_oai_sft", "Who invented the telephone?", ), + ], + ) + @servicePreparer() + @_pass_deploy_args + @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) + def test_deploy_and_infer_job( + self, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs + ): + completed_job_id = kwargs.get(job_id_env_var) + self._test_deploy_and_infer_helper( + completed_job_id, + deployment_format, + deployment_capacity, + test_prefix, + inference_content, + **kwargs, + ) + + @pytest.mark.skipif(not is_live(), reason="live only test") + @pytest.mark.parametrize( + "job_id_env_var,deployment_format,deployment_capacity,test_prefix,inference_content", + [ ( "completed_oai_model_rft_fine_tuning_job_id", "OpenAI", @@ -695,7 +797,7 @@ def test_finetuning_list_checkpoints(self, **kwargs): @servicePreparer() @_pass_deploy_args @recorded_by_proxy(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - def test_deploy_and_infer_job( + def test_deploy_and_infer_job_live_extended( self, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs ): completed_job_id = kwargs.get(job_id_env_var) diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py index 7bb0c20c526b..5e1f79f612d0 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py @@ -418,13 +418,32 @@ async def _test_deploy_and_infer_helper_async( "job_type,model_type,training_type", [ (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), + (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), + (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), + ], + ) + @servicePreparer() + @_pass_create_args_async + @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) + async def test_create_job_async(self, job_type, model_type, training_type, **kwargs): + if job_type == SFT_JOB_TYPE: + await self._test_sft_create_job_helper_async(model_type, training_type, **kwargs) + elif job_type == DPO_JOB_TYPE: + await self._test_dpo_create_job_helper_async(model_type, training_type, **kwargs) + elif job_type == RFT_JOB_TYPE: + await self._test_rft_create_job_helper_async(model_type, training_type, **kwargs) + else: + raise ValueError(f"Unsupported job type: {job_type}") + + @pytest.mark.skipif(not is_live(), reason="live only test") + @pytest.mark.parametrize( + "job_type,model_type,training_type", + [ (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), (SFT_JOB_TYPE, OSS_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), - (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), (DPO_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), - (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE), (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE), (RFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE), ], @@ -432,7 +451,7 @@ async def _test_deploy_and_infer_helper_async( @servicePreparer() @_pass_create_args_async @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_create_job_async(self, job_type, model_type, training_type, **kwargs): + async def test_create_job_async_live_extended(self, job_type, model_type, training_type, **kwargs): if job_type == SFT_JOB_TYPE: await self._test_sft_create_job_helper_async(model_type, training_type, **kwargs) elif job_type == DPO_JOB_TYPE: @@ -446,8 +465,6 @@ async def test_create_job_async(self, job_type, model_type, training_type, **kwa "job_type,expected_method_type", [ (SFT_JOB_TYPE, SUPERVISED_METHOD_TYPE), - (DPO_JOB_TYPE, DPO_METHOD_TYPE), - (RFT_JOB_TYPE, REINFORCEMENT_METHOD_TYPE), ], ) @servicePreparer() @@ -495,6 +512,59 @@ async def test_retrieve_job_async(self, job_type, expected_method_type, **kwargs await self._cleanup_test_file_async(openai_client, train_file.id) await self._cleanup_test_file_async(openai_client, validation_file.id) + @pytest.mark.skipif(not is_live(), reason="live only test") + @pytest.mark.parametrize( + "job_type,expected_method_type", + [ + (DPO_JOB_TYPE, DPO_METHOD_TYPE), + (RFT_JOB_TYPE, REINFORCEMENT_METHOD_TYPE), + ], + ) + @servicePreparer() + @_pass_retrieve_args_async + @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) + async def test_retrieve_job_async_live_extended(self, job_type, expected_method_type, **kwargs): + project_client = self.create_async_client(**kwargs) + openai_client = project_client.get_openai_client() + + async with project_client: + + train_file, validation_file = await self._upload_test_files_async(openai_client, job_type) + + if job_type == SFT_JOB_TYPE: + fine_tuning_job = await self._create_sft_finetuning_job_async( + openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, OPENAI_MODEL_TYPE + ) + elif job_type == DPO_JOB_TYPE: + fine_tuning_job = await self._create_dpo_finetuning_job_async( + openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, OPENAI_MODEL_TYPE + ) + elif job_type == RFT_JOB_TYPE: + fine_tuning_job = await self._create_rft_finetuning_job_async( + openai_client, train_file.id, validation_file.id, STANDARD_TRAINING_TYPE, OPENAI_MODEL_TYPE + ) + + print(f"[test_finetuning_retrieve_{job_type}_live] Created job: {fine_tuning_job.id}") + + retrieved_job = await openai_client.fine_tuning.jobs.retrieve(fine_tuning_job.id) + print(f"[test_finetuning_retrieve_{job_type}_live] Retrieved job: {retrieved_job.id}") + + TestBase.validate_fine_tuning_job(retrieved_job, expected_job_id=fine_tuning_job.id) + TestBase.assert_equal_or_not_none(retrieved_job.training_file, train_file.id) + TestBase.assert_equal_or_not_none(retrieved_job.validation_file, validation_file.id) + TestBase.assert_equal_or_not_none(retrieved_job.trainingType.lower(), STANDARD_TRAINING_TYPE.lower()) + assert retrieved_job.method is not None, f"Method should not be None for {job_type} job" + TestBase.assert_equal_or_not_none(retrieved_job.method.type, expected_method_type) + assert ( + self.test_finetuning_params[job_type][OPENAI_MODEL_TYPE]["model_name"] in retrieved_job.model + ), f"Expected model name {self.test_finetuning_params[job_type][OPENAI_MODEL_TYPE]['model_name']} not found in {retrieved_job.model}" + + await openai_client.fine_tuning.jobs.cancel(fine_tuning_job.id) + print(f"[test_finetuning_retrieve_{job_type}_live] Cancelled job: {fine_tuning_job.id}") + + await self._cleanup_test_file_async(openai_client, train_file.id) + await self._cleanup_test_file_async(openai_client, validation_file.id) + @servicePreparer() @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) async def test_finetuning_list_jobs_async(self, **kwargs): @@ -522,6 +592,18 @@ async def test_finetuning_list_jobs_async(self, **kwargs): "job_type,model_type,training_type,expected_method_type", [ (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), + ], + ) + @servicePreparer() + @_pass_cancel_args_async + @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) + async def test_cancel_job_async(self, job_type, model_type, training_type, expected_method_type, **kwargs): + await self._test_cancel_job_helper_async(job_type, model_type, training_type, expected_method_type, **kwargs) + + @pytest.mark.skipif(not is_live(), reason="live only test") + @pytest.mark.parametrize( + "job_type,model_type,training_type,expected_method_type", + [ (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), (SFT_JOB_TYPE, OPENAI_MODEL_TYPE, DEVELOPER_TIER_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), (SFT_JOB_TYPE, OSS_MODEL_TYPE, GLOBAL_STANDARD_TRAINING_TYPE, SUPERVISED_METHOD_TYPE), @@ -536,7 +618,9 @@ async def test_finetuning_list_jobs_async(self, **kwargs): @servicePreparer() @_pass_cancel_args_async @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_cancel_job_async(self, job_type, model_type, training_type, expected_method_type, **kwargs): + async def test_cancel_job_async_live_extended( + self, job_type, model_type, training_type, expected_method_type, **kwargs + ): await self._test_cancel_job_helper_async(job_type, model_type, training_type, expected_method_type, **kwargs) @servicePreparer() @@ -697,6 +781,28 @@ async def test_finetuning_list_checkpoints_async(self, **kwargs): "test_deploy_infer_oai_sft", "Who invented the telephone?", ), + ], + ) + @servicePreparer() + @_pass_deploy_args_async + @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) + async def test_deploy_and_infer_job_async( + self, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs + ): + completed_job_id = kwargs.get(job_id_env_var) + await self._test_deploy_and_infer_helper_async( + completed_job_id, + deployment_format, + deployment_capacity, + test_prefix, + inference_content, + **kwargs, + ) + + @pytest.mark.skipif(not is_live(), reason="live only test") + @pytest.mark.parametrize( + "job_id_env_var,deployment_format,deployment_capacity,test_prefix,inference_content", + [ ( "completed_oai_model_rft_fine_tuning_job_id", "OpenAI", @@ -723,7 +829,7 @@ async def test_finetuning_list_checkpoints_async(self, **kwargs): @servicePreparer() @_pass_deploy_args_async @recorded_by_proxy_async(RecordedTransport.AZURE_CORE, RecordedTransport.HTTPX) - async def test_deploy_and_infer_job_async( + async def test_deploy_and_infer_job_async_live_extended( self, job_id_env_var, deployment_format, deployment_capacity, test_prefix, inference_content, **kwargs ): completed_job_id = kwargs.get(job_id_env_var) From 8fca7c4e5335516a80708764881bf56ff0dbd37e Mon Sep 17 00:00:00 2001 From: Jayesh Tanna Date: Thu, 5 Feb 2026 20:45:21 +0530 Subject: [PATCH 6/9] using liveandrecording fun --- .../tests/finetuning/test_finetuning.py | 10 +++++----- .../tests/finetuning/test_finetuning_async.py | 10 +++++----- 2 files changed, 10 insertions(+), 10 deletions(-) diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py index 9b23658500aa..2edaab8c5329 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py @@ -22,7 +22,7 @@ OPENAI_MODEL_TYPE, OSS_MODEL_TYPE, ) -from devtools_testutils import recorded_by_proxy, RecordedTransport, is_live +from devtools_testutils import recorded_by_proxy, RecordedTransport, is_live, is_live_and_not_recording from azure.mgmt.cognitiveservices import CognitiveServicesManagementClient from azure.mgmt.cognitiveservices.models import Deployment, DeploymentProperties, DeploymentModel, Sku @@ -424,7 +424,7 @@ def test_create_job(self, job_type, model_type, training_type, **kwargs): else: raise ValueError(f"Unsupported job type: {job_type}") - @pytest.mark.skipif(not is_live(), reason="live only test") + @pytest.mark.skipif(not is_live_and_not_recording(), reason="live only test") @pytest.mark.parametrize( "job_type,model_type,training_type", [ @@ -499,7 +499,7 @@ def test_retrieve_job(self, job_type, expected_method_type, **kwargs): self._cleanup_test_file(openai_client, train_file.id) self._cleanup_test_file(openai_client, validation_file.id) - @pytest.mark.skipif(not is_live(), reason="live only test") + @pytest.mark.skipif(not is_live_and_not_recording(), reason="live only test") @servicePreparer() @pytest.mark.parametrize( "job_type,expected_method_type", @@ -580,7 +580,7 @@ def test_finetuning_list_jobs(self, **kwargs): def test_cancel_job(self, job_type, model_type, training_type, expected_method_type, **kwargs): self._test_cancel_job_helper(job_type, model_type, training_type, expected_method_type, **kwargs) - @pytest.mark.skipif(not is_live(), reason="live only test") + @pytest.mark.skipif(not is_live_and_not_recording(), reason="live only test") @pytest.mark.parametrize( "job_type,model_type,training_type,expected_method_type", [ @@ -767,7 +767,7 @@ def test_deploy_and_infer_job( **kwargs, ) - @pytest.mark.skipif(not is_live(), reason="live only test") + @pytest.mark.skipif(not is_live_and_not_recording(), reason="live only test") @pytest.mark.parametrize( "job_id_env_var,deployment_format,deployment_capacity,test_prefix,inference_content", [ diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py index 5e1f79f612d0..38768db9e063 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py @@ -23,7 +23,7 @@ OSS_MODEL_TYPE, ) from devtools_testutils.aio import recorded_by_proxy_async -from devtools_testutils import is_live, RecordedTransport +from devtools_testutils import is_live, RecordedTransport, is_live_and_not_recording from azure.mgmt.cognitiveservices.aio import CognitiveServicesManagementClient as CognitiveServicesManagementClientAsync from azure.mgmt.cognitiveservices.models import Deployment, DeploymentProperties, DeploymentModel, Sku @@ -435,7 +435,7 @@ async def test_create_job_async(self, job_type, model_type, training_type, **kwa else: raise ValueError(f"Unsupported job type: {job_type}") - @pytest.mark.skipif(not is_live(), reason="live only test") + @pytest.mark.skipif(not is_live_and_not_recording(), reason="live only test") @pytest.mark.parametrize( "job_type,model_type,training_type", [ @@ -512,7 +512,7 @@ async def test_retrieve_job_async(self, job_type, expected_method_type, **kwargs await self._cleanup_test_file_async(openai_client, train_file.id) await self._cleanup_test_file_async(openai_client, validation_file.id) - @pytest.mark.skipif(not is_live(), reason="live only test") + @pytest.mark.skipif(not is_live_and_not_recording(), reason="live only test") @pytest.mark.parametrize( "job_type,expected_method_type", [ @@ -600,7 +600,7 @@ async def test_finetuning_list_jobs_async(self, **kwargs): async def test_cancel_job_async(self, job_type, model_type, training_type, expected_method_type, **kwargs): await self._test_cancel_job_helper_async(job_type, model_type, training_type, expected_method_type, **kwargs) - @pytest.mark.skipif(not is_live(), reason="live only test") + @pytest.mark.skipif(not is_live_and_not_recording(), reason="live only test") @pytest.mark.parametrize( "job_type,model_type,training_type,expected_method_type", [ @@ -799,7 +799,7 @@ async def test_deploy_and_infer_job_async( **kwargs, ) - @pytest.mark.skipif(not is_live(), reason="live only test") + @pytest.mark.skipif(not is_live_and_not_recording(), reason="live only test") @pytest.mark.parametrize( "job_id_env_var,deployment_format,deployment_capacity,test_prefix,inference_content", [ From 45ad960d5c081a667c445c3a704aa1b32c5d8c6c Mon Sep 17 00:00:00 2001 From: Jayesh Tanna Date: Thu, 5 Feb 2026 22:13:29 +0530 Subject: [PATCH 7/9] adding new env --- sdk/ai/azure-ai-projects/.env.template | 5 ++++- .../tests/finetuning/test_finetuning.py | 21 +++++++++++++++---- .../tests/finetuning/test_finetuning_async.py | 21 +++++++++++++++---- 3 files changed, 38 insertions(+), 9 deletions(-) diff --git a/sdk/ai/azure-ai-projects/.env.template b/sdk/ai/azure-ai-projects/.env.template index 023b15d3bb74..ca5a9a9beb85 100644 --- a/sdk/ai/azure-ai-projects/.env.template +++ b/sdk/ai/azure-ai-projects/.env.template @@ -64,4 +64,7 @@ COMPLETED_OSS_MODEL_SFT_FINE_TUNING_JOB_ID= RUNNING_FINE_TUNING_JOB_ID= PAUSED_FINE_TUNING_JOB_ID= AZURE_SUBSCRIPTION_ID= -AZURE_RESOURCE_GROUP= \ No newline at end of file +AZURE_RESOURCE_GROUP= + +# Set to true to run extended finetuning live tests +RUN_EXTENDED_FINE_TUNING_LIVE_TESTS=false \ No newline at end of file diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py index 2edaab8c5329..bdc91209e3e1 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py @@ -4,6 +4,7 @@ # Licensed under the MIT License. # ------------------------------------ +import os import pytest import time from pathlib import Path @@ -424,7 +425,10 @@ def test_create_job(self, job_type, model_type, training_type, **kwargs): else: raise ValueError(f"Unsupported job type: {job_type}") - @pytest.mark.skipif(not is_live_and_not_recording(), reason="live only test") + @pytest.mark.skipif( + not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", + reason="live only test", + ) @pytest.mark.parametrize( "job_type,model_type,training_type", [ @@ -499,7 +503,10 @@ def test_retrieve_job(self, job_type, expected_method_type, **kwargs): self._cleanup_test_file(openai_client, train_file.id) self._cleanup_test_file(openai_client, validation_file.id) - @pytest.mark.skipif(not is_live_and_not_recording(), reason="live only test") + @pytest.mark.skipif( + not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", + reason="live only test", + ) @servicePreparer() @pytest.mark.parametrize( "job_type,expected_method_type", @@ -580,7 +587,10 @@ def test_finetuning_list_jobs(self, **kwargs): def test_cancel_job(self, job_type, model_type, training_type, expected_method_type, **kwargs): self._test_cancel_job_helper(job_type, model_type, training_type, expected_method_type, **kwargs) - @pytest.mark.skipif(not is_live_and_not_recording(), reason="live only test") + @pytest.mark.skipif( + not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", + reason="live only test", + ) @pytest.mark.parametrize( "job_type,model_type,training_type,expected_method_type", [ @@ -767,7 +777,10 @@ def test_deploy_and_infer_job( **kwargs, ) - @pytest.mark.skipif(not is_live_and_not_recording(), reason="live only test") + @pytest.mark.skipif( + not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", + reason="live only test", + ) @pytest.mark.parametrize( "job_id_env_var,deployment_format,deployment_capacity,test_prefix,inference_content", [ diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py index 38768db9e063..04c42709ec30 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py @@ -4,6 +4,7 @@ # Licensed under the MIT License. # ------------------------------------ +import os import pytest import asyncio from pathlib import Path @@ -435,7 +436,10 @@ async def test_create_job_async(self, job_type, model_type, training_type, **kwa else: raise ValueError(f"Unsupported job type: {job_type}") - @pytest.mark.skipif(not is_live_and_not_recording(), reason="live only test") + @pytest.mark.skipif( + not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", + reason="live only test", + ) @pytest.mark.parametrize( "job_type,model_type,training_type", [ @@ -512,7 +516,10 @@ async def test_retrieve_job_async(self, job_type, expected_method_type, **kwargs await self._cleanup_test_file_async(openai_client, train_file.id) await self._cleanup_test_file_async(openai_client, validation_file.id) - @pytest.mark.skipif(not is_live_and_not_recording(), reason="live only test") + @pytest.mark.skipif( + not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", + reason="live only test", + ) @pytest.mark.parametrize( "job_type,expected_method_type", [ @@ -600,7 +607,10 @@ async def test_finetuning_list_jobs_async(self, **kwargs): async def test_cancel_job_async(self, job_type, model_type, training_type, expected_method_type, **kwargs): await self._test_cancel_job_helper_async(job_type, model_type, training_type, expected_method_type, **kwargs) - @pytest.mark.skipif(not is_live_and_not_recording(), reason="live only test") + @pytest.mark.skipif( + not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", + reason="live only test", + ) @pytest.mark.parametrize( "job_type,model_type,training_type,expected_method_type", [ @@ -799,7 +809,10 @@ async def test_deploy_and_infer_job_async( **kwargs, ) - @pytest.mark.skipif(not is_live_and_not_recording(), reason="live only test") + @pytest.mark.skipif( + not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", + reason="live only test", + ) @pytest.mark.parametrize( "job_id_env_var,deployment_format,deployment_capacity,test_prefix,inference_content", [ From 9d48faa12716fd9f617eef8c7a8dba401e6916b4 Mon Sep 17 00:00:00 2001 From: Jayesh Tanna Date: Thu, 5 Feb 2026 22:25:21 +0530 Subject: [PATCH 8/9] updating msg --- .../azure-ai-projects/tests/finetuning/test_finetuning.py | 8 ++++---- .../tests/finetuning/test_finetuning_async.py | 8 ++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py index bdc91209e3e1..6a265f3ada48 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning.py @@ -427,7 +427,7 @@ def test_create_job(self, job_type, model_type, training_type, **kwargs): @pytest.mark.skipif( not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", - reason="live only test", + reason="Skipped extended FT live tests. Those only run live, without recordings, when RUN_EXTENDED_FINE_TUNING_LIVE_TESTS=true", ) @pytest.mark.parametrize( "job_type,model_type,training_type", @@ -505,7 +505,7 @@ def test_retrieve_job(self, job_type, expected_method_type, **kwargs): @pytest.mark.skipif( not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", - reason="live only test", + reason="Skipped extended FT live tests. Those only run live, without recordings, when RUN_EXTENDED_FINE_TUNING_LIVE_TESTS=true", ) @servicePreparer() @pytest.mark.parametrize( @@ -589,7 +589,7 @@ def test_cancel_job(self, job_type, model_type, training_type, expected_method_t @pytest.mark.skipif( not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", - reason="live only test", + reason="Skipped extended FT live tests. Those only run live, without recordings, when RUN_EXTENDED_FINE_TUNING_LIVE_TESTS=true", ) @pytest.mark.parametrize( "job_type,model_type,training_type,expected_method_type", @@ -779,7 +779,7 @@ def test_deploy_and_infer_job( @pytest.mark.skipif( not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", - reason="live only test", + reason="Skipped extended FT live tests. Those only run live, without recordings, when RUN_EXTENDED_FINE_TUNING_LIVE_TESTS=true", ) @pytest.mark.parametrize( "job_id_env_var,deployment_format,deployment_capacity,test_prefix,inference_content", diff --git a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py index 04c42709ec30..06838474a2c9 100644 --- a/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py +++ b/sdk/ai/azure-ai-projects/tests/finetuning/test_finetuning_async.py @@ -438,7 +438,7 @@ async def test_create_job_async(self, job_type, model_type, training_type, **kwa @pytest.mark.skipif( not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", - reason="live only test", + reason="Skipped extended FT live tests. Those only run live, without recordings, when RUN_EXTENDED_FINE_TUNING_LIVE_TESTS=true", ) @pytest.mark.parametrize( "job_type,model_type,training_type", @@ -518,7 +518,7 @@ async def test_retrieve_job_async(self, job_type, expected_method_type, **kwargs @pytest.mark.skipif( not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", - reason="live only test", + reason="Skipped extended FT live tests. Those only run live, without recordings, when RUN_EXTENDED_FINE_TUNING_LIVE_TESTS=true", ) @pytest.mark.parametrize( "job_type,expected_method_type", @@ -609,7 +609,7 @@ async def test_cancel_job_async(self, job_type, model_type, training_type, expec @pytest.mark.skipif( not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", - reason="live only test", + reason="Skipped extended FT live tests. Those only run live, without recordings, when RUN_EXTENDED_FINE_TUNING_LIVE_TESTS=true", ) @pytest.mark.parametrize( "job_type,model_type,training_type,expected_method_type", @@ -811,7 +811,7 @@ async def test_deploy_and_infer_job_async( @pytest.mark.skipif( not is_live_and_not_recording() or os.getenv("RUN_EXTENDED_FINE_TUNING_LIVE_TESTS", "false").lower() != "true", - reason="live only test", + reason="Skipped extended FT live tests. Those only run live, without recordings, when RUN_EXTENDED_FINE_TUNING_LIVE_TESTS=true", ) @pytest.mark.parametrize( "job_id_env_var,deployment_format,deployment_capacity,test_prefix,inference_content", From dee279ef01f649bc7b528aa5e1558dd66b3d63e7 Mon Sep 17 00:00:00 2001 From: Jayesh Tanna Date: Fri, 6 Feb 2026 05:16:43 +0530 Subject: [PATCH 9/9] updating assetsjson file --- sdk/ai/azure-ai-projects/assets.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/sdk/ai/azure-ai-projects/assets.json b/sdk/ai/azure-ai-projects/assets.json index 8b65bb82abee..b16ab096305e 100644 --- a/sdk/ai/azure-ai-projects/assets.json +++ b/sdk/ai/azure-ai-projects/assets.json @@ -2,5 +2,5 @@ "AssetsRepo": "Azure/azure-sdk-assets", "AssetsRepoPrefixPath": "python", "TagPrefix": "python/ai/azure-ai-projects", - "Tag": "python/ai/azure-ai-projects_d283620c2f" + "Tag": "python/ai/azure-ai-projects_8d150306ae" }