fix(azure/passthrough): populate standard_logging_object via logging hook

This commit is contained in:
michelligabriele 2026-04-14 08:10:39 +02:00
parent e64d98f725
commit 63281e8330
No known key found for this signature in database
2 changed files with 134 additions and 0 deletions

View file

@ -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

View file

@ -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