feat(fine-tuning): address greptile review feedback (greploop iteration 4)

- Add cancel/retrieve overrides in AzureOpenAIFineTuningAPI to normalize responses
- Expand _AZURE_STATUS_MAP to handle all known Azure statuses
- Add "pending" to OpenAIFileObject.status allowed values
- Fix async test mock to return awaitable LiteLLMFineTuningJob
- Add test_openai_file_object_accepts_pending_status

Made-with: Cursor
This commit is contained in:
Sameer Kankute 2026-03-23 12:54:33 +05:30
parent e937507637
commit 30af125ef2
5 changed files with 142 additions and 6 deletions

View file

@ -42,6 +42,26 @@ class AzureOpenAIFineTuningAPI(OpenAIFineTuningAPI, BaseAzureLLM):
)
return _litellm_fine_tuning_job_from_response(response, is_azure=True)
async def acancel_fine_tuning_job(
self,
fine_tuning_job_id: str,
openai_client: Union[AsyncOpenAI, AsyncAzureOpenAI],
) -> LiteLLMFineTuningJob:
response = await openai_client.fine_tuning.jobs.cancel(
fine_tuning_job_id=fine_tuning_job_id
)
return _litellm_fine_tuning_job_from_response(response, is_azure=True)
async def aretrieve_fine_tuning_job(
self,
fine_tuning_job_id: str,
openai_client: Union[AsyncOpenAI, AsyncAzureOpenAI],
) -> LiteLLMFineTuningJob:
response = await openai_client.fine_tuning.jobs.retrieve(
fine_tuning_job_id=fine_tuning_job_id
)
return _litellm_fine_tuning_job_from_response(response, is_azure=True)
def create_fine_tuning_job(
self,
_is_async: bool,
@ -93,6 +113,98 @@ class AzureOpenAIFineTuningAPI(OpenAIFineTuningAPI, BaseAzureLLM):
)
return _litellm_fine_tuning_job_from_response(response, is_azure=True)
def cancel_fine_tuning_job(
self,
_is_async: bool,
fine_tuning_job_id: str,
api_key: Optional[str],
api_base: Optional[str],
api_version: Optional[str],
timeout: Union[float, httpx.Timeout],
max_retries: Optional[int],
organization: Optional[str],
client: Optional[
Union[OpenAI, AsyncOpenAI, AzureOpenAI, AsyncAzureOpenAI]
] = None,
) -> Union[LiteLLMFineTuningJob, Coroutine[Any, Any, LiteLLMFineTuningJob]]:
openai_client: Optional[
Union[OpenAI, AsyncOpenAI, AzureOpenAI, AsyncAzureOpenAI]
] = self.get_openai_client(
api_key=api_key,
api_base=api_base,
timeout=timeout,
max_retries=max_retries,
organization=organization,
client=client,
_is_async=_is_async,
api_version=api_version,
)
if openai_client is None:
raise ValueError(
"Azure OpenAI client is not initialized. Make sure api_key is passed or AZURE_API_KEY is set in the environment."
)
if _is_async is True:
if not isinstance(openai_client, (AsyncOpenAI, AsyncAzureOpenAI)):
raise ValueError(
"OpenAI client is not an instance of AsyncOpenAI. Make sure you passed an AsyncOpenAI client."
)
return self.acancel_fine_tuning_job(
fine_tuning_job_id=fine_tuning_job_id,
openai_client=openai_client,
)
response = cast(OpenAI, openai_client).fine_tuning.jobs.cancel(
fine_tuning_job_id=fine_tuning_job_id
)
return _litellm_fine_tuning_job_from_response(response, is_azure=True)
def retrieve_fine_tuning_job(
self,
_is_async: bool,
fine_tuning_job_id: str,
api_key: Optional[str],
api_base: Optional[str],
api_version: Optional[str],
timeout: Union[float, httpx.Timeout],
max_retries: Optional[int],
organization: Optional[str],
client: Optional[
Union[OpenAI, AsyncOpenAI, AzureOpenAI, AsyncAzureOpenAI]
] = None,
) -> Union[LiteLLMFineTuningJob, Coroutine[Any, Any, LiteLLMFineTuningJob]]:
openai_client: Optional[
Union[OpenAI, AsyncOpenAI, AzureOpenAI, AsyncAzureOpenAI]
] = self.get_openai_client(
api_key=api_key,
api_base=api_base,
timeout=timeout,
max_retries=max_retries,
organization=organization,
client=client,
_is_async=_is_async,
api_version=api_version,
)
if openai_client is None:
raise ValueError(
"Azure OpenAI client is not initialized. Make sure api_key is passed or AZURE_API_KEY is set in the environment."
)
if _is_async is True:
if not isinstance(openai_client, (AsyncOpenAI, AsyncAzureOpenAI)):
raise ValueError(
"OpenAI client is not an instance of AsyncOpenAI. Make sure you passed an AsyncOpenAI client."
)
return self.aretrieve_fine_tuning_job(
fine_tuning_job_id=fine_tuning_job_id,
openai_client=openai_client,
)
response = cast(OpenAI, openai_client).fine_tuning.jobs.retrieve(
fine_tuning_job_id=fine_tuning_job_id
)
return _litellm_fine_tuning_job_from_response(response, is_azure=True)
def get_openai_client(
self,
api_key: Optional[str],

View file

@ -8,6 +8,12 @@ from litellm.types.utils import LiteLLMFineTuningJob
_AZURE_STATUS_MAP = {
"pending": "queued",
"notRunning": "queued",
"running": "running",
"succeeded": "succeeded",
"failed": "failed",
"canceled": "cancelled",
"canceling": "cancelled",
}

View file

@ -315,11 +315,11 @@ class OpenAIFileObject(BaseModel):
`fine-tune`, `fine-tune-results`, `vision`, and `user_data`.
"""
status: Optional[Literal["uploaded", "processed", "error"]] = None
status: Optional[Literal["uploaded", "processed", "error", "pending"]] = None
"""Deprecated.
The current status of the file, which can be either `uploaded`, `processed`, or
`error`.
The current status of the file, which can be either `uploaded`, `processed`,
`error`, or `pending` (Azure may return `pending` immediately after upload).
"""
expires_at: Optional[int] = None

View file

@ -616,11 +616,11 @@ async def test_mock_openai_retrieve_fine_tune_job():
@pytest.mark.asyncio
async def test_mock_azure_create_fine_tune_job_with_azure_specific_params():
"""Test that Azure-specific parameters are passed through extra_body"""
from openai import AsyncAzureOpenAI
from openai.types.fine_tuning.fine_tuning_job import FineTuningJob
from openai.types.fine_tuning.fine_tuning_job import Hyperparameters as OAIHyperparameters
from litellm.types.utils import LiteLLMFineTuningJob
mock_response = FineTuningJob(
mock_response = LiteLLMFineTuningJob(
id="ft-azure-123",
model="gpt-4.1-mini-2025-04-14",
created_at=1677610602,
@ -634,8 +634,11 @@ async def test_mock_azure_create_fine_tune_job_with_azure_specific_params():
result_files=[],
)
async def mock_async_create(*args, **kwargs):
return mock_response
with patch("litellm.llms.azure.fine_tuning.handler.AzureOpenAIFineTuningAPI.create_fine_tuning_job") as mock_create:
mock_create.return_value = mock_response
mock_create.return_value = mock_async_create()
response = await litellm.acreate_fine_tuning_job(
model="gpt-4.1-mini-2025-04-14",

View file

@ -438,3 +438,18 @@ def test_normalize_fine_tuning_job_dict_openai_unchanged():
data = {"organization_id": None, "result_files": None, "status": "pending"}
out = _normalize_fine_tuning_job_dict(data, is_azure=False)
assert out is data
def test_openai_file_object_accepts_pending_status():
from litellm.types.llms.openai import OpenAIFileObject
file_obj = OpenAIFileObject(
id="file-123",
bytes=1024,
created_at=1677610602,
filename="train.jsonl",
object="file",
purpose="fine-tune",
status="pending",
)
assert file_obj.status == "pending"