mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-08 22:21:35 +00:00
fix(azure/passthrough): populate standard_logging_object via logging hook
This commit is contained in:
parent
e64d98f725
commit
63281e8330
2 changed files with 134 additions and 0 deletions
|
|
@ -1,7 +1,9 @@
|
|||
from typing import TYPE_CHECKING, List, Optional, Tuple
|
||||
|
||||
import httpx
|
||||
from httpx import Response
|
||||
|
||||
from litellm.litellm_core_utils.litellm_logging import Logging
|
||||
from litellm.llms.azure.common_utils import BaseAzureLLM
|
||||
from litellm.llms.base_llm.passthrough.transformation import BasePassthroughConfig
|
||||
from litellm.secret_managers.main import get_secret_str
|
||||
|
|
@ -11,6 +13,8 @@ from litellm.types.router import GenericLiteLLMParams
|
|||
if TYPE_CHECKING:
|
||||
from httpx import URL
|
||||
|
||||
from litellm.types.utils import CostResponseTypes
|
||||
|
||||
|
||||
class AzurePassthroughConfig(BasePassthroughConfig):
|
||||
def is_streaming_request(self, endpoint: str, request_data: dict) -> bool:
|
||||
|
|
@ -83,3 +87,36 @@ class AzurePassthroughConfig(BasePassthroughConfig):
|
|||
self, api_key: Optional[str] = None, api_base: Optional[str] = None
|
||||
) -> List[str]:
|
||||
return super().get_models(api_key, api_base)
|
||||
|
||||
def logging_non_streaming_response(
|
||||
self,
|
||||
model: str,
|
||||
custom_llm_provider: str,
|
||||
httpx_response: Response,
|
||||
request_data: dict,
|
||||
logging_obj: Logging,
|
||||
endpoint: str,
|
||||
) -> Optional["CostResponseTypes"]:
|
||||
from litellm import encoding
|
||||
from litellm.llms.openai.chat.gpt_transformation import OpenAIGPTConfig
|
||||
from litellm.types.utils import ModelResponse
|
||||
|
||||
if "chat/completions" not in endpoint:
|
||||
return None
|
||||
|
||||
openai_chat_config = OpenAIGPTConfig()
|
||||
|
||||
litellm_model_response: ModelResponse = openai_chat_config.transform_response(
|
||||
model=model,
|
||||
messages=[{"role": "user", "content": "no-message-pass-through-endpoint"}],
|
||||
raw_response=httpx_response,
|
||||
model_response=ModelResponse(),
|
||||
logging_obj=logging_obj,
|
||||
optional_params={},
|
||||
litellm_params={},
|
||||
api_key="",
|
||||
request_data=request_data,
|
||||
encoding=encoding,
|
||||
)
|
||||
|
||||
return litellm_model_response
|
||||
|
|
|
|||
|
|
@ -0,0 +1,97 @@
|
|||
import json
|
||||
import os
|
||||
import sys
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import httpx
|
||||
|
||||
sys.path.insert(0, os.path.abspath("../../../../.."))
|
||||
|
||||
from litellm.llms.azure.passthrough.transformation import AzurePassthroughConfig
|
||||
from litellm.types.utils import ModelResponse
|
||||
|
||||
|
||||
def _azure_chat_completion_body():
|
||||
return {
|
||||
"id": "chatcmpl-abc123",
|
||||
"object": "chat.completion",
|
||||
"created": 1700000000,
|
||||
"model": "gpt-4.1-mini-2025-04-14",
|
||||
"choices": [
|
||||
{
|
||||
"index": 0,
|
||||
"message": {
|
||||
"role": "assistant",
|
||||
"content": "Hello! How can I assist you today?",
|
||||
},
|
||||
"finish_reason": "stop",
|
||||
}
|
||||
],
|
||||
"usage": {
|
||||
"prompt_tokens": 10,
|
||||
"completion_tokens": 8,
|
||||
"total_tokens": 18,
|
||||
},
|
||||
}
|
||||
|
||||
|
||||
def _make_httpx_response(body: dict) -> httpx.Response:
|
||||
return httpx.Response(
|
||||
status_code=200,
|
||||
headers={"content-type": "application/json"},
|
||||
content=json.dumps(body).encode("utf-8"),
|
||||
request=httpx.Request(
|
||||
"POST",
|
||||
"https://example.openai.azure.com/openai/deployments/gpt-4.1-mini/chat/completions",
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
def test_azure_passthrough_logging_non_streaming_response_chat_completions():
|
||||
"""
|
||||
Returns a populated ModelResponse (with usage + content) for a chat/completions
|
||||
endpoint. This is what _success_handler_helper_fn needs to build
|
||||
standard_logging_object — without it, Datadog/cost-tracking/router-success all
|
||||
raise on every Azure passthrough request.
|
||||
"""
|
||||
config = AzurePassthroughConfig()
|
||||
logging_obj = MagicMock()
|
||||
|
||||
result = config.logging_non_streaming_response(
|
||||
model="gpt-4.1-mini",
|
||||
custom_llm_provider="azure",
|
||||
httpx_response=_make_httpx_response(_azure_chat_completion_body()),
|
||||
request_data={
|
||||
"model": "gpt-4.1-mini",
|
||||
"messages": [{"role": "user", "content": "hi"}],
|
||||
},
|
||||
logging_obj=logging_obj,
|
||||
endpoint="openai/deployments/gpt-4.1-mini/chat/completions",
|
||||
)
|
||||
|
||||
assert isinstance(result, ModelResponse)
|
||||
assert result.choices[0].message.content == "Hello! How can I assist you today?"
|
||||
assert result.usage.prompt_tokens == 10
|
||||
assert result.usage.completion_tokens == 8
|
||||
assert result.usage.total_tokens == 18
|
||||
|
||||
|
||||
def test_azure_passthrough_logging_non_streaming_response_unknown_endpoint_returns_none():
|
||||
"""
|
||||
Endpoints other than chat/completions (responses, messages, images) fall
|
||||
through to None — matches base-class behavior and Bedrock's "unknown
|
||||
endpoint" handling. Not a regression; just scoping.
|
||||
"""
|
||||
config = AzurePassthroughConfig()
|
||||
logging_obj = MagicMock()
|
||||
|
||||
result = config.logging_non_streaming_response(
|
||||
model="gpt-4.1-mini",
|
||||
custom_llm_provider="azure",
|
||||
httpx_response=_make_httpx_response(_azure_chat_completion_body()),
|
||||
request_data={},
|
||||
logging_obj=logging_obj,
|
||||
endpoint="openai/responses",
|
||||
)
|
||||
|
||||
assert result is None
|
||||
Loading…
Add table
Reference in a new issue