mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-14 23:21:35 +00:00
fix(spend): preserve deployment identity in spend logs when litellm_metadata is present
Spend logging reads litellm_metadata when a request carries one, but the router writes the selected deployment's identity (model_info.id, model_group, deployment name) into metadata. add_missing_spend_metadata_to_litellm_metadata only copied user_api_key* keys, so openai/ passthrough deployments logged a blank model_id and a provider-prefix-stripped model for requests that included litellm_metadata. Fixes #35472 Co-Authored-By: Devin AI <158243242+devin-ai-integration[bot]@users.noreply.github.com>
This commit is contained in:
parent
23de7a15d9
commit
61e5ac0930
2 changed files with 87 additions and 3 deletions
|
|
@ -164,17 +164,33 @@ def remove_items_at_indices(items: Optional[List[Any]], indices: Iterable[int])
|
|||
items.pop(index)
|
||||
|
||||
|
||||
SPEND_TRACKING_DEPLOYMENT_IDENTITY_KEYS = (
|
||||
"model_info",
|
||||
"model_group",
|
||||
"deployment",
|
||||
"deployment_model_name",
|
||||
)
|
||||
|
||||
|
||||
def add_missing_spend_metadata_to_litellm_metadata(litellm_metadata: dict, metadata: dict) -> dict:
|
||||
"""
|
||||
Helper to get litellm metadata for spend tracking
|
||||
|
||||
PATCH for issue where both `litellm_metadata` and `metadata` are present in the kwargs
|
||||
and user_api_key values are in 'metadata'.
|
||||
and spend-tracking values are in 'metadata'.
|
||||
|
||||
The router always writes the selected deployment's identity (model_info.id,
|
||||
model_group, deployment name) into `metadata`, but spend logging reads from
|
||||
`litellm_metadata` whenever a request carries one (e.g. caller-supplied tags).
|
||||
Without copying these over, the same deployment logs a blank `model_id` and a
|
||||
provider-prefix-stripped `model` for requests that include `litellm_metadata`.
|
||||
"""
|
||||
potential_spend_tracking_metadata_substring = "user_api_key"
|
||||
for key, value in metadata.items():
|
||||
if potential_spend_tracking_metadata_substring in key:
|
||||
if "user_api_key" in key:
|
||||
litellm_metadata[key] = value
|
||||
for key in SPEND_TRACKING_DEPLOYMENT_IDENTITY_KEYS:
|
||||
if key not in litellm_metadata and key in metadata:
|
||||
litellm_metadata[key] = metadata[key]
|
||||
return litellm_metadata
|
||||
|
||||
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import pytest
|
|||
|
||||
from litellm.litellm_core_utils.core_helpers import (
|
||||
_FINISH_REASON_MAP,
|
||||
get_litellm_metadata_from_kwargs,
|
||||
get_or_create_metadata_bucket,
|
||||
map_finish_reason,
|
||||
reconstruct_model_name,
|
||||
|
|
@ -11,6 +12,73 @@ from litellm.litellm_core_utils.core_helpers import (
|
|||
)
|
||||
|
||||
|
||||
def _spend_log_identity(kwargs: dict) -> dict:
|
||||
"""Mirror how spend logging derives model / model_id from request kwargs."""
|
||||
metadata = get_litellm_metadata_from_kwargs(kwargs)
|
||||
return {
|
||||
"model": reconstruct_model_name(
|
||||
kwargs.get("model") or "", kwargs.get("custom_llm_provider"), metadata or {}
|
||||
),
|
||||
"model_id": metadata.get("model_info", {}).get("id", ""),
|
||||
"model_group": metadata.get("model_group", ""),
|
||||
}
|
||||
|
||||
|
||||
def test_spend_log_deployment_identity_consistent_with_litellm_metadata():
|
||||
"""Regression for #35472: an openai/ passthrough deployment must log the same
|
||||
model / model_id whether or not the request also carries litellm_metadata.
|
||||
|
||||
The router writes the deployment identity into `metadata`, but spend logging
|
||||
reads `litellm_metadata` when present, so those keys must be carried over."""
|
||||
router_metadata = {
|
||||
"deployment": "openai/anthropic/claude-sonnet-5",
|
||||
"model_info": {"id": "9da5dfc9-2223-4f77-b3c9-f9100d9cb2a0"},
|
||||
"model_group": "my-model",
|
||||
"user_api_key_hash": "abc",
|
||||
}
|
||||
|
||||
without_litellm_metadata = _spend_log_identity(
|
||||
{
|
||||
"model": "anthropic/claude-sonnet-5",
|
||||
"custom_llm_provider": "openai",
|
||||
"litellm_params": {"metadata": dict(router_metadata)},
|
||||
}
|
||||
)
|
||||
with_litellm_metadata = _spend_log_identity(
|
||||
{
|
||||
"model": "anthropic/claude-sonnet-5",
|
||||
"custom_llm_provider": "openai",
|
||||
"litellm_params": {
|
||||
"metadata": dict(router_metadata),
|
||||
"litellm_metadata": {"tags": ["t1"]},
|
||||
},
|
||||
}
|
||||
)
|
||||
|
||||
expected = {
|
||||
"model": "openai/anthropic/claude-sonnet-5",
|
||||
"model_id": "9da5dfc9-2223-4f77-b3c9-f9100d9cb2a0",
|
||||
"model_group": "my-model",
|
||||
}
|
||||
assert without_litellm_metadata == expected
|
||||
assert with_litellm_metadata == expected
|
||||
|
||||
|
||||
def test_get_litellm_metadata_from_kwargs_does_not_overwrite_existing_identity():
|
||||
"""Caller-supplied identity keys in litellm_metadata must win over metadata."""
|
||||
kwargs = {
|
||||
"litellm_params": {
|
||||
"metadata": {"model_group": "router-group", "deployment": "router-dep"},
|
||||
"litellm_metadata": {"model_group": "caller-group"},
|
||||
}
|
||||
}
|
||||
|
||||
metadata = get_litellm_metadata_from_kwargs(kwargs)
|
||||
|
||||
assert metadata["model_group"] == "caller-group"
|
||||
assert metadata["deployment"] == "router-dep"
|
||||
|
||||
|
||||
class TestGetOrCreateMetadataBucket:
|
||||
"""The single owner every guardrail writer and reader shares, so the response
|
||||
header and the spend log can never disagree about which dict a record lives in."""
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue