mirror of
https://github.com/BerriAI/litellm.git
synced 2026-09-12 23:01:41 +00:00
fix(pass-through): propagate x-litellm-spend-logs-metadata header (#26120)
* fix(pass-through): propagate x-litellm-spend-logs-metadata header
OpenAI-compatible endpoints parse `x-litellm-spend-logs-metadata` in
`LiteLLMProxyRequestSetup._get_spend_logs_metadata_from_request_headers`
and place it at `data["metadata"]["spend_logs_metadata"]`. Pass-through
endpoints (/anthropic/v1/messages, /openai/v1/*, /v2/chat, /vertex_ai/*,
/gemini/*, Cursor) bypass that pipeline, so
`StandardLoggingPayload.metadata.spend_logs_metadata` was always None
for `call_type=pass_through_endpoint`, hiding per-call metadata
(e.g. task / issue) from `/spend/logs`.
This change extracts the header at both `PassthroughStandardLoggingPayload`
construction sites (HTTP and WebSocket), adds an optional
`spend_logs_metadata` field to the TypedDict, and propagates it into
`kwargs["litellm_params"]["metadata"]` at every provider-handler
payload builder (base, Anthropic, OpenAI, Cohere, Gemini, Vertex) — both
the non-streaming and streaming (collected-chunks) paths. Two small
shared helpers on `BasePassthroughLoggingHandler` keep the propagation
consistent across handlers:
- `_apply_spend_logs_metadata(kwargs, payload)` — writes to the same
key path OpenAI-compatible flows already use, so
`get_standard_logging_object_payload` surfaces the value identically.
- `_seed_streaming_kwargs_from_logging_obj(logging_obj)` — seeds the
fresh kwargs used by streaming logging paths with the payload that
`success_handler.pass_through_async_success_handler` stashes on
`model_call_details` before any provider handler runs.
Tests in `tests/test_litellm/proxy/pass_through_endpoints/test_spend_logs_metadata_propagation.py`
cover: header extraction (valid / missing / malformed JSON), helper
writes to the matching key path, preservation of existing metadata
keys, no-op when payload or metadata is absent, streaming-kwargs seed
behavior.
* fix: move BasePassthroughLoggingHandler imports to local scope to avoid cyclic import warnings
* fix(pass-through): use logging_obj fallback for passthrough_logging_payload in non-streaming paths
passthrough_logging_payload is a named parameter of pass_through_async_success_handler
and is therefore consumed from **kwargs before reaching provider handlers.
All non-streaming kwargs.get('passthrough_logging_payload') calls returned None,
making _apply_spend_logs_metadata a no-op on every non-streaming path.
Fix: add logging_obj.model_call_details.get('passthrough_logging_payload') fallback
in base, anthropic, cohere, openai (first path), gemini, and vertex handlers.
The streaming path already uses _seed_streaming_kwargs_from_logging_obj which
reads from the same logging_obj.model_call_details source.
---------
Co-authored-by: ishaan-berri <155045088+ishaan-berri@users.noreply.github.com>
This commit is contained in:
parent
b9bedc8153
commit
eda0653e0e
10 changed files with 261 additions and 6 deletions
|
|
@ -59,6 +59,7 @@ sequenceDiagram
|
|||
|-----------|-------------|
|
||||
| `x-pass-*` headers | Strip prefix and forward (e.g., `x-pass-anthropic-beta` → `anthropic-beta`) |
|
||||
| `x-litellm-tags` header | Extract tags and add to request metadata for logging |
|
||||
| `x-litellm-spend-logs-metadata` header | Parse as JSON and propagate into `StandardLoggingPayload.metadata.spend_logs_metadata` (matches OpenAI-compatible endpoints) |
|
||||
| Streaming chunk collection | Collect chunks async for logging after stream completes |
|
||||
| Multipart form handling | Reconstruct multipart/form-data requests for file uploads |
|
||||
| Guardrails (opt-in) | Run content filtering when explicitly configured |
|
||||
|
|
|
|||
|
|
@ -19,6 +19,7 @@ from litellm.types.passthrough_endpoints.pass_through_endpoints import (
|
|||
)
|
||||
from litellm.types.utils import LiteLLMBatch, ModelResponse, TextCompletionResponse
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from litellm.types.passthrough_endpoints.pass_through_endpoints import EndpointType
|
||||
|
||||
|
|
@ -146,6 +147,7 @@ class AnthropicPassthroughLoggingHandler:
|
|||
kwargs["model"] = model
|
||||
passthrough_logging_payload: Optional[PassthroughStandardLoggingPayload] = ( # type: ignore
|
||||
kwargs.get("passthrough_logging_payload")
|
||||
or logging_obj.model_call_details.get("passthrough_logging_payload")
|
||||
)
|
||||
if passthrough_logging_payload:
|
||||
user = AnthropicPassthroughLoggingHandler._get_user_from_metadata(
|
||||
|
|
@ -157,6 +159,11 @@ class AnthropicPassthroughLoggingHandler:
|
|||
{"proxy_server_request": {"body": {"user": user}}}
|
||||
)
|
||||
|
||||
from .base_passthrough_logging_handler import BasePassthroughLoggingHandler # noqa: PLC0415
|
||||
BasePassthroughLoggingHandler._apply_spend_logs_metadata(
|
||||
kwargs, passthrough_logging_payload
|
||||
)
|
||||
|
||||
# pretty print standard logging object
|
||||
verbose_proxy_logger.debug(
|
||||
"kwargs= %s",
|
||||
|
|
@ -221,10 +228,11 @@ class AnthropicPassthroughLoggingHandler:
|
|||
"result": None,
|
||||
"kwargs": {},
|
||||
}
|
||||
from .base_passthrough_logging_handler import BasePassthroughLoggingHandler # noqa: PLC0415
|
||||
kwargs = AnthropicPassthroughLoggingHandler._create_anthropic_response_logging_payload(
|
||||
litellm_model_response=complete_streaming_response,
|
||||
model=model,
|
||||
kwargs={},
|
||||
kwargs=BasePassthroughLoggingHandler._seed_streaming_kwargs_from_logging_obj(litellm_logging_obj),
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
logging_obj=litellm_logging_obj,
|
||||
|
|
|
|||
|
|
@ -93,6 +93,49 @@ class BasePassthroughLoggingHandler(ABC):
|
|||
return get_end_user_id_from_request_body(request_body)
|
||||
return None
|
||||
|
||||
@staticmethod
|
||||
def _apply_spend_logs_metadata(
|
||||
kwargs: dict,
|
||||
passthrough_logging_payload: Optional["PassthroughStandardLoggingPayload"],
|
||||
) -> None:
|
||||
"""
|
||||
Propagate `spend_logs_metadata` from a pass-through logging payload into
|
||||
`kwargs["litellm_params"]["metadata"]`.
|
||||
|
||||
OpenAI-compatible endpoints write the value at
|
||||
`data["metadata"]["spend_logs_metadata"]` before the call; this helper
|
||||
matches that key path at logging time so downstream
|
||||
`get_standard_logging_object_payload` surfaces the same field for
|
||||
pass-through traffic.
|
||||
"""
|
||||
if not passthrough_logging_payload:
|
||||
return
|
||||
metadata = passthrough_logging_payload.get("spend_logs_metadata")
|
||||
if not metadata:
|
||||
return
|
||||
kwargs.setdefault("litellm_params", {})
|
||||
kwargs["litellm_params"].setdefault("metadata", {})
|
||||
kwargs["litellm_params"]["metadata"]["spend_logs_metadata"] = metadata
|
||||
|
||||
@staticmethod
|
||||
def _seed_streaming_kwargs_from_logging_obj(
|
||||
litellm_logging_obj: LiteLLMLoggingObj,
|
||||
) -> dict:
|
||||
"""
|
||||
Build the initial `kwargs` dict for streaming logging paths so the
|
||||
pass-through payload (populated by `success_handler.pass_through_async_success_handler`
|
||||
before any provider handler runs) survives into the response logging
|
||||
builder. Without this seed, streaming paths would lose the
|
||||
`spend_logs_metadata` that non-streaming traffic carries via kwargs.
|
||||
"""
|
||||
initial: dict = {}
|
||||
passthrough_payload = litellm_logging_obj.model_call_details.get(
|
||||
"passthrough_logging_payload"
|
||||
)
|
||||
if passthrough_payload is not None:
|
||||
initial["passthrough_logging_payload"] = passthrough_payload
|
||||
return initial
|
||||
|
||||
def _create_response_logging_payload(
|
||||
self,
|
||||
litellm_model_response: Union[ModelResponse, TextCompletionResponse],
|
||||
|
|
@ -118,6 +161,7 @@ class BasePassthroughLoggingHandler(ABC):
|
|||
kwargs["model"] = model
|
||||
passthrough_logging_payload: Optional[PassthroughStandardLoggingPayload] = ( # type: ignore
|
||||
kwargs.get("passthrough_logging_payload")
|
||||
or logging_obj.model_call_details.get("passthrough_logging_payload")
|
||||
)
|
||||
if passthrough_logging_payload:
|
||||
user = self._get_user_from_metadata(
|
||||
|
|
@ -129,6 +173,8 @@ class BasePassthroughLoggingHandler(ABC):
|
|||
{"proxy_server_request": {"body": {"user": user}}}
|
||||
)
|
||||
|
||||
self._apply_spend_logs_metadata(kwargs, passthrough_logging_payload)
|
||||
|
||||
# Make standard logging object for Anthropic
|
||||
standard_logging_object = get_standard_logging_object_payload(
|
||||
kwargs=kwargs,
|
||||
|
|
@ -209,7 +255,7 @@ class BasePassthroughLoggingHandler(ABC):
|
|||
kwargs = self._create_response_logging_payload(
|
||||
litellm_model_response=complete_streaming_response,
|
||||
model=model,
|
||||
kwargs={},
|
||||
kwargs=self._seed_streaming_kwargs_from_logging_obj(litellm_logging_obj),
|
||||
start_time=start_time,
|
||||
end_time=end_time,
|
||||
logging_obj=litellm_logging_obj,
|
||||
|
|
|
|||
|
|
@ -131,7 +131,10 @@ class CoherePassthroughLoggingHandler(BasePassthroughLoggingHandler):
|
|||
# Extract user information for tracking
|
||||
passthrough_logging_payload: Optional[
|
||||
PassthroughStandardLoggingPayload
|
||||
] = kwargs.get("passthrough_logging_payload")
|
||||
] = (
|
||||
kwargs.get("passthrough_logging_payload")
|
||||
or logging_obj.model_call_details.get("passthrough_logging_payload")
|
||||
)
|
||||
if passthrough_logging_payload:
|
||||
user = handler_instance._get_user_from_metadata(
|
||||
passthrough_logging_payload=passthrough_logging_payload,
|
||||
|
|
@ -142,6 +145,10 @@ class CoherePassthroughLoggingHandler(BasePassthroughLoggingHandler):
|
|||
{"proxy_server_request": {"body": {"user": user}}}
|
||||
)
|
||||
|
||||
BasePassthroughLoggingHandler._apply_spend_logs_metadata(
|
||||
kwargs, passthrough_logging_payload
|
||||
)
|
||||
|
||||
# Create standard logging object
|
||||
if litellm_model_response is not None:
|
||||
get_standard_logging_object_payload(
|
||||
|
|
|
|||
|
|
@ -17,6 +17,7 @@ from litellm.types.utils import (
|
|||
TextCompletionResponse,
|
||||
)
|
||||
|
||||
|
||||
if TYPE_CHECKING:
|
||||
from litellm.types.passthrough_endpoints.pass_through_endpoints import EndpointType
|
||||
|
||||
|
|
@ -139,7 +140,12 @@ class GeminiPassthroughLoggingHandler:
|
|||
- Creates standard logging object
|
||||
- Logs in litellm callbacks
|
||||
"""
|
||||
kwargs: Dict[str, Any] = {}
|
||||
from .base_passthrough_logging_handler import BasePassthroughLoggingHandler # noqa: PLC0415
|
||||
kwargs: Dict[str, Any] = (
|
||||
BasePassthroughLoggingHandler._seed_streaming_kwargs_from_logging_obj(
|
||||
litellm_logging_obj
|
||||
)
|
||||
)
|
||||
model = model or GeminiPassthroughLoggingHandler.extract_model_from_url(
|
||||
url_route
|
||||
)
|
||||
|
|
@ -242,6 +248,13 @@ class GeminiPassthroughLoggingHandler:
|
|||
kwargs["model"] = model
|
||||
kwargs["custom_llm_provider"] = custom_llm_provider
|
||||
|
||||
from .base_passthrough_logging_handler import BasePassthroughLoggingHandler # noqa: PLC0415
|
||||
BasePassthroughLoggingHandler._apply_spend_logs_metadata(
|
||||
kwargs,
|
||||
kwargs.get("passthrough_logging_payload")
|
||||
or logging_obj.model_call_details.get("passthrough_logging_payload"),
|
||||
)
|
||||
|
||||
# pretty print standard logging object
|
||||
verbose_proxy_logger.debug("kwargs= %s", kwargs)
|
||||
|
||||
|
|
|
|||
|
|
@ -367,8 +367,11 @@ class OpenAIPassthroughLoggingHandler(BasePassthroughLoggingHandler):
|
|||
kwargs["custom_llm_provider"] = custom_llm_provider
|
||||
|
||||
# Extract user information for tracking
|
||||
passthrough_logging_payload: Optional[PassthroughStandardLoggingPayload] = (
|
||||
passthrough_logging_payload: Optional[
|
||||
PassthroughStandardLoggingPayload
|
||||
] = (
|
||||
kwargs.get("passthrough_logging_payload")
|
||||
or logging_obj.model_call_details.get("passthrough_logging_payload")
|
||||
)
|
||||
if passthrough_logging_payload:
|
||||
user = handler_instance._get_user_from_metadata(
|
||||
|
|
@ -379,6 +382,10 @@ class OpenAIPassthroughLoggingHandler(BasePassthroughLoggingHandler):
|
|||
"proxy_server_request", {}
|
||||
).setdefault("body", {})["user"] = user
|
||||
|
||||
BasePassthroughLoggingHandler._apply_spend_logs_metadata(
|
||||
kwargs, passthrough_logging_payload
|
||||
)
|
||||
|
||||
# Create standard logging object
|
||||
if litellm_model_response is not None:
|
||||
get_standard_logging_object_payload(
|
||||
|
|
@ -570,6 +577,10 @@ class OpenAIPassthroughLoggingHandler(BasePassthroughLoggingHandler):
|
|||
"proxy_server_request", {}
|
||||
).setdefault("body", {})["user"] = user
|
||||
|
||||
BasePassthroughLoggingHandler._apply_spend_logs_metadata(
|
||||
kwargs, passthrough_logging_payload
|
||||
)
|
||||
|
||||
# Create standard logging object
|
||||
get_standard_logging_object_payload(
|
||||
kwargs=kwargs,
|
||||
|
|
|
|||
|
|
@ -341,7 +341,12 @@ class VertexPassthroughLoggingHandler:
|
|||
- Creates standard logging object
|
||||
- Logs in litellm callbacks
|
||||
"""
|
||||
kwargs: Dict[str, Any] = {}
|
||||
from litellm.proxy.pass_through_endpoints.llm_provider_handlers.base_passthrough_logging_handler import BasePassthroughLoggingHandler # noqa: PLC0415
|
||||
kwargs: Dict[str, Any] = (
|
||||
BasePassthroughLoggingHandler._seed_streaming_kwargs_from_logging_obj(
|
||||
litellm_logging_obj
|
||||
)
|
||||
)
|
||||
model = model or VertexPassthroughLoggingHandler.extract_model_from_url(
|
||||
url_route
|
||||
)
|
||||
|
|
@ -547,6 +552,13 @@ class VertexPassthroughLoggingHandler:
|
|||
kwargs["response_cost"] = response_cost
|
||||
kwargs["model"] = model
|
||||
|
||||
from litellm.proxy.pass_through_endpoints.llm_provider_handlers.base_passthrough_logging_handler import BasePassthroughLoggingHandler # noqa: PLC0415
|
||||
BasePassthroughLoggingHandler._apply_spend_logs_metadata(
|
||||
kwargs,
|
||||
kwargs.get("passthrough_logging_payload")
|
||||
or logging_obj.model_call_details.get("passthrough_logging_payload"),
|
||||
)
|
||||
|
||||
# pretty print standard logging object
|
||||
verbose_proxy_logger.debug("kwargs= %s", kwargs)
|
||||
|
||||
|
|
|
|||
|
|
@ -752,11 +752,17 @@ async def pass_through_request( # noqa: PLR0915
|
|||
params={"timeout": 600},
|
||||
)
|
||||
async_client = async_client_obj.client
|
||||
spend_logs_metadata = (
|
||||
LiteLLMProxyRequestSetup._get_spend_logs_metadata_from_request_headers(
|
||||
_safe_get_request_headers(request)
|
||||
)
|
||||
)
|
||||
passthrough_logging_payload = PassthroughStandardLoggingPayload(
|
||||
url=str(url),
|
||||
request_body=_parsed_body,
|
||||
request_method=getattr(request, "method", None),
|
||||
cost_per_request=cost_per_request,
|
||||
spend_logs_metadata=spend_logs_metadata,
|
||||
)
|
||||
kwargs = HttpPassThroughEndpointHelpers._init_kwargs_for_pass_through_endpoint(
|
||||
user_api_key_dict=user_api_key_dict,
|
||||
|
|
@ -1386,11 +1392,18 @@ async def websocket_passthrough_request( # noqa: PLR0915
|
|||
)
|
||||
|
||||
# Create passthrough logging payload
|
||||
websocket_headers = dict(websocket.headers) if hasattr(websocket, "headers") else {}
|
||||
spend_logs_metadata = (
|
||||
LiteLLMProxyRequestSetup._get_spend_logs_metadata_from_request_headers(
|
||||
websocket_headers
|
||||
)
|
||||
)
|
||||
passthrough_logging_payload = PassthroughStandardLoggingPayload(
|
||||
url=target,
|
||||
request_body={}, # WebSocket doesn't have a traditional request body
|
||||
request_method="WEBSOCKET",
|
||||
cost_per_request=cost_per_request,
|
||||
spend_logs_metadata=spend_logs_metadata,
|
||||
)
|
||||
|
||||
# Create a dummy request object for WebSocket connections to maintain compatibility
|
||||
|
|
|
|||
|
|
@ -46,3 +46,12 @@ class PassthroughStandardLoggingPayload(TypedDict, total=False):
|
|||
|
||||
Optional field, we use this for cost tracking only if it's set.
|
||||
"""
|
||||
|
||||
spend_logs_metadata: Optional[dict]
|
||||
"""
|
||||
Parsed `x-litellm-spend-logs-metadata` request header, if present.
|
||||
|
||||
Propagated into `StandardLoggingPayload.metadata.spend_logs_metadata` so
|
||||
pass-through traffic surfaces the same per-call metadata (e.g. `task`,
|
||||
`issue`) that OpenAI-compatible endpoints already emit.
|
||||
"""
|
||||
|
|
|
|||
|
|
@ -0,0 +1,135 @@
|
|||
"""Tests for `x-litellm-spend-logs-metadata` propagation through pass-through endpoints.
|
||||
|
||||
OpenAI-compatible endpoints parse this header in
|
||||
`LiteLLMProxyRequestSetup._get_spend_logs_metadata_from_request_headers` and
|
||||
stuff it into `data["metadata"]["spend_logs_metadata"]`. Pass-through endpoints
|
||||
used to skip this extraction, so `/spend/logs` entries for
|
||||
`call_type=pass_through_endpoint` always had
|
||||
`metadata.spend_logs_metadata = None`.
|
||||
|
||||
These tests pin the fix:
|
||||
|
||||
1. Header extraction handles present/missing/malformed input.
|
||||
2. `BasePassthroughLoggingHandler._apply_spend_logs_metadata` writes the
|
||||
metadata to the same key path the OpenAI-compatible flow uses.
|
||||
3. `_seed_streaming_kwargs_from_logging_obj` threads the pass-through payload
|
||||
from `logging_obj.model_call_details` into fresh streaming kwargs, so
|
||||
streaming responses retain the metadata.
|
||||
"""
|
||||
|
||||
import json
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"headers,expected",
|
||||
[
|
||||
({"x-litellm-spend-logs-metadata": json.dumps({"task": "t1"})}, {"task": "t1"}),
|
||||
({}, None),
|
||||
({"x-litellm-spend-logs-metadata": "not-json"}, None),
|
||||
],
|
||||
)
|
||||
def test_header_extraction(headers, expected):
|
||||
"""Helper handles valid JSON, missing header, and malformed JSON."""
|
||||
from litellm.proxy.litellm_pre_call_utils import LiteLLMProxyRequestSetup
|
||||
|
||||
assert (
|
||||
LiteLLMProxyRequestSetup._get_spend_logs_metadata_from_request_headers(headers)
|
||||
== expected
|
||||
)
|
||||
|
||||
|
||||
def test_apply_spend_logs_metadata_writes_matching_key_path():
|
||||
"""The helper must write to `kwargs["litellm_params"]["metadata"]["spend_logs_metadata"]`
|
||||
— the same key path OpenAI-compatible flows use, so
|
||||
`get_standard_logging_object_payload` picks it up identically.
|
||||
"""
|
||||
from litellm.proxy.pass_through_endpoints.llm_provider_handlers.base_passthrough_logging_handler import (
|
||||
BasePassthroughLoggingHandler,
|
||||
)
|
||||
|
||||
kwargs: dict = {}
|
||||
metadata = {"task": "cost-report", "issue": "#42"}
|
||||
|
||||
BasePassthroughLoggingHandler._apply_spend_logs_metadata(
|
||||
kwargs,
|
||||
{"url": "x", "spend_logs_metadata": metadata},
|
||||
)
|
||||
|
||||
assert kwargs["litellm_params"]["metadata"]["spend_logs_metadata"] == metadata
|
||||
|
||||
|
||||
def test_apply_spend_logs_metadata_preserves_existing_metadata():
|
||||
"""Existing metadata keys must not be clobbered when attaching
|
||||
`spend_logs_metadata`.
|
||||
"""
|
||||
from litellm.proxy.pass_through_endpoints.llm_provider_handlers.base_passthrough_logging_handler import (
|
||||
BasePassthroughLoggingHandler,
|
||||
)
|
||||
|
||||
kwargs: dict = {"litellm_params": {"metadata": {"tags": ["prod"]}}}
|
||||
|
||||
BasePassthroughLoggingHandler._apply_spend_logs_metadata(
|
||||
kwargs,
|
||||
{"url": "x", "spend_logs_metadata": {"task": "t1"}},
|
||||
)
|
||||
|
||||
assert kwargs["litellm_params"]["metadata"] == {
|
||||
"tags": ["prod"],
|
||||
"spend_logs_metadata": {"task": "t1"},
|
||||
}
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"payload",
|
||||
[None, {}, {"url": "x"}, {"url": "x", "spend_logs_metadata": None}],
|
||||
)
|
||||
def test_apply_spend_logs_metadata_is_noop_when_missing(payload):
|
||||
"""When the payload is missing or carries no metadata, the helper must
|
||||
leave kwargs untouched — this is the path for existing callers that don't
|
||||
send the header, and it must remain backward-compatible.
|
||||
"""
|
||||
from litellm.proxy.pass_through_endpoints.llm_provider_handlers.base_passthrough_logging_handler import (
|
||||
BasePassthroughLoggingHandler,
|
||||
)
|
||||
|
||||
kwargs: dict = {}
|
||||
BasePassthroughLoggingHandler._apply_spend_logs_metadata(kwargs, payload)
|
||||
assert kwargs == {}
|
||||
|
||||
|
||||
def test_seed_streaming_kwargs_copies_payload_from_logging_obj():
|
||||
"""Streaming logging paths build fresh kwargs; the seed helper must lift
|
||||
the pass-through payload stashed by `success_handler` onto that fresh dict
|
||||
so `_create_*_response_logging_payload` can still see it.
|
||||
"""
|
||||
from litellm.proxy.pass_through_endpoints.llm_provider_handlers.base_passthrough_logging_handler import (
|
||||
BasePassthroughLoggingHandler,
|
||||
)
|
||||
|
||||
payload = {"url": "x", "spend_logs_metadata": {"task": "t1"}}
|
||||
logging_obj = MagicMock(model_call_details={"passthrough_logging_payload": payload})
|
||||
|
||||
seeded = BasePassthroughLoggingHandler._seed_streaming_kwargs_from_logging_obj(
|
||||
logging_obj
|
||||
)
|
||||
|
||||
assert seeded == {"passthrough_logging_payload": payload}
|
||||
|
||||
|
||||
def test_seed_streaming_kwargs_empty_when_no_payload():
|
||||
"""No pass-through payload on `logging_obj` → empty dict (prior behavior)."""
|
||||
from litellm.proxy.pass_through_endpoints.llm_provider_handlers.base_passthrough_logging_handler import (
|
||||
BasePassthroughLoggingHandler,
|
||||
)
|
||||
|
||||
logging_obj = MagicMock(model_call_details={})
|
||||
|
||||
assert (
|
||||
BasePassthroughLoggingHandler._seed_streaming_kwargs_from_logging_obj(
|
||||
logging_obj
|
||||
)
|
||||
== {}
|
||||
)
|
||||
Loading…
Add table
Reference in a new issue