fix(logging): stop the spend metadata merge from walking a dict the logging thread writes to (#46044)

This commit is contained in:
devin-ai-integration[bot] 2026-10-16 17:37:18 -07:00 • committed by GitHub
parent 3ef0b35255
commit aa32e4cc88
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
2 changed files with 57 additions and 2 deletions

View file

@ -318,7 +318,7 @@ def remove_items_at_indices(items: _PoppableByIndex | None, indices: Iterable[in
items.pop(index)
def add_missing_spend_metadata_to_litellm_metadata(litellm_metadata: dict, metadata: dict) -> dict:
def add_missing_spend_metadata_to_litellm_metadata(litellm_metadata: dict, metadata: Mapping[str, object]) -> dict:
"""
Helper to get litellm metadata for spend tracking
@ -326,7 +326,7 @@ def add_missing_spend_metadata_to_litellm_metadata(litellm_metadata: dict, metad
and user_api_key values are in 'metadata'.
"""
potential_spend_tracking_metadata_substring: Final = "user_api_key"
for key, value in metadata.items():
for key, value in dict(metadata).items():
if potential_spend_tracking_metadata_substring in key:
litellm_metadata[key] = value
return litellm_metadata

View file

@ -1,6 +1,8 @@
"""Tests for litellm_core_utils.core_helpers module."""
import logging
import threading
from collections.abc import Iterator, Mapping
from typing import Final
import httpx
@ -13,6 +15,7 @@ from litellm.litellm_core_utils.core_helpers import (
budget_reservation_from_metadata,
drop_params_env_flag,
drop_params_flag,
get_litellm_metadata_from_kwargs,
get_parent_otel_span_from_kwargs,
get_or_create_metadata_bucket,
get_provider_response_headers_from_hidden_params,
@ -587,3 +590,55 @@ class TestProviderResponseHeadersInHiddenParams:
assert get_provider_response_headers_from_hidden_params(sibling) is None
assert "additional_headers" not in sibling._hidden_params
def test_spend_metadata_merge_survives_the_logging_thread_inserting_into_the_request_metadata() -> None:
metadata: Final[dict[str, object]] = {}
def logging_thread_records_hidden_params() -> None:
metadata["hidden_params"] = {"response_cost": 0.1}
class KeyCheckedWhileTheLoggingThreadRuns(str):
def __contains__(self, key: str) -> bool:
writer: Final = threading.Thread(target=logging_thread_records_hidden_params)
writer.start()
writer.join()
return super().__contains__(key)
metadata[KeyCheckedWhileTheLoggingThreadRuns("user_api_key_alias")] = "alias"
metadata["user_api_key_hash"] = "hash"
metadata["requester_ip_address"] = "10.0.0.1"
litellm_metadata: Final[dict[str, object]] = {"user_api_key_alias": "stale", "model_group": "claude"}
merged: Final = get_litellm_metadata_from_kwargs(
{"litellm_params": {"metadata": metadata, "litellm_metadata": litellm_metadata}}
)
assert "hidden_params" in metadata
assert merged == {"user_api_key_alias": "alias", "user_api_key_hash": "hash", "model_group": "claude"}
def test_spend_metadata_merge_reads_request_metadata_that_is_a_read_only_mapping() -> None:
class ReadOnlyMetadata(Mapping[str, object]):
def __init__(self, values: dict[str, object]) -> None:
self._values: Final = values
def __getitem__(self, key: str) -> object:
return self._values[key]
def __iter__(self) -> Iterator[str]:
return iter(self._values)
def __len__(self) -> int:
return len(self._values)
merged: Final = get_litellm_metadata_from_kwargs(
{
"litellm_params": {
"metadata": ReadOnlyMetadata({"user_api_key_hash": "hash", "requester_ip_address": "10.0.0.1"}),
"litellm_metadata": {"model_group": "claude"},
}
}
)
assert merged == {"user_api_key_hash": "hash", "model_group": "claude"}