From c07e9432ed8fbffe2dc438332165e3559444337e Mon Sep 17 00:00:00 2001 From: Deepanshu Pal <40927968+DeepanshuPal@users.noreply.github.com> Date: Sat, 12 Sep 2026 18:39:14 +0530 Subject: [PATCH 1/3] fix(proxy): honor caller litellm_session_id in spend log session_id The default path of _get_session_id_for_spend_log never consulted kwargs['litellm_session_id'], so a caller-supplied session was overwritten by the per-call trace id and LiteLLM_SpendLogs.session_id could never group by litellm_session_id. Prefer it after the batch lifecycle's deterministic session: when the caller sent no session the value is only the derived trace-id copy, so sessionless requests are unchanged. Fixes #40851 --- .../spend_tracking/spend_tracking_utils.py | 12 ++++++--- .../test_spend_tracking_utils.py | 25 +++++++++++++++++++ 2 files changed, 34 insertions(+), 3 deletions(-) diff --git a/litellm/proxy/spend_tracking/spend_tracking_utils.py b/litellm/proxy/spend_tracking/spend_tracking_utils.py index 3ef21996b9c..6eb4ae54bba 100644 --- a/litellm/proxy/spend_tracking/spend_tracking_utils.py +++ b/litellm/proxy/spend_tracking/spend_tracking_utils.py @@ -682,9 +682,12 @@ def _get_session_id_for_spend_log( omit_when_missing: bool, batch_trace_session_id: str | None = None, ) -> str | None: - """Under `omit` only `metadata.session_id`, the key Langfuse reads, counts as a session; `litellm_session_id` may - be a copied trace id. Batch call types carry a deterministic session derived from the batch id, which outranks - the per-request trace ids because those differ between the create call and the cost poller's row.""" + """Under `omit` only `metadata.session_id`, the key Langfuse reads, counts as a session. Batch call types + carry a deterministic session derived from the batch id, which outranks everything else because the + per-request trace ids differ between the create call and the cost poller's row. Otherwise the caller's + `litellm_session_id` wins: it is only absent or a copy of the trace id when the caller did not send one, + so preferring it changes nothing for sessionless requests while letting a real session id reach the + spend log instead of being overwritten by the per-call trace id.""" if omit_when_missing: session_id: Final = metadata.get("session_id") if metadata else None return str(session_id) if session_id else None @@ -693,6 +696,9 @@ def _get_session_id_for_spend_log( if batch_trace_session_id is not None: return batch_trace_session_id + caller_session_id: Final = kwargs.get("litellm_session_id") + if caller_session_id is not None: + return str(caller_session_id) if standard_logging_payload is not None and standard_logging_payload.get("trace_id") is not None: return str(standard_logging_payload.get("trace_id")) if kwargs.get("litellm_trace_id") is not None: diff --git a/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py b/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py index a72b4e28143..579ddf6cc28 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py +++ b/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py @@ -233,6 +233,31 @@ def test_batch_session_outranks_the_per_request_trace_id(): assert session_id == "batch-uid-1" +def test_caller_litellm_session_id_wins_over_the_per_request_trace_id(): + """A caller-supplied litellm_session_id is a real session, not a copied trace id; the + spend log must keep it instead of overwriting it with the per-call trace id.""" + session_id: Final = _get_session_id_for_spend_log( + kwargs={"litellm_session_id": "sess-1", "litellm_trace_id": "trace-abc"}, + metadata={"trace_id": "trace-abc"}, + standard_logging_payload=_TRACE_ONLY_STANDARD_LOGGING, + omit_when_missing=False, + ) + assert session_id == "sess-1" + + +def test_batch_session_outranks_a_caller_litellm_session_id(): + """The batch lifecycle grouping must survive even when the create call carries a + caller session id, or the poller's cost row would land in a different session.""" + session_id: Final = _get_session_id_for_spend_log( + kwargs={"litellm_session_id": "sess-1", "litellm_trace_id": "trace-abc"}, + metadata={"trace_id": "trace-abc"}, + standard_logging_payload=_TRACE_ONLY_STANDARD_LOGGING, + omit_when_missing=False, + batch_trace_session_id="batch-uid-1", + ) + assert session_id == "batch-uid-1" + + def test_omit_policy_still_suppresses_batch_sessions(): session_id: Final = _get_session_id_for_spend_log( kwargs={}, From 07fd7ee60726e195f66f6822dada5adc87b24ab8 Mon Sep 17 00:00:00 2001 From: Deepanshu Pal <40927968+DeepanshuPal@users.noreply.github.com> Date: Sun, 13 Sep 2026 17:37:54 +0530 Subject: [PATCH 2/3] fix(proxy): harden spend log session precedence per review --- litellm/proxy/spend_tracking/spend_tracking_utils.py | 10 +++------- .../proxy/spend_tracking/test_spend_tracking_utils.py | 6 ++---- 2 files changed, 5 insertions(+), 11 deletions(-) diff --git a/litellm/proxy/spend_tracking/spend_tracking_utils.py b/litellm/proxy/spend_tracking/spend_tracking_utils.py index 6eb4ae54bba..436297d2233 100644 --- a/litellm/proxy/spend_tracking/spend_tracking_utils.py +++ b/litellm/proxy/spend_tracking/spend_tracking_utils.py @@ -682,12 +682,8 @@ def _get_session_id_for_spend_log( omit_when_missing: bool, batch_trace_session_id: str | None = None, ) -> str | None: - """Under `omit` only `metadata.session_id`, the key Langfuse reads, counts as a session. Batch call types - carry a deterministic session derived from the batch id, which outranks everything else because the - per-request trace ids differ between the create call and the cost poller's row. Otherwise the caller's - `litellm_session_id` wins: it is only absent or a copy of the trace id when the caller did not send one, - so preferring it changes nothing for sessionless requests while letting a real session id reach the - spend log instead of being overwritten by the per-call trace id.""" + """Resolve the session id for the spend log row: `omit` honors only metadata.session_id, + batch sessions outrank everything, then the caller's litellm_session_id, then trace ids.""" if omit_when_missing: session_id: Final = metadata.get("session_id") if metadata else None return str(session_id) if session_id else None @@ -697,7 +693,7 @@ def _get_session_id_for_spend_log( if batch_trace_session_id is not None: return batch_trace_session_id caller_session_id: Final = kwargs.get("litellm_session_id") - if caller_session_id is not None: + if caller_session_id: return str(caller_session_id) if standard_logging_payload is not None and standard_logging_payload.get("trace_id") is not None: return str(standard_logging_payload.get("trace_id")) diff --git a/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py b/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py index 579ddf6cc28..aa63404ea08 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py +++ b/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py @@ -234,8 +234,7 @@ def test_batch_session_outranks_the_per_request_trace_id(): def test_caller_litellm_session_id_wins_over_the_per_request_trace_id(): - """A caller-supplied litellm_session_id is a real session, not a copied trace id; the - spend log must keep it instead of overwriting it with the per-call trace id.""" + """Caller litellm_session_id wins over the per-call trace id.""" session_id: Final = _get_session_id_for_spend_log( kwargs={"litellm_session_id": "sess-1", "litellm_trace_id": "trace-abc"}, metadata={"trace_id": "trace-abc"}, @@ -246,8 +245,7 @@ def test_caller_litellm_session_id_wins_over_the_per_request_trace_id(): def test_batch_session_outranks_a_caller_litellm_session_id(): - """The batch lifecycle grouping must survive even when the create call carries a - caller session id, or the poller's cost row would land in a different session.""" + """Batch session still outranks a caller session id.""" session_id: Final = _get_session_id_for_spend_log( kwargs={"litellm_session_id": "sess-1", "litellm_trace_id": "trace-abc"}, metadata={"trace_id": "trace-abc"}, From e1730f3f720a6ff0bdb0dc3a5abfcc6e2f2e3d78 Mon Sep 17 00:00:00 2001 From: Deepanshu Pal <40927968+DeepanshuPal@users.noreply.github.com> Date: Sun, 13 Sep 2026 17:46:33 +0530 Subject: [PATCH 3/3] test(proxy): drop redundant docstrings per repo comment guidance --- .../proxy/spend_tracking/test_spend_tracking_utils.py | 2 -- 1 file changed, 2 deletions(-) diff --git a/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py b/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py index aa63404ea08..e5103644090 100644 --- a/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py +++ b/tests/test_litellm/proxy/spend_tracking/test_spend_tracking_utils.py @@ -234,7 +234,6 @@ def test_batch_session_outranks_the_per_request_trace_id(): def test_caller_litellm_session_id_wins_over_the_per_request_trace_id(): - """Caller litellm_session_id wins over the per-call trace id.""" session_id: Final = _get_session_id_for_spend_log( kwargs={"litellm_session_id": "sess-1", "litellm_trace_id": "trace-abc"}, metadata={"trace_id": "trace-abc"}, @@ -245,7 +244,6 @@ def test_caller_litellm_session_id_wins_over_the_per_request_trace_id(): def test_batch_session_outranks_a_caller_litellm_session_id(): - """Batch session still outranks a caller session id.""" session_id: Final = _get_session_id_for_spend_log( kwargs={"litellm_session_id": "sess-1", "litellm_trace_id": "trace-abc"}, metadata={"trace_id": "trace-abc"},