mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
fix(proxy): log rejected unknown-model requests under a placeholder model name
A request whose model field matched no configured model was rejected with 400 but its failure row still persisted the raw client string as the model, so a client that concatenated its prompt into the model field wrote that prompt into LiteLLM_SpendLogs and the daily spend tables, where /user/daily/activity/aggregated returned it as a breakdown.models key. The spend log payload now records such rejections under the constant unknown-model, keeping the failed request counted without persisting client input as a model name.
This commit is contained in:
parent
2f114d44ed
commit
fe6f615c7a
3 changed files with 45 additions and 2 deletions
|
|
@ -1999,6 +1999,8 @@ NON_INFERENCE_CALL_TYPES: Final[frozenset[str]] = frozenset(
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
UNKNOWN_MODEL_SPEND_LOG_MODEL: Final[str] = "unknown-model"
|
||||||
|
|
||||||
# PTU reservation rollup writes rows to LiteLLM_DailyTeamSpend with this
|
# PTU reservation rollup writes rows to LiteLLM_DailyTeamSpend with this
|
||||||
# sentinel api_key so PTU flat cost stays distinguishable from real per-request
|
# sentinel api_key so PTU flat cost stays distinguishable from real per-request
|
||||||
# spend under the table's composite unique constraint.
|
# spend under the table's composite unique constraint.
|
||||||
|
|
|
||||||
|
|
@ -18,6 +18,7 @@ from litellm.constants import (
|
||||||
LITTELM_INTERNAL_HEALTH_SERVICE_ACCOUNT_NAME,
|
LITTELM_INTERNAL_HEALTH_SERVICE_ACCOUNT_NAME,
|
||||||
REDACTED_BY_LITELM_STRING,
|
REDACTED_BY_LITELM_STRING,
|
||||||
SESSION_ID_OMITTED_METADATA_KEY,
|
SESSION_ID_OMITTED_METADATA_KEY,
|
||||||
|
UNKNOWN_MODEL_SPEND_LOG_MODEL,
|
||||||
)
|
)
|
||||||
from litellm.constants import (
|
from litellm.constants import (
|
||||||
MAX_STRING_LENGTH_PROMPT_IN_DB as DEFAULT_MAX_STRING_LENGTH_PROMPT_IN_DB,
|
MAX_STRING_LENGTH_PROMPT_IN_DB as DEFAULT_MAX_STRING_LENGTH_PROMPT_IN_DB,
|
||||||
|
|
@ -34,6 +35,7 @@ from litellm.litellm_core_utils.litellm_logging import (
|
||||||
)
|
)
|
||||||
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps, strip_null_bytes
|
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps, strip_null_bytes
|
||||||
from litellm.proxy._types import SpendLogsMetadata, SpendLogsPayload, SpendLogsRouterMetadata
|
from litellm.proxy._types import SpendLogsMetadata, SpendLogsPayload, SpendLogsRouterMetadata
|
||||||
|
from litellm.proxy.route_llm_request import ProxyModelNotFoundError
|
||||||
from litellm.proxy.spend_tracking.spend_log_error_logger import spend_log_error
|
from litellm.proxy.spend_tracking.spend_log_error_logger import spend_log_error
|
||||||
from litellm.proxy.utils import PrismaClient, hash_token
|
from litellm.proxy.utils import PrismaClient, hash_token
|
||||||
from litellm.types.utils import (
|
from litellm.types.utils import (
|
||||||
|
|
@ -335,6 +337,7 @@ def get_logging_payload(kwargs, response_obj, start_time, end_time) -> SpendLogs
|
||||||
if kwargs is None:
|
if kwargs is None:
|
||||||
kwargs = {}
|
kwargs = {}
|
||||||
|
|
||||||
|
rejected_as_unknown_model: Final = isinstance(response_obj, ProxyModelNotFoundError)
|
||||||
if response_obj is None:
|
if response_obj is None:
|
||||||
response_obj = {}
|
response_obj = {}
|
||||||
elif not isinstance(response_obj, BaseModel) and not isinstance(response_obj, dict):
|
elif not isinstance(response_obj, BaseModel) and not isinstance(response_obj, dict):
|
||||||
|
|
@ -433,8 +436,11 @@ def get_logging_payload(kwargs, response_obj, start_time, end_time) -> SpendLogs
|
||||||
)
|
)
|
||||||
raw_model: Final = cast(str, kwargs.get("model") or "")
|
raw_model: Final = cast(str, kwargs.get("model") or "")
|
||||||
model_name: Final = (
|
model_name: Final = (
|
||||||
standard_logging_payload.get("model") if standard_logging_payload is not None else None
|
UNKNOWN_MODEL_SPEND_LOG_MODEL
|
||||||
) or reconstruct_model_name(raw_model, custom_llm_provider, metadata or {})
|
if rejected_as_unknown_model
|
||||||
|
else (standard_logging_payload.get("model") if standard_logging_payload is not None else None)
|
||||||
|
or reconstruct_model_name(raw_model, custom_llm_provider, metadata or {})
|
||||||
|
)
|
||||||
litellm_call_id: Final = cast(
|
litellm_call_id: Final = cast(
|
||||||
str | None,
|
str | None,
|
||||||
kwargs.get("litellm_call_id") or litellm_params.get("litellm_call_id"),
|
kwargs.get("litellm_call_id") or litellm_params.get("litellm_call_id"),
|
||||||
|
|
|
||||||
|
|
@ -17,10 +17,12 @@ from litellm.constants import (
|
||||||
LITTELM_INTERNAL_HEALTH_SERVICE_ACCOUNT_NAME,
|
LITTELM_INTERNAL_HEALTH_SERVICE_ACCOUNT_NAME,
|
||||||
REDACTED_BY_LITELM_STRING,
|
REDACTED_BY_LITELM_STRING,
|
||||||
SESSION_ID_OMITTED_METADATA_KEY,
|
SESSION_ID_OMITTED_METADATA_KEY,
|
||||||
|
UNKNOWN_MODEL_SPEND_LOG_MODEL,
|
||||||
)
|
)
|
||||||
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
|
from litellm.litellm_core_utils.safe_json_dumps import safe_dumps
|
||||||
from litellm.proxy._types import UserAPIKeyAuth
|
from litellm.proxy._types import UserAPIKeyAuth
|
||||||
from litellm.proxy.litellm_pre_call_utils import LiteLLMProxyRequestSetup
|
from litellm.proxy.litellm_pre_call_utils import LiteLLMProxyRequestSetup
|
||||||
|
from litellm.proxy.route_llm_request import ProxyModelNotFoundError
|
||||||
from litellm.proxy.spend_tracking.spend_tracking_utils import (
|
from litellm.proxy.spend_tracking.spend_tracking_utils import (
|
||||||
_get_messages_for_spend_logs_payload,
|
_get_messages_for_spend_logs_payload,
|
||||||
_get_proxy_server_request_for_spend_logs_payload,
|
_get_proxy_server_request_for_spend_logs_payload,
|
||||||
|
|
@ -922,6 +924,39 @@ def test_safe_dumps_complex_metadata_like_object():
|
||||||
assert parsed["model"] == "gpt-4"
|
assert parsed["model"] == "gpt-4"
|
||||||
|
|
||||||
|
|
||||||
|
_RAW_MODEL_WITH_PROMPT: Final = "opus-4.6 Please summarize my medical records\nPatient has diabetes"
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.mark.parametrize(
|
||||||
|
("rejection", "expected_model"),
|
||||||
|
[
|
||||||
|
(
|
||||||
|
ProxyModelNotFoundError(route="acompletion", model_name=_RAW_MODEL_WITH_PROMPT),
|
||||||
|
UNKNOWN_MODEL_SPEND_LOG_MODEL,
|
||||||
|
),
|
||||||
|
(ValueError("provider timed out"), _RAW_MODEL_WITH_PROMPT),
|
||||||
|
],
|
||||||
|
)
|
||||||
|
def test_get_logging_payload_replaces_model_only_when_router_rejected_it_as_unknown(
|
||||||
|
rejection: Exception, expected_model: str
|
||||||
|
):
|
||||||
|
kwargs: Final = {
|
||||||
|
"model": _RAW_MODEL_WITH_PROMPT,
|
||||||
|
"messages": [{"role": "user", "content": "hi"}],
|
||||||
|
"call_type": "acompletion",
|
||||||
|
"litellm_params": {"metadata": {"user_api_key": "sk-test", "status": "failure"}},
|
||||||
|
}
|
||||||
|
|
||||||
|
payload: Final = get_logging_payload(
|
||||||
|
kwargs=kwargs,
|
||||||
|
response_obj=rejection,
|
||||||
|
start_time=datetime.datetime.now(timezone.utc),
|
||||||
|
end_time=datetime.datetime.now(timezone.utc),
|
||||||
|
)
|
||||||
|
|
||||||
|
assert payload["model"] == expected_model
|
||||||
|
|
||||||
|
|
||||||
@patch("litellm.proxy.proxy_server.master_key", None)
|
@patch("litellm.proxy.proxy_server.master_key", None)
|
||||||
@patch("litellm.proxy.proxy_server.general_settings", {})
|
@patch("litellm.proxy.proxy_server.general_settings", {})
|
||||||
def test_get_logging_payload_api_key_preserved_when_standard_logging_payload_is_none():
|
def test_get_logging_payload_api_key_preserved_when_standard_logging_payload_is_none():
|
||||||
|
|
|
||||||
Loading…
Add table
Reference in a new issue