mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-10 22:41:41 +00:00
address greptile review feedback (greploop iteration 1)
- Move trainingType injection to AzureOpenAIFineTuningAPI handler - Guard normalization with is_azure flag to only apply to Azure responses - Override acreate_fine_tuning_job in Azure handler to use is_azure=True - Update test to directly test _ensure_training_type method - Add test for OpenAI unchanged behavior Made-with: Cursor
This commit is contained in:
parent
9248ac8d7e
commit
230dcec902
5 changed files with 110 additions and 35 deletions
|
|
@ -245,15 +245,6 @@ def create_fine_tuning_job(
|
|||
)
|
||||
# Azure OpenAI
|
||||
elif custom_llm_provider == "azure":
|
||||
# Azure requires trainingType (e.g. 1 = supervised). Omitting it yields a misleading
|
||||
# "The specified base model does not support fine-tuning" error from the service.
|
||||
if kwargs.get("trainingType") is None:
|
||||
_eb = (
|
||||
kwargs.get("extra_body") or optional_params.get("extra_body") or {}
|
||||
)
|
||||
if not (isinstance(_eb, dict) and _eb.get("trainingType") is not None):
|
||||
kwargs["trainingType"] = 1
|
||||
|
||||
api_base = optional_params.api_base or litellm.api_base or get_secret_str("AZURE_API_BASE") # type: ignore
|
||||
|
||||
api_version = (
|
||||
|
|
|
|||
|
|
@ -1,10 +1,15 @@
|
|||
from typing import Optional, Union
|
||||
from typing import Any, Coroutine, Dict, Optional, Union, cast
|
||||
|
||||
import httpx
|
||||
from openai import AsyncAzureOpenAI, AsyncOpenAI, AzureOpenAI, OpenAI
|
||||
|
||||
from litellm._logging import verbose_logger
|
||||
from litellm.llms.azure.common_utils import BaseAzureLLM
|
||||
from litellm.llms.openai.fine_tuning.handler import OpenAIFineTuningAPI
|
||||
from litellm.llms.openai.fine_tuning.handler import (
|
||||
OpenAIFineTuningAPI,
|
||||
_litellm_fine_tuning_job_from_response,
|
||||
)
|
||||
from litellm.types.utils import LiteLLMFineTuningJob
|
||||
|
||||
|
||||
class AzureOpenAIFineTuningAPI(OpenAIFineTuningAPI, BaseAzureLLM):
|
||||
|
|
@ -12,6 +17,82 @@ class AzureOpenAIFineTuningAPI(OpenAIFineTuningAPI, BaseAzureLLM):
|
|||
AzureOpenAI methods to support fine tuning, inherits from OpenAIFineTuningAPI.
|
||||
"""
|
||||
|
||||
@staticmethod
|
||||
def _ensure_training_type(create_fine_tuning_job_data: Dict[str, Any]) -> None:
|
||||
"""
|
||||
Azure requires trainingType in extra_body. Default to 1 (supervised) if omitted.
|
||||
"""
|
||||
extra_body = create_fine_tuning_job_data.get("extra_body") or {}
|
||||
if not isinstance(extra_body, dict):
|
||||
extra_body = {}
|
||||
if extra_body.get("trainingType") is None:
|
||||
extra_body["trainingType"] = 1
|
||||
create_fine_tuning_job_data["extra_body"] = extra_body
|
||||
verbose_logger.debug(
|
||||
"Azure fine-tuning: defaulting trainingType=1 (supervised)"
|
||||
)
|
||||
|
||||
async def acreate_fine_tuning_job(
|
||||
self,
|
||||
create_fine_tuning_job_data: dict,
|
||||
openai_client: Union[AsyncOpenAI, AsyncAzureOpenAI],
|
||||
) -> LiteLLMFineTuningJob:
|
||||
response = await openai_client.fine_tuning.jobs.create(
|
||||
**create_fine_tuning_job_data
|
||||
)
|
||||
return _litellm_fine_tuning_job_from_response(response, is_azure=True)
|
||||
|
||||
def create_fine_tuning_job(
|
||||
self,
|
||||
_is_async: bool,
|
||||
create_fine_tuning_job_data: dict,
|
||||
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]]:
|
||||
self._ensure_training_type(create_fine_tuning_job_data)
|
||||
|
||||
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.acreate_fine_tuning_job(
|
||||
create_fine_tuning_job_data=create_fine_tuning_job_data,
|
||||
openai_client=openai_client,
|
||||
)
|
||||
|
||||
verbose_logger.debug(
|
||||
"creating fine tuning job, args= %s", create_fine_tuning_job_data
|
||||
)
|
||||
response = cast(OpenAI, openai_client).fine_tuning.jobs.create(
|
||||
**create_fine_tuning_job_data
|
||||
)
|
||||
return _litellm_fine_tuning_job_from_response(response, is_azure=True)
|
||||
|
||||
def get_openai_client(
|
||||
self,
|
||||
api_key: Optional[str],
|
||||
|
|
|
|||
|
|
@ -7,7 +7,9 @@ from litellm._logging import verbose_logger
|
|||
from litellm.types.utils import LiteLLMFineTuningJob
|
||||
|
||||
|
||||
def _normalize_fine_tuning_job_dict(data: Dict[str, Any]) -> Dict[str, Any]:
|
||||
def _normalize_fine_tuning_job_dict(
|
||||
data: Dict[str, Any], is_azure: bool = False
|
||||
) -> Dict[str, Any]:
|
||||
"""
|
||||
Normalize Azure OpenAI FineTuningJob response to match OpenAI schema.
|
||||
|
||||
|
|
@ -16,6 +18,9 @@ def _normalize_fine_tuning_job_dict(data: Dict[str, Any]) -> Dict[str, Any]:
|
|||
- result_files: null → []
|
||||
- status: "pending" → "queued"
|
||||
"""
|
||||
if not is_azure:
|
||||
return data
|
||||
|
||||
normalized = data.copy()
|
||||
|
||||
if normalized.get("organization_id") is None:
|
||||
|
|
@ -30,9 +35,11 @@ def _normalize_fine_tuning_job_dict(data: Dict[str, Any]) -> Dict[str, Any]:
|
|||
return normalized
|
||||
|
||||
|
||||
def _litellm_fine_tuning_job_from_response(response: Any) -> LiteLLMFineTuningJob:
|
||||
def _litellm_fine_tuning_job_from_response(
|
||||
response: Any, is_azure: bool = False
|
||||
) -> LiteLLMFineTuningJob:
|
||||
return LiteLLMFineTuningJob(
|
||||
**_normalize_fine_tuning_job_dict(response.model_dump())
|
||||
**_normalize_fine_tuning_job_dict(response.model_dump(), is_azure=is_azure)
|
||||
)
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -182,30 +182,17 @@ async def test_azure_create_fine_tune_jobs_async():
|
|||
|
||||
def test_azure_trainingtype_defaults_to_one():
|
||||
"""
|
||||
Azure requires trainingType in extra_body. When omitted, LiteLLM defaults it to 1.
|
||||
Azure requires trainingType in extra_body. When omitted, AzureOpenAIFineTuningAPI defaults it to 1.
|
||||
"""
|
||||
from unittest.mock import MagicMock, patch
|
||||
from litellm.fine_tuning.main import create_fine_tuning_job
|
||||
from litellm.llms.azure.fine_tuning.handler import AzureOpenAIFineTuningAPI
|
||||
|
||||
with patch(
|
||||
"litellm.fine_tuning.main.azure_fine_tuning_apis_instance.create_fine_tuning_job"
|
||||
) as mock_create:
|
||||
mock_create.return_value = MagicMock(
|
||||
id="ftjob-test", status="queued", model="gpt-4o-mini"
|
||||
)
|
||||
handler = AzureOpenAIFineTuningAPI()
|
||||
create_data = {"model": "gpt-4o-mini", "training_file": "file-test"}
|
||||
|
||||
create_fine_tuning_job(
|
||||
model="gpt-4o-mini",
|
||||
training_file="file-test",
|
||||
custom_llm_provider="azure",
|
||||
api_base="https://test.openai.azure.com",
|
||||
api_key="test-key",
|
||||
)
|
||||
handler._ensure_training_type(create_data)
|
||||
|
||||
call_kwargs = mock_create.call_args[1]
|
||||
create_data = call_kwargs["create_fine_tuning_job_data"]
|
||||
assert "extra_body" in create_data
|
||||
assert create_data["extra_body"].get("trainingType") == 1
|
||||
assert "extra_body" in create_data
|
||||
assert create_data["extra_body"]["trainingType"] == 1
|
||||
|
||||
|
||||
@pytest.mark.asyncio()
|
||||
|
|
|
|||
|
|
@ -424,8 +424,17 @@ def test_normalize_fine_tuning_job_dict_maps_azure_pending():
|
|||
from litellm.llms.openai.fine_tuning.handler import _normalize_fine_tuning_job_dict
|
||||
|
||||
out = _normalize_fine_tuning_job_dict(
|
||||
{"organization_id": None, "result_files": None, "status": "pending"}
|
||||
{"organization_id": None, "result_files": None, "status": "pending"},
|
||||
is_azure=True,
|
||||
)
|
||||
assert out["organization_id"] == ""
|
||||
assert out["result_files"] == []
|
||||
assert out["status"] == "queued"
|
||||
|
||||
|
||||
def test_normalize_fine_tuning_job_dict_openai_unchanged():
|
||||
from litellm.llms.openai.fine_tuning.handler import _normalize_fine_tuning_job_dict
|
||||
|
||||
data = {"organization_id": None, "result_files": None, "status": "pending"}
|
||||
out = _normalize_fine_tuning_job_dict(data, is_azure=False)
|
||||
assert out is data
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue